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}