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.resources.chaos; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import com.google.common.base.Strings; import com.netflix.simianarmy.Monkey; import com.sun.jersey.spi.resource.Singleton; import org.apache.commons.lang.StringUtils; import org.codehaus.jackson.JsonEncoding; import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.map.MappingJsonFactory; import org.codehaus.jackson.map.ObjectMapper; 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; /** * The Class ChaosMonkeyResource for json REST apis. */ @Path("/v1/chaos") @Singleton public class ChaosMonkeyResource { /** The Constant JSON_FACTORY. */ private static final MappingJsonFactory JSON_FACTORY = new MappingJsonFactory(); /** The monkey. */ private ChaosMonkey monkey = null; /** The Constant LOGGER. */ private static final Logger LOGGER = LoggerFactory.getLogger(ChaosMonkeyResource.class); /** * Instantiates a chaos monkey resource with a specific chaos monkey. * * @param monkey * the chaos monkey */ public ChaosMonkeyResource(ChaosMonkey monkey) { this.monkey = monkey; } /** * Instantiates a chaos monkey resource using a registered chaos monkey from factory. */ public ChaosMonkeyResource() { for (Monkey runningMonkey : MonkeyRunner.getInstance().getMonkeys()) { if (runningMonkey instanceof ChaosMonkey) { this.monkey = (ChaosMonkey) runningMonkey; break; } } if (this.monkey == null) { LOGGER.info("Creating a new Chaos monkey instance for the resource."); this.monkey = MonkeyRunner.getInstance().factory(ChaosMonkey.class); } } /** * Gets the chaos events. Creates GET /api/v1/chaos api which outputs the chaos events in json. Users can specify * cgi query params to filter the results and use "since" query param to set the start of a timerange. "since" will * number of milliseconds since the epoch. * * @param uriInfo * the uri info * @return the chaos events json response * @throws IOException * Signals that an I/O exception has occurred. */ @GET public Response getChaosEvents(@Context UriInfo uriInfo) throws IOException { Map<String, String> query = new HashMap<String, String>(); Date date = null; for (Map.Entry<String, List<String>> pair : uriInfo.getQueryParameters().entrySet()) { if (pair.getValue().isEmpty()) { continue; } if (pair.getKey().equals("since")) { date = new Date(Long.parseLong(pair.getValue().get(0))); } else { query.put(pair.getKey(), pair.getValue().get(0)); } } // if "since" not set, default to 24 hours ago if (date == null) { Calendar now = monkey.context().calendar().now(); now.add(Calendar.DAY_OF_YEAR, -1); date = now.getTime(); } List<Event> evts = monkey.context().recorder().findEvents(ChaosMonkey.Type.CHAOS, ChaosMonkey.EventTypes.CHAOS_TERMINATION, query, date); ByteArrayOutputStream baos = new ByteArrayOutputStream(); JsonGenerator gen = JSON_FACTORY.createJsonGenerator(baos, JsonEncoding.UTF8); gen.writeStartArray(); for (Event evt : evts) { gen.writeStartObject(); gen.writeStringField("monkeyType", evt.monkeyType().name()); gen.writeStringField("eventType", evt.eventType().name()); gen.writeNumberField("eventTime", evt.eventTime().getTime()); gen.writeStringField("region", evt.region()); for (Map.Entry<String, String> pair : evt.fields().entrySet()) { gen.writeStringField(pair.getKey(), pair.getValue()); } gen.writeEndObject(); } gen.writeEndArray(); gen.close(); return Response.status(Response.Status.OK).entity(baos.toString("UTF-8")).build(); } /** * POST /api/v1/chaos will try a add a new event with the information in the url context, * ignoring the monkey probability and max termination configurations, for a specific instance group. * * @param content * the Json content passed to the http POST request * @return the response * @throws IOException */ @POST public Response addEvent(String content) throws IOException { ObjectMapper mapper = new ObjectMapper(); LOGGER.info(String.format("JSON content: '%s'", content)); JsonNode input = mapper.readTree(content); String eventType = getStringField(input, "eventType"); String groupType = getStringField(input, "groupType"); String groupName = getStringField(input, "groupName"); String chaosTypeName = getStringField(input, "chaosType"); ChaosType chaosType; if (!Strings.isNullOrEmpty(chaosTypeName)) { chaosType = ChaosType.parse(this.monkey.getChaosTypes(), chaosTypeName); } else { chaosType = new ShutdownInstanceChaosType(monkey.context().configuration()); } Response.Status responseStatus; ByteArrayOutputStream baos = new ByteArrayOutputStream(); JsonGenerator gen = JSON_FACTORY.createJsonGenerator(baos, JsonEncoding.UTF8); gen.writeStartObject(); gen.writeStringField("eventType", eventType); gen.writeStringField("groupType", groupType); gen.writeStringField("groupName", groupName); gen.writeStringField("chaosType", chaosType.getKey()); 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); } else { responseStatus = Response.Status.BAD_REQUEST; gen.writeStringField("message", String.format("Unrecognized event type: %s", eventType)); } } gen.writeEndObject(); gen.close(); LOGGER.info("entity content is '{}'", baos.toString("UTF-8")); return Response.status(responseStatus).entity(baos.toString("UTF-8")).build(); } private Response.Status addTerminationEvent(String groupType, String groupName, ChaosType chaosType, 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); if (evt != null) { responseStatus = Response.Status.OK; gen.writeStringField("monkeyType", evt.monkeyType().name()); gen.writeStringField("eventId", evt.id()); gen.writeNumberField("eventTime", evt.eventTime().getTime()); gen.writeStringField("region", evt.region()); for (Map.Entry<String, String> pair : evt.fields().entrySet()) { gen.writeStringField(pair.getKey(), pair.getValue()); } } else { responseStatus = Response.Status.INTERNAL_SERVER_ERROR; gen.writeStringField("message", String.format("Failed to terminate instance in group %s [type %s]", groupName, groupType)); } } catch (FeatureNotEnabledException e) { responseStatus = Response.Status.FORBIDDEN; gen.writeStringField("message", e.getMessage()); } catch (InstanceGroupNotFoundException e) { responseStatus = Response.Status.NOT_FOUND; gen.writeStringField("message", e.getMessage()); } catch (NotFoundException e) { // Available instance cannot be found to terminate, maybe the instance is already gone responseStatus = Response.Status.GONE; gen.writeStringField("message", e.getMessage()); } LOGGER.info("On-demand termination completed."); return responseStatus; } private String getStringField(JsonNode input, String field) { JsonNode node = input.get(field); if (node == null) { return null; } return node.getTextValue(); } }