| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.hadoop.hbase; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.ServerSocket; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.concurrent.ThreadLocalRandom; |
| import org.apache.commons.io.FileUtils; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.fs.Path; |
| import org.apache.hadoop.hbase.Waiter.Predicate; |
| import org.apache.hadoop.hbase.io.compress.Compression; |
| import org.apache.yetus.audience.InterfaceAudience; |
| import org.apache.yetus.audience.InterfaceStability; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Common helpers for testing HBase that do not depend on specific server/etc. things. |
| */ |
| @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.PHOENIX) |
| @InterfaceStability.Evolving |
| public class HBaseCommonTestingUtil { |
| protected static final Logger LOG = LoggerFactory.getLogger(HBaseCommonTestingUtil.class); |
| |
| /** |
| * Compression algorithms to use in parameterized JUnit 4 tests |
| */ |
| public static final List<Object[]> COMPRESSION_ALGORITHMS_PARAMETERIZED = |
| Arrays.asList(new Object[][] { { Compression.Algorithm.NONE }, { Compression.Algorithm.GZ } }); |
| |
| /** |
| * This is for unit tests parameterized with a two booleans. |
| */ |
| public static final List<Object[]> BOOLEAN_PARAMETERIZED = |
| Arrays.asList(new Object[][] { { false }, { true } }); |
| |
| /** |
| * Compression algorithms to use in testing |
| */ |
| public static final Compression.Algorithm[] COMPRESSION_ALGORITHMS = |
| { Compression.Algorithm.NONE, Compression.Algorithm.GZ }; |
| |
| protected final Configuration conf; |
| |
| public HBaseCommonTestingUtil() { |
| this(null); |
| } |
| |
| public HBaseCommonTestingUtil(Configuration conf) { |
| this.conf = (conf == null ? HBaseConfiguration.create() : conf); |
| } |
| |
| /** |
| * Returns this classes's instance of {@link Configuration}. |
| * @return Instance of Configuration. |
| */ |
| public Configuration getConfiguration() { |
| return this.conf; |
| } |
| |
| /** |
| * System property key to get base test directory value |
| */ |
| public static final String BASE_TEST_DIRECTORY_KEY = "test.build.data.basedirectory"; |
| |
| /** |
| * Default base directory for test output. |
| */ |
| public static final String DEFAULT_BASE_TEST_DIRECTORY = "target/test-data"; |
| |
| /** |
| * Directory where we put the data for this instance of HBaseTestingUtility |
| */ |
| private File dataTestDir = null; |
| |
| /** |
| * Returns Where to write test data on local filesystem, specific to the test. Useful for tests |
| * that do not use a cluster. Creates it if it does not exist already. |
| */ |
| public Path getDataTestDir() { |
| if (this.dataTestDir == null) { |
| setupDataTestDir(); |
| } |
| return new Path(this.dataTestDir.getAbsolutePath()); |
| } |
| |
| /** |
| * Returns the path to a subdirectory or file named {code subdirName} under |
| * {@link #getDataTestDir()}. Does *NOT* create the directory or file if it does not exist. |
| * @param name the name of a subdirectory or file in the test data directory |
| */ |
| public Path getDataTestDir(final String name) { |
| return new Path(getDataTestDir(), name); |
| } |
| |
| /** |
| * Sets up a directory for a test to use. |
| * @return New directory path, if created. |
| */ |
| protected Path setupDataTestDir() { |
| if (this.dataTestDir != null) { |
| LOG.warn("Data test dir already setup in " + dataTestDir.getAbsolutePath()); |
| return null; |
| } |
| Path testPath = getRandomDir(); |
| this.dataTestDir = new File(testPath.toString()).getAbsoluteFile(); |
| // Set this property so if mapreduce jobs run, they will use this as their home dir. |
| System.setProperty("test.build.dir", this.dataTestDir.toString()); |
| |
| if (deleteOnExit()) { |
| this.dataTestDir.deleteOnExit(); |
| } |
| |
| createSubDir("hbase.local.dir", testPath, "hbase-local-dir"); |
| |
| return testPath; |
| } |
| |
| /** |
| * Returns a dir with a random (uuid) name under the test dir |
| * @see #getBaseTestDir() |
| */ |
| public Path getRandomDir() { |
| return new Path(getBaseTestDir(), getRandomUUID().toString()); |
| } |
| |
| public static UUID getRandomUUID() { |
| return new UUID(ThreadLocalRandom.current().nextLong(), ThreadLocalRandom.current().nextLong()); |
| } |
| |
| protected void createSubDir(String propertyName, Path parent, String subDirName) { |
| Path newPath = new Path(parent, subDirName); |
| File newDir = new File(newPath.toString()).getAbsoluteFile(); |
| |
| if (deleteOnExit()) { |
| newDir.deleteOnExit(); |
| } |
| |
| conf.set(propertyName, newDir.getAbsolutePath()); |
| } |
| |
| /** |
| * Returns true if we should delete testing dirs on exit. |
| */ |
| boolean deleteOnExit() { |
| String v = System.getProperty("hbase.testing.preserve.testdir"); |
| // Let default be true, to delete on exit. |
| return v == null ? true : !Boolean.parseBoolean(v); |
| } |
| |
| /** |
| * Returns true if we removed the test dirs |
| */ |
| public boolean cleanupTestDir() { |
| if (deleteDir(this.dataTestDir)) { |
| this.dataTestDir = null; |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns true if we removed the test dir |
| * @param subdir Test subdir name. |
| */ |
| public boolean cleanupTestDir(final String subdir) { |
| if (this.dataTestDir == null) { |
| return false; |
| } |
| return deleteDir(new File(this.dataTestDir, subdir)); |
| } |
| |
| /** |
| * Returns Where to write test data on local filesystem; usually |
| * {@link #DEFAULT_BASE_TEST_DIRECTORY} Should not be used by the unit tests, hence its's private. |
| * Unit test will use a subdirectory of this directory. |
| * @see #setupDataTestDir() |
| */ |
| private Path getBaseTestDir() { |
| String PathName = System.getProperty(BASE_TEST_DIRECTORY_KEY, DEFAULT_BASE_TEST_DIRECTORY); |
| |
| return new Path(PathName); |
| } |
| |
| /** |
| * Returns true if we deleted it. |
| * @param dir Directory to delete |
| */ |
| boolean deleteDir(final File dir) { |
| if (dir == null || !dir.exists()) { |
| return true; |
| } |
| int ntries = 0; |
| do { |
| ntries += 1; |
| try { |
| if (deleteOnExit()) { |
| FileUtils.deleteDirectory(dir); |
| } |
| |
| return true; |
| } catch (IOException ex) { |
| LOG.warn("Failed to delete " + dir.getAbsolutePath()); |
| } catch (IllegalArgumentException ex) { |
| LOG.warn("Failed to delete " + dir.getAbsolutePath(), ex); |
| } |
| } while (ntries < 30); |
| |
| return false; |
| } |
| |
| /** |
| * Wrapper method for {@link Waiter#waitFor(Configuration, long, Predicate)}. |
| */ |
| public <E extends Exception> long waitFor(long timeout, Predicate<E> predicate) throws E { |
| return Waiter.waitFor(this.conf, timeout, predicate); |
| } |
| |
| /** |
| * Wrapper method for {@link Waiter#waitFor(Configuration, long, long, Predicate)}. |
| */ |
| public <E extends Exception> long waitFor(long timeout, long interval, Predicate<E> predicate) |
| throws E { |
| return Waiter.waitFor(this.conf, timeout, interval, predicate); |
| } |
| |
| /** |
| * Wrapper method for {@link Waiter#waitFor(Configuration, long, long, boolean, Predicate)}. |
| */ |
| public <E extends Exception> long waitFor(long timeout, long interval, boolean failIfTimeout, |
| Predicate<E> predicate) throws E { |
| return Waiter.waitFor(this.conf, timeout, interval, failIfTimeout, predicate); |
| } |
| |
| // Support for Random Port Generation. |
| static Random random = new Random(); |
| |
| private static final PortAllocator portAllocator = new PortAllocator(random); |
| |
| public static int randomFreePort() { |
| return portAllocator.randomFreePort(); |
| } |
| |
| static class PortAllocator { |
| private static final int MIN_RANDOM_PORT = 0xc000; |
| private static final int MAX_RANDOM_PORT = 0xfffe; |
| |
| /** A set of ports that have been claimed using {@link #randomFreePort()}. */ |
| private final Set<Integer> takenRandomPorts = new HashSet<>(); |
| |
| private final Random random; |
| private final AvailablePortChecker portChecker; |
| |
| public PortAllocator(Random random) { |
| this.random = random; |
| this.portChecker = new AvailablePortChecker() { |
| @Override |
| public boolean available(int port) { |
| try { |
| ServerSocket sock = new ServerSocket(port); |
| sock.close(); |
| return true; |
| } catch (IOException ex) { |
| return false; |
| } |
| } |
| }; |
| } |
| |
| public PortAllocator(Random random, AvailablePortChecker portChecker) { |
| this.random = random; |
| this.portChecker = portChecker; |
| } |
| |
| /** |
| * Returns a random free port and marks that port as taken. Not thread-safe. Expected to be |
| * called from single-threaded test setup code/ |
| */ |
| public int randomFreePort() { |
| int port = 0; |
| do { |
| port = randomPort(); |
| if (takenRandomPorts.contains(port)) { |
| port = 0; |
| continue; |
| } |
| takenRandomPorts.add(port); |
| |
| if (!portChecker.available(port)) { |
| port = 0; |
| } |
| } while (port == 0); |
| return port; |
| } |
| |
| /** |
| * Returns a random port. These ports cannot be registered with IANA and are intended for |
| * dynamic allocation (see http://bit.ly/dynports). |
| */ |
| private int randomPort() { |
| return MIN_RANDOM_PORT + random.nextInt(MAX_RANDOM_PORT - MIN_RANDOM_PORT); |
| } |
| |
| interface AvailablePortChecker { |
| boolean available(int port); |
| } |
| } |
| } |