Java tutorial
/* * * Copyright 2012 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.netflix.simianarmy.basic; import java.io.InputStream; import java.lang.reflect.Constructor; import java.util.LinkedList; import java.util.Map.Entry; import java.util.Properties; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.netflix.simianarmy.CloudClient; import com.netflix.simianarmy.Monkey; import com.netflix.simianarmy.MonkeyCalendar; import com.netflix.simianarmy.MonkeyConfiguration; import com.netflix.simianarmy.MonkeyRecorder; import com.netflix.simianarmy.MonkeyRecorder.Event; import com.netflix.simianarmy.MonkeyScheduler; import com.netflix.simianarmy.aws.SimpleDBRecorder; import com.netflix.simianarmy.aws.STSAssumeRoleSessionCredentialsProvider; import com.netflix.simianarmy.client.aws.AWSClient; /** * The Class BasicSimianArmyContext. */ public class BasicSimianArmyContext implements Monkey.Context { /** The Constant LOGGER. */ private static final Logger LOGGER = LoggerFactory.getLogger(BasicSimianArmyContext.class); /** The configuration properties. */ private final Properties properties = new Properties(); /** The Constant MONKEY_THREADS. */ private static final int MONKEY_THREADS = 1; /** The scheduler. */ private MonkeyScheduler scheduler; /** The calendar. */ private MonkeyCalendar calendar; /** The config. */ private BasicConfiguration config; /** The client. */ private AWSClient client; /** The recorder. */ private MonkeyRecorder recorder; /** The reported events. */ private final LinkedList<Event> eventReport; /** The AWS credentials provider to be used. */ private AWSCredentialsProvider awsCredentialsProvider = new DefaultAWSCredentialsProviderChain(); /** If configured, the ARN of Role to be assumed. */ private final String assumeRoleArn; private final String account; private final String secret; private final String region; /** protected constructor as the Shell is meant to be subclassed. */ protected BasicSimianArmyContext(String... configFiles) { eventReport = new LinkedList<Event>(); // Load the config files into props following the provided order. for (String configFile : configFiles) { loadConfigurationFileIntoProperties(configFile); } LOGGER.info("The following are properties in the context."); for (Entry<Object, Object> prop : properties.entrySet()) { LOGGER.info(String.format("%s = %s", prop.getKey(), prop.getValue())); } config = new BasicConfiguration(properties); calendar = new BasicCalendar(config); account = config.getStr("simianarmy.client.aws.accountKey"); secret = config.getStr("simianarmy.client.aws.secretKey"); region = config.getStrOrElse("simianarmy.client.aws.region", "us-east-1"); assumeRoleArn = config.getStr("simianarmy.client.aws.assumeRoleArn"); if (assumeRoleArn != null) { this.awsCredentialsProvider = new STSAssumeRoleSessionCredentialsProvider(assumeRoleArn); } // if credentials are set explicitly make them available to the AWS SDK if (StringUtils.isNotBlank(account) && StringUtils.isNotBlank(secret)) { this.exportCredentials(account, secret); } createClient(); createScheduler(); createRecorder(); } /** loads the given config on top of the config read by previous calls. */ protected void loadConfigurationFileIntoProperties(String propertyFileName) { String propFile = System.getProperty(propertyFileName, "/" + propertyFileName); try { InputStream is = BasicSimianArmyContext.class.getResourceAsStream(propFile); try { properties.load(is); } finally { is.close(); } } catch (Exception e) { String msg = "Unable to load properties file " + propFile + " set System property \"" + propertyFileName + "\" to valid file"; LOGGER.error(msg); throw new RuntimeException(msg, e); } } private void createScheduler() { int freq = (int) config.getNumOrElse("simianarmy.scheduler.frequency", 1); TimeUnit freqUnit = TimeUnit.valueOf(config.getStrOrElse("simianarmy.scheduler.frequencyUnit", "HOURS")); int threads = (int) config.getNumOrElse("simianarmy.scheduler.threads", MONKEY_THREADS); setScheduler(new BasicScheduler(freq, freqUnit, threads)); } @SuppressWarnings("unchecked") private void createRecorder() { @SuppressWarnings("rawtypes") Class recorderClass = loadClientClass("simianarmy.client.recorder.class"); if (recorderClass == null || recorderClass.equals(SimpleDBRecorder.class)) { String domain = config.getStrOrElse("simianarmy.recorder.sdb.domain", "SIMIAN_ARMY"); if (client != null) { SimpleDBRecorder simpleDbRecorder = new SimpleDBRecorder(client, domain); simpleDbRecorder.init(); setRecorder(simpleDbRecorder); } } else { setRecorder((MonkeyRecorder) factory(recorderClass)); } } /** * Create the specific client with region taken from properties. * Override to provide your own client. */ protected void createClient() { createClient(region); } /** * Create the specific client within passed region, using the appropriate AWS credentials provider. * @param clientRegion */ protected void createClient(String clientRegion) { this.client = new AWSClient(clientRegion, awsCredentialsProvider); setCloudClient(this.client); } /** * Gets the AWS client. * @return the AWS client */ public AWSClient awsClient() { return client; } /** * Gets the region. * @return the region */ public String region() { return region; } @Override public void reportEvent(Event evt) { this.eventReport.add(evt); } @Override public void resetEventReport() { eventReport.clear(); } @Override public String getEventReport() { StringBuilder report = new StringBuilder(); for (Event event : this.eventReport) { report.append(String.format("%s %s (", event.eventType(), event.id())); boolean isFirst = true; for (Entry<String, String> field : event.fields().entrySet()) { if (!isFirst) { report.append(", "); } else { isFirst = false; } report.append(String.format("%s:%s", field.getKey(), field.getValue())); } report.append(")\n"); } return report.toString(); } /** * Exports credentials as Java system properties * to be picked up by AWS SDK clients. * @param accountKey * @param secretKey */ public void exportCredentials(String accountKey, String secretKey) { System.setProperty("aws.accessKeyId", accountKey); System.setProperty("aws.secretKey", secretKey); } /** {@inheritDoc} */ @Override public MonkeyScheduler scheduler() { return scheduler; } /** * Sets the scheduler. * * @param scheduler * the new scheduler */ protected void setScheduler(MonkeyScheduler scheduler) { this.scheduler = scheduler; } /** {@inheritDoc} */ @Override public MonkeyCalendar calendar() { return calendar; } /** * Sets the calendar. * * @param calendar * the new calendar */ protected void setCalendar(MonkeyCalendar calendar) { this.calendar = calendar; } /** {@inheritDoc} */ @Override public MonkeyConfiguration configuration() { return config; } /** * Sets the configuration. * * @param configuration * the new configuration */ protected void setConfiguration(MonkeyConfiguration configuration) { this.config = (BasicConfiguration) configuration; } /** {@inheritDoc} */ @Override public CloudClient cloudClient() { return client; } /** * Sets the cloud client. * * @param cloudClient * the new cloud client */ protected void setCloudClient(CloudClient cloudClient) { this.client = (AWSClient) cloudClient; } /** {@inheritDoc} */ @Override public MonkeyRecorder recorder() { return recorder; } /** * Sets the recorder. * * @param recorder * the new recorder */ protected void setRecorder(MonkeyRecorder recorder) { this.recorder = recorder; } /** * Gets the configuration properties. * @return the configuration properties */ protected Properties getProperties() { return this.properties; } /** * Gets the AWS credentials provider. * @return the AWS credentials provider */ public AWSCredentialsProvider getAwsCredentialsProvider() { return awsCredentialsProvider; } /** * Load a class specified by the config; for drop-in replacements. * (Duplicates a method in MonkeyServer; refactor to util?). * * @param key * @return */ @SuppressWarnings("rawtypes") private Class loadClientClass(String key) { ClassLoader classLoader = getClass().getClassLoader(); try { String clientClassName = config.getStrOrElse(key, null); if (clientClassName == null || clientClassName.isEmpty()) { LOGGER.info("using standard class for " + key); return null; } Class newClass = classLoader.loadClass(clientClassName); LOGGER.info("using " + key + " loaded " + newClass.getCanonicalName()); return newClass; } catch (ClassNotFoundException e) { throw new RuntimeException("Could not load " + key, e); } } /** * Generic factory to create monkey collateral types. * * @param <T> * the generic type to create * @param implClass * the actual concrete type to instantiate. * @return an object of the requested type */ private <T> T factory(Class<T> implClass) { try { // then find corresponding ctor for (Constructor<?> ctor : implClass.getDeclaredConstructors()) { Class<?>[] paramTypes = ctor.getParameterTypes(); if (paramTypes.length != 1) { continue; } if (paramTypes[0].getName().endsWith("Configuration")) { @SuppressWarnings("unchecked") T impl = (T) ctor.newInstance(config); return impl; } } // Last ditch; try no-arg. return implClass.newInstance(); } catch (Exception e) { LOGGER.error("context config error, cannot make an instance of " + implClass.getName(), e); } return null; } }