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}