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.util;
019
020import java.io.File;
021import java.io.IOException;
022import java.util.UUID;
023import java.util.concurrent.ThreadLocalRandom;
024import org.apache.hadoop.conf.Configuration;
025import org.apache.hadoop.fs.FileSystem;
026import org.apache.hadoop.fs.Path;
027import org.apache.hadoop.hbase.fs.HFileSystem;
028import org.apache.yetus.audience.InterfaceAudience;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032/**
033 * This class contains code copied from HBaseTestingUtil and its super classes required by
034 * WALPerformanceEvaluation. This was done as part of refactoring for hbase-diagnostics module
035 * creation in HBASE-28432 to break cyclic dependency.
036 */
037@InterfaceAudience.Private
038public class WALPerformanceEvaluationUtil {
039  private static final Logger LOG = LoggerFactory.getLogger(WALPerformanceEvaluationUtil.class);
040
041  /**
042   * Directory on test filesystem where we put the data for this instance of
043   * WALPerformanceEvaluationUtil
044   */
045  private Path dataTestDirOnTestFS = null;
046  /**
047   * Directory where we put the data for this instance of WALPerformanceEvaluationUtil
048   */
049  private File dataTestDir = null;
050  /**
051   * System property key to get base test directory value
052   */
053  private static final String BASE_TEST_DIRECTORY_KEY = "test.build.data.basedirectory";
054
055  /**
056   * Default base directory for test output.
057   */
058  private static final String DEFAULT_BASE_TEST_DIRECTORY = "target/test-data";
059
060  private Configuration conf;
061
062  public WALPerformanceEvaluationUtil(Configuration conf) {
063    this.conf = conf;
064  }
065
066  /**
067   * @return Where to write test data on local filesystem; usually
068   *         {@link #DEFAULT_BASE_TEST_DIRECTORY} Should not be used by the unit tests, hence its's
069   *         private. Unit test will use a subdirectory of this directory.
070   * @see #setupDataTestDir()
071   */
072  private Path getBaseTestDir() {
073    String PathName = System.getProperty(BASE_TEST_DIRECTORY_KEY, DEFAULT_BASE_TEST_DIRECTORY);
074
075    return new Path(PathName);
076  }
077
078  private static UUID getRandomUUID() {
079    return new UUID(ThreadLocalRandom.current().nextLong(), ThreadLocalRandom.current().nextLong());
080  }
081
082  /**
083   * @return A dir with a random (uuid) name under the test dir
084   * @see #getBaseTestDir()
085   */
086  private Path getRandomDir() {
087    return new Path(getBaseTestDir(), getRandomUUID().toString());
088  }
089
090  private void createSubDir(String propertyName, Path parent, String subDirName) {
091    Path newPath = new Path(parent, subDirName);
092    File newDir = new File(newPath.toString()).getAbsoluteFile();
093
094    if (deleteOnExit()) {
095      newDir.deleteOnExit();
096    }
097
098    conf.set(propertyName, newDir.getAbsolutePath());
099  }
100
101  /**
102   * Home our data in a dir under {@link #DEFAULT_BASE_TEST_DIRECTORY}. Give it a random name so can
103   * have many concurrent tests running if we need to. Moding a System property is not the way to do
104   * concurrent instances -- another instance could grab the temporary value unintentionally -- but
105   * not anything can do about it at moment; single instance only is how the minidfscluster works.
106   * We also create the underlying directory names for hadoop.log.dir, mapreduce.cluster.local.dir
107   * and hadoop.tmp.dir, and set the values in the conf, and as a system property for hadoop.tmp.dir
108   * (We do not create them!).
109   * @return The calculated data test build directory, if newly-created.
110   */
111  protected Path setupDataTestDir() {
112    Path testPath = setupDataTestDirInternal();
113    if (null == testPath) {
114      return null;
115    }
116
117    createSubDirAndSystemProperty("hadoop.log.dir", testPath, "hadoop-log-dir");
118
119    // This is defaulted in core-default.xml to /tmp/hadoop-${user.name}, but
120    // we want our own value to ensure uniqueness on the same machine
121    createSubDirAndSystemProperty("hadoop.tmp.dir", testPath, "hadoop-tmp-dir");
122
123    // Read and modified in org.apache.hadoop.mapred.MiniMRCluster
124    createSubDir("mapreduce.cluster.local.dir", testPath, "mapred-local-dir");
125    return testPath;
126  }
127
128  /**
129   * Sets up a directory for a test to use.
130   * @return New directory path, if created.
131   */
132  private Path setupDataTestDirInternal() {
133    if (this.dataTestDir != null) {
134      LOG.warn("Data test dir already setup in " + dataTestDir.getAbsolutePath());
135      return null;
136    }
137    Path testPath = getRandomDir();
138    this.dataTestDir = new File(testPath.toString()).getAbsoluteFile();
139    // Set this property so if mapreduce jobs run, they will use this as their home dir.
140    System.setProperty("test.build.dir", this.dataTestDir.toString());
141
142    if (deleteOnExit()) {
143      this.dataTestDir.deleteOnExit();
144    }
145
146    createSubDir("hbase.local.dir", testPath, "hbase-local-dir");
147
148    return testPath;
149  }
150
151  private void createSubDirAndSystemProperty(String propertyName, Path parent, String subDirName) {
152
153    String sysValue = System.getProperty(propertyName);
154
155    if (sysValue != null) {
156      // There is already a value set. So we do nothing but hope
157      // that there will be no conflicts
158      LOG.info("System.getProperty(\"" + propertyName + "\") already set to: " + sysValue
159        + " so I do NOT create it in " + parent);
160      String confValue = conf.get(propertyName);
161      if (confValue != null && !confValue.endsWith(sysValue)) {
162        LOG.warn(propertyName + " property value differs in configuration and system: "
163          + "Configuration=" + confValue + " while System=" + sysValue
164          + " Erasing configuration value by system value.");
165      }
166      conf.set(propertyName, sysValue);
167    } else {
168      // Ok, it's not set, so we create it as a subdirectory
169      createSubDir(propertyName, parent, subDirName);
170      System.setProperty(propertyName, conf.get(propertyName));
171    }
172  }
173
174  private FileSystem getTestFileSystem() throws IOException {
175    return HFileSystem.get(conf);
176  }
177
178  /**
179   * @return Where to write test data on the test filesystem; Returns working directory for the test
180   *         filesystem by default
181   * @see #setupDataTestDirOnTestFS()
182   * @see #getTestFileSystem()
183   */
184  private Path getBaseTestDirOnTestFS() throws IOException {
185    FileSystem fs = getTestFileSystem();
186    return new Path(fs.getWorkingDirectory(), "test-data");
187  }
188
189  /**
190   * Returns True if we should delete testing dirs on exit.
191   */
192  private boolean deleteOnExit() {
193    String v = System.getProperty("hbase.testing.preserve.testdir");
194    // Let default be true, to delete on exit.
195    return v == null ? true : !Boolean.parseBoolean(v);
196  }
197
198  /**
199   * @return Where to write test data on local filesystem, specific to the test. Useful for tests
200   *         that do not use a cluster. Creates it if it does not exist already.
201   */
202  private Path getDataTestDir() {
203    if (this.dataTestDir == null) {
204      setupDataTestDir();
205    }
206    return new Path(this.dataTestDir.getAbsolutePath());
207  }
208
209  /**
210   * Sets up a new path in test filesystem to be used by tests.
211   */
212  private Path getNewDataTestDirOnTestFS() throws IOException {
213    // The file system can be either local, mini dfs, or if the configuration
214    // is supplied externally, it can be an external cluster FS. If it is a local
215    // file system, the tests should use getBaseTestDir, otherwise, we can use
216    // the working directory, and create a unique sub dir there
217    FileSystem fs = getTestFileSystem();
218    Path newDataTestDir;
219    String randomStr = getRandomUUID().toString();
220    if (fs.getUri().getScheme().equals(FileSystem.getLocal(conf).getUri().getScheme())) {
221      newDataTestDir = new Path(getDataTestDir(), randomStr);
222      File dataTestDir = new File(newDataTestDir.toString());
223      if (deleteOnExit()) {
224        dataTestDir.deleteOnExit();
225      }
226    } else {
227      Path base = getBaseTestDirOnTestFS();
228      newDataTestDir = new Path(base, randomStr);
229      if (deleteOnExit()) {
230        fs.deleteOnExit(newDataTestDir);
231      }
232    }
233    return newDataTestDir;
234  }
235
236  /**
237   * Sets up a path in test filesystem to be used by tests. Creates a new directory if not already
238   * setup.
239   */
240  private void setupDataTestDirOnTestFS() throws IOException {
241    if (dataTestDirOnTestFS != null) {
242      LOG.warn("Data test on test fs dir already setup in " + dataTestDirOnTestFS.toString());
243      return;
244    }
245    dataTestDirOnTestFS = getNewDataTestDirOnTestFS();
246  }
247
248  /**
249   * Returns a Path in the test filesystem, obtained from {@link #getTestFileSystem()} to write
250   * temporary test data. Call this method after setting up the mini dfs cluster if the test relies
251   * on it.
252   * @return a unique path in the test filesystem
253   */
254  private Path getDataTestDirOnTestFS() throws IOException {
255    if (dataTestDirOnTestFS == null) {
256      setupDataTestDirOnTestFS();
257    }
258
259    return dataTestDirOnTestFS;
260  }
261
262  /**
263   * Returns a Path in the test filesystem, obtained from {@link #getTestFileSystem()} to write
264   * temporary test data. Call this method after setting up the mini dfs cluster if the test relies
265   * on it.
266   * @param subdirName name of the subdir to create under the base test dir
267   * @return a unique path in the test filesystem
268   */
269  public Path getDataTestDirOnTestFS(final String subdirName) throws IOException {
270    return new Path(getDataTestDirOnTestFS(), subdirName);
271  }
272}