001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase;
019
020import java.io.File;
021import java.io.IOException;
022
023import org.apache.hadoop.conf.Configuration;
024import org.apache.hadoop.fs.Path;
025import org.apache.hadoop.hbase.zookeeper.MiniZooKeeperCluster;
026import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
027import org.apache.yetus.audience.InterfaceAudience;
028
029/**
030 * Helpers for testing HBase that do not depend on specific server/etc. things. The main difference
031 * from {@link HBaseCommonTestingUtility} is that we can start a zookeeper cluster.
032 */
033@InterfaceAudience.Public
034public class HBaseZKTestingUtility extends HBaseCommonTestingUtility {
035  private MiniZooKeeperCluster zkCluster;
036
037  /**
038   * Set if we were passed a zkCluster. If so, we won't shutdown zk as part of general shutdown.
039   */
040  private boolean passedZkCluster;
041
042  protected ZKWatcher zooKeeperWatcher;
043
044  /** Directory (a subdirectory of dataTestDir) used by the dfs cluster if any */
045  protected File clusterTestDir;
046
047  public HBaseZKTestingUtility() {
048    this(HBaseConfiguration.create());
049  }
050
051  public HBaseZKTestingUtility(Configuration conf) {
052    super(conf);
053  }
054
055  /**
056   * @return Where the cluster will write data on the local subsystem. Creates it if it does not
057   *         exist already. A subdir of {@code HBaseCommonTestingUtility#getBaseTestDir()}
058   */
059  Path getClusterTestDir() {
060    if (clusterTestDir == null) {
061      setupClusterTestDir();
062    }
063    return new Path(clusterTestDir.getAbsolutePath());
064  }
065
066  /**
067   * Creates a directory for the cluster, under the test data
068   */
069  protected void setupClusterTestDir() {
070    if (clusterTestDir != null) {
071      return;
072    }
073
074    // Using randomUUID ensures that multiple clusters can be launched by
075    // a same test, if it stops & starts them
076    Path testDir = getDataTestDir("cluster_" + getRandomUUID().toString());
077    clusterTestDir = new File(testDir.toString()).getAbsoluteFile();
078    // Have it cleaned up on exit
079    boolean b = deleteOnExit();
080    if (b) {
081      clusterTestDir.deleteOnExit();
082    }
083    LOG.info("Created new mini-cluster data directory: " + clusterTestDir + ", deleteOnExit=" + b);
084  }
085
086  /**
087   * Call this if you only want a zk cluster.
088   * @see #shutdownMiniZKCluster()
089   * @return zk cluster started.
090   */
091  public MiniZooKeeperCluster startMiniZKCluster() throws Exception {
092    return startMiniZKCluster(1);
093  }
094
095  /**
096   * Call this if you only want a zk cluster.
097   * @see #shutdownMiniZKCluster()
098   * @return zk cluster started.
099   */
100  public MiniZooKeeperCluster startMiniZKCluster(int zooKeeperServerNum, int... clientPortList)
101      throws Exception {
102    setupClusterTestDir();
103    return startMiniZKCluster(clusterTestDir, zooKeeperServerNum, clientPortList);
104  }
105
106  /**
107   * Start a mini ZK cluster. If the property "test.hbase.zookeeper.property.clientPort" is set the
108   * port mentioned is used as the default port for ZooKeeper.
109   */
110  private MiniZooKeeperCluster startMiniZKCluster(File dir, int zooKeeperServerNum,
111      int[] clientPortList) throws Exception {
112    if (this.zkCluster != null) {
113      throw new IOException("Cluster already running at " + dir);
114    }
115    this.passedZkCluster = false;
116    this.zkCluster = new MiniZooKeeperCluster(this.getConfiguration());
117    int defPort = this.conf.getInt("test.hbase.zookeeper.property.clientPort", 0);
118    if (defPort > 0) {
119      // If there is a port in the config file, we use it.
120      this.zkCluster.setDefaultClientPort(defPort);
121    }
122
123    if (clientPortList != null) {
124      // Ignore extra client ports
125      int clientPortListSize = Math.min(clientPortList.length, zooKeeperServerNum);
126      for (int i = 0; i < clientPortListSize; i++) {
127        this.zkCluster.addClientPort(clientPortList[i]);
128      }
129    }
130    int clientPort = this.zkCluster.startup(dir, zooKeeperServerNum);
131    this.conf.set(HConstants.ZOOKEEPER_CLIENT_PORT, Integer.toString(clientPort));
132    return this.zkCluster;
133  }
134
135  public MiniZooKeeperCluster getZkCluster() {
136    return zkCluster;
137  }
138
139  public void setZkCluster(MiniZooKeeperCluster zkCluster) {
140    this.passedZkCluster = true;
141    this.zkCluster = zkCluster;
142    conf.setInt(HConstants.ZOOKEEPER_CLIENT_PORT, zkCluster.getClientPort());
143  }
144
145  /**
146   * Shuts down zk cluster created by call to {@link #startMiniZKCluster(File)} or does nothing.
147   * @see #startMiniZKCluster()
148   */
149  public void shutdownMiniZKCluster() throws IOException {
150    if (!passedZkCluster && this.zkCluster != null) {
151      this.zkCluster.shutdown();
152      this.zkCluster = null;
153    }
154  }
155
156  /**
157   * Returns a ZKWatcher instance. This instance is shared between HBaseTestingUtility instance
158   * users. Don't close it, it will be closed automatically when the cluster shutdowns
159   * @return The ZKWatcher instance.
160   */
161  public synchronized ZKWatcher getZooKeeperWatcher() throws IOException {
162    if (zooKeeperWatcher == null) {
163      zooKeeperWatcher = new ZKWatcher(conf, "testing utility", new Abortable() {
164        @Override
165        public void abort(String why, Throwable e) {
166          throw new RuntimeException("Unexpected abort in HBaseZKTestingUtility:" + why, e);
167        }
168
169        @Override
170        public boolean isAborted() {
171          return false;
172        }
173      });
174    }
175    return zooKeeperWatcher;
176  }
177
178  /**
179   * Gets a ZKWatcher.
180   */
181  public static ZKWatcher getZooKeeperWatcher(HBaseZKTestingUtility testUtil) throws IOException {
182    return new ZKWatcher(testUtil.getConfiguration(), "unittest", new Abortable() {
183      boolean aborted = false;
184
185      @Override
186      public void abort(String why, Throwable e) {
187        aborted = true;
188        throw new RuntimeException("Fatal ZK error, why=" + why, e);
189      }
190
191      @Override
192      public boolean isAborted() {
193        return aborted;
194      }
195    });
196  }
197
198  /**
199   * @return True if we removed the test dirs
200   */
201  @Override
202  public boolean cleanupTestDir() {
203    boolean ret = super.cleanupTestDir();
204    if (deleteDir(this.clusterTestDir)) {
205      this.clusterTestDir = null;
206      return ret;
207    }
208    return false;
209  }
210}