com.netflix.simianarmy.resources.sniper.SniperMonkeyResource.java Source code

Java tutorial

Introduction

Here is the source code for com.netflix.simianarmy.resources.sniper.SniperMonkeyResource.java

Source

/*
 *
 *  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.sniper;

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.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.sniper.SniperMonkey;

/**
 * The Class SniperMonkeyResource for json REST apis.
 */
@Path("/v1/sniper")
@Singleton
public class SniperMonkeyResource {

    /** The Constant JSON_FACTORY. */
    private static final MappingJsonFactory JSON_FACTORY = new MappingJsonFactory();

    /** The monkey. */
    private SniperMonkey monkey = null;

    /** The Constant LOGGER. */
    private static final Logger LOGGER = LoggerFactory.getLogger(SniperMonkeyResource.class);

    /**
     * Instantiates a sniper monkey resource with a specific sniper monkey.
     *
     * @param monkey
     *          the sniper monkey
     */
    public SniperMonkeyResource(SniperMonkey monkey) {
        this.monkey = monkey;
    }

    /**
     * Instantiates a sniper monkey resource using a registered sniper monkey from factory.
     */
    public SniperMonkeyResource() {
        for (Monkey runningMonkey : MonkeyRunner.getInstance().getMonkeys()) {
            if (runningMonkey instanceof SniperMonkey) {
                this.monkey = (SniperMonkey) runningMonkey;
                break;
            }
        }
        if (this.monkey == null) {
            LOGGER.info("Creating a new Sniper monkey instance for the resource.");
            this.monkey = MonkeyRunner.getInstance().factory(SniperMonkey.class);
        }
    }

    /**
     * Gets the sniper events. Creates GET /api/v1/sniper api which outputs the sniper 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 sniper events json response
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    @GET
    public Response getSniperEvents(@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(SniperMonkey.Type.SNIPER,
                SniperMonkey.EventTypes.SNIPER_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/sniper 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");

        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);

        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("SNIPER_TERMINATION")) {
                responseStatus = addTerminationEvent(groupType, groupName, 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, 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);
            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();
    }

}