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.net.ServerSocket;
023import java.util.Arrays;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Random;
027import java.util.Set;
028import java.util.UUID;
029import java.util.concurrent.ThreadLocalRandom;
030import org.apache.commons.io.FileUtils;
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.fs.Path;
033import org.apache.hadoop.hbase.Waiter.Predicate;
034import org.apache.hadoop.hbase.io.compress.Compression;
035import org.apache.yetus.audience.InterfaceAudience;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039/**
040 * Common helpers for testing HBase that do not depend on specific server/etc. things.
041 * @see org.apache.hadoop.hbase.HBaseCommonTestingUtility
042 */
043@InterfaceAudience.Public
044public class HBaseCommonTestingUtility {
045  protected static final Logger LOG = LoggerFactory.getLogger(HBaseCommonTestingUtility.class);
046
047  /**
048   * Compression algorithms to use in parameterized JUnit 4 tests
049   */
050  public static final List<Object[]> COMPRESSION_ALGORITHMS_PARAMETERIZED =
051    Arrays.asList(new Object[][] { { Compression.Algorithm.NONE }, { Compression.Algorithm.GZ } });
052
053  /**
054   * This is for unit tests parameterized with a two booleans.
055   */
056  public static final List<Object[]> BOOLEAN_PARAMETERIZED =
057    Arrays.asList(new Object[][] { { false }, { true } });
058
059  /**
060   * Compression algorithms to use in testing
061   */
062  public static final Compression.Algorithm[] COMPRESSION_ALGORITHMS =
063    { Compression.Algorithm.NONE, Compression.Algorithm.GZ };
064
065  protected final Configuration conf;
066
067  public HBaseCommonTestingUtility() {
068    this(null);
069  }
070
071  public HBaseCommonTestingUtility(Configuration conf) {
072    this.conf = (conf == null ? HBaseConfiguration.create() : conf);
073  }
074
075  /**
076   * Returns this classes's instance of {@link Configuration}.
077   * @return Instance of Configuration.
078   */
079  public Configuration getConfiguration() {
080    return this.conf;
081  }
082
083  /**
084   * System property key to get base test directory value
085   */
086  public static final String BASE_TEST_DIRECTORY_KEY = "test.build.data.basedirectory";
087
088  /**
089   * Default base directory for test output.
090   */
091  public static final String DEFAULT_BASE_TEST_DIRECTORY = "target/test-data";
092
093  /**
094   * Directory where we put the data for this instance of HBaseTestingUtility
095   */
096  private File dataTestDir = null;
097
098  /**
099   * @return Where to write test data on local filesystem, specific to the test. Useful for tests
100   *         that do not use a cluster. Creates it if it does not exist already.
101   */
102  public Path getDataTestDir() {
103    if (this.dataTestDir == null) {
104      setupDataTestDir();
105    }
106    return new Path(this.dataTestDir.getAbsolutePath());
107  }
108
109  /**
110   * @param name the name of a subdirectory or file in the test data directory
111   * @return Path to a subdirectory or file named {code subdirName} under {@link #getDataTestDir()}.
112   *         Does *NOT* create the directory or file if it does not exist.
113   */
114  public Path getDataTestDir(final String name) {
115    return new Path(getDataTestDir(), name);
116  }
117
118  /**
119   * Sets up a directory for a test to use.
120   * @return New directory path, if created.
121   */
122  protected Path setupDataTestDir() {
123    if (this.dataTestDir != null) {
124      LOG.warn("Data test dir already setup in " + dataTestDir.getAbsolutePath());
125      return null;
126    }
127    Path testPath = getRandomDir();
128    this.dataTestDir = new File(testPath.toString()).getAbsoluteFile();
129    // Set this property so if mapreduce jobs run, they will use this as their home dir.
130    System.setProperty("test.build.dir", this.dataTestDir.toString());
131
132    if (deleteOnExit()) {
133      this.dataTestDir.deleteOnExit();
134    }
135
136    createSubDir("hbase.local.dir", testPath, "hbase-local-dir");
137
138    return testPath;
139  }
140
141  /**
142   * @return A dir with a random (uuid) name under the test dir
143   * @see #getBaseTestDir()
144   */
145  public Path getRandomDir() {
146    return new Path(getBaseTestDir(), getRandomUUID().toString());
147  }
148
149  public static UUID getRandomUUID() {
150    return new UUID(ThreadLocalRandom.current().nextLong(), ThreadLocalRandom.current().nextLong());
151  }
152
153  protected void createSubDir(String propertyName, Path parent, String subDirName) {
154    Path newPath = new Path(parent, subDirName);
155    File newDir = new File(newPath.toString()).getAbsoluteFile();
156
157    if (deleteOnExit()) {
158      newDir.deleteOnExit();
159    }
160
161    conf.set(propertyName, newDir.getAbsolutePath());
162  }
163
164  /** Returns True if we should delete testing dirs on exit. */
165  boolean deleteOnExit() {
166    String v = System.getProperty("hbase.testing.preserve.testdir");
167    // Let default be true, to delete on exit.
168    return v == null ? true : !Boolean.parseBoolean(v);
169  }
170
171  /** Returns True if we removed the test dirs */
172  public boolean cleanupTestDir() {
173    if (deleteDir(this.dataTestDir)) {
174      this.dataTestDir = null;
175      return true;
176    }
177    return false;
178  }
179
180  /**
181   * @param subdir Test subdir name.
182   * @return True if we removed the test dir
183   */
184  boolean cleanupTestDir(final String subdir) {
185    if (this.dataTestDir == null) {
186      return false;
187    }
188    return deleteDir(new File(this.dataTestDir, subdir));
189  }
190
191  /**
192   * @return Where to write test data on local filesystem; usually
193   *         {@link #DEFAULT_BASE_TEST_DIRECTORY} Should not be used by the unit tests, hence its's
194   *         private. Unit test will use a subdirectory of this directory.
195   * @see #setupDataTestDir()
196   */
197  private Path getBaseTestDir() {
198    String PathName = System.getProperty(BASE_TEST_DIRECTORY_KEY, DEFAULT_BASE_TEST_DIRECTORY);
199
200    return new Path(PathName);
201  }
202
203  /**
204   * @param dir Directory to delete
205   * @return True if we deleted it.
206   */
207  boolean deleteDir(final File dir) {
208    if (dir == null || !dir.exists()) {
209      return true;
210    }
211    int ntries = 0;
212    do {
213      ntries += 1;
214      try {
215        if (deleteOnExit()) {
216          FileUtils.deleteDirectory(dir);
217        }
218
219        return true;
220      } catch (IOException ex) {
221        LOG.warn("Failed to delete " + dir.getAbsolutePath());
222      } catch (IllegalArgumentException ex) {
223        LOG.warn("Failed to delete " + dir.getAbsolutePath(), ex);
224      }
225    } while (ntries < 30);
226
227    return false;
228  }
229
230  /**
231   * Wrapper method for {@link Waiter#waitFor(Configuration, long, Predicate)}.
232   */
233  public <E extends Exception> long waitFor(long timeout, Predicate<E> predicate) throws E {
234    return Waiter.waitFor(this.conf, timeout, predicate);
235  }
236
237  /**
238   * Wrapper method for {@link Waiter#waitFor(Configuration, long, long, Predicate)}.
239   */
240  public <E extends Exception> long waitFor(long timeout, long interval, Predicate<E> predicate)
241    throws E {
242    return Waiter.waitFor(this.conf, timeout, interval, predicate);
243  }
244
245  /**
246   * Wrapper method for {@link Waiter#waitFor(Configuration, long, long, boolean, Predicate)}.
247   */
248  public <E extends Exception> long waitFor(long timeout, long interval, boolean failIfTimeout,
249    Predicate<E> predicate) throws E {
250    return Waiter.waitFor(this.conf, timeout, interval, failIfTimeout, predicate);
251  }
252
253  private static final PortAllocator portAllocator = new PortAllocator();
254
255  public static int randomFreePort() {
256    return portAllocator.randomFreePort();
257  }
258
259  static class PortAllocator {
260    private static final int MIN_RANDOM_PORT = 0xc000;
261    private static final int MAX_RANDOM_PORT = 0xfffe;
262
263    /** A set of ports that have been claimed using {@link #randomFreePort()}. */
264    private final Set<Integer> takenRandomPorts = new HashSet<>();
265    private final Random random;
266    private final AvailablePortChecker portChecker;
267
268    public PortAllocator() {
269      this.random = new Random();
270      this.portChecker = new AvailablePortChecker() {
271        @Override
272        public boolean available(int port) {
273          try {
274            ServerSocket sock = new ServerSocket(port);
275            sock.close();
276            return true;
277          } catch (IOException ex) {
278            return false;
279          }
280        }
281      };
282    }
283
284    public PortAllocator(Random random, AvailablePortChecker portChecker) {
285      this.random = random;
286      this.portChecker = portChecker;
287    }
288
289    public PortAllocator(AvailablePortChecker portChecker) {
290      this(new Random(), portChecker);
291    }
292
293    /**
294     * Returns a random free port and marks that port as taken. Not thread-safe. Expected to be
295     * called from single-threaded test setup code/
296     */
297    public int randomFreePort() {
298      int port = 0;
299      do {
300        port = randomPort();
301        if (takenRandomPorts.contains(port)) {
302          port = 0;
303          continue;
304        }
305        takenRandomPorts.add(port);
306
307        if (!portChecker.available(port)) {
308          port = 0;
309        }
310      } while (port == 0);
311      return port;
312    }
313
314    /**
315     * Returns a random port. These ports cannot be registered with IANA and are intended for
316     * dynamic allocation (see http://bit.ly/dynports).
317     */
318    private int randomPort() {
319      return MIN_RANDOM_PORT + random.nextInt(MAX_RANDOM_PORT - MIN_RANDOM_PORT);
320    }
321
322    interface AvailablePortChecker {
323      boolean available(int port);
324    }
325  }
326}