controllers.AlertController.java Source code

Java tutorial

Introduction

Here is the source code for controllers.AlertController.java

Source

/**
 * Copyright 2015 Groupon.com
 *
 * 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 controllers;

import com.arpnetworking.commons.jackson.databind.ObjectMapperFactory;
import com.arpnetworking.metrics.portal.alerts.AlertRepository;
import com.arpnetworking.steno.Logger;
import com.arpnetworking.steno.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.net.HttpHeaders;
import com.google.inject.Inject;
import models.internal.Alert;
import models.internal.AlertQuery;
import models.internal.Context;
import models.internal.NagiosExtension;
import models.internal.Operator;
import models.internal.Quantity;
import models.internal.QueryResult;
import models.internal.impl.DefaultAlert;
import models.internal.impl.DefaultQuantity;
import models.view.PagedContainer;
import models.view.Pagination;
import org.joda.time.Period;
import play.Configuration;
import play.libs.Json;
import play.mvc.Controller;
import play.mvc.Http;
import play.mvc.Result;

import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.inject.Singleton;

/**
 * Metrics portal alert controller. Exposes APIs to query and manipulate alerts.
 *
 * @author Ville Koskela (ville dot koskela at inscopemetrics dot com)
 */
@Singleton
public class AlertController extends Controller {

    /**
     * Public constructor.
     *
     * @param configuration Instance of Play's <code>Configuration</code>.
     * @param alertRepository Instance of <code>AlertRepository</code>.
     */
    @Inject
    public AlertController(final Configuration configuration, final AlertRepository alertRepository) {
        this(configuration.getInt("alerts.limit", DEFAULT_MAX_LIMIT), alertRepository);
    }

    /**
     * Adds an alert in the alert repository.
     *
     * @return Ok if the alert was created or updated successfully, a failure HTTP status code otherwise.
     */
    public Result addOrUpdate() {
        final Alert alert;
        try {
            final models.view.Alert viewAlert = buildViewAlert(request().body());
            alert = convertToInternalAlert(viewAlert);
        } catch (final IOException e) {
            LOGGER.error().setMessage("Failed to build an alert.").setThrowable(e).log();
            return badRequest("Invalid request body.");
        }

        try {
            _alertRepository.addOrUpdateAlert(alert);
            // CHECKSTYLE.OFF: IllegalCatch - Convert any exception to 500
        } catch (final Exception e) {
            // CHECKSTYLE.ON: IllegalCatch
            LOGGER.error().setMessage("Failed to add an alert.").setThrowable(e).log();
            return internalServerError();
        }
        return ok();
    }

    /**
     * Query for alerts.
     *
     * @param contains The text to search for. Optional.
     * @param context The context of the alert. Optional.
     * @param cluster The cluster of the statistic to evaluate as part of the alert. Optional.
     * @param service The service of the statistic to evaluate as part of the alert. Optional.
     * @param limit The maximum number of results to return. Optional.
     * @param offset The number of results to skip. Optional.
     * @return <code>Result</code> paginated matching alerts.
     */
    // CHECKSTYLE.OFF: ParameterNameCheck - Names must match query parameters.
    public Result query(final String contains, final String context, final String cluster, final String service,
            final Integer limit, final Integer offset) {
        // CHECKSTYLE.ON: ParameterNameCheck

        // Convert and validate parameters
        final Optional<String> argContains = Optional.ofNullable(contains);
        final Context contextValue;
        try {
            contextValue = context == null ? null : Context.valueOf(context);
        } catch (final IllegalArgumentException iae) {
            return badRequest("Invalid context argument");
        }
        final Optional<Context> argContext = Optional.ofNullable(contextValue);
        final Optional<String> argCluster = Optional.ofNullable(cluster);
        final Optional<String> argService = Optional.ofNullable(service);
        final Optional<Integer> argOffset = Optional.ofNullable(offset);
        final int argLimit = Math.min(_maxLimit, Optional.of(MoreObjects.firstNonNull(limit, _maxLimit)).get());
        if (argLimit < 0) {
            return badRequest("Invalid limit; must be greater than or equal to 0");
        }
        if (argOffset.isPresent() && argOffset.get() < 0) {
            return badRequest("Invalid offset; must be greater than or equal to 0");
        }

        // Build conditions map
        final Map<String, String> conditions = Maps.newHashMap();
        if (argContains.isPresent()) {
            conditions.put("contains", argContains.get());
        }
        if (argContext.isPresent()) {
            conditions.put("context", argContext.get().toString());
        }
        if (argCluster.isPresent()) {
            conditions.put("cluster", argCluster.get());
        }
        if (argService.isPresent()) {
            conditions.put("service", argService.get());
        }

        // Build a host repository query
        final AlertQuery query = _alertRepository.createQuery().contains(argContains).context(argContext)
                .service(argService).cluster(argCluster).limit(argLimit).offset(argOffset);

        // Execute the query
        final QueryResult<Alert> result;
        try {
            result = query.execute();
            // CHECKSTYLE.OFF: IllegalCatch - Convert any exception to 500
        } catch (final Exception e) {
            // CHECKSTYLE.ON: IllegalCatch
            LOGGER.error().setMessage("Alert query failed").setThrowable(e).log();
            return internalServerError();
        }

        // Wrap the query results and return as JSON
        if (result.etag().isPresent()) {
            response().setHeader(HttpHeaders.ETAG, result.etag().get());
        }
        return ok(Json.toJson(new PagedContainer<>(
                result.values().stream().map(this::internalModelToViewModel).collect(Collectors.toList()),
                new Pagination(request().path(), result.total(), result.values().size(), argLimit, argOffset,
                        conditions))));
    }

