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