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