    /**
     * Get specific alert.
     *
     * @param id The identifier of the alert.
     * @return Matching alert.
     */
    public Result get(final String id) {
        final UUID identifier = UUID.fromString(id);
        final Optional<Alert> result = _alertRepository.get(identifier);
        if (!result.isPresent()) {
            return notFound();
        }
        // Return as JSON
        return ok(Json.toJson(result.get()));
    }

    private models.view.Alert internalModelToViewModel(final Alert alert) {
        final models.view.Alert viewAlert = new models.view.Alert();
        viewAlert.setCluster(alert.getCluster());
        viewAlert.setContext(alert.getContext().toString());
        viewAlert.setExtensions(mergeExtensions(alert.getNagiosExtension()));
        viewAlert.setId(alert.getId().toString());
        viewAlert.setMetric(alert.getMetric());
        viewAlert.setName(alert.getName());
        viewAlert.setOperator(alert.getOperator().toString());
        viewAlert.setPeriod(alert.getPeriod().toString());
        viewAlert.setService(alert.getService());
        viewAlert.setStatistic(alert.getStatistic());
        final models.view.Quantity viewValue = new models.view.Quantity();
        viewValue.setValue(alert.getValue().getValue());
        if (alert.getValue().getUnit().isPresent()) {
            viewValue.setUnit(alert.getValue().getUnit().get());
        }
        viewAlert.setValue(viewValue);
        return viewAlert;
    }

    private Quantity convertToInternalQuantity(final models.view.Quantity viewQuantity) {
        return new DefaultQuantity.Builder().setUnit(viewQuantity.getUnit()).setValue(viewQuantity.getValue())
                .build();
    }

    private Optional<NagiosExtension> convertToInternalNagiosExtension(final Map<String, Object> extensionsMap) {
        try {
            return Optional.of(OBJECT_MAPPER.convertValue(extensionsMap, NagiosExtension.Builder.class).build());
            // CHECKSTYLE.OFF: IllegalCatch - Assume there is no Nagios data on build failure.
        } catch (final Exception e) {
            // CHECKSTYLE.ON: IllegalCatch
            return Optional.empty();
        }
    }

    private Alert convertToInternalAlert(final models.view.Alert viewAlert) throws IOException {
        try {
            final DefaultAlert.Builder alertBuilder = new DefaultAlert.Builder().setCluster(viewAlert.getCluster())
                    .setMetric(viewAlert.getMetric()).setName(viewAlert.getName())
                    .setService(viewAlert.getService()).setStatistic(viewAlert.getStatistic());
            if (viewAlert.getValue() != null) {
                alertBuilder.setValue(convertToInternalQuantity(viewAlert.getValue()));
            }
            if (viewAlert.getId() != null) {
                alertBuilder.setId(UUID.fromString(viewAlert.getId()));
            }
            if (viewAlert.getContext() != null) {
                alertBuilder.setContext(Context.valueOf(viewAlert.getContext()));
            }
            if (viewAlert.getOperator() != null) {
                alertBuilder.setOperator(Operator.valueOf(viewAlert.getOperator()));
            }
            if (viewAlert.getPeriod() != null) {
                alertBuilder.setPeriod(Period.parse(viewAlert.getPeriod()));
            }
            if (viewAlert.getExtensions() != null) {
                alertBuilder.setNagiosExtension(
                        convertToInternalNagiosExtension(viewAlert.getExtensions()).orElse(null));
            }
            return alertBuilder.build();
            // CHECKSTYLE.OFF: IllegalCatch - Translate any failure to bad input.
        } catch (final RuntimeException e) {
            // CHECKSTYLE.ON: IllegalCatch
            throw new IOException(e);
        }
    }

    private ImmutableMap<String, Object> mergeExtensions(final NagiosExtension nagiosExtension) {
        final ImmutableMap.Builder<String, Object> nagiosMapBuilder = ImmutableMap.builder();
        nagiosMapBuilder.put(NAGIOS_EXTENSION_SEVERITY_KEY, nagiosExtension.getSeverity());
        nagiosMapBuilder.put(NAGIOS_EXTENSION_NOTIFY_KEY, nagiosExtension.getNotify());
        nagiosMapBuilder.put(NAGIOS_EXTENSION_MAX_CHECK_ATTEMPTS_KEY, nagiosExtension.getMaxCheckAttempts());
        nagiosMapBuilder.put(NAGIOS_EXTENSION_FRESHNESS_THRESHOLD_KEY,
                nagiosExtension.getFreshnessThreshold().getStandardSeconds());
        return nagiosMapBuilder.build();
    }

    private models.view.Alert buildViewAlert(final Http.RequestBody body) throws IOException {
        final JsonNode jsonBody = body.asJson();
        if (jsonBody == null) {
            throw new IOException();
        }
        return OBJECT_MAPPER.readValue(jsonBody.toString(), models.view.Alert.class);
    }

    private AlertController(final int maxLimit, final AlertRepository alertRepository) {
        _maxLimit = maxLimit;
        _alertRepository = alertRepository;
    }

    private final int _maxLimit;
    private final AlertRepository _alertRepository;

    private static final int DEFAULT_MAX_LIMIT = 1000;
    private static final Logger LOGGER = LoggerFactory.getLogger(AlertController.class);
    private static final String NAGIOS_EXTENSION_SEVERITY_KEY = "severity";
    private static final String NAGIOS_EXTENSION_NOTIFY_KEY = "notify";
    private static final String NAGIOS_EXTENSION_MAX_CHECK_ATTEMPTS_KEY = "max_check_attempts";
    private static final String NAGIOS_EXTENSION_FRESHNESS_THRESHOLD_KEY = "freshness_threshold";
    private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getInstance();
}