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.jupiter.api.AfterEach; 031import org.junit.jupiter.api.BeforeEach; 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 140 // https://hbase.apache.org/docs/operational-management/tools#running-canary-in-a-kerberos-enabled-cluster 141 // for more details. 142 final ScheduledChore authChore = AuthUtil.getAuthChore(conf); 143 if (authChore != null) { 144 choreService = new ChoreService("INTEGRATION_TEST"); 145 choreService.scheduleChore(authChore); 146 } 147 148 setUp(); 149 int result = -1; 150 try { 151 result = runTestFromCommandLine(); 152 } finally { 153 cleanUp(); 154 } 155 156 if (choreService != null) { 157 choreService.shutdown(); 158 } 159 160 return result; 161 } 162 163 @BeforeEach 164 public void setUp() throws Exception { 165 setUpCluster(); 166 setUpMonkey(); 167 } 168 169 @AfterEach 170 public void cleanUp() throws Exception { 171 cleanUpMonkey(); 172 cleanUpCluster(); 173 } 174 175 public void setUpMonkey() throws Exception { 176 util = getTestingUtil(getConf()); 177 MonkeyFactory fact = MonkeyFactory.getFactory(monkeyToUse); 178 if (fact == null) { 179 fact = getDefaultMonkeyFactory(); 180 } 181 LOG.info("Using chaos monkey factory: {}", fact.getClass()); 182 monkey = fact.setUtil(util).setTableName(getTablename()).setProperties(monkeyProps) 183 .setColumnFamilies(getColumnFamilies()).build(); 184 startMonkey(); 185 } 186 187 protected MonkeyFactory getDefaultMonkeyFactory() { 188 // Run with no monkey in distributed context, with real monkey in local test context. 189 return MonkeyFactory.getFactory( 190 util.isDistributedCluster() ? MonkeyFactory.CALM : MonkeyFactory.SLOW_DETERMINISTIC); 191 } 192 193 protected void startMonkey() throws Exception { 194 monkey.start(); 195 } 196 197 public void cleanUpMonkey() throws Exception { 198 cleanUpMonkey("Ending test"); 199 } 200 201 protected void cleanUpMonkey(String why) throws Exception { 202 if (monkey != null && !monkey.isStopped()) { 203 monkey.stop(why); 204 monkey.waitForStop(); 205 } 206 } 207 208 protected IntegrationTestingUtility getTestingUtil(Configuration conf) { 209 if (this.util == null) { 210 if (conf == null) { 211 this.util = new IntegrationTestingUtility(); 212 this.setConf(util.getConfiguration()); 213 } else { 214 this.util = new IntegrationTestingUtility(conf); 215 } 216 } 217 return util; 218 } 219 220 public abstract void setUpCluster() throws Exception; 221 222 public void cleanUpCluster() throws Exception { 223 if (util.isDistributedCluster() && (monkey == null || !monkey.isDestructive())) { 224 noClusterCleanUp = true; 225 } 226 if (noClusterCleanUp) { 227 LOG.debug("noClusterCleanUp is set, skip restoring the cluster"); 228 return; 229 } 230 LOG.debug("Restoring the cluster"); 231 util.restoreCluster(); 232 LOG.debug("Done restoring the cluster"); 233 } 234 235 public abstract int runTestFromCommandLine() throws Exception; 236 237 /** 238 * Provides the name of the table that is protected from random Chaos monkey activity 239 * @return table to not delete. 240 */ 241 public abstract TableName getTablename(); 242 243 /** 244 * Provides the name of the CFs that are protected from random Chaos monkey activity (alter) 245 * @return set of cf names to protect. 246 */ 247 protected abstract Set<String> getColumnFamilies(); 248}