diff --git a/build.gradle b/build.gradle index e751322b..b935ee32 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,15 @@ apply plugin: 'jetty' sourceCompatibility = 1.7 targetCompatibility = 1.7 +configurations.all { + resolutionStrategy.eachDependency() { + DependencyResolveDetails details -> + if (details.requested.group == 'com.google.inject.extensions') { + details.useVersion '4.0' + } + } +} + dependencies { // for the VMWareClient compile 'com.cloudbees.thirdparty:vijava:5.0.0' @@ -43,10 +52,10 @@ dependencies { compile 'com.google.guava:guava:11.0.2' compile 'org.apache.httpcomponents:httpclient:4.3' compile 'com.google.auto.service:auto-service:1.0-rc2' - compile 'org.apache.jclouds.driver:jclouds-jsch:1.9.0' - compile 'org.apache.jclouds.driver:jclouds-slf4j:1.9.0' - compile 'org.apache.jclouds.api:ec2:1.9.0' - compile 'org.apache.jclouds.provider:aws-ec2:1.9.0' + compile 'org.apache.jclouds.driver:jclouds-jsch:2.0.0' + compile 'org.apache.jclouds.driver:jclouds-slf4j:2.0.0' + compile 'org.apache.jclouds.api:ec2:2.0.0' + compile 'org.apache.jclouds.provider:aws-ec2:2.0.0' compile 'com.netflix.servo:servo-core:0.12.11' compile 'org.springframework:spring-jdbc:4.2.5.RELEASE' compile 'com.zaxxer:HikariCP:2.4.7' diff --git a/src/main/java/com/netflix/simianarmy/Instance.java b/src/main/java/com/netflix/simianarmy/Instance.java new file mode 100644 index 00000000..f6e763f5 --- /dev/null +++ b/src/main/java/com/netflix/simianarmy/Instance.java @@ -0,0 +1,59 @@ +/* + * + * 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; + +import com.netflix.simianarmy.Tag; +import java.util.List; + + +/** + * The interface that holds the EC2 instance metadata + */ + +public interface Instance { + + /** + * Gets instance id. + * + * @return instance id as string + */ + String getInstanceId(); + + /** + * Gets name. + * + * @return name as string + */ + String getName(); + + /** + * Gets host name. + * + * @return host name as string + */ + String getHostname(); + + /** + * Gets the user created tags for the instance. + * + * @return list of tags + */ + List getTags(); + + +} diff --git a/src/main/java/com/netflix/simianarmy/NoInstanceWithTagsFoundException.java b/src/main/java/com/netflix/simianarmy/NoInstanceWithTagsFoundException.java new file mode 100644 index 00000000..14c639cb --- /dev/null +++ b/src/main/java/com/netflix/simianarmy/NoInstanceWithTagsFoundException.java @@ -0,0 +1,55 @@ +/* + * + * 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; +import com.netflix.simianarmy.Tag; + +import java.util.List; + +/** + * The Class NoInstanceWithTagsFoundException. + * + * These exceptions will be thrown when an instance with all tags are NOT found + */ +public class NoInstanceWithTagsFoundException extends Exception { + private static final long serialVersionUID = 01072016L; + private List ec2Tags; + + /** + * Instantiates an NoInstanceWithTagsFoundException with the tags. + * @param ec2Tags + */ + public NoInstanceWithTagsFoundException(List ec2Tags) { + super(errorMessage(ec2Tags)); + this.ec2Tags = ec2Tags; + } + + @Override + public String toString() { + return errorMessage(ec2Tags); + } + + private static String errorMessage(List ec2Tags) { + StringBuilder error = new StringBuilder(1000); + error.append(" No Instances with the following tags were found: "); + for (Tag ec2Tag : ec2Tags) { + error.append(ec2Tag.getKey() + ":" + ec2Tag.getValue() + ", "); + } + return error.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/netflix/simianarmy/Tag.java b/src/main/java/com/netflix/simianarmy/Tag.java new file mode 100644 index 00000000..7ca93e1d --- /dev/null +++ b/src/main/java/com/netflix/simianarmy/Tag.java @@ -0,0 +1,78 @@ +/* + * + * 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; + +import java.util.Objects; + +/** + * The Class that holds the EC2 tags in name value pair. + */ +public class Tag { + + private String key; + private String value; + + public Tag() {} + + public Tag(String key, String value) { + this.key = key; + this.value = value; + } + + @Override + public String toString() { + return "Tag{" + + "key='" + key + '\'' + + ", value='" + value + '\'' + + '}'; + } + + @Override + public int hashCode() { + return (key+value).hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Tag tag = (Tag) obj; + return Objects.equals(key, tag.getKey()) + && Objects.equals(value, tag.getValue()); + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + public void setName(String key) { + this.key = key; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/src/main/java/com/netflix/simianarmy/basic/BasicInstance.java b/src/main/java/com/netflix/simianarmy/basic/BasicInstance.java new file mode 100644 index 00000000..815f4da7 --- /dev/null +++ b/src/main/java/com/netflix/simianarmy/basic/BasicInstance.java @@ -0,0 +1,62 @@ +/* + * + * 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 com.netflix.simianarmy.Instance; +import com.netflix.simianarmy.Tag; + +import java.util.List; + +public class BasicInstance implements Instance { + + private String instanceId; + private String name; + private String hostName; + private List tags; + + public BasicInstance(String instanceId) { + this.instanceId = instanceId; + } + + public BasicInstance(String instanceId, String name, String hostName, List tags) { + this.instanceId = instanceId; + this.name = name; + this.hostName = hostName; + this.tags = tags; + } + + @Override + public String getInstanceId() { + return instanceId; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getHostname() { + return hostName; + } + + @Override + public List getTags() { + return tags; + } +} diff --git a/src/main/java/com/netflix/simianarmy/basic/chaos/BasicChaosInstanceSelector.java b/src/main/java/com/netflix/simianarmy/basic/chaos/BasicChaosInstanceSelector.java index 6f0c5055..91875516 100644 --- a/src/main/java/com/netflix/simianarmy/basic/chaos/BasicChaosInstanceSelector.java +++ b/src/main/java/com/netflix/simianarmy/basic/chaos/BasicChaosInstanceSelector.java @@ -17,19 +17,18 @@ */ package com.netflix.simianarmy.basic.chaos; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Random; +import java.util.*; import com.google.common.collect.Lists; +import com.netflix.simianarmy.Instance; import org.apache.commons.lang.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.netflix.simianarmy.chaos.ChaosCrawler.InstanceGroup; import com.netflix.simianarmy.chaos.ChaosInstanceSelector; - +import com.netflix.simianarmy.Tag; +import com.netflix.simianarmy.NoInstanceWithTagsFoundException; /** * The Class BasicChaosInstanceSelector. */ @@ -54,13 +53,35 @@ protected Logger logger() { public Collection select(InstanceGroup group, double probability) { int n = ((int) probability); String selected = selectOneInstance(group, probability - n); - Collection result = selectNInstances(group.instances(), n, selected); + Collection result = selectNInstances(group.instanceIds(), n, selected); if (selected != null) { result.add(selected); } return result; } + /** {@inheritDoc} */ + @Override + public Collection selectOneByTags(InstanceGroup group, List ec2TagsSent) throws NoInstanceWithTagsFoundException { + List matchInstances = new ArrayList(); + Set ec2TagsSentSet = new HashSet(ec2TagsSent); + + for (Instance inst : group.instances()) { + Set ec2TagsInstSet = new HashSet(inst.getTags()); + if (ec2TagsInstSet.containsAll(ec2TagsSentSet)) + matchInstances.add(inst.getInstanceId()); + } + Collection result = null; + if (matchInstances.size() > 0){ + result = selectNInstances(matchInstances, 1, null); + } + if (result == null) { + logger().info("No instances with those sent Tags were found"); + throw new NoInstanceWithTagsFoundException(ec2TagsSent); + } + return result; + } + private Collection selectNInstances(Collection instances, int n, String selected) { logger().info("Randomly selecting {} from {} instances, excluding {}", new Object[] {n, instances.size(), selected}); @@ -90,6 +111,6 @@ private String selectOneInstance(InstanceGroup group, double probability) { new Object[] {group.name(), group.type(), rand, probability}); return null; } - return group.instances().get(RANDOM.nextInt(group.instances().size())); + return group.instanceIds().get(RANDOM.nextInt(group.instanceIds().size())); } } diff --git a/src/main/java/com/netflix/simianarmy/basic/chaos/BasicChaosMonkey.java b/src/main/java/com/netflix/simianarmy/basic/chaos/BasicChaosMonkey.java index a32b9c6f..71ddf2a4 100644 --- a/src/main/java/com/netflix/simianarmy/basic/chaos/BasicChaosMonkey.java +++ b/src/main/java/com/netflix/simianarmy/basic/chaos/BasicChaosMonkey.java @@ -152,8 +152,8 @@ private ChaosType pickChaosType(CloudClient cloudClient, String instanceId) { } @Override - public Event terminateNow(String type, String name, ChaosType chaosType) - throws FeatureNotEnabledException, InstanceGroupNotFoundException { + public Event terminateNow(String type, String name, ChaosType chaosType, List ec2TagsSent) + throws FeatureNotEnabledException, InstanceGroupNotFoundException, NoInstanceWithTagsFoundException { Validate.notNull(type); Validate.notNull(name); cfg.reload(name); @@ -169,7 +169,12 @@ public Event terminateNow(String type, String name, ChaosType chaosType) if (group == null) { throw new InstanceGroupNotFoundException(type, name); } - Collection instances = context().chaosInstanceSelector().select(group, 1.0); + Collection instances = null; + if (ec2TagsSent == null) + instances = context().chaosInstanceSelector().select(group, 1.0); + else { + instances = context().chaosInstanceSelector().selectOneByTags(group, ec2TagsSent); + } Validate.isTrue(instances.size() <= 1); if (instances.size() == 1) { return terminateInstance(group, instances.iterator().next(), chaosType); diff --git a/src/main/java/com/netflix/simianarmy/basic/chaos/BasicInstanceGroup.java b/src/main/java/com/netflix/simianarmy/basic/chaos/BasicInstanceGroup.java index c0cdbeeb..70368280 100644 --- a/src/main/java/com/netflix/simianarmy/basic/chaos/BasicInstanceGroup.java +++ b/src/main/java/com/netflix/simianarmy/basic/chaos/BasicInstanceGroup.java @@ -17,12 +17,14 @@ */ package com.netflix.simianarmy.basic.chaos; +import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import com.amazonaws.services.autoscaling.model.TagDescription; import com.netflix.simianarmy.GroupType; +import com.netflix.simianarmy.Instance; import com.netflix.simianarmy.chaos.ChaosCrawler.InstanceGroup; /** @@ -82,17 +84,26 @@ public List tags() { } /** The list. */ - private List list = new LinkedList(); + private List list = new LinkedList(); /** {@inheritDoc} */ @Override - public List instances() { + public List instances() { return Collections.unmodifiableList(list); } + @Override + public List instanceIds() { + List instanceIds = new ArrayList(); + for (Instance instance : list) { + instanceIds.add(instance.getInstanceId()); + } + return Collections.unmodifiableList(instanceIds); + } + /** {@inheritDoc} */ @Override - public void addInstance(String instance) { + public void addInstance(Instance instance) { list.add(instance); } @@ -100,9 +111,15 @@ public void addInstance(String instance) { @Override public BasicInstanceGroup copyAs(String newName) { BasicInstanceGroup newGroup = new BasicInstanceGroup(newName, this.type(), this.region(), this.tags()); - for (String instance: this.instances()) { + for (Instance instance: this.instances()) { newGroup.addInstance(instance); } return newGroup; } + + @Override + public void addInstanceList(List instances) { + //need to add + list.addAll(instances); + }; } diff --git a/src/main/java/com/netflix/simianarmy/chaos/ChaosCrawler.java b/src/main/java/com/netflix/simianarmy/chaos/ChaosCrawler.java index fcc3df10..43c3271d 100644 --- a/src/main/java/com/netflix/simianarmy/chaos/ChaosCrawler.java +++ b/src/main/java/com/netflix/simianarmy/chaos/ChaosCrawler.java @@ -22,6 +22,7 @@ import com.amazonaws.services.autoscaling.model.TagDescription; import com.netflix.simianarmy.GroupType; +import com.netflix.simianarmy.Instance; /** * The Interface ChaosCrawler. @@ -66,15 +67,22 @@ public interface InstanceGroup { * * @return the list of instances */ - List instances(); + List instances(); + + /** + * Instances Ids as Strings. + * + * @return the list list of instances as strings + */ + List instanceIds(); /** * Adds the instance. * * @param instance - * the instance + * */ - void addInstance(String instance); + void addInstance(Instance instance); /** * Copies the Instance group replacing its name with @@ -85,6 +93,15 @@ public interface InstanceGroup { * @return the new instance group */ InstanceGroup copyAs(String name); + + /** + * Adds the list of instances + * the supplied name. + * + * @param instanceIds + * + */ + void addInstanceList(List instanceIds); } /** diff --git a/src/main/java/com/netflix/simianarmy/chaos/ChaosInstanceSelector.java b/src/main/java/com/netflix/simianarmy/chaos/ChaosInstanceSelector.java index 51f10b56..ee3f07d3 100644 --- a/src/main/java/com/netflix/simianarmy/chaos/ChaosInstanceSelector.java +++ b/src/main/java/com/netflix/simianarmy/chaos/ChaosInstanceSelector.java @@ -17,9 +17,12 @@ */ package com.netflix.simianarmy.chaos; +import com.netflix.simianarmy.NoInstanceWithTagsFoundException; +import com.netflix.simianarmy.Tag; import com.netflix.simianarmy.chaos.ChaosCrawler.InstanceGroup; import java.util.Collection; +import java.util.List; /** * The Interface ChaosInstanceSelector. @@ -52,4 +55,6 @@ public interface ChaosInstanceSelector { * @return the instance */ Collection select(InstanceGroup group, double probability); + + Collection selectOneByTags(InstanceGroup group, List ec2TagsSent) throws NoInstanceWithTagsFoundException; } diff --git a/src/main/java/com/netflix/simianarmy/chaos/ChaosMonkey.java b/src/main/java/com/netflix/simianarmy/chaos/ChaosMonkey.java index e38f6254..8f3c1f27 100644 --- a/src/main/java/com/netflix/simianarmy/chaos/ChaosMonkey.java +++ b/src/main/java/com/netflix/simianarmy/chaos/ChaosMonkey.java @@ -20,13 +20,8 @@ import java.util.Date; import java.util.List; -import com.netflix.simianarmy.EventType; -import com.netflix.simianarmy.FeatureNotEnabledException; -import com.netflix.simianarmy.InstanceGroupNotFoundException; -import com.netflix.simianarmy.Monkey; -import com.netflix.simianarmy.MonkeyConfiguration; +import com.netflix.simianarmy.*; import com.netflix.simianarmy.MonkeyRecorder.Event; -import com.netflix.simianarmy.MonkeyType; /** * The Class ChaosMonkey. @@ -147,8 +142,8 @@ public Context context() { * @throws FeatureNotEnabledException * @throws InstanceGroupNotFoundException */ - public abstract Event terminateNow(String type, String name, ChaosType chaosType) - throws FeatureNotEnabledException, InstanceGroupNotFoundException; + public abstract Event terminateNow(String type, String name, ChaosType chaosType, List ec2TagsSent) + throws FeatureNotEnabledException, InstanceGroupNotFoundException, NoInstanceWithTagsFoundException; /** * Sends notification for the termination to the instance owners. diff --git a/src/main/java/com/netflix/simianarmy/client/MonkeyRestClient.java b/src/main/java/com/netflix/simianarmy/client/MonkeyRestClient.java index d9c5d9b1..f66cc80d 100644 --- a/src/main/java/com/netflix/simianarmy/client/MonkeyRestClient.java +++ b/src/main/java/com/netflix/simianarmy/client/MonkeyRestClient.java @@ -109,6 +109,7 @@ public JsonNode getJsonNodeFromUrl(String url) throws IOException { public abstract String getBaseUrl(String region); public static class DataReadException extends RuntimeException { + private static final long serialVersionUID = 101072016L; public DataReadException(int code, String url, String jsonContent) { super(String.format("Response code %d from url %s: %s", code, url, jsonContent)); } diff --git a/src/main/java/com/netflix/simianarmy/client/aws/AWSClient.java b/src/main/java/com/netflix/simianarmy/client/aws/AWSClient.java index c85b6cbc..d96c761e 100644 --- a/src/main/java/com/netflix/simianarmy/client/aws/AWSClient.java +++ b/src/main/java/com/netflix/simianarmy/client/aws/AWSClient.java @@ -46,6 +46,7 @@ import com.google.inject.Module; import com.netflix.simianarmy.CloudClient; import com.netflix.simianarmy.NotFoundException; +import com.netflix.simianarmy.basic.chaos.BasicInstanceGroup; import org.apache.commons.lang.Validate; import org.jclouds.ContextBuilder; import org.jclouds.compute.ComputeService; @@ -953,7 +954,6 @@ String getVpcId(String instanceId) { if (Strings.isNullOrEmpty(vpcId)) { return null; } - return vpcId; } @@ -962,4 +962,30 @@ String getVpcId(String instanceId) { public boolean canChangeInstanceSecurityGroups(String instanceId) { return null != getVpcId(instanceId); } + + public List convertToSimianArmyInstance(List ec2InstList) { + if (ec2InstList != null) { + List instList = new ArrayList<>(); + + for (Instance ec2Inst : ec2InstList) { + List ec2Tags = new ArrayList<>(); + if (ec2Inst.getTags() != null) { + for (Tag ec2Tag : ec2Inst.getTags()) { + com.netflix.simianarmy.Tag SimianArmyTag = new com.netflix.simianarmy.Tag(ec2Tag.getKey(), ec2Tag.getValue()); + ec2Tags.add(SimianArmyTag); + } + } + + com.netflix.simianarmy.Instance SimianArmyInst = new com.netflix.simianarmy.basic.BasicInstance( + ec2Inst.getInstanceId(), + ec2Inst.getKeyName(), + ec2Inst.getPublicDnsName(), + ec2Tags); + instList.add(SimianArmyInst); + } + + return instList; + } + return null; + } } diff --git a/src/main/java/com/netflix/simianarmy/client/aws/chaos/ASGChaosCrawler.java b/src/main/java/com/netflix/simianarmy/client/aws/chaos/ASGChaosCrawler.java index bd2d6272..8ead463f 100644 --- a/src/main/java/com/netflix/simianarmy/client/aws/chaos/ASGChaosCrawler.java +++ b/src/main/java/com/netflix/simianarmy/client/aws/chaos/ASGChaosCrawler.java @@ -17,16 +17,17 @@ */ package com.netflix.simianarmy.client.aws.chaos; +import java.util.ArrayList; import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import com.amazonaws.services.autoscaling.model.AutoScalingGroup; -import com.amazonaws.services.autoscaling.model.Instance; import com.amazonaws.services.autoscaling.model.TagDescription; + import com.netflix.simianarmy.GroupType; -import com.netflix.simianarmy.basic.chaos.BasicChaosMonkey; import com.netflix.simianarmy.basic.chaos.BasicInstanceGroup; +import com.netflix.simianarmy.Instance; import com.netflix.simianarmy.chaos.ChaosCrawler; import com.netflix.simianarmy.client.aws.AWSClient; import com.netflix.simianarmy.tunable.TunableInstanceGroup; @@ -85,13 +86,18 @@ public List groups(String... names) { List list = new LinkedList(); for (AutoScalingGroup asg : awsClient.describeAutoScalingGroups(names)) { - InstanceGroup ig = getInstanceGroup(asg, findAggressionCoefficient(asg)); - - for (Instance inst : asg.getInstances()) { - ig.addInstance(inst.getInstanceId()); + + List instanceIds = new ArrayList(); + for (com.amazonaws.services.autoscaling.model.Instance inst : asg.getInstances()) { + instanceIds.add(inst.getInstanceId()); } - + String[] instanceIdsArr = new String[instanceIds.size()]; + instanceIdsArr = instanceIds.toArray(instanceIdsArr); + List ec2InstList = awsClient.describeInstances(instanceIdsArr); + List instList = awsClient.convertToSimianArmyInstance(ec2InstList); + ig.addInstanceList(instList); + list.add(ig); } return list; diff --git a/src/main/java/com/netflix/simianarmy/resources/chaos/ChaosMonkeyResource.java b/src/main/java/com/netflix/simianarmy/resources/chaos/ChaosMonkeyResource.java index f81c9fcf..3ac0beb3 100644 --- a/src/main/java/com/netflix/simianarmy/resources/chaos/ChaosMonkeyResource.java +++ b/src/main/java/com/netflix/simianarmy/resources/chaos/ChaosMonkeyResource.java @@ -35,7 +35,7 @@ import javax.ws.rs.core.UriInfo; import com.google.common.base.Strings; -import com.netflix.simianarmy.Monkey; +import com.netflix.simianarmy.*; import com.sun.jersey.spi.resource.Singleton; import org.apache.commons.lang.StringUtils; @@ -44,14 +44,12 @@ import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.map.MappingJsonFactory; import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.type.TypeReference; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.netflix.simianarmy.FeatureNotEnabledException; -import com.netflix.simianarmy.InstanceGroupNotFoundException; import com.netflix.simianarmy.MonkeyRecorder.Event; -import com.netflix.simianarmy.MonkeyRunner; -import com.netflix.simianarmy.NotFoundException; import com.netflix.simianarmy.chaos.ChaosMonkey; import com.netflix.simianarmy.chaos.ChaosType; import com.netflix.simianarmy.chaos.ShutdownInstanceChaosType; @@ -174,6 +172,21 @@ public Response addEvent(String content) throws IOException { String groupName = getStringField(input, "groupName"); String chaosTypeName = getStringField(input, "chaosType"); + //all tags must match for termination + JsonNode tagsNode = input.get("tags"); + List ec2TagsSent = null; + try { + ec2TagsSent = mapper.readValue(tagsNode, new TypeReference>() {}); + } + catch (NullPointerException e) + { + ec2TagsSent = null; + } + //if there are no key=value pairs then set to null and act like there was no ec2TagsSent JSON object + if (ec2TagsSent != null) { + if (ec2TagsSent.size() == 0) + ec2TagsSent = null; + } ChaosType chaosType; if (!Strings.isNullOrEmpty(chaosTypeName)) { chaosType = ChaosType.parse(this.monkey.getChaosTypes(), chaosTypeName); @@ -189,13 +202,14 @@ public Response addEvent(String content) throws IOException { gen.writeStringField("groupType", groupType); gen.writeStringField("groupName", groupName); gen.writeStringField("chaosType", chaosType.getKey()); + gen.writeObjectField("tags", ec2TagsSent); if (StringUtils.isEmpty(eventType) || StringUtils.isEmpty(groupType) || StringUtils.isEmpty(groupName)) { responseStatus = Response.Status.BAD_REQUEST; gen.writeStringField("message", "eventType, groupType, and groupName parameters are all required"); } else { if (eventType.equals("CHAOS_TERMINATION")) { - responseStatus = addTerminationEvent(groupType, groupName, chaosType, gen); + responseStatus = addTerminationEvent(groupType, groupName, chaosType, ec2TagsSent, gen); } else { responseStatus = Response.Status.BAD_REQUEST; gen.writeStringField("message", String.format("Unrecognized event type: %s", eventType)); @@ -208,13 +222,13 @@ public Response addEvent(String content) throws IOException { } private Response.Status addTerminationEvent(String groupType, - String groupName, ChaosType chaosType, JsonGenerator gen) + String groupName, ChaosType chaosType, List ec2TagsSent, JsonGenerator gen) throws IOException { LOGGER.info("Running on-demand termination for instance group type '{}' and name '{}'", groupType, groupName); Response.Status responseStatus; try { - Event evt = monkey.terminateNow(groupType, groupName, chaosType); + Event evt = monkey.terminateNow(groupType, groupName, chaosType, ec2TagsSent); if (evt != null) { responseStatus = Response.Status.OK; gen.writeStringField("monkeyType", evt.monkeyType().name()); @@ -240,6 +254,9 @@ private Response.Status addTerminationEvent(String groupType, // Available instance cannot be found to terminate, maybe the instance is already gone responseStatus = Response.Status.GONE; gen.writeStringField("message", e.getMessage()); + } catch (NoInstanceWithTagsFoundException e) { + responseStatus = Response.Status.GONE; + gen.writeStringField("message", e.getMessage()); } LOGGER.info("On-demand termination completed."); return responseStatus; diff --git a/src/test/java/com/netflix/simianarmy/basic/chaos/TestBasicChaosInstanceSelector.java b/src/test/java/com/netflix/simianarmy/basic/chaos/TestBasicChaosInstanceSelector.java index 68e9858d..a431b28f 100644 --- a/src/test/java/com/netflix/simianarmy/basic/chaos/TestBasicChaosInstanceSelector.java +++ b/src/test/java/com/netflix/simianarmy/basic/chaos/TestBasicChaosInstanceSelector.java @@ -24,6 +24,8 @@ import com.netflix.simianarmy.GroupType; import com.netflix.simianarmy.chaos.ChaosInstanceSelector; import com.netflix.simianarmy.chaos.ChaosCrawler.InstanceGroup; +import com.netflix.simianarmy.Instance; +import com.netflix.simianarmy.basic.BasicInstance; import org.testng.annotations.Test; import org.testng.annotations.DataProvider; @@ -46,6 +48,40 @@ public enum Types implements GroupType { } private InstanceGroup group = new InstanceGroup() { + + List instancesList = new ArrayList(); + { + Instance i1 = new BasicInstance("i-123456789012345670"); + instancesList.add(i1); + + Instance i2 = new BasicInstance("i-123456789012345671"); + instancesList.add(i2); + + Instance i3 = new BasicInstance("i-123456789012345672"); + instancesList.add(i3); + + Instance i4 = new BasicInstance("i-123456789012345673"); + instancesList.add(i4); + + Instance i5 = new BasicInstance("i-123456789012345674"); + instancesList.add(i5); + + Instance i6 = new BasicInstance("i-123456789012345675"); + instancesList.add(i6); + + Instance i7 = new BasicInstance("i-123456789012345676"); + instancesList.add(i7); + + Instance i8 = new BasicInstance("i-123456789012345677"); + instancesList.add(i8); + + Instance i9 = new BasicInstance("i-123456789012345678"); + instancesList.add(i9); + + Instance i10 = new BasicInstance("i-123456789012345679"); + instancesList.add(i10); + } + public GroupType type() { return Types.TEST; } @@ -62,9 +98,20 @@ public List tags() { return Collections.emptyList(); } - public List instances() { - return Arrays.asList("i-123456789012345670", "i-123456789012345671", "i-123456789012345672", "i-123456789012345673", "i-123456789012345674", - "i-123456789012345675", "i-123456789012345676", "i-123456789012345677", "i-123456789012345678", "i-123456789012345679"); + public List instanceIds() { + List instanceIdsList = new ArrayList(); + for (Instance inst : instancesList) { + instanceIdsList.add(inst.getInstanceId()); + } + return instanceIdsList; + } + + @Override + public void addInstance(Instance instance) { + } + + public List instances() { + return instancesList; } public void addInstance(String ignored) { @@ -74,6 +121,12 @@ public void addInstance(String ignored) { public InstanceGroup copyAs(String name) { return this; } + + @Override + public void addInstanceList(List instance) { + instancesList.addAll(instance); + } + }; @Test diff --git a/src/test/java/com/netflix/simianarmy/chaos/TestChaosMonkeyContext.java b/src/test/java/com/netflix/simianarmy/chaos/TestChaosMonkeyContext.java index 044e46f9..0ab8da69 100644 --- a/src/test/java/com/netflix/simianarmy/chaos/TestChaosMonkeyContext.java +++ b/src/test/java/com/netflix/simianarmy/chaos/TestChaosMonkeyContext.java @@ -19,6 +19,7 @@ package com.netflix.simianarmy.chaos; import com.amazonaws.services.autoscaling.model.TagDescription; +import com.netflix.simianarmy.Instance; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -27,6 +28,7 @@ import com.netflix.simianarmy.MonkeyConfiguration; import com.netflix.simianarmy.TestMonkeyContext; import com.netflix.simianarmy.basic.BasicConfiguration; +import com.netflix.simianarmy.basic.BasicInstance; import com.netflix.simianarmy.basic.chaos.BasicChaosInstanceSelector; import com.netflix.simianarmy.chaos.ChaosCrawler.InstanceGroup; import org.jclouds.compute.ComputeService; @@ -79,7 +81,7 @@ public static class TestInstanceGroup implements InstanceGroup { private final GroupType type; private final String name; private final String region; - private final List instances = new ArrayList(); + private final List instances = new ArrayList(); private final List tags = new ArrayList(); public TestInstanceGroup(GroupType type, String name, String region, String... instances) { @@ -87,7 +89,8 @@ public TestInstanceGroup(GroupType type, String name, String region, String... i this.name = name; this.region = region; for (String i : instances) { - this.instances.add(i); + Instance inst = new BasicInstance(i); + this.instances.add(inst); } } @@ -112,21 +115,43 @@ public String region() { } @Override - public List instances() { + public List instanceIds() { + List instanceIds = new ArrayList(); + for (Instance instance : instances) { + instanceIds.add(instance.getInstanceId()); + } + return Collections.unmodifiableList(instanceIds); + } + + @Override + public List instances() { return Collections.unmodifiableList(instances); } @Override - public void addInstance(String ignored) { + public void addInstance(Instance ignored) { } - public void deleteInstance(String id) { - instances.remove(id); + public void deleteInstance(String instanceId) { + //this.instances.remove(id); + Iterator itr = this.instances.iterator(); + while(itr.hasNext()) { + Instance i = itr.next(); + if (instanceId == i.getInstanceId()) + itr.remove(); + } } @Override public InstanceGroup copyAs(String newName) { - return new TestInstanceGroup(this.type, newName, this.region, instances().toString()); + String[] instanceIdsArr = new String[this.instanceIds().size()]; + instanceIdsArr = this.instanceIds().toArray(instanceIdsArr); + return new TestInstanceGroup(this.type, newName, this.region, instanceIdsArr); + } + + @Override + public void addInstanceList(List instList) { + instances.addAll(instList); } } @@ -384,6 +409,11 @@ public ExecChannel execChannel(String command) { throw new UnsupportedOperationException(); } + @Override + public boolean isConnected() { + return true; + } + @Override public void connect() { } diff --git a/src/test/java/com/netflix/simianarmy/client/aws/chaos/TestASGChaosCrawler.java b/src/test/java/com/netflix/simianarmy/client/aws/chaos/TestASGChaosCrawler.java index e0ba9af5..94d5552a 100644 --- a/src/test/java/com/netflix/simianarmy/client/aws/chaos/TestASGChaosCrawler.java +++ b/src/test/java/com/netflix/simianarmy/client/aws/chaos/TestASGChaosCrawler.java @@ -23,12 +23,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; +import java.util.*; import org.testng.Assert; import org.testng.annotations.Test; @@ -70,26 +65,55 @@ public void testGroupTypes() { @Test public void testGroups() { List asgList = new LinkedList(); + + List ec2ModelInstances1 = new ArrayList(); + com.amazonaws.services.ec2.model.Instance inst1 = new com.amazonaws.services.ec2.model.Instance(); + inst1.setInstanceId("i-123456789012345670"); + ec2ModelInstances1.add(inst1); + + List instList1 = new ArrayList(); + com.netflix.simianarmy.Instance simianArmyinst1 = new com.netflix.simianarmy.basic.BasicInstance("i-123456789012345670"); + instList1.add(simianArmyinst1); + + List ec2ModelInstances2 = new ArrayList(); + com.amazonaws.services.ec2.model.Instance inst2 = new com.amazonaws.services.ec2.model.Instance(); + inst2.setInstanceId("i-123456789012345671"); + ec2ModelInstances2.add(inst2); + + List instList2 = new ArrayList(); + com.netflix.simianarmy.Instance simianArmyinst2 = new com.netflix.simianarmy.basic.BasicInstance("i-123456789012345671"); + instList2.add(simianArmyinst2); + + asgList.add(mkAsg("asg1", "i-123456789012345670")); asgList.add(mkAsg("asg2", "i-123456789012345671")); + String[] instanceIdsArr = {"i-123456789012345670"}; + String[] instanceIdsArr2 = {"i-123456789012345671"}; + when(awsMock.describeAutoScalingGroups((String[]) null)).thenReturn(asgList); + when(awsMock.describeInstances(instanceIdsArr)).thenReturn(ec2ModelInstances1); + when(awsMock.describeInstances(instanceIdsArr2)).thenReturn(ec2ModelInstances2); + when(awsMock.convertToSimianArmyInstance(ec2ModelInstances1)).thenReturn(instList1); + when(awsMock.convertToSimianArmyInstance(ec2ModelInstances2)).thenReturn(instList2); List groups = crawler.groups(); verify(awsMock, times(1)).describeAutoScalingGroups((String[]) null); + verify(awsMock, times(1)).describeInstances(instanceIdsArr); + verify(awsMock, times(1)).describeInstances(instanceIdsArr2); Assert.assertEquals(groups.size(), 2); Assert.assertEquals(groups.get(0).type(), ASGChaosCrawler.Types.ASG); Assert.assertEquals(groups.get(0).name(), "asg1"); Assert.assertEquals(groups.get(0).instances().size(), 1); - Assert.assertEquals(groups.get(0).instances().get(0), "i-123456789012345670"); + Assert.assertEquals(groups.get(0).instances().get(0).getInstanceId(), "i-123456789012345670"); Assert.assertEquals(groups.get(1).type(), ASGChaosCrawler.Types.ASG); Assert.assertEquals(groups.get(1).name(), "asg2"); Assert.assertEquals(groups.get(1).instances().size(), 1); - Assert.assertEquals(groups.get(1).instances().get(0), "i-123456789012345671"); + Assert.assertEquals(groups.get(1).instances().get(0).getInstanceId(), "i-123456789012345671"); } @Test