All Downloads are FREE. Search and download functionalities are using the official Maven repository.

controllers.AlertController Maven / Gradle / Ivy

There is a newer version: 0.9.2
Show newest version
/**
 * 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.metrics.portal.alerts.AlertRepository;
import com.arpnetworking.steno.Logger;
import com.arpnetworking.steno.LoggerFactory;
import com.google.common.base.MoreObjects;
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.QueryResult;
import models.view.PagedContainer;
import models.view.Pagination;
import play.Configuration;
import play.libs.Json;
import play.mvc.Controller;
import play.mvc.Result;

import java.util.Collections;
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 (vkoskela at groupon dot com)
 */
@Singleton
public class AlertController extends Controller {

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

    /**
     * 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 Result 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 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 argContext = Optional.ofNullable(contextValue);
        final Optional argCluster = Optional.ofNullable(cluster);
        final Optional argService = Optional.ofNullable(service);
        final Optional 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 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 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))));
    }

    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().toString());
        }
        viewAlert.setValue(viewValue);
        return viewAlert;
    }

    private Map mergeExtensions(final NagiosExtension nagiosExtension) {
        if (nagiosExtension == null) {
            return Collections.emptyMap();
        }
        final Map extensionMap = Maps.newHashMap();
        extensionMap.put(NAGIOS_EXTENSION_SEVERITY_KEY, nagiosExtension.getSeverity());
        extensionMap.put(NAGIOS_EXTENSION_NOTIFY_KEY, nagiosExtension.getNotify());
        extensionMap.put(NAGIOS_EXTENSION_MAX_CHECK_ATTEMPTS_KEY, nagiosExtension.getMaxCheckAttempts());
        extensionMap.put(NAGIOS_EXTENSION_FRESHNESS_THRESHOLD_KEY, nagiosExtension.getFreshnessThreshold().getStandardSeconds());
        return extensionMap;
    }

    /**
     * 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 result = _alertRepository.get(identifier);
        if (!result.isPresent()) {
            return notFound();
        }
        // Return as JSON
        return ok(Json.toJson(result.get()));
    }

    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";
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy