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.chaos.util; 019 020import java.io.IOException; 021import java.util.Properties; 022import java.util.Set; 023import org.apache.commons.lang3.StringUtils; 024import org.apache.hadoop.conf.Configuration; 025import org.apache.hadoop.hbase.HBaseConfiguration; 026import org.apache.hadoop.hbase.HConstants; 027import org.apache.hadoop.hbase.IntegrationTestingUtility; 028import org.apache.hadoop.hbase.TableName; 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.apache.hadoop.util.ToolRunner; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036import org.apache.hbase.thirdparty.com.google.common.collect.Sets; 037import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine; 038 039public class ChaosMonkeyRunner extends AbstractHBaseTool { 040 private static final Logger LOG = LoggerFactory.getLogger(ChaosMonkeyRunner.class); 041 public static final String MONKEY_LONG_OPT = "monkey"; 042 public static final String CHAOS_MONKEY_PROPS = "monkeyProps"; 043 public static final String TABLE_NAME_OPT = "tableName"; 044 public static final String FAMILY_NAME_OPT = "familyName"; 045 protected IntegrationTestingUtility util; 046 protected ChaosMonkey monkey; 047 protected String monkeyToUse; 048 protected Properties monkeyProps; 049 protected boolean noClusterCleanUp = false; 050 private String tableName = "ChaosMonkeyRunner.tableName"; 051 private String familyName = "ChaosMonkeyRunner.familyName"; 052 053 @Override 054 public void addOptions() { 055 // The -c option is processed down in the main, not along w/ other options. Added here so shows 056 // in the usage output. 057 addOptWithArg("c", "Name of extra configurations file to find on CLASSPATH"); 058 addOptWithArg("m", MONKEY_LONG_OPT, "Which chaos monkey to run"); 059 addOptWithArg(CHAOS_MONKEY_PROPS, 060 "The properties file for specifying chaos " + "monkey properties."); 061 addOptWithArg(TABLE_NAME_OPT, "Table name in the test to run chaos monkey against"); 062 addOptWithArg(FAMILY_NAME_OPT, "Family name in the test to run chaos monkey against"); 063 } 064 065 @Override 066 protected void processOptions(CommandLine cmd) { 067 if (cmd.hasOption(MONKEY_LONG_OPT)) { 068 monkeyToUse = cmd.getOptionValue(MONKEY_LONG_OPT); 069 } 070 monkeyProps = new Properties(); 071 if (cmd.hasOption(CHAOS_MONKEY_PROPS)) { 072 String chaosMonkeyPropsFile = cmd.getOptionValue(CHAOS_MONKEY_PROPS); 073 if (StringUtils.isNotEmpty(chaosMonkeyPropsFile)) { 074 try { 075 monkeyProps 076 .load(this.getClass().getClassLoader().getResourceAsStream(chaosMonkeyPropsFile)); 077 } catch (IOException e) { 078 LOG.warn(e.toString(), e); 079 System.exit(EXIT_FAILURE); 080 } 081 } 082 } 083 if (cmd.hasOption(TABLE_NAME_OPT)) { 084 this.tableName = cmd.getOptionValue(TABLE_NAME_OPT); 085 } 086 if (cmd.hasOption(FAMILY_NAME_OPT)) { 087 this.familyName = cmd.getOptionValue(FAMILY_NAME_OPT); 088 } 089 } 090 091 @Override 092 protected int doWork() throws Exception { 093 setUpCluster(); 094 getAndStartMonkey(); 095 while (!monkey.isStopped()) { 096 // loop here until got killed 097 try { 098 // TODO: make sleep time configurable 099 Thread.sleep(5000); // 5 seconds 100 } catch (InterruptedException ite) { 101 // Chaos monkeys got interrupted. 102 // It is ok to stop monkeys and exit. 103 monkey.stop("Interruption occurred."); 104 break; 105 } 106 } 107 monkey.waitForStop(); 108 return 0; 109 } 110 111 public void stopRunner() { 112 if (monkey != null) { 113 monkey.stop("Program Control"); 114 } 115 } 116 117 public void setUpCluster() throws Exception { 118 util = getTestingUtil(getConf()); 119 boolean isDistributed = isDistributedCluster(getConf()); 120 if (isDistributed) { 121 util.createDistributedHBaseCluster(); 122 util.checkNodeCount(1);// make sure there's at least 1 alive rs 123 } else { 124 throw new RuntimeException("ChaosMonkeyRunner must run against a distributed cluster," 125 + " please check and point to the right configuration dir"); 126 } 127 this.setConf(util.getConfiguration()); 128 } 129 130 private boolean isDistributedCluster(Configuration conf) { 131 return conf.getBoolean(HConstants.CLUSTER_DISTRIBUTED, false); 132 } 133 134 public void getAndStartMonkey() throws Exception { 135 util = getTestingUtil(getConf()); 136 MonkeyFactory fact = MonkeyFactory.getFactory(monkeyToUse); 137 if (fact == null) { 138 fact = getDefaultMonkeyFactory(); 139 } 140 monkey = fact.setUtil(util).setTableName(getTablename()).setProperties(monkeyProps) 141 .setColumnFamilies(getColumnFamilies()).build(); 142 monkey.start(); 143 } 144 145 protected IntegrationTestingUtility getTestingUtil(Configuration conf) { 146 if (this.util == null) { 147 if (conf == null) { 148 this.util = new IntegrationTestingUtility(); 149 this.setConf(util.getConfiguration()); 150 } else { 151 this.util = new IntegrationTestingUtility(conf); 152 } 153 } 154 return util; 155 } 156 157 protected MonkeyFactory getDefaultMonkeyFactory() { 158 // Run with slow deterministic monkey by default 159 return MonkeyFactory.getFactory(MonkeyFactory.SLOW_DETERMINISTIC); 160 } 161 162 public TableName getTablename() { 163 return TableName.valueOf(tableName); 164 } 165 166 protected Set<String> getColumnFamilies() { 167 return Sets.newHashSet(familyName); 168 } 169 170 /** 171 * If caller wants to add config parameters from a file, the path to the conf file can be passed 172 * like this: -c <path-to-conf>. The file is presumed to have the Configuration file xml format 173 * and is added as a new Resource to the current Configuration. Use this mechanism to set 174 * Configuration such as what ClusterManager to use, etc. Here is an example file you might 175 * references that sets an alternate ClusterManager: {code} <?xml version="1.0" encoding="UTF-8"?> 176 * <configuration> <property> <name>hbase.it.clustermanager.class</name> 177 * <value>org.apache.hadoop.hbase.MyCustomClusterManager</value> </property> </configuration> 178 * {code} NOTE: The code searches for the file name passed on the CLASSPATH! Passing the path to a 179 * file will not work! Add the file to the CLASSPATH and then pass the filename as the '-c' arg. 180 */ 181 public static void main(String[] args) throws Exception { 182 Configuration conf = HBaseConfiguration.create(); 183 String[] actualArgs = args; 184 if (args.length > 0 && "-c".equals(args[0])) { 185 int argCount = args.length - 2; 186 if (argCount < 0) { 187 throw new IllegalArgumentException("Missing path for -c parameter"); 188 } 189 // Load the resource specified by the second parameter. We load from the classpath, not 190 // from filesystem path. 191 conf.addResource(args[1]); 192 actualArgs = new String[argCount]; 193 System.arraycopy(args, 2, actualArgs, 0, argCount); 194 } 195 IntegrationTestingUtility.setUseDistributedCluster(conf); 196 int ret = ToolRunner.run(conf, new ChaosMonkeyRunner(), actualArgs); 197 System.exit(ret); 198 } 199}