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 */ 018 019package org.apache.hadoop.hbase; 020 021import java.io.IOException; 022import java.util.Map.Entry; 023import java.util.Properties; 024import java.util.Set; 025 026import org.apache.commons.lang3.StringUtils; 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.hbase.chaos.factories.MonkeyConstants; 029import org.apache.hadoop.hbase.chaos.factories.MonkeyFactory; 030import org.apache.hadoop.hbase.chaos.monkies.ChaosMonkey; 031import org.apache.hadoop.hbase.util.AbstractHBaseTool; 032import org.junit.After; 033import org.junit.Before; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine; 038 039/** 040 * Base class for HBase integration tests that want to use the Chaos Monkey. 041 * Usage: bin/hbase <sub_class_of_IntegrationTestBase> <options> 042 * Options: -h,--help Show usage 043 * -m,--monkey <arg> Which chaos monkey to run 044 * -monkeyProps <arg> The properties file for specifying chaos monkey properties. 045 * -ncc Option to not clean up the cluster at the end. 046 */ 047public abstract class IntegrationTestBase extends AbstractHBaseTool { 048 049 public static final String NO_CLUSTER_CLEANUP_LONG_OPT = "noClusterCleanUp"; 050 public static final String MONKEY_LONG_OPT = "monkey"; 051 public static final String CHAOS_MONKEY_PROPS = "monkeyProps"; 052 private static final Logger LOG = LoggerFactory.getLogger(IntegrationTestBase.class); 053 054 protected IntegrationTestingUtility util; 055 protected ChaosMonkey monkey; 056 protected String monkeyToUse; 057 protected Properties monkeyProps; 058 protected boolean noClusterCleanUp = false; 059 060 public IntegrationTestBase() { 061 this(null); 062 } 063 064 public IntegrationTestBase(String monkeyToUse) { 065 this.monkeyToUse = monkeyToUse; 066 } 067 068 @Override 069 protected void addOptions() { 070 addOptWithArg("m", MONKEY_LONG_OPT, "Which chaos monkey to run"); 071 addOptNoArg("ncc", NO_CLUSTER_CLEANUP_LONG_OPT, 072 "Don't clean up the cluster at the end"); 073 addOptWithArg(CHAOS_MONKEY_PROPS, "The properties file for specifying chaos " 074 + "monkey properties."); 075 } 076 077 /** 078 * This allows tests that subclass children of this base class such as 079 * {@link org.apache.hadoop.hbase.test.IntegrationTestReplication} to 080 * include the base options without having to also include the options from the test. 081 * 082 * @param cmd the command line 083 */ 084 protected void processBaseOptions(CommandLine cmd) { 085 if (cmd.hasOption(MONKEY_LONG_OPT)) { 086 monkeyToUse = cmd.getOptionValue(MONKEY_LONG_OPT); 087 } 088 if (cmd.hasOption(NO_CLUSTER_CLEANUP_LONG_OPT)) { 089 noClusterCleanUp = true; 090 } 091 monkeyProps = new Properties(); 092 // Add entries for the CM from hbase-site.xml as a convenience. 093 // Do this prior to loading from the properties file to make sure those in the properties 094 // file are given precedence to those in hbase-site.xml (backwards compatibility). 095 loadMonkeyProperties(monkeyProps, conf); 096 if (cmd.hasOption(CHAOS_MONKEY_PROPS)) { 097 String chaosMonkeyPropsFile = cmd.getOptionValue(CHAOS_MONKEY_PROPS); 098 if (StringUtils.isNotEmpty(chaosMonkeyPropsFile)) { 099 try { 100 monkeyProps.load(this.getClass().getClassLoader() 101 .getResourceAsStream(chaosMonkeyPropsFile)); 102 } catch (IOException e) { 103 LOG.warn(e.toString(), e); 104 System.exit(EXIT_FAILURE); 105 } 106 } 107 } 108 } 109 110 /** 111 * Loads entries from the provided {@code conf} into {@code props} when the configuration key 112 * is one that may be configuring ChaosMonkey actions. 113 */ 114 public static void loadMonkeyProperties(Properties props, Configuration conf) { 115 for (Entry<String,String> entry : conf) { 116 for (String prefix : MonkeyConstants.MONKEY_CONFIGURATION_KEY_PREFIXES) { 117 if (entry.getKey().startsWith(prefix)) { 118 props.put(entry.getKey(), entry.getValue()); 119 break; 120 } 121 } 122 } 123 } 124 125 @Override 126 protected void processOptions(CommandLine cmd) { 127 processBaseOptions(cmd); 128 } 129 130 @Override 131 public Configuration getConf() { 132 Configuration c = super.getConf(); 133 if (c == null && util != null) { 134 conf = util.getConfiguration(); 135 c = conf; 136 } 137 return c; 138 } 139 140 @Override 141 protected int doWork() throws Exception { 142 ChoreService choreService = null; 143 144 // Launches chore for refreshing kerberos credentials if security is enabled. 145 // Please see http://hbase.apache.org/book.html#_running_canary_in_a_kerberos_enabled_cluster 146 // for more details. 147 final ScheduledChore authChore = AuthUtil.getAuthChore(conf); 148 if (authChore != null) { 149 choreService = new ChoreService("INTEGRATION_TEST"); 150 choreService.scheduleChore(authChore); 151 } 152 153 setUp(); 154 int result = -1; 155 try { 156 result = runTestFromCommandLine(); 157 } finally { 158 cleanUp(); 159 } 160 161 if (choreService != null) { 162 choreService.shutdown(); 163 } 164 165 return result; 166 } 167 168 @Before 169 public void setUp() throws Exception { 170 setUpCluster(); 171 setUpMonkey(); 172 } 173 174 @After 175 public void cleanUp() throws Exception { 176 cleanUpMonkey(); 177 cleanUpCluster(); 178 } 179 180 public void setUpMonkey() throws Exception { 181 util = getTestingUtil(getConf()); 182 MonkeyFactory fact = MonkeyFactory.getFactory(monkeyToUse); 183 if (fact == null) { 184 fact = getDefaultMonkeyFactory(); 185 } 186 LOG.info("Using chaos monkey factory: {}", fact.getClass()); 187 monkey = fact.setUtil(util) 188 .setTableName(getTablename()) 189 .setProperties(monkeyProps) 190 .setColumnFamilies(getColumnFamilies()).build(); 191 startMonkey(); 192 } 193 194 protected MonkeyFactory getDefaultMonkeyFactory() { 195 // Run with no monkey in distributed context, with real monkey in local test context. 196 return MonkeyFactory.getFactory( 197 util.isDistributedCluster() ? MonkeyFactory.CALM : MonkeyFactory.SLOW_DETERMINISTIC); 198 } 199 200 protected void startMonkey() throws Exception { 201 monkey.start(); 202 } 203 204 public void cleanUpMonkey() throws Exception { 205 cleanUpMonkey("Ending test"); 206 } 207 208 protected void cleanUpMonkey(String why) throws Exception { 209 if (monkey != null && !monkey.isStopped()) { 210 monkey.stop(why); 211 monkey.waitForStop(); 212 } 213 } 214 215 protected IntegrationTestingUtility getTestingUtil(Configuration conf) { 216 if (this.util == null) { 217 if (conf == null) { 218 this.util = new IntegrationTestingUtility(); 219 this.setConf(util.getConfiguration()); 220 } else { 221 this.util = new IntegrationTestingUtility(conf); 222 } 223 } 224 return util; 225 } 226 227 public abstract void setUpCluster() throws Exception; 228 229 public void cleanUpCluster() throws Exception { 230 if (util.isDistributedCluster() && (monkey == null || !monkey.isDestructive())) { 231 noClusterCleanUp = true; 232 } 233 if (noClusterCleanUp) { 234 LOG.debug("noClusterCleanUp is set, skip restoring the cluster"); 235 return; 236 } 237 LOG.debug("Restoring the cluster"); 238 util.restoreCluster(); 239 LOG.debug("Done restoring the cluster"); 240 } 241 242 public abstract int runTestFromCommandLine() throws Exception; 243 244 /** 245 * Provides the name of the table that is protected from random Chaos monkey activity 246 * @return table to not delete. 247 */ 248 public abstract TableName getTablename(); 249 250 /** 251 * Provides the name of the CFs that are protected from random Chaos monkey activity (alter) 252 * @return set of cf names to protect. 253 */ 254 protected abstract Set<String> getColumnFamilies(); 255}