diff --git a/app/com/arpnetworking/metrics/portal/alerts/AlertJob.java b/app/com/arpnetworking/metrics/portal/alerts/AlertJob.java new file mode 100644 index 000000000..12884fb3f --- /dev/null +++ b/app/com/arpnetworking/metrics/portal/alerts/AlertJob.java @@ -0,0 +1,80 @@ +/* + * Copyright 2020 Dropbox, 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.arpnetworking.metrics.portal.alerts; + +import com.arpnetworking.metrics.portal.scheduling.Schedule; +import com.arpnetworking.metrics.portal.scheduling.impl.NeverSchedule; +import com.google.inject.Injector; +import models.internal.alerts.Alert; +import models.internal.alerts.AlertEvaluationResult; +import models.internal.scheduling.Job; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +/** + * A wrapper around {@code Alert} instances to allow for scheduling and evaluation of Alert queries. + * + * @author Christian Briones (cbriones at dropbox dot com) + */ +public class AlertJob implements Job { + private final Alert _alert; + + /** + * Create a job from an alert. + * + * @param alert The alert that this job will evaluate. + */ + public AlertJob(final Alert alert) { + this._alert = alert; + } + + @Override + public UUID getId() { + return _alert.getId(); + } + + @Override + public Optional getETag() { + return Optional.empty(); + } + + @Override + public Schedule getSchedule() { + // TODO(cbriones): If the alert is enabled, this should return an interval corresponding to the smallest query period + // in that alert's query. + return NeverSchedule.getInstance(); + } + + @Override + public Duration getTimeout() { + return Duration.of(1, ChronoUnit.MINUTES); + } + + @Override + public CompletionStage execute(final Injector injector, final Instant scheduled) { + final CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(new UnsupportedOperationException("Alert execution is not implemented")); + return future; + } + +} diff --git a/app/com/arpnetworking/metrics/portal/alerts/AlertRepository.java b/app/com/arpnetworking/metrics/portal/alerts/AlertRepository.java index ee21d0f04..56771af1a 100644 --- a/app/com/arpnetworking/metrics/portal/alerts/AlertRepository.java +++ b/app/com/arpnetworking/metrics/portal/alerts/AlertRepository.java @@ -15,10 +15,10 @@ */ package com.arpnetworking.metrics.portal.alerts; -import models.internal.Alert; import models.internal.AlertQuery; import models.internal.Organization; import models.internal.QueryResult; +import models.internal.alerts.Alert; import java.util.Optional; import java.util.UUID; diff --git a/app/com/arpnetworking/metrics/portal/alerts/impl/CassandraAlertRepository.java b/app/com/arpnetworking/metrics/portal/alerts/impl/CassandraAlertRepository.java deleted file mode 100644 index b84e2f119..000000000 --- a/app/com/arpnetworking/metrics/portal/alerts/impl/CassandraAlertRepository.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright 2017 Smartsheet.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 com.arpnetworking.metrics.portal.alerts.impl; - -import com.arpnetworking.metrics.portal.alerts.AlertRepository; -import com.arpnetworking.steno.Logger; -import com.arpnetworking.steno.LoggerFactory; -import com.datastax.driver.core.Session; -import com.datastax.driver.mapping.Mapper; -import com.datastax.driver.mapping.MappingManager; -import com.datastax.driver.mapping.Result; -import com.google.common.collect.ImmutableMap; -import models.internal.Alert; -import models.internal.AlertQuery; -import models.internal.NagiosExtension; -import models.internal.Organization; -import models.internal.QueryResult; -import models.internal.impl.DefaultAlert; -import models.internal.impl.DefaultAlertQuery; -import models.internal.impl.DefaultQuantity; -import models.internal.impl.DefaultQueryResult; - -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Spliterator; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; -import javax.annotation.Nullable; -import javax.inject.Inject; - -/** - * Implementation of an {@link AlertRepository} that stores the data in Cassandra. - * - * @author Brandon Arp (brandon dot arp at smartsheet dot com) - */ -public final class CassandraAlertRepository implements AlertRepository { - /** - * Public constructor. - * - * @param cassandraSession a Session to use to query data - * @param mappingManager a MappingManager providing ORM for the Cassandra objects - */ - @Inject - public CassandraAlertRepository(final Session cassandraSession, final MappingManager mappingManager) { - _cassandraSession = cassandraSession; - _mappingManager = mappingManager; - } - - @Override - public void open() { - assertIsOpen(false); - LOGGER.debug().setMessage("Opening alert repository").log(); - _isOpen.set(true); - } - - @Override - public void close() { - assertIsOpen(); - LOGGER.debug().setMessage("Closing alert repository").log(); - _isOpen.set(false); - _cassandraSession.close(); - } - - @Override - public Optional getAlert(final UUID identifier, final Organization organization) { - assertIsOpen(); - LOGGER.debug() - .setMessage("Getting alert") - .addData("alertId", identifier) - .addData("organization", organization) - .log(); - final Mapper mapper = _mappingManager.mapper(models.cassandra.Alert.class); - final models.cassandra.Alert cassandraAlert = mapper.get(identifier); - - if (cassandraAlert == null) { - return Optional.empty(); - } - - return Optional.of(convertFromCassandraAlert(cassandraAlert)); - } - - @Override - public int deleteAlert(final UUID identifier, final Organization organization) { - assertIsOpen(); - LOGGER.debug() - .setMessage("Deleting alert") - .addData("alertId", identifier) - .addData("organization", organization) - .log(); - final Optional alert = getAlert(identifier, organization); - if (alert.isPresent()) { - final Mapper mapper = _mappingManager.mapper(models.cassandra.Alert.class); - mapper.delete(identifier); - return 1; - } else { - return 0; - } - } - - @Override - public AlertQuery createAlertQuery(final Organization organization) { - assertIsOpen(); - LOGGER.debug() - .setMessage("Preparing query") - .addData("organization", organization) - .log(); - return new DefaultAlertQuery(this, organization); - } - - @Override - public QueryResult queryAlerts(final AlertQuery query) { - final Mapper mapper = _mappingManager.mapper(models.cassandra.Alert.class); - final models.cassandra.Alert.AlertQueries accessor = mapper.getManager().createAccessor(models.cassandra.Alert.AlertQueries.class); - final Result result = accessor.getAlertsForOrganization(query.getOrganization().getId()); - final Spliterator allAlerts = result.spliterator(); - final int start = query.getOffset().orElse(0); - - Stream alertStream = StreamSupport.stream(allAlerts, false); - - if (query.getCluster().isPresent()) { - alertStream = alertStream.filter(alert -> alert.getCluster().equals(query.getCluster().get())); - } - - if (query.getService().isPresent()) { - alertStream = alertStream.filter(alert -> alert.getService().equals(query.getService().get())); - } - - if (query.getContext().isPresent()) { - alertStream = alertStream.filter(alert -> alert.getContext().equals(query.getContext().get())); - } - - if (query.getContains().isPresent()) { - alertStream = alertStream.filter(alert -> { - final String contains = query.getContains().get(); - return alert.getService().contains(contains) - || alert.getCluster().contains(contains) - || alert.getMetric().contains(contains) - || alert.getOperator().toString().contains(contains) - || alert.getName().contains(contains) - || alert.getStatistic().contains(contains); - }); - } - - final List alerts = alertStream - .map(this::convertFromCassandraAlert) - .collect(Collectors.toList()); - final List paginated = alerts.stream().skip(start).limit(query.getLimit()).collect(Collectors.toList()); - return new DefaultQueryResult<>(paginated, alerts.size()); - } - - @Override - public long getAlertCount(final Organization organization) { - final Mapper mapper = _mappingManager.mapper(models.cassandra.Alert.class); - final models.cassandra.Alert.AlertQueries accessor = mapper.getManager().createAccessor(models.cassandra.Alert.AlertQueries.class); - final Result result = accessor.getAlertsForOrganization(organization.getId()); - return StreamSupport.stream(result.spliterator(), false).count(); - } - - @Override - public void addOrUpdateAlert(final Alert alert, final Organization organization) { - assertIsOpen(); - LOGGER.debug() - .setMessage("Upserting alert") - .addData("alert", alert) - .addData("organization", organization) - .log(); - - final models.cassandra.Alert cassAlert = new models.cassandra.Alert(); - cassAlert.setUuid(alert.getId()); - cassAlert.setOrganization(organization.getId()); - cassAlert.setCluster(alert.getCluster()); - cassAlert.setMetric(alert.getMetric()); - cassAlert.setContext(alert.getContext()); - cassAlert.setNagiosExtensions(convertToCassandraNagiosExtension(alert.getNagiosExtension())); - cassAlert.setName(alert.getName()); - cassAlert.setOperator(alert.getOperator()); - cassAlert.setPeriodInSeconds((int) alert.getPeriod().getSeconds()); - cassAlert.setQuantityValue(alert.getValue().getValue()); - cassAlert.setQuantityUnit(alert.getValue().getUnit().orElse(null)); - cassAlert.setStatistic(alert.getStatistic()); - cassAlert.setService(alert.getService()); - - final Mapper mapper = _mappingManager.mapper(models.cassandra.Alert.class); - mapper.save(cassAlert); - } - - private void assertIsOpen() { - assertIsOpen(true); - } - - private void assertIsOpen(final boolean expectedState) { - if (_isOpen.get() != expectedState) { - throw new IllegalStateException(String.format("Alert repository is not %s", expectedState ? "open" : "closed")); - } - } - - private Alert convertFromCassandraAlert(final models.cassandra.Alert cassandraAlert) { - return new DefaultAlert.Builder() - .setCluster(cassandraAlert.getCluster()) - .setContext(cassandraAlert.getContext()) - .setId(cassandraAlert.getUuid()) - .setMetric(cassandraAlert.getMetric()) - .setName(cassandraAlert.getName()) - .setOperator(cassandraAlert.getOperator()) - .setPeriod(Duration.ofSeconds(cassandraAlert.getPeriodInSeconds())) - .setService(cassandraAlert.getService()) - .setStatistic(cassandraAlert.getStatistic()) - .setValue(new DefaultQuantity.Builder() - .setValue(cassandraAlert.getQuantityValue()) - .setUnit(cassandraAlert.getQuantityUnit()) - .build()) - .setNagiosExtension(convertToInternalNagiosExtension(cassandraAlert.getNagiosExtensions())) - .build(); - } - - @Nullable - private NagiosExtension convertToInternalNagiosExtension(final Map nagiosExtensions) { - if (nagiosExtensions == null) { - return null; - } - - final NagiosExtension.Builder internal = new NagiosExtension.Builder(); - Optional.ofNullable(nagiosExtensions.get("severity")).ifPresent(internal::setSeverity); - Optional.ofNullable(nagiosExtensions.get("notify")).ifPresent(internal::setNotify); - Optional.ofNullable(nagiosExtensions.get("attempts")).ifPresent(value -> - internal.setMaxCheckAttempts(Integer.parseInt(value))); - Optional.ofNullable(nagiosExtensions.get("freshness")).ifPresent(value -> - internal.setFreshnessThresholdInSeconds(Long.parseLong(value))); - return internal.build(); - } - - private Map convertToCassandraNagiosExtension(final NagiosExtension nagiosExtension) { - final ImmutableMap.Builder nagiosMapBuilder = new ImmutableMap.Builder<>(); - if (nagiosExtension != null) { - nagiosMapBuilder.put("severity", nagiosExtension.getSeverity()); - nagiosMapBuilder.put("notify", nagiosExtension.getNotify()); - nagiosMapBuilder.put("attempts", Integer.toString(nagiosExtension.getMaxCheckAttempts())); - nagiosMapBuilder.put("freshness", Long.toString(nagiosExtension.getFreshnessThreshold().getSeconds())); - } - return nagiosMapBuilder.build(); - } - - private final Session _cassandraSession; - private final MappingManager _mappingManager; - private final AtomicBoolean _isOpen = new AtomicBoolean(false); - - private static final Logger LOGGER = LoggerFactory.getLogger(CassandraAlertRepository.class); -} diff --git a/app/com/arpnetworking/metrics/portal/alerts/impl/DatabaseAlertRepository.java b/app/com/arpnetworking/metrics/portal/alerts/impl/DatabaseAlertRepository.java deleted file mode 100644 index 5e5ea40b9..000000000 --- a/app/com/arpnetworking/metrics/portal/alerts/impl/DatabaseAlertRepository.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * 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 com.arpnetworking.metrics.portal.alerts.impl; - -import com.arpnetworking.metrics.portal.alerts.AlertRepository; -import com.arpnetworking.steno.Logger; -import com.arpnetworking.steno.LoggerFactory; -import com.google.inject.Inject; -import com.typesafe.config.Config; -import io.ebean.EbeanServer; -import io.ebean.ExpressionList; -import io.ebean.Junction; -import io.ebean.PagedList; -import io.ebean.Query; -import io.ebean.Transaction; -import models.ebean.AlertEtags; -import models.ebean.NagiosExtension; -import models.internal.Alert; -import models.internal.AlertQuery; -import models.internal.Organization; -import models.internal.QueryResult; -import models.internal.impl.DefaultAlert; -import models.internal.impl.DefaultAlertQuery; -import models.internal.impl.DefaultQuantity; -import models.internal.impl.DefaultQueryResult; -import play.Environment; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; -import javax.annotation.Nullable; -import javax.inject.Named; -import javax.persistence.PersistenceException; - -/** - * Implementation of {@link AlertRepository} using SQL database. - * - * @author Deepika Misra (deepika at groupon dot com) - */ -public class DatabaseAlertRepository implements AlertRepository { - - /** - * Public constructor. - * - * @param environment Play's {@code Environment} instance. - * @param config Play's {@code Configuration} instance. - * @param ebeanServer Play's {@code EbeanServer} for this repository. - */ - @Inject - public DatabaseAlertRepository( - final Environment environment, - final Config config, - @Named("metrics_portal") final EbeanServer ebeanServer) { - this(ebeanServer); - } - - /** - * Public constructor for manual configuration. This is intended for testing. - * - * @param ebeanServer Play's {@code EbeanServer} for this repository. - */ - public DatabaseAlertRepository(final EbeanServer ebeanServer) { - _ebeanServer = ebeanServer; - } - - @Override - public void open() { - assertIsOpen(false); - LOGGER.debug().setMessage("Opening alert repository").log(); - _isOpen.set(true); - } - - @Override - public void close() { - assertIsOpen(); - LOGGER.debug().setMessage("Closing alert repository").log(); - _isOpen.set(false); - } - - @Override - public Optional getAlert(final UUID identifier, final Organization organization) { - assertIsOpen(); - LOGGER.debug() - .setMessage("Getting alert") - .addData("alertId", identifier) - .addData("organization", organization) - .log(); - - return _ebeanServer.find(models.ebean.Alert.class) - .where() - .eq("uuid", identifier) - .eq("organization.uuid", organization.getId()) - .findOneOrEmpty() - .map(this::convertFromEbeanAlert); - } - - @Override - public AlertQuery createAlertQuery(final Organization organization) { - assertIsOpen(); - LOGGER.debug() - .setMessage("Preparing query") - .addData("organization", organization) - .log(); - return new DefaultAlertQuery(this, organization); - } - - @Override - public QueryResult queryAlerts(final AlertQuery query) { - assertIsOpen(); - LOGGER.debug() - .setMessage("Querying") - .addData("query", query) - .log(); - - // Create the base query - final PagedList pagedAlerts = createAlertQuery(_ebeanServer, query); - - // Compute the etag - // TODO(deepika): Obfuscate the etag [ISSUE-7] - final long etag = _ebeanServer.createQuery(AlertEtags.class) - .where() - .eq("organization.uuid", query.getOrganization().getId()) - .findOneOrEmpty() - .map(AlertEtags::getEtag) - .orElse(0L); - - final List values = new ArrayList<>(); - pagedAlerts.getList().forEach(ebeanAlert -> values.add(convertFromEbeanAlert(ebeanAlert))); - - // Transform the results - return new DefaultQueryResult<>(values, pagedAlerts.getTotalCount(), String.valueOf(etag)); - } - - @Override - public long getAlertCount(final Organization organization) { - assertIsOpen(); - return _ebeanServer.find(models.ebean.Alert.class) - .where() - .eq("organization.uuid", organization.getId()) - .findCount(); - } - - @Override - public int deleteAlert(final UUID identifier, final Organization organization) { - LOGGER.debug() - .setMessage("Deleting alert") - .addData("id", identifier) - .addData("organization", organization) - .log(); - return _ebeanServer.find(models.ebean.Alert.class) - .where() - .eq("uuid", identifier) - .eq("organization.uuid", organization.getId()) - .delete(); - } - - @Override - public void addOrUpdateAlert(final Alert alert, final Organization organization) { - assertIsOpen(); - LOGGER.debug() - .setMessage("Upserting alert") - .addData("alert", alert) - .addData("organization", organization) - .log(); - - - try (Transaction transaction = _ebeanServer.beginTransaction()) { - final models.ebean.Alert ebeanAlert = _ebeanServer.find(models.ebean.Alert.class) - .where() - .eq("uuid", alert.getId()) - .eq("organization.uuid", organization.getId()) - .findOneOrEmpty() - .orElse(new models.ebean.Alert()); - - final Optional ebeanOrganization = - models.ebean.Organization.findByOrganization(_ebeanServer, organization); - if (!ebeanOrganization.isPresent()) { - throw new IllegalArgumentException("Organization not found: " + organization); - } - - ebeanAlert.setOrganization(ebeanOrganization.get()); - ebeanAlert.setCluster(alert.getCluster()); - ebeanAlert.setUuid(alert.getId()); - ebeanAlert.setMetric(alert.getMetric()); - ebeanAlert.setContext(alert.getContext()); - ebeanAlert.setNagiosExtension(convertToEbeanNagiosExtension(alert.getNagiosExtension())); - ebeanAlert.setName(alert.getName()); - ebeanAlert.setOperator(alert.getOperator()); - ebeanAlert.setPeriod((int) alert.getPeriod().getSeconds()); - ebeanAlert.setQuantityValue(alert.getValue().getValue()); - ebeanAlert.setQuantityUnit(alert.getValue().getUnit().orElse(null)); - ebeanAlert.setStatistic(alert.getStatistic()); - ebeanAlert.setService(alert.getService()); - _ebeanServer.save(ebeanAlert); - transaction.commit(); - - LOGGER.info() - .setMessage("Upserted alert") - .addData("alert", alert) - .addData("organization", organization) - .log(); - // CHECKSTYLE.OFF: IllegalCatchCheck - } catch (final RuntimeException e) { - // CHECKSTYLE.ON: IllegalCatchCheck - LOGGER.error() - .setMessage("Failed to upsert alert") - .addData("alert", alert) - .addData("organization", organization) - .setThrowable(e) - .log(); - throw new PersistenceException(e); - } - } - - private static PagedList createAlertQuery( - final EbeanServer server, - final AlertQuery query) { - ExpressionList ebeanExpressionList = server.find(models.ebean.Alert.class).where(); - ebeanExpressionList = ebeanExpressionList.eq("organization.uuid", query.getOrganization().getId()); - if (query.getCluster().isPresent()) { - ebeanExpressionList = ebeanExpressionList.eq("cluster", query.getCluster().get()); - } - if (query.getContext().isPresent()) { - ebeanExpressionList = ebeanExpressionList.eq("context", query.getContext().get().toString()); - } - if (query.getService().isPresent()) { - ebeanExpressionList = ebeanExpressionList.eq("service", query.getService().get()); - } - - //TODO(deepika): Add full text search [ISSUE-11] - if (query.getContains().isPresent()) { - final Junction junction = ebeanExpressionList.disjunction(); - ebeanExpressionList = junction.contains("name", query.getContains().get()); - if (!query.getCluster().isPresent()) { - ebeanExpressionList = junction.contains("cluster", query.getContains().get()); - } - if (!query.getService().isPresent()) { - ebeanExpressionList = junction.contains("service", query.getContains().get()); - } - ebeanExpressionList = junction.contains("metric", query.getContains().get()); - ebeanExpressionList = junction.contains("statistic", query.getContains().get()); - ebeanExpressionList = junction.contains("operator", query.getContains().get()); - ebeanExpressionList = ebeanExpressionList.endJunction(); - } - final Query ebeanQuery = ebeanExpressionList.query(); - int offset = 0; - if (query.getOffset().isPresent()) { - offset = query.getOffset().get(); - } - return ebeanQuery.setFirstRow(offset).setMaxRows(query.getLimit()).findPagedList(); - } - - private void assertIsOpen() { - assertIsOpen(true); - } - - private void assertIsOpen(final boolean expectedState) { - if (_isOpen.get() != expectedState) { - throw new IllegalStateException(String.format("Alert repository is not %s", expectedState ? "open" : "closed")); - } - } - - private Alert convertFromEbeanAlert(final models.ebean.Alert ebeanAlert) { - return new DefaultAlert.Builder() - .setCluster(ebeanAlert.getCluster()) - .setContext(ebeanAlert.getContext()) - .setId(ebeanAlert.getUuid()) - .setMetric(ebeanAlert.getMetric()) - .setName(ebeanAlert.getName()) - .setOperator(ebeanAlert.getOperator()) - .setPeriod(Duration.ofSeconds(ebeanAlert.getPeriod())) - .setService(ebeanAlert.getService()) - .setStatistic(ebeanAlert.getStatistic()) - .setValue(new DefaultQuantity.Builder() - .setValue(ebeanAlert.getQuantityValue()) - .setUnit(ebeanAlert.getQuantityUnit()) - .build()) - .setNagiosExtension(convertToInternalNagiosExtension(ebeanAlert.getNagiosExtension())) - .build(); - } - - - @Nullable - private models.internal.NagiosExtension convertToInternalNagiosExtension(@Nullable final NagiosExtension ebeanExtension) { - if (ebeanExtension == null) { - return null; - } - return new models.internal.NagiosExtension.Builder() - .setSeverity(ebeanExtension.getSeverity()) - .setNotify(ebeanExtension.getNotify()) - .setMaxCheckAttempts(ebeanExtension.getMaxCheckAttempts()) - .setFreshnessThresholdInSeconds(ebeanExtension.getFreshnessThreshold()) - .build(); - } - - @Nullable - private NagiosExtension convertToEbeanNagiosExtension(@Nullable final models.internal.NagiosExtension internalExtension) { - if (internalExtension == null) { - return null; - } - final NagiosExtension extension = new NagiosExtension(); - extension.setSeverity(internalExtension.getSeverity()); - extension.setNotify(internalExtension.getNotify()); - extension.setMaxCheckAttempts(internalExtension.getMaxCheckAttempts()); - extension.setFreshnessThreshold(internalExtension.getFreshnessThreshold().getSeconds()); - return extension; - } - - private final AtomicBoolean _isOpen = new AtomicBoolean(false); - private final EbeanServer _ebeanServer; - - private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseAlertRepository.class); -} diff --git a/app/com/arpnetworking/metrics/portal/alerts/impl/NoAlertRepository.java b/app/com/arpnetworking/metrics/portal/alerts/impl/NoAlertRepository.java index 01f4061a6..9c5b8467e 100644 --- a/app/com/arpnetworking/metrics/portal/alerts/impl/NoAlertRepository.java +++ b/app/com/arpnetworking/metrics/portal/alerts/impl/NoAlertRepository.java @@ -21,10 +21,10 @@ import com.arpnetworking.steno.Logger; import com.arpnetworking.steno.LoggerFactory; import com.google.inject.Inject; -import models.internal.Alert; import models.internal.AlertQuery; import models.internal.Organization; import models.internal.QueryResult; +import models.internal.alerts.Alert; import models.internal.impl.DefaultAlertQuery; import models.internal.impl.DefaultQueryResult; diff --git a/app/com/arpnetworking/metrics/portal/hosts/impl/CassandraHostRepository.java b/app/com/arpnetworking/metrics/portal/hosts/impl/CassandraHostRepository.java index ca2bed4ed..bcc6d018a 100644 --- a/app/com/arpnetworking/metrics/portal/hosts/impl/CassandraHostRepository.java +++ b/app/com/arpnetworking/metrics/portal/hosts/impl/CassandraHostRepository.java @@ -15,7 +15,6 @@ */ package com.arpnetworking.metrics.portal.hosts.impl; -import com.arpnetworking.metrics.portal.alerts.impl.CassandraAlertRepository; import com.arpnetworking.metrics.portal.hosts.HostRepository; import com.arpnetworking.steno.Logger; import com.arpnetworking.steno.LoggerFactory; @@ -64,14 +63,14 @@ public CassandraHostRepository( @Override public void open() { assertIsOpen(false); - LOGGER.debug().setMessage("Opening alert repository").log(); + LOGGER.debug().setMessage("Opening host repository").log(); _isOpen.set(true); } @Override public void close() { assertIsOpen(); - LOGGER.debug().setMessage("Closing alert repository").log(); + LOGGER.debug().setMessage("Closing host repository").log(); _isOpen.set(false); _cassandraSession.close(); } @@ -122,8 +121,8 @@ public void deleteHost(final String hostname, final Organization organization) { .addData("hostname", hostname) .addData("organization", organization) .log(); - final Optional alert = getHost(hostname, organization); - if (alert.isPresent()) { + final Optional host = getHost(hostname, organization); + if (host.isPresent()) { final Mapper mapper = _mappingManager.mapper(models.cassandra.Host.class); mapper.delete(organization, hostname); } @@ -222,5 +221,5 @@ private void assertIsOpen(final boolean expectedState) { private final MappingManager _mappingManager; private final AtomicBoolean _isOpen = new AtomicBoolean(false); - private static final Logger LOGGER = LoggerFactory.getLogger(CassandraAlertRepository.class); + private static final Logger LOGGER = LoggerFactory.getLogger(CassandraHostRepository.class); } diff --git a/app/controllers/AlertController.java b/app/controllers/AlertController.java deleted file mode 100644 index 640c0a279..000000000 --- a/app/controllers/AlertController.java +++ /dev/null @@ -1,350 +0,0 @@ -/* - * 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.metrics.portal.organizations.OrganizationRepository; -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 com.typesafe.config.Config; -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 play.libs.Json; -import play.mvc.Controller; -import play.mvc.Http; -import play.mvc.Result; - -import java.io.IOException; -import java.time.Duration; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; -import javax.annotation.Nullable; -import javax.inject.Singleton; - -/** - * Metrics portal alert controller. Exposes APIs to query and manipulate alerts. - * - * @author Ville Koskela (ville dot koskela at inscopemetrics dot io) - */ -@Singleton -public class AlertController extends Controller { - - /** - * Public constructor. - * - * @param configuration Instance of Play's {@link Config}. - * @param alertRepository Instance of {@link AlertRepository}. - * @param organizationRepository Instance of {@link OrganizationRepository}. - */ - @Inject - public AlertController( - final Config configuration, - final AlertRepository alertRepository, - final OrganizationRepository organizationRepository) { - this(configuration.getInt("alerts.limit"), alertRepository, organizationRepository); - } - - /** - * 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, _organizationRepository.get(request())); - // 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 noContent(); - } - - /** - * 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 {@link Result} paginated matching alerts. - */ - // CHECKSTYLE.OFF: ParameterNameCheck - Names must match query parameters. - public Result query( - @Nullable final String contains, - @Nullable final String context, - @Nullable final String cluster, - @Nullable final String service, - @Nullable final Integer limit, - @Nullable 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, MoreObjects.firstNonNull(limit, _maxLimit)); - 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(); - argContains.ifPresent(v -> conditions.put("contains", v)); - argContext.ifPresent(v -> conditions.put("context", v.toString())); - argCluster.ifPresent(v -> conditions.put("cluster", v)); - argService.ifPresent(v -> conditions.put("service", v)); - - // Build a host repository query - final AlertQuery query = _alertRepository.createAlertQuery(_organizationRepository.get(request())) - .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)))); - } - - /** - * Get specific alert. - * - * @param id The identifier of the alert. - * @return Matching alert. - */ - public Result get(final String id) { - final UUID identifier; - try { - identifier = UUID.fromString(id); - } catch (final IllegalArgumentException e) { - return badRequest(); - } - final Optional result = _alertRepository.getAlert(identifier, _organizationRepository.get(request())); - if (!result.isPresent()) { - return notFound(); - } - // Return as JSON - return ok(Json.toJson(result.get())); - } - - /** - * Delete a specific alert. - * - * @param id The identifier of the alert. - * @return No content - */ - public Result delete(final String id) { - final UUID identifier = UUID.fromString(id); - final int deleted = _alertRepository.deleteAlert(identifier, _organizationRepository.get(request())); - if (deleted > 0) { - return noContent(); - } else { - return notFound(); - } - } - - 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 convertToInternalNagiosExtension(final Map 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(Duration.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 mergeExtensions(@Nullable final NagiosExtension nagiosExtension) { - final ImmutableMap.Builder nagiosMapBuilder = ImmutableMap.builder(); - if (nagiosExtension != null) { - 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().getSeconds()); - } - 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, - final OrganizationRepository organizationRepository) { - _maxLimit = maxLimit; - _alertRepository = alertRepository; - _organizationRepository = organizationRepository; - } - - private final int _maxLimit; - private final AlertRepository _alertRepository; - private final OrganizationRepository _organizationRepository; - - 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(); -} diff --git a/app/global/MainModule.java b/app/global/MainModule.java index e3a884282..69b03bde2 100644 --- a/app/global/MainModule.java +++ b/app/global/MainModule.java @@ -75,7 +75,6 @@ import com.arpnetworking.utility.ConfigTypedProvider; import com.arpnetworking.utility.ConfigurationOverrideModule; import com.datastax.driver.core.CodecRegistry; -import com.datastax.driver.extras.codecs.enums.EnumNameCodec; import com.datastax.driver.extras.codecs.jdk8.InstantCodec; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; @@ -91,9 +90,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.ebean.Ebean; import io.ebean.EbeanServer; -import models.internal.Context; import models.internal.Features; -import models.internal.Operator; import models.internal.impl.DefaultFeatures; import play.Environment; import play.api.Configuration; @@ -243,8 +240,6 @@ private DevToolsFactory provideChromeDevToolsFactory(final Config config, final private CodecRegistry provideCodecRegistry() { final CodecRegistry registry = CodecRegistry.DEFAULT_INSTANCE; registry.register(InstantCodec.instance); - registry.register(new EnumNameCodec<>(Operator.class)); - registry.register(new EnumNameCodec<>(Context.class)); return registry; } diff --git a/app/models/cassandra/Alert.java b/app/models/cassandra/Alert.java deleted file mode 100644 index fd6848297..000000000 --- a/app/models/cassandra/Alert.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2017 Smartsheet.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 models.cassandra; - -import com.datastax.driver.mapping.Result; -import com.datastax.driver.mapping.annotations.Accessor; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.Frozen; -import com.datastax.driver.mapping.annotations.Param; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Query; -import com.datastax.driver.mapping.annotations.Table; -import models.internal.Context; -import models.internal.Operator; - -import java.time.Instant; -import java.util.Map; -import java.util.UUID; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Version; - -/** - * Model for alerts stored in Cassandra. - * - * @author Brandon Arp (brandon dot arp at smartsheet dot com) - */ -// CHECKSTYLE.OFF: MemberNameCheck -@Table(name = "alerts", keyspace = "portal") -public class Alert { - @Version - @Column(name = "version") - private Long version; - - @Column(name = "created_at") - private Instant createdAt; - - @Column(name = "updated_at") - private Instant updatedAt; - - @Column(name = "uuid") - @PartitionKey - private UUID uuid; - - @Column(name = "name") - private String name; - - @Column(name = "cluster") - private String cluster; - - @Column(name = "service") - private String service; - - @Enumerated(EnumType.STRING) - @Column(name = "context") - private Context context; - - @Column(name = "metric") - private String metric; - - @Column(name = "statistic") - private String statistic; - - @Column(name = "period_in_seconds") - private int periodInSeconds; - - @Enumerated(EnumType.STRING) - @Column(name = "operator") - private Operator operator; - - @Column(name = "quantity_value") - private double quantityValue; - - @Column(name = "quantity_unit") - private String quantityUnit; - - @Column(name = "organization") - private UUID organization; - - @Frozen - @Column(name = "nagios_extensions") - private Map nagiosExtensions; - - public Long getVersion() { - return version; - } - - public void setVersion(final Long value) { - version = value; - } - - public Instant getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(final Instant value) { - createdAt = value; - } - - public Instant getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(final Instant value) { - updatedAt = value; - } - - public UUID getUuid() { - return uuid; - } - - public void setUuid(final UUID value) { - uuid = value; - } - - public String getName() { - return name; - } - - public void setName(final String value) { - name = value; - } - - public String getCluster() { - return cluster; - } - - public void setCluster(final String value) { - cluster = value; - } - - public String getService() { - return service; - } - - public void setService(final String value) { - service = value; - } - - public Context getContext() { - return context; - } - - public void setContext(final Context value) { - context = value; - } - - public String getMetric() { - return metric; - } - - public void setMetric(final String value) { - metric = value; - } - - public String getStatistic() { - return statistic; - } - - public void setStatistic(final String value) { - statistic = value; - } - - public int getPeriodInSeconds() { - return periodInSeconds; - } - - public void setPeriodInSeconds(final int value) { - periodInSeconds = value; - } - - public Operator getOperator() { - return operator; - } - - public void setOperator(final Operator value) { - operator = value; - } - - public double getQuantityValue() { - return quantityValue; - } - - public void setQuantityValue(final double value) { - quantityValue = value; - } - - public String getQuantityUnit() { - return quantityUnit; - } - - public void setQuantityUnit(final String value) { - quantityUnit = value; - } - - public UUID getOrganization() { - return organization; - } - - public void setOrganization(final UUID value) { - organization = value; - } - - public Map getNagiosExtensions() { - return nagiosExtensions; - } - - public void setNagiosExtensions(final Map value) { - nagiosExtensions = value; - } - - - /** - * Queries for alerts. - * - * @author Brandon Arp (brandon dot arp at smartsheet dot com) - */ - @Accessor - public interface AlertQueries { - /** - * Queries for all alerts in an organization. - * - * @param organization Organization owning the alerts - * @return Mapped query results - */ - @Query("select * from portal.alerts_by_organization where organization = :org") - Result getAlertsForOrganization(@Param("org") UUID organization); - } -} -// CHECKSTYLE.ON: MemberNameCheck diff --git a/app/models/ebean/Alert.java b/app/models/ebean/Alert.java deleted file mode 100644 index 279e1bf9b..000000000 --- a/app/models/ebean/Alert.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * 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 models.ebean; - -import io.ebean.annotation.CreatedTimestamp; -import io.ebean.annotation.UpdatedTimestamp; -import models.internal.Context; -import models.internal.Operator; - -import java.sql.Timestamp; -import java.util.UUID; -import javax.annotation.Nullable; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.OneToOne; -import javax.persistence.Table; -import javax.persistence.Version; - -/** - * Data model for alerts. - * - * NOTE: This class is enhanced by Ebean to do things like lazy loading and - * resolving relationships between beans. Therefore, including functionality - * which serializes the state of the object can be dangerous (e.g. {@code toString}, - * {@code @Loggable}, etc.). - * - * @author Deepika Misra (deepika at groupon dot com) - */ -// CHECKSTYLE.OFF: MemberNameCheck -@Entity -@Table(name = "alerts", schema = "portal") -public class Alert { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; - - @Version - @Column(name = "version") - private Long version; - - @CreatedTimestamp - @Column(name = "created_at") - private Timestamp createdAt; - - @UpdatedTimestamp - @Column(name = "updated_at") - private Timestamp updatedAt; - - @Column(name = "uuid") - private UUID uuid; - - @Column(name = "name") - private String name; - - @Column(name = "cluster") - private String cluster; - - @Column(name = "service") - private String service; - - @Enumerated(EnumType.STRING) - @Column(name = "context") - private Context context; - - @Column(name = "metric") - private String metric; - - @Column(name = "statistic") - private String statistic; - - @Column(name = "period_in_seconds") - private int periodInSeconds; - - @Enumerated(EnumType.STRING) - @Column(name = "operator") - private Operator operator; - - @Column(name = "quantity_value") - private double quantityValue; - - @Column(name = "quantity_unit") - private String quantityUnit; - - @OneToOne(mappedBy = "alert", cascade = CascadeType.ALL) - private NagiosExtension nagiosExtension; - - @ManyToOne(optional = false) - @JoinColumn(name = "organization") - private Organization organization; - - public Long getId() { - return id; - } - - public void setId(final Long value) { - id = value; - } - - public Long getVersion() { - return version; - } - - public void setVersion(final Long value) { - version = value; - } - - public Timestamp getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(final Timestamp value) { - createdAt = value; - } - - public Timestamp getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(final Timestamp value) { - updatedAt = value; - } - - public UUID getUuid() { - return uuid; - } - - public void setUuid(final UUID value) { - uuid = value; - } - - public String getName() { - return name; - } - - public void setName(final String value) { - name = value; - } - - public String getCluster() { - return cluster; - } - - public void setCluster(final String value) { - cluster = value; - } - - public String getService() { - return service; - } - - public void setService(final String value) { - service = value; - } - - public Context getContext() { - return context; - } - - public void setContext(final Context value) { - context = value; - } - - public String getMetric() { - return metric; - } - - public void setMetric(final String value) { - metric = value; - } - - public String getStatistic() { - return statistic; - } - - public void setStatistic(final String value) { - statistic = value; - } - - public int getPeriod() { - return periodInSeconds; - } - - public void setPeriod(final int value) { - periodInSeconds = value; - } - - public Operator getOperator() { - return operator; - } - - public void setOperator(final Operator value) { - operator = value; - } - - public double getQuantityValue() { - return quantityValue; - } - - public void setQuantityValue(final double value) { - quantityValue = value; - } - - public String getQuantityUnit() { - return quantityUnit; - } - - public void setQuantityUnit(final String value) { - quantityUnit = value; - } - - @Nullable - public NagiosExtension getNagiosExtension() { - return nagiosExtension; - } - - public void setNagiosExtension(final NagiosExtension value) { - nagiosExtension = value; - } - - public Organization getOrganization() { - return organization; - } - - public void setOrganization(final Organization organizationValue) { - this.organization = organizationValue; - } -} -// CHECKSTYLE.ON: MemberNameCheck diff --git a/app/models/ebean/AlertEtags.java b/app/models/ebean/AlertEtags.java deleted file mode 100644 index 8d23b90e5..000000000 --- a/app/models/ebean/AlertEtags.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2016 Smartsheet.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 models.ebean; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.Table; - -/** - * Model class to represent alert etag records. - * - * NOTE: This class is enhanced by Ebean to do things like lazy loading and - * resolving relationships between beans. Therefore, including functionality - * which serializes the state of the object can be dangerous (e.g. {@code toString}, - * {@code @Loggable}, etc.). - * - * @author Brandon Arp (brandon dot arp at smartsheet dot com) - */ -// CHECKSTYLE.OFF: MemberNameCheck -@Entity -@Table(name = "alerts_etags", schema = "portal") -public class AlertEtags { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; - - @ManyToOne(optional = false) - @JoinColumn(name = "organization") - private Organization organization; - - @Column(nullable = false) - private long etag; - - public Long getId() { - return id; - } - - public void setId(final Long value) { - id = value; - } - - public Organization getOrganization() { - return organization; - } - - public void setOrganization(final Organization value) { - organization = value; - } - - public long getEtag() { - return etag; - } - - public void setEtag(final long value) { - etag = value; - } -} -// CHECKSTYLE.ON: MemberNameCheck diff --git a/app/models/ebean/BaseExecution.java b/app/models/ebean/BaseExecution.java index 76c9678da..ad24701e7 100644 --- a/app/models/ebean/BaseExecution.java +++ b/app/models/ebean/BaseExecution.java @@ -30,7 +30,7 @@ import javax.persistence.MappedSuperclass; /** - * A execution event for a {@link models.internal.Alert}. + * A generic execution event for a {@code Job}. *

* NOTE: This class is enhanced by Ebean to do things like lazy loading and * resolving relationships between beans. Therefore, including functionality diff --git a/app/models/ebean/NagiosExtension.java b/app/models/ebean/NagiosExtension.java deleted file mode 100644 index 22c7c2c51..000000000 --- a/app/models/ebean/NagiosExtension.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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 models.ebean; - -import io.ebean.annotation.CreatedTimestamp; -import io.ebean.annotation.UpdatedTimestamp; - -import java.sql.Timestamp; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.OneToOne; -import javax.persistence.Table; -import javax.persistence.Version; - -/** - * Data model for extensions data in alerts. - * - * NOTE: This class is enhanced by Ebean to do things like lazy loading and - * resolving relationships between beans. Therefore, including functionality - * which serializes the state of the object can be dangerous (e.g. {@code toString}, - * {@code @Loggable}, etc.). - * - * @author Deepika Misra (deepika at groupon dot com) - */ -// CHECKSTYLE.OFF: MemberNameCheck -@Entity -@Table(name = "nagios_extensions", schema = "portal") -public class NagiosExtension { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; - - @Version - @Column(name = "version") - private Long version; - - @CreatedTimestamp - @Column(name = "created_at") - private Timestamp createdAt; - - @UpdatedTimestamp - @Column(name = "updated_at") - private Timestamp updatedAt; - - @OneToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "alert_id") - private Alert alert; - - @Column(name = "severity") - private String severity; - - @Column(name = "notify") - private String notify; - - @Column(name = "max_check_attempts") - private int maxCheckAttempts; - - @Column(name = "freshness_threshold_in_seconds") - private long freshnessThresholdInSeconds; - - public Alert getAlert() { - return alert; - } - - public void setAlert(final Alert value) { - alert = value; - } - - public Long getVersion() { - return version; - } - - public void setVersion(final Long value) { - version = value; - } - - public Timestamp getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(final Timestamp value) { - createdAt = value; - } - - public Timestamp getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(final Timestamp value) { - updatedAt = value; - } - - public String getSeverity() { - return severity; - } - - public void setSeverity(final String value) { - severity = value; - } - - public String getNotify() { - return notify; - } - - public void setNotify(final String value) { - notify = value; - } - - public int getMaxCheckAttempts() { - return maxCheckAttempts; - } - - public void setMaxCheckAttempts(final int value) { - maxCheckAttempts = value; - } - - public long getFreshnessThreshold() { - return freshnessThresholdInSeconds; - } - - public void setFreshnessThreshold(final long value) { - freshnessThresholdInSeconds = value; - } -} -// CHECKSTYLE.ON: MemberNameCheck diff --git a/app/models/internal/Alert.java b/app/models/internal/Alert.java deleted file mode 100644 index b996ce57b..000000000 --- a/app/models/internal/Alert.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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 models.internal; - -import java.time.Duration; -import java.util.UUID; -import javax.annotation.Nullable; - -/** - * Internal model interface for an alert. - * - * @author Ville Koskela (ville dot koskela at inscopemetrics dot io) - */ -public interface Alert { - - /** - * The unique identifier of the alert. - * - * @return The unique identifier of the alert. - */ - UUID getId(); - - /** - * The context of the alert. Either a host or cluster. - * - * @return The context of the alert. - */ - Context getContext(); - - /** - * The name of the alert. - * - * @return The name of the alert. - */ - String getName(); - - /** - * The name of the cluster for statistic identifier of condition left-hand side. - * - * @return The name of the cluster for statistic identifier of condition left-hand side. - */ - String getCluster(); - - /** - * The name of the service for statistic identifier of condition left-hand side. - * - * @return The name of the service for statistic identifier of condition left-hand side. - */ - String getService(); - - /** - * The name of the metric for statistic identifier of condition left-hand side. - * - * @return The name of the metric for statistic identifier of condition left-hand side. - */ - String getMetric(); - - /** - * The name of the statistic for statistic identifier of condition left-hand side. - * - * @return The name of the statistic for statistic identifier of condition left-hand side. - */ - String getStatistic(); - - /** - * The period to evaluate the condition in. - * - * @return The period to evaluate the condition in. - */ - Duration getPeriod(); - - /** - * The condition operator. - * - * @return The condition operator. - */ - Operator getOperator(); - - /** - * The value of condition right-hand side. - * - * @return The value of condition right-hand side. - */ - Quantity getValue(); - - /** - * Nagios specific extensions. - * - * @return Nagios specific extensions. - */ - @Nullable - NagiosExtension getNagiosExtension(); -} diff --git a/app/models/internal/AlertQuery.java b/app/models/internal/AlertQuery.java index 05bf5d32b..e360307d2 100644 --- a/app/models/internal/AlertQuery.java +++ b/app/models/internal/AlertQuery.java @@ -15,6 +15,8 @@ */ package models.internal; +import models.internal.alerts.Alert; + import java.util.Optional; /** @@ -32,30 +34,6 @@ public interface AlertQuery { */ AlertQuery contains(Optional contains); - /** - * Set the context to query for. Optional. Defaults to all context types. - * - * @param context The context to match. - * @return This instance of {@code AlertQuery}. - */ - AlertQuery context(Optional context); - - /** - * Set the cluster to query for. Optional. Defaults to all clusters. - * - * @param cluster The cluster to match. - * @return This instance of {@link AlertQuery}. - */ - AlertQuery cluster(Optional cluster); - - /** - * Set the service to query for. Optional. Defaults to all services. - * - * @param service The service to match. - * @return This instance of {@link AlertQuery}. - */ - AlertQuery service(Optional service); - /** * The maximum number of alerts to return. Optional. Default is 1000. * @@ -93,27 +71,6 @@ public interface AlertQuery { */ Optional getContains(); - /** - * Accessor for the context. - * - * @return The context. - */ - Optional getContext(); - - /** - * Accessor for the cluster. - * - * @return The cluster. - */ - Optional getCluster(); - - /** - * Accessor for the service. - * - * @return The service. - */ - Optional getService(); - /** * Accessor for the limit. * diff --git a/app/models/internal/NagiosExtension.java b/app/models/internal/NagiosExtension.java deleted file mode 100644 index 0ada3db3c..000000000 --- a/app/models/internal/NagiosExtension.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2016 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 models.internal; - -import com.arpnetworking.commons.builder.OvalBuilder; -import com.arpnetworking.logback.annotations.Loggable; -import com.google.common.base.MoreObjects; -import net.sf.oval.constraint.Min; -import net.sf.oval.constraint.NotEmpty; -import net.sf.oval.constraint.NotNull; - -import java.time.Duration; -import java.util.Objects; - -/** - * Represents the Nagios specific data for an {@link Alert}. - * - * @author Deepika Misra (deepika at groupon dot com) - */ -@Loggable -public final class NagiosExtension { - - public String getSeverity() { - return _severity; - } - - public String getNotify() { - return _notify; - } - - public int getMaxCheckAttempts() { - return _maxCheckAttempts; - } - - public Duration getFreshnessThreshold() { - return _freshnessThreshold; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("id", Integer.toHexString(System.identityHashCode(this))) - .add("class", this.getClass()) - .add("Severity", _severity) - .add("Notify", _notify) - .add("MaxCheckAttempts", _maxCheckAttempts) - .add("FreshnessThreshold", _freshnessThreshold) - .toString(); - } - - @Override - public boolean equals(final Object other) { - if (this == other) { - return true; - } - - if (!(other instanceof NagiosExtension)) { - return false; - } - - final NagiosExtension otherExtension = (NagiosExtension) other; - return Objects.equals(_severity, otherExtension._severity) - && Objects.equals(_notify, otherExtension._notify) - && _maxCheckAttempts == otherExtension._maxCheckAttempts - && Objects.equals(_freshnessThreshold, otherExtension._freshnessThreshold); - } - - @Override - public int hashCode() { - return Objects.hash( - _severity, - _notify, - _maxCheckAttempts, - _freshnessThreshold); - } - - private NagiosExtension(final Builder builder) { - _severity = builder._severity; - _notify = builder._notify; - _maxCheckAttempts = builder._maxCheckAttempts; - _freshnessThreshold = Duration.ofSeconds(builder._freshnessThresholdInSeconds); - } - - private final String _severity; - private final String _notify; - private final int _maxCheckAttempts; - private final Duration _freshnessThreshold; - - /** - * {@code Builder} implementation for {@link NagiosExtension}. - */ - public static final class Builder extends OvalBuilder { - - /** - * Public constructor. - */ - public Builder() { - super(NagiosExtension::new); - } - - /** - * The severity. Required. Cannot be null. - * - * @param value The severity. - * @return This instance of {@link Builder}. - */ - public Builder setSeverity(final String value) { - _severity = value; - return this; - } - - /** - * The email to notify to. Required. Cannot be null. - * - * @param value The notification address. - * @return This instance of {@link Builder}. - */ - public Builder setNotify(final String value) { - _notify = value; - return this; - } - - /** - * The maximum number of attempts to check the value. Required. Cannot be null. - * - * @param value The maximum number of check attempts. - * @return This instance of {@link Builder}. - */ - public Builder setMaxCheckAttempts(final Integer value) { - _maxCheckAttempts = value; - return this; - } - - /** - * The freshness threshold in seconds. Required. Cannot be null. - * - * @param value The freshness threshold in seconds. - * @return This instance of {@link Builder}. - */ - public Builder setFreshnessThresholdInSeconds(final Long value) { - _freshnessThresholdInSeconds = value; - return this; - } - - @NotNull - @NotEmpty - private String _severity; - @NotNull - @NotEmpty - private String _notify; - @NotNull - @Min(1) - private Integer _maxCheckAttempts; - @NotNull - @Min(0) - private Long _freshnessThresholdInSeconds; - } - -} diff --git a/app/models/internal/Operator.java b/app/models/internal/Operator.java deleted file mode 100644 index 2c8a52154..000000000 --- a/app/models/internal/Operator.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 models.internal; - -/** - * Alert condition operators. - * - * @author Ville Koskela (ville dot koskela at inscopemetrics dot io) - */ -public enum Operator { - - /** - * Equal to. - */ - EQUAL_TO, - - /** - * Not equal to. - */ - NOT_EQUAL_TO, - - /** - * Less than. - */ - LESS_THAN, - - /** - * Less than or equal to. - */ - LESS_THAN_OR_EQUAL_TO, - - /** - * Greater than. - */ - GREATER_THAN, - - /** - * Greater than or equal to. - */ - GREATER_THAN_OR_EQUAL_TO; -} diff --git a/app/models/internal/alerts/Alert.java b/app/models/internal/alerts/Alert.java new file mode 100644 index 000000000..9d56cfe10 --- /dev/null +++ b/app/models/internal/alerts/Alert.java @@ -0,0 +1,92 @@ +/* + * Copyright 2020 Dropbox, 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 models.internal.alerts; + +import com.arpnetworking.metrics.portal.alerts.AlertRepository; +import com.google.common.collect.ImmutableMap; +import models.internal.MetricsQuery; +import models.internal.Organization; + +import java.util.UUID; + +/** + * Internal model interface for an alert. + * + * @author Ville Koskela (ville dot koskela at inscopemetrics dot io) + */ +public interface Alert { + + /** + * The unique identifier of the alert. + * + * @return The unique identifier of the alert. + */ + UUID getId(); + + /** + * The organization for this alert. + * + * @return The organization. + */ + Organization getOrganization(); + + /** + * The name of the alert. + * + * @return The name of the alert. + */ + String getName(); + + /** + * The description of this alert. + *

+ * This generally includes an explanation of the behavior that the alert query attempts to capture, as well + * as possible remediation steps. + * + * @return The alert description. + */ + String getDescription(); + + /** + * The query to evaluate. + *

+ * If an alert query evaluates to a nonempty series, then it is considered firing. + * + * @return the alert query. + */ + MetricsQuery getQuery(); + + /** + * Returns {@code true} iff this alert is enabled. + * + * @return true if this alert is enabled, otherwise false. + */ + boolean isEnabled(); + + /** + * Implementation-defined metadata for this alert. + *

+ * The contents of this field are intentionally left unspecified and are up to the particular alert implementation. + *

+ * This is intended to act as a mechanism for implementations to pass around additional context outside of this interface + * (e.g. fields configured elsewhere that may be used outside of metrics portal). + * + * Particular implementations of {@link AlertRepository} may make use of this field, but that is not required. + * + * @return the metadata. + */ + ImmutableMap getAdditionalMetadata(); +} diff --git a/app/models/internal/alerts/AlertEvaluationResult.java b/app/models/internal/alerts/AlertEvaluationResult.java new file mode 100644 index 000000000..9b74b4a93 --- /dev/null +++ b/app/models/internal/alerts/AlertEvaluationResult.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Dropbox, 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 models.internal.alerts; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import models.internal.impl.DefaultAlertEvaluationResult; +import models.internal.scheduling.JobExecution; + +/** + * The result of evaluating an Alert. + *

+ * This is not useful on its own since it does not expose any event timestamps or alert metadata. + * It's expected that those values will be obtained from the associated {@link JobExecution} instances + * along with the alert definition itself. + * + * @author Christian Briones (cbriones at dropbox dot com) + */ +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "@name" +) +@JsonSubTypes( + @JsonSubTypes.Type( + value = DefaultAlertEvaluationResult.class, + name = "DefaultAlertEvaluationResult" + ) +) +public interface AlertEvaluationResult { + + // XXX(cbriones): This is likely to change once we actually integrate alerts with the job scheduling code. + + /** + * A list of firing tag-sets at the time of evaluation. + * + * @return the tag sets + */ + ImmutableList> getFiringTags(); +} diff --git a/app/models/internal/Context.java b/app/models/internal/alerts/package-info.java similarity index 65% rename from app/models/internal/Context.java rename to app/models/internal/alerts/package-info.java index ec93e451f..f4f8cea10 100644 --- a/app/models/internal/Context.java +++ b/app/models/internal/alerts/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Groupon.com + * Copyright 2020 Dropbox, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,22 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package models.internal; -/** - * The type of alert context. Either host or cluster. - * - * @author Ville Koskela (ville dot koskela at inscopemetrics dot io) - */ -public enum Context { +@ParametersAreNonnullByDefault +@ReturnValuesAreNonnullByDefault +package models.internal.alerts; - /** - * Host. - */ - HOST, +import com.arpnetworking.commons.javax.annotation.ReturnValuesAreNonnullByDefault; - /** - * Cluster. - */ - CLUSTER; -} +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/app/models/internal/impl/DefaultAlert.java b/app/models/internal/impl/DefaultAlert.java index 23ece4729..7953852a8 100644 --- a/app/models/internal/impl/DefaultAlert.java +++ b/app/models/internal/impl/DefaultAlert.java @@ -18,18 +18,16 @@ import com.arpnetworking.commons.builder.OvalBuilder; import com.arpnetworking.logback.annotations.Loggable; import com.google.common.base.MoreObjects; -import models.internal.Alert; -import models.internal.Context; -import models.internal.NagiosExtension; -import models.internal.Operator; -import models.internal.Quantity; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableMap; +import models.internal.MetricsQuery; +import models.internal.Organization; +import models.internal.alerts.Alert; import net.sf.oval.constraint.NotEmpty; import net.sf.oval.constraint.NotNull; -import java.time.Duration; -import java.util.Objects; +import java.util.Map; import java.util.UUID; -import javax.annotation.Nullable; /** * Default internal model implementation for an alert. @@ -39,14 +37,32 @@ @Loggable public final class DefaultAlert implements Alert { + private final UUID _id; + private final Organization _organization; + private final String _name; + private final String _description; + private final MetricsQuery _query; + private final boolean _enabled; + private final ImmutableMap _additionalMetadata; + + private DefaultAlert(final Builder builder) { + _id = builder._id; + _organization = builder._organization; + _name = builder._name; + _description = builder._description; + _query = builder._query; + _enabled = builder._enabled; + _additionalMetadata = builder._additionalMetadata; + } + @Override public UUID getId() { return _id; } @Override - public Context getContext() { - return _context; + public Organization getOrganization() { + return _organization; } @Override @@ -55,144 +71,91 @@ public String getName() { } @Override - public String getCluster() { - return _cluster; - } - - @Override - public String getService() { - return _service; - } - - @Override - public String getMetric() { - return _metric; - } - - @Override - public String getStatistic() { - return _statistic; - } - - @Override - public Duration getPeriod() { - return _period; + public String getDescription() { + return _description; } @Override - public Operator getOperator() { - return _operator; + public MetricsQuery getQuery() { + return _query; } @Override - public Quantity getValue() { - return _value; + public boolean isEnabled() { + return _enabled; } @Override - public NagiosExtension getNagiosExtension() { - return _nagiosExtension; + public ImmutableMap getAdditionalMetadata() { + return _additionalMetadata; } @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("id", Integer.toHexString(System.identityHashCode(this))) - .add("class", this.getClass()) - .add("Id", _id) - .add("Context", _context) - .add("Name", _name) - .add("Cluster", _cluster) - .add("Service", _service) - .add("Metric", _metric) - .add("Statistic", _statistic) - .add("Period", _period) - .add("Operator", _operator) - .add("Value", _value) - .add("NagiosExtensions", _nagiosExtension) - .toString(); - } - - @Override - public boolean equals(final Object other) { - if (this == other) { + public boolean equals(final Object o) { + if (this == o) { return true; } - - if (!(other instanceof DefaultAlert)) { + if (o == null || getClass() != o.getClass()) { return false; } - - final DefaultAlert otherAlert = (DefaultAlert) other; - return Objects.equals(_id, otherAlert._id) - && Objects.equals(_context, otherAlert._context) - && Objects.equals(_name, otherAlert._name) - && Objects.equals(_cluster, otherAlert._cluster) - && Objects.equals(_service, otherAlert._service) - && Objects.equals(_metric, otherAlert._metric) - && Objects.equals(_statistic, otherAlert._statistic) - && Objects.equals(_period, otherAlert._period) - && Objects.equals(_operator, otherAlert._operator) - && Objects.equals(_value, otherAlert._value) - && Objects.equals(_nagiosExtension, otherAlert._nagiosExtension); + final DefaultAlert that = (DefaultAlert) o; + return _enabled == that._enabled + && Objects.equal(_id, that._id) + && Objects.equal(_organization, that._organization) + && Objects.equal(_name, that._name) + && Objects.equal(_description, that._description) + && Objects.equal(_query, that._query) + && Objects.equal(_additionalMetadata, that._additionalMetadata); } @Override public int hashCode() { - return Objects.hash( - _id, - _context, - _name, - _cluster, - _service, - _metric, - _statistic, - _period, - _operator, - _value, - _nagiosExtension); + return Objects.hashCode(_id, _organization, _name, _description, _query, _enabled, _additionalMetadata); } - private DefaultAlert(final Builder builder) { - _id = builder._id; - _context = builder._context; - _name = builder._name; - _cluster = builder._cluster; - _service = builder._service; - _metric = builder._metric; - _statistic = builder._statistic; - _period = builder._period; - _operator = builder._operator; - _value = builder._value; - _nagiosExtension = builder._nagiosExtension; + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("id", _id) + .add("organization", _organization) + .add("name", _name) + .add("description", _description) + .add("query", _query) + .add("enabled", _enabled) + .add("additionalMetadata", _additionalMetadata) + .toString(); } - private final UUID _id; - private final Context _context; - private final String _name; - private final String _cluster; - private final String _service; - private final String _metric; - private final String _statistic; - private final Duration _period; - private final Operator _operator; - private final Quantity _value; - private final NagiosExtension _nagiosExtension; - /** * Builder implementation for {@link DefaultAlert}. */ public static final class Builder extends OvalBuilder { + @NotNull + private UUID _id; + @NotNull + private Organization _organization; + @NotNull + @NotEmpty + private String _name; + @NotNull + private String _description; + @NotNull + private MetricsQuery _query; + @NotNull + private Boolean _enabled; + private ImmutableMap _additionalMetadata; + /** * Public constructor. */ public Builder() { super(DefaultAlert::new); + _additionalMetadata = ImmutableMap.of(); } /** - * The identifier. Required. Cannot be null. + * The alert identifier. Required. Cannot be null. * * @param value The identifier. * @return This instance of {@link Builder}. @@ -203,18 +166,18 @@ public Builder setId(final UUID value) { } /** - * The context. Required. Cannot be null or empty. + * The alert organization. Required. Cannot be null. * - * @param value The context. + * @param value The organization. * @return This instance of {@link Builder}. */ - public Builder setContext(final Context value) { - _context = value; + public Builder setOrganization(final Organization value) { + _organization = value; return this; } /** - * The name. Required. Cannot be null or empty. + * The alert name. Required. Cannot be null or empty. * * @param value The name. * @return This instance of {@link Builder}. @@ -225,119 +188,47 @@ public Builder setName(final String value) { } /** - * The cluster. Required. Cannot be null or empty. - * - * @param value The cluster. - * @return This instance of {@link Builder}. - */ - public Builder setCluster(final String value) { - _cluster = value; - return this; - } - - /** - * The service. Required. Cannot be null or empty. + * The alert description. Defaults to an empty description. * - * @param value The service. + * @param value The description. * @return This instance of {@link Builder}. */ - public Builder setService(final String value) { - _service = value; + public Builder setDescription(final String value) { + _description = value; return this; } /** - * The metric. Required. Cannot be null or empty. + * The alert query. Required. Cannot be null. * - * @param value The metric. + * @param value The query. * @return This instance of {@link Builder}. */ - public Builder setMetric(final String value) { - _metric = value; + public Builder setQuery(final MetricsQuery value) { + _query = value; return this; } /** - * The statistic. Required. Cannot be null or empty. + * The enabled flag. Required. * - * @param value The statistic. + * @param value Whether or not this alert is enabled. * @return This instance of {@link Builder}. */ - public Builder setStatistic(final String value) { - _statistic = value; + public Builder setEnabled(final boolean value) { + _enabled = value; return this; } /** - * The period. Required. Cannot be null or empty. + * The additional metadata. Defaults to an empty map. * * @param value The period. * @return This instance of {@link Builder}. */ - public Builder setPeriod(final Duration value) { - _period = value; - return this; - } - - /** - * The operator. Required. Cannot be null or empty. - * - * @param value The operator. - * @return This instance of {@link Builder}. - */ - public Builder setOperator(final Operator value) { - _operator = value; - return this; - } - - /** - * The value. Required. Cannot be null or empty. - * - * @param value The value. - * @return This instance of {@link Builder}. - */ - public Builder setValue(final Quantity value) { - _value = value; + public Builder setAdditionalMetadata(final Map value) { + _additionalMetadata = ImmutableMap.copyOf(value); return this; } - - /** - * The nagios specific extensions. - * - * @param value The extensions. - * @return This instance of {@link Builder}. - */ - public Builder setNagiosExtension(@Nullable final NagiosExtension value) { - _nagiosExtension = value; - return this; - } - - @NotNull - private UUID _id; - @NotNull - private Context _context; - @NotNull - @NotEmpty - private String _name; - @NotNull - @NotEmpty - private String _cluster; - @NotNull - @NotEmpty - private String _service; - @NotNull - @NotEmpty - private String _metric; - @NotNull - @NotEmpty - private String _statistic; - @NotNull - private Duration _period; - @NotNull - private Operator _operator; - @NotNull - private Quantity _value; - @Nullable - private NagiosExtension _nagiosExtension; } } diff --git a/app/models/internal/impl/DefaultAlertEvaluationResult.java b/app/models/internal/impl/DefaultAlertEvaluationResult.java new file mode 100644 index 000000000..60f6c938f --- /dev/null +++ b/app/models/internal/impl/DefaultAlertEvaluationResult.java @@ -0,0 +1,98 @@ +/* + * Copyright 2020 Dropbox, 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 models.internal.impl; + +import com.arpnetworking.commons.builder.OvalBuilder; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import models.internal.alerts.AlertEvaluationResult; + +import java.util.List; +import java.util.Map; + +/** + * Default implementation for an {@code AlertEvaluationResult}. + * + * @author Christian Briones (cbriones at dropbox dot com) + */ +@JsonDeserialize(builder = DefaultAlertEvaluationResult.Builder.class) +public final class DefaultAlertEvaluationResult implements AlertEvaluationResult { + private final ImmutableList> _firingTags; + + private DefaultAlertEvaluationResult(final Builder builder) { + _firingTags = builder._firingTags; + } + + @Override + public ImmutableList> getFiringTags() { + return _firingTags; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final DefaultAlertEvaluationResult that = (DefaultAlertEvaluationResult) o; + return Objects.equal(_firingTags, that._firingTags); + } + + @Override + public int hashCode() { + return Objects.hashCode(_firingTags); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("firingTags", _firingTags) + .toString(); + } + + /** + * Builder class for instances of {@code DefaultAlertEvaluationResult}. + */ + public static class Builder extends OvalBuilder { + private ImmutableList> _firingTags = ImmutableList.of(); + + /** + * Default constructor for an empty builder. + */ + public Builder() { + super(DefaultAlertEvaluationResult::new); + } + + /** + * Set the firing tags. Defaults to empty. + * + * @param firingTags The set of firing tags. + * @return This instance of {@code Builder}. + */ + @JsonProperty("firingTags") + public Builder setFiringTags(final List> firingTags) { + _firingTags = firingTags.stream().map(ImmutableMap::copyOf).collect(ImmutableList.toImmutableList()); + return this; + } + } +} diff --git a/app/models/internal/impl/DefaultAlertQuery.java b/app/models/internal/impl/DefaultAlertQuery.java index 662c473ad..7869ee00b 100644 --- a/app/models/internal/impl/DefaultAlertQuery.java +++ b/app/models/internal/impl/DefaultAlertQuery.java @@ -18,11 +18,10 @@ import com.arpnetworking.logback.annotations.Loggable; import com.arpnetworking.metrics.portal.alerts.AlertRepository; import com.google.common.base.MoreObjects; -import models.internal.Alert; import models.internal.AlertQuery; -import models.internal.Context; import models.internal.Organization; import models.internal.QueryResult; +import models.internal.alerts.Alert; import java.util.Optional; @@ -51,24 +50,6 @@ public AlertQuery contains(final Optional contains) { return this; } - @Override - public AlertQuery context(final Optional context) { - _context = context; - return this; - } - - @Override - public AlertQuery cluster(final Optional cluster) { - _cluster = cluster; - return this; - } - - @Override - public AlertQuery service(final Optional service) { - _service = service; - return this; - } - @Override public AlertQuery limit(final int limit) { _limit = limit; @@ -96,21 +77,6 @@ public Optional getContains() { return _contains; } - @Override - public Optional getContext() { - return _context; - } - - @Override - public Optional getCluster() { - return _cluster; - } - - @Override - public Optional getService() { - return _service; - } - @Override public int getLimit() { return _limit; @@ -128,9 +94,6 @@ public String toString() { .add("class", this.getClass()) .add("Repository", _repository) .add("Contains", _contains) - .add("Context", _context) - .add("Cluster", _cluster) - .add("Service", _service) .add("Limit", _limit) .add("Offset", _offset) .toString(); @@ -139,9 +102,6 @@ public String toString() { private final AlertRepository _repository; private final Organization _organization; private Optional _contains = Optional.empty(); - private Optional _context = Optional.empty(); - private Optional _cluster = Optional.empty(); - private Optional _service = Optional.empty(); private int _limit = DEFAULT_LIMIT; private Optional _offset = Optional.empty(); diff --git a/app/models/view/Alert.java b/app/models/view/Alert.java index a488e4491..7d928e905 100644 --- a/app/models/view/Alert.java +++ b/app/models/view/Alert.java @@ -20,7 +20,7 @@ import com.google.common.collect.ImmutableMap; /** - * View model of {@link models.internal.Alert}. Play view models are mutable. + * View model of {@link models.internal.alerts.Alert}. Play view models are mutable. * * TODO(ville): Update view model with appropriate nullability of fields. * diff --git a/conf/portal.routes b/conf/portal.routes index 472435e08..ea2316775 100644 --- a/conf/portal.routes +++ b/conf/portal.routes @@ -34,12 +34,6 @@ GET /v1/hosts/query controllers.HostController.query GET /v1/hosts/:id controllers.HostController.get(id: String) PUT /v1/hosts controllers.HostController.addOrUpdate -# Alerts -GET /v1/alerts/query controllers.AlertController.query(contains: String ?= null, context: String ?= null, cluster: String ?= null, service: String ?= null, limit: java.lang.Integer ?= null, offset: java.lang.Integer ?= null) -GET /v1/alerts/:id controllers.AlertController.get(id: String) -DELETE /v1/alerts/:id controllers.AlertController.delete(id: String) -PUT /v1/alerts controllers.AlertController.addOrUpdate - # Reports GET /v1/reports/query controllers.ReportController.query(limit: java.lang.Integer ?= null, offset: java.lang.Integer ?= null) GET /v1/reports/:id controllers.ReportController.get(id: java.util.UUID) diff --git a/conf/postgresql.application.conf b/conf/postgresql.application.conf index 8571d4f41..c696e2048 100644 --- a/conf/postgresql.application.conf +++ b/conf/postgresql.application.conf @@ -91,10 +91,6 @@ ebean.metrics_portal = ["models.ebean.*"] # ~~~~~ hostRepository.type = "com.arpnetworking.metrics.portal.hosts.impl.DatabaseHostRepository" -# Alerts -# ~~~~~ -alertRepository.type = "com.arpnetworking.metrics.portal.alerts.impl.DatabaseAlertRepository" - # Reports # ~~~~~ reportRepository.type = "com.arpnetworking.metrics.portal.reports.impl.DatabaseReportRepository" diff --git a/test/java/com/arpnetworking/metrics/portal/TestBeanFactory.java b/test/java/com/arpnetworking/metrics/portal/TestBeanFactory.java index 864cb147a..f38aadec9 100644 --- a/test/java/com/arpnetworking/metrics/portal/TestBeanFactory.java +++ b/test/java/com/arpnetworking/metrics/portal/TestBeanFactory.java @@ -29,14 +29,10 @@ import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSetMultimap; import models.cassandra.Host; -import models.internal.Context; import models.internal.MetricsSoftwareState; -import models.internal.Operator; import models.internal.Organization; import models.internal.TimeRange; -import models.internal.impl.DefaultAlert; import models.internal.impl.DefaultOrganization; -import models.internal.impl.DefaultQuantity; import models.internal.impl.DefaultRecipient; import models.internal.impl.DefaultRenderedReport; import models.internal.impl.DefaultReport; @@ -56,7 +52,6 @@ import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.Random; import java.util.UUID; @@ -71,18 +66,10 @@ public final class TestBeanFactory { private static final String TEST_CLUSTER = "test-cluster"; private static final String TEST_METRIC = "test-metric"; private static final String TEST_SERVICE = "test-service"; - private static final List CONTEXTS = Arrays.asList(Context.CLUSTER, Context.HOST); private static final String TEST_NAME = "test-name"; private static final String TEST_ETAG = "test-etag"; private static final String TEST_TITLE = "test-title"; private static final URI TEST_URI = URI.create("http://example.com"); - private static final List OPERATORS = Arrays.asList( - Operator.EQUAL_TO, - Operator.GREATER_THAN, - Operator.GREATER_THAN_OR_EQUAL_TO, - Operator.LESS_THAN_OR_EQUAL_TO, - Operator.LESS_THAN, - Operator.NOT_EQUAL_TO); private static final int TEST_PERIOD_IN_SECONDS = 600; private static final String TEST_STATISTIC = "metrics_seen_sum"; private static final String TEST_QUANTITY_UNIT = "test-unit"; @@ -307,118 +294,6 @@ public static models.ebean.Organization createEbeanOrganization() { return organization; } - /** - * Factory method to create an alert builder. - * - * @return an alert builder - */ - public static DefaultAlert.Builder createAlertBuilder() { - return new DefaultAlert.Builder() - .setId(UUID.randomUUID()) - .setCluster(TEST_CLUSTER + UUID.randomUUID().toString()) - .setMetric(TEST_METRIC + UUID.randomUUID().toString()) - .setContext(CONTEXTS.get(RANDOM.nextInt(CONTEXTS.size()))) - .setService(TEST_SERVICE + UUID.randomUUID().toString()) - .setNagiosExtension(createNagiosExtension()) - .setName(TEST_NAME + UUID.randomUUID().toString()) - .setOperator(OPERATORS.get(RANDOM.nextInt(OPERATORS.size()))) - .setPeriod(Duration.ofSeconds(RANDOM.nextInt(100))) - .setStatistic(TEST_STATISTIC + UUID.randomUUID().toString()) - .setValue(new DefaultQuantity.Builder() - .setValue(100 + RANDOM.nextDouble()) - .setUnit(TEST_QUANTITY_UNIT + RANDOM.nextInt(100)) - .build()); - } - - /** - * Factory method to create an ebean alert to a specified organization. - * - * @param organization the {@code models.ebean.Organization} to associate the alert with - * @return random Ebean alert instance - */ - public static models.ebean.Alert createEbeanAlert(final models.ebean.Organization organization) { - final models.ebean.Alert ebeanAlert = new models.ebean.Alert(); - ebeanAlert.setOrganization(organization); - ebeanAlert.setUuid(UUID.randomUUID()); - ebeanAlert.setNagiosExtension(createEbeanNagiosExtension()); - ebeanAlert.setName(TEST_NAME + UUID.randomUUID().toString()); - ebeanAlert.setOperator(OPERATORS.get(RANDOM.nextInt(OPERATORS.size()))); - ebeanAlert.setPeriod(TEST_PERIOD_IN_SECONDS + RANDOM.nextInt(100)); - ebeanAlert.setStatistic(TEST_STATISTIC + UUID.randomUUID().toString()); - ebeanAlert.setQuantityValue(100 + RANDOM.nextDouble()); - ebeanAlert.setQuantityUnit(TEST_QUANTITY_UNIT + RANDOM.nextInt(100)); - ebeanAlert.setCluster(TEST_CLUSTER + UUID.randomUUID().toString()); - ebeanAlert.setMetric(TEST_METRIC + UUID.randomUUID().toString()); - ebeanAlert.setContext(CONTEXTS.get(RANDOM.nextInt(CONTEXTS.size()))); - ebeanAlert.setService(TEST_SERVICE + UUID.randomUUID().toString()); - return ebeanAlert; - } - - /** - * Factory method to create a cassandra alert. - * - * @return a cassandra alert - */ - public static models.cassandra.Alert createCassandraAlert() { - final models.cassandra.Alert cassandraAlert = new models.cassandra.Alert(); - cassandraAlert.setOrganization(UUID.randomUUID()); - cassandraAlert.setUuid(UUID.randomUUID()); - cassandraAlert.setNagiosExtensions(createCassandraNagiosExtension()); - cassandraAlert.setName(TEST_NAME + UUID.randomUUID().toString()); - cassandraAlert.setOperator(OPERATORS.get(RANDOM.nextInt(OPERATORS.size()))); - cassandraAlert.setPeriodInSeconds(TEST_PERIOD_IN_SECONDS + RANDOM.nextInt(100)); - cassandraAlert.setStatistic(TEST_STATISTIC + UUID.randomUUID().toString()); - cassandraAlert.setQuantityValue(100 + RANDOM.nextDouble()); - cassandraAlert.setQuantityUnit(TEST_QUANTITY_UNIT + RANDOM.nextInt(100)); - cassandraAlert.setCluster(TEST_CLUSTER + UUID.randomUUID().toString()); - cassandraAlert.setMetric(TEST_METRIC + UUID.randomUUID().toString()); - cassandraAlert.setContext(CONTEXTS.get(RANDOM.nextInt(CONTEXTS.size()))); - cassandraAlert.setService(TEST_SERVICE + UUID.randomUUID().toString()); - return cassandraAlert; - } - - /** - * Factory method create create a cassandra nagios extension map. - * - * @return map of nagios extension key/value pairs - */ - public static Map createCassandraNagiosExtension() { - return new ImmutableMap.Builder() - .put("severity", NAGIOS_SEVERITY.get(RANDOM.nextInt(NAGIOS_SEVERITY.size()))) - .put("notify", TEST_NAGIOS_NOTIFY) - .put("attempts", Integer.toString(1 + RANDOM.nextInt(10))) - .put("freshness", Long.toString((long) RANDOM.nextInt(1000))) - .build(); - } - - /** - * Factory method to create a nagios extension. - * - * @return a nagios extension - */ - public static models.internal.NagiosExtension createNagiosExtension() { - return new models.internal.NagiosExtension.Builder() - .setSeverity(NAGIOS_SEVERITY.get(RANDOM.nextInt(NAGIOS_SEVERITY.size()))) - .setNotify(TEST_NAGIOS_NOTIFY) - .setMaxCheckAttempts(1 + RANDOM.nextInt(10)) - .setFreshnessThresholdInSeconds((long) RANDOM.nextInt(1000)) - .build(); - } - - /** - * Factory method to create an ebean nagios extension. - * - * @return an ebean nagios extension - */ - public static models.ebean.NagiosExtension createEbeanNagiosExtension() { - final models.ebean.NagiosExtension nagiosExtension = new models.ebean.NagiosExtension(); - nagiosExtension.setSeverity(NAGIOS_SEVERITY.get(RANDOM.nextInt(NAGIOS_SEVERITY.size()))); - nagiosExtension.setNotify(TEST_NAGIOS_NOTIFY); - nagiosExtension.setMaxCheckAttempts(1 + RANDOM.nextInt(10)); - nagiosExtension.setFreshnessThreshold((long) RANDOM.nextInt(1000)); - return nagiosExtension; - } - /** * Factory method for creating a cassandra host. * diff --git a/test/java/com/arpnetworking/metrics/portal/integration/controllers/AlertControllerIT.java b/test/java/com/arpnetworking/metrics/portal/integration/controllers/AlertControllerIT.java deleted file mode 100644 index 9db0cd448..000000000 --- a/test/java/com/arpnetworking/metrics/portal/integration/controllers/AlertControllerIT.java +++ /dev/null @@ -1,304 +0,0 @@ -/* - * 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 com.arpnetworking.metrics.portal.integration.controllers; - -import com.arpnetworking.metrics.portal.TestBeanFactory; -import com.arpnetworking.metrics.portal.alerts.impl.DatabaseAlertRepository; -import com.arpnetworking.metrics.portal.integration.test.EbeanServerHelper; -import com.arpnetworking.metrics.portal.integration.test.WebServerHelper; -import com.arpnetworking.utility.test.ResourceHelper; -import io.ebean.EbeanServer; -import models.ebean.NagiosExtension; -import models.internal.Alert; -import models.internal.Context; -import models.internal.Operator; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHeaders; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.StringEntity; -import org.apache.http.message.BasicHeader; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import play.mvc.Http; - -import java.io.IOException; -import java.util.UUID; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -/** - * Integration tests for {@code AlertController}. - * - * TODO(ville): Most of these test cases don't belong here as they test view model constraints. - * ^ Such tests should be expressed on the view model as unit tests not as integration tests against the server. - * - * TODO(ville): Move controller integration test data from JSON files to random view models from the factory. - * ^ This will allow controller tests to be run repeatedly w/o being influenced by data store state. - * - * @author Deepika Misra (deepika at groupon dot com) - */ -public final class AlertControllerIT { - - @Before - public void setUp() { - _ebeanServer = EbeanServerHelper.getMetricsDatabase(); - _alertRepo = new DatabaseAlertRepository(_ebeanServer); - _alertRepo.open(); - } - - @After - public void tearDown() { - _alertRepo.close(); - } - - @Test - public void testCreateValidCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateValidCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.NO_CONTENT, response.getStatusLine().getStatusCode()); - - final models.ebean.Alert alert = _ebeanServer.find(models.ebean.Alert.class) - .where() - .eq("uuid", UUID.fromString("88410734-aed7-11e1-8e54-00259060b612")) - .findOne(); - - assertNotNull(alert); - assertEquals(Context.CLUSTER, alert.getContext()); - assertEquals("test-cluster", alert.getCluster()); - assertEquals("test-name", alert.getName()); - assertEquals("test-metric", alert.getMetric()); - assertEquals("test-service", alert.getService()); - assertEquals(1, alert.getPeriod()); - assertEquals(Operator.EQUAL_TO, alert.getOperator()); - assertEquals(12, alert.getQuantityValue(), 0.01); - assertEquals("MEGABYTE", alert.getQuantityUnit()); - - final NagiosExtension extension = alert.getNagiosExtension(); - assertNotNull(extension); - assertEquals("CRITICAL", extension.getSeverity()); - assertEquals("abc@example.com", extension.getNotify()); - assertEquals(3, extension.getMaxCheckAttempts()); - assertEquals(300, extension.getFreshnessThreshold()); - } - } - - @Test - public void testCreateMissingBodyCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.BAD_REQUEST, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateMissingIdCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateMissingIdCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.BAD_REQUEST, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateMissingContextCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateMissingContextCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.BAD_REQUEST, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateInvalidContextCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateInvalidContextCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.BAD_REQUEST, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateMissingNameCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateMissingNameCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.BAD_REQUEST, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateMissingClusterCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateMissingClusterCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.BAD_REQUEST, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateMissingMetricCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateMissingMetricCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.BAD_REQUEST, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateMissingStatisticCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateMissingStatisticCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.BAD_REQUEST, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateMissingServiceCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateMissingServiceCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.BAD_REQUEST, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateMissingPeriodCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateMissingPeriodCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.BAD_REQUEST, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateInvalidPeriodCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateInvalidPeriodCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.BAD_REQUEST, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateMissingOperatorCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateMissingOperatorCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.BAD_REQUEST, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateInvalidOperatorCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateInvalidOperatorCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.BAD_REQUEST, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateMissingValueCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateMissingValueCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.BAD_REQUEST, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateMissingExtensionsCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateMissingExtensionsCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.NO_CONTENT, response.getStatusLine().getStatusCode()); - } - } - - @Test - public void testCreateEmptyExtensionsCase() throws IOException { - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testCreateEmptyExtensionsCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.NO_CONTENT, response.getStatusLine().getStatusCode()); - } - } - - @Test - @SuppressWarnings("deprecation") - public void testUpdateValidCase() throws IOException { - // TODO(ville): We need a strategy for testing against particular organizations! - final UUID uuid = UUID.fromString("e62368dc-1421-11e3-91c1-00259069c2f0"); - final Alert originalAlert = TestBeanFactory.createAlertBuilder().setId(uuid).build(); - _alertRepo.addOrUpdateAlert(originalAlert, TestBeanFactory.getDefautOrganization()); - - final HttpPut request = new HttpPut(WebServerHelper.getUri("/v1/alerts")); - request.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); - request.setEntity(createEntity("testUpdateValidCase")); - - try (CloseableHttpResponse response = WebServerHelper.getClient().execute(request)) { - assertEquals(Http.Status.NO_CONTENT, response.getStatusLine().getStatusCode()); - } - - // TODO(ville): We should validate that the update actually did something! - } - - private HttpEntity createEntity(final String resourceSuffix) throws IOException { - return new StringEntity(ResourceHelper.loadResource(getClass(), resourceSuffix)); - } - - private EbeanServer _ebeanServer; - private DatabaseAlertRepository _alertRepo; -} diff --git a/test/java/com/arpnetworking/metrics/portal/integration/repositories/CassandraAlertRepositoryIT.java b/test/java/com/arpnetworking/metrics/portal/integration/repositories/CassandraAlertRepositoryIT.java deleted file mode 100644 index 205ec94a9..000000000 --- a/test/java/com/arpnetworking/metrics/portal/integration/repositories/CassandraAlertRepositoryIT.java +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright 2017 Smartsheet.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 com.arpnetworking.metrics.portal.integration.repositories; - -import com.arpnetworking.metrics.portal.TestBeanFactory; -import com.arpnetworking.metrics.portal.alerts.impl.CassandraAlertRepository; -import com.arpnetworking.metrics.portal.integration.test.CassandraServerHelper; -import com.datastax.driver.core.Session; -import com.datastax.driver.mapping.Mapper; -import com.datastax.driver.mapping.MappingManager; -import models.internal.Alert; -import models.internal.AlertQuery; -import models.internal.Context; -import models.internal.NagiosExtension; -import models.internal.Organization; -import models.internal.QueryResult; -import models.internal.impl.DefaultAlertQuery; -import org.hamcrest.Matchers; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.time.Duration; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -/** - * Tests class {@link CassandraAlertRepository}. - * - * @author Brandon Arp (brandon dot arp at smartsheet dot com) - */ -public final class CassandraAlertRepositoryIT { - - @Before - public void setUp() { - final Session cassandraSession = CassandraServerHelper.createSession(); - _mappingManager = new MappingManager(cassandraSession); - _alertRepo = new CassandraAlertRepository(cassandraSession, _mappingManager); - _alertRepo.open(); - } - - @After - public void tearDown() { - _alertRepo.close(); - } - - @Test - public void testGetForInvalidId() { - assertFalse(_alertRepo.getAlert(UUID.randomUUID(), TestBeanFactory.newOrganization()).isPresent()); - } - - @Test - public void testGetForValidId() { - final UUID uuid = UUID.randomUUID(); - final models.cassandra.Alert cassandraAlert = TestBeanFactory.createCassandraAlert(); - final Organization org = TestBeanFactory.organizationFrom(cassandraAlert.getOrganization()); - assertFalse(_alertRepo.getAlert(uuid, org).isPresent()); - cassandraAlert.setUuid(uuid); - - final Mapper mapper = _mappingManager.mapper(models.cassandra.Alert.class); - mapper.save(cassandraAlert); - - final Optional expected = _alertRepo.getAlert(uuid, org); - assertTrue(expected.isPresent()); - assertAlertCassandraEquivalent(expected.get(), cassandraAlert); - } - - @Test - public void testGetAlertCountWithNoAlert() { - assertEquals(0, _alertRepo.getAlertCount(TestBeanFactory.newOrganization())); - } - - @Test - public void testGetAlertCountWithMultipleAlert() { - final Organization org = TestBeanFactory.newOrganization(); - - assertEquals(0, _alertRepo.getAlertCount(org)); - final Mapper mapper = _mappingManager.mapper(models.cassandra.Alert.class); - - final models.cassandra.Alert cassandraAlert1 = TestBeanFactory.createCassandraAlert(); - cassandraAlert1.setOrganization(org.getId()); - mapper.save(cassandraAlert1); - - final models.cassandra.Alert cassandraAlert2 = TestBeanFactory.createCassandraAlert(); - cassandraAlert2.setOrganization(org.getId()); - mapper.save(cassandraAlert2); - - assertEquals(2, _alertRepo.getAlertCount(org)); - } - - @Test - public void testAddOrUpdateAlertAddCase() { - final UUID uuid = UUID.randomUUID(); - final Organization org = TestBeanFactory.newOrganization(); - assertFalse(_alertRepo.getAlert(uuid, org).isPresent()); - final Alert actualAlert = TestBeanFactory.createAlertBuilder().setId(uuid).build(); - _alertRepo.addOrUpdateAlert(actualAlert, org); - final Optional expected = _alertRepo.getAlert(uuid, org); - assertTrue(expected.isPresent()); - assertEquals(expected.get(), actualAlert); - } - - @Test - public void testAddAlertWithNoExtension() { - final UUID uuid = UUID.randomUUID(); - final Organization org = TestBeanFactory.newOrganization(); - final Alert alert = TestBeanFactory.createAlertBuilder() - .setId(uuid) - .setNagiosExtension(null) - .build(); - _alertRepo.addOrUpdateAlert(alert, org); - final Alert expectedAlert = _alertRepo.getAlert(uuid, org).get(); - assertNull(expectedAlert.getNagiosExtension()); - } - - @Test - public void testQueryClauseWithClusterOnly() { - final Organization org = TestBeanFactory.newOrganization(); - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .setCluster("my-test-cluster") - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .build(); - _alertRepo.addOrUpdateAlert(alert1, org); - _alertRepo.addOrUpdateAlert(alert2, org); - final AlertQuery successQuery = new DefaultAlertQuery(_alertRepo, org); - successQuery.cluster(Optional.of("my-test-cluster")); - final QueryResult successResult = _alertRepo.queryAlerts(successQuery); - assertEquals(1, successResult.total()); - final AlertQuery failQuery = new DefaultAlertQuery(_alertRepo, org); - failQuery.cluster(Optional.of("some-random-cluster")); - final QueryResult failResult = _alertRepo.queryAlerts(failQuery); - assertEquals(0, failResult.total()); - } - - @Test - public void testQueryClauseWithContextOnly() { - final Organization org = TestBeanFactory.newOrganization(); - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .setContext(Context.CLUSTER) - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .setContext(Context.HOST) - .build(); - _alertRepo.addOrUpdateAlert(alert1, org); - _alertRepo.addOrUpdateAlert(alert2, org); - final AlertQuery successQuery = new DefaultAlertQuery(_alertRepo, org); - successQuery.context(Optional.of(Context.CLUSTER)); - final QueryResult successResult = _alertRepo.queryAlerts(successQuery); - assertEquals(1, successResult.total()); - } - - @Test - public void testQueryClauseWithServiceOnly() { - final Organization org = TestBeanFactory.newOrganization(); - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .setService("my-test-service") - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .build(); - _alertRepo.addOrUpdateAlert(alert1, org); - _alertRepo.addOrUpdateAlert(alert2, org); - final AlertQuery successQuery = new DefaultAlertQuery(_alertRepo, org); - successQuery.service(Optional.of("my-test-service")); - final QueryResult successResult = _alertRepo.queryAlerts(successQuery); - assertEquals(1, successResult.total()); - final AlertQuery failQuery = new DefaultAlertQuery(_alertRepo, org); - failQuery.service(Optional.of("some-random-service")); - final QueryResult failResult = _alertRepo.queryAlerts(failQuery); - assertEquals(0, failResult.total()); - } - - @Test - public void testQueryClauseWithContainsOnly() { - final Organization org = TestBeanFactory.newOrganization(); - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .setService("my-contained-service") - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .setCluster("my-cluster") - .setId(UUID.randomUUID()) - .build(); - final Alert alert3 = TestBeanFactory.createAlertBuilder() - .setName("my-contained-name") - .setId(UUID.randomUUID()) - .build(); - final Alert alert4 = TestBeanFactory.createAlertBuilder() - .setMetric("my-contained-metric") - .setId(UUID.randomUUID()) - .build(); - final Alert alert5 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .build(); - _alertRepo.addOrUpdateAlert(alert1, org); - _alertRepo.addOrUpdateAlert(alert2, org); - _alertRepo.addOrUpdateAlert(alert3, org); - _alertRepo.addOrUpdateAlert(alert4, org); - _alertRepo.addOrUpdateAlert(alert5, org); - final AlertQuery successQuery = new DefaultAlertQuery(_alertRepo, org); - successQuery.contains(Optional.of("contained")); - final QueryResult successResult = _alertRepo.queryAlerts(successQuery); - assertEquals(3, successResult.total()); - } - - @Test - public void testQueryClauseWithLimit() { - final Organization org = TestBeanFactory.newOrganization(); - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .setService("my-test-service") - .setCluster("my-test-cluster") - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .setService("my-test-service") - .setCluster("my-test-cluster") - .build(); - _alertRepo.addOrUpdateAlert(alert1, org); - _alertRepo.addOrUpdateAlert(alert2, org); - final AlertQuery query1 = new DefaultAlertQuery(_alertRepo, org); - query1.service(Optional.of("my-test-service")); - query1.cluster(Optional.of("my-test-cluster")); - query1.limit(1); - final QueryResult result1 = _alertRepo.queryAlerts(query1); - assertEquals(1, result1.values().size()); - final AlertQuery query2 = new DefaultAlertQuery(_alertRepo, org); - query2.service(Optional.of("my-test-service")); - query2.cluster(Optional.of("my-test-cluster")); - query2.limit(2); - final QueryResult result2 = _alertRepo.queryAlerts(query2); - assertEquals(2, result2.values().size()); - } - - @Test - public void testQueryClauseWithOffsetAndLimit() { - final Organization org = TestBeanFactory.newOrganization(); - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .setService("my-test-service") - .setCluster("my-test-cluster") - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .setService("my-test-service") - .setCluster("my-test-cluster") - .build(); - final Alert alert3 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .setService("my-test-service") - .setCluster("my-test-cluster") - .build(); - _alertRepo.addOrUpdateAlert(alert1, org); - _alertRepo.addOrUpdateAlert(alert2, org); - _alertRepo.addOrUpdateAlert(alert3, org); - final AlertQuery query = new DefaultAlertQuery(_alertRepo, org); - query.service(Optional.of("my-test-service")); - query.cluster(Optional.of("my-test-cluster")); - query.offset(Optional.of(2)); - query.limit(2); - final QueryResult result = _alertRepo.queryAlerts(query); - assertEquals(1, result.values().size()); - assertThat(result.values().get(0).getId(), Matchers.anyOf( - Matchers.equalTo(alert1.getId()), - Matchers.equalTo(alert2.getId()), - Matchers.equalTo(alert3.getId()))); - } - - @Test - public void testQueryWithContainsAndClusterClause() { - final Organization org = TestBeanFactory.newOrganization(); - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .setMetric("my-contained-metric") - .setCluster("my-cluster") - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .setService("my-contained-service") - .setCluster("my-cluster") - .build(); - final Alert alert3 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .build(); - _alertRepo.addOrUpdateAlert(alert1, org); - _alertRepo.addOrUpdateAlert(alert2, org); - _alertRepo.addOrUpdateAlert(alert3, org); - final AlertQuery query = new DefaultAlertQuery(_alertRepo, org); - query.contains(Optional.of("contained")); - query.cluster(Optional.of("my-cluster")); - final QueryResult result = _alertRepo.queryAlerts(query); - assertEquals(2, result.values().size()); - assertTrue(result.values().stream().anyMatch(i -> i.getId().equals(alert1.getId()))); - assertTrue(result.values().stream().anyMatch(i -> i.getId().equals(alert2.getId()))); - } - - @Test - public void testQueryWithContainsAndServiceClause() { - final Organization org = TestBeanFactory.newOrganization(); - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .setMetric("my-contained-metric") - .setService("my-service") - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .setCluster("my-contained-cluster") - .setService("my-service") - .build(); - final Alert alert3 = TestBeanFactory.createAlertBuilder() - .setId(UUID.randomUUID()) - .build(); - _alertRepo.addOrUpdateAlert(alert1, org); - _alertRepo.addOrUpdateAlert(alert2, org); - _alertRepo.addOrUpdateAlert(alert3, org); - final AlertQuery query = new DefaultAlertQuery(_alertRepo, org); - query.contains(Optional.of("contained")); - query.service(Optional.of("my-service")); - final QueryResult result = _alertRepo.queryAlerts(query); - assertEquals(2, result.values().size()); - assertTrue(result.values().stream().anyMatch(i -> i.getId().equals(alert1.getId()))); - assertTrue(result.values().stream().anyMatch(i -> i.getId().equals(alert2.getId()))); - } - - private void assertAlertCassandraEquivalent(final Alert alert, final models.cassandra.Alert cassandraAlert) { - assertEquals(alert.getId(), cassandraAlert.getUuid()); - assertEquals(alert.getCluster(), cassandraAlert.getCluster()); - assertEquals(alert.getMetric(), cassandraAlert.getMetric()); - assertNagiosExtensionCassandraEquivalent(alert.getNagiosExtension(), cassandraAlert.getNagiosExtensions()); - assertEquals(alert.getService(), cassandraAlert.getService()); - assertEquals(alert.getName(), cassandraAlert.getName()); - assertEquals(alert.getOperator(), cassandraAlert.getOperator()); - assertEquals(alert.getPeriod(), Duration.ofSeconds(cassandraAlert.getPeriodInSeconds())); - assertEquals(alert.getStatistic(), cassandraAlert.getStatistic()); - assertEquals(alert.getValue().getUnit(), Optional.of(cassandraAlert.getQuantityUnit())); - assertEquals(alert.getValue().getValue(), cassandraAlert.getQuantityValue(), 0.001); - assertEquals(alert.getContext(), cassandraAlert.getContext()); - } - - private static void assertNagiosExtensionCassandraEquivalent( - final NagiosExtension extension, - final Map cassExtension) { - assertEquals(extension.getSeverity(), cassExtension.get("severity")); - assertEquals(extension.getNotify(), cassExtension.get("notify")); - assertEquals(extension.getMaxCheckAttempts(), Integer.parseInt(cassExtension.get("attempts"))); - assertEquals(extension.getFreshnessThreshold().getSeconds(), Long.parseLong(cassExtension.get("freshness"))); - } - - private CassandraAlertRepository _alertRepo; - private MappingManager _mappingManager; -} diff --git a/test/java/com/arpnetworking/metrics/portal/integration/repositories/DatabaseAlertRepositoryIT.java b/test/java/com/arpnetworking/metrics/portal/integration/repositories/DatabaseAlertRepositoryIT.java deleted file mode 100644 index aaa29f812..000000000 --- a/test/java/com/arpnetworking/metrics/portal/integration/repositories/DatabaseAlertRepositoryIT.java +++ /dev/null @@ -1,379 +0,0 @@ -/* - * 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 com.arpnetworking.metrics.portal.integration.repositories; - -import com.arpnetworking.metrics.portal.TestBeanFactory; -import com.arpnetworking.metrics.portal.alerts.impl.DatabaseAlertRepository; -import com.arpnetworking.metrics.portal.integration.test.EbeanServerHelper; -import io.ebean.EbeanServer; -import io.ebean.Transaction; -import models.internal.Alert; -import models.internal.AlertQuery; -import models.internal.Context; -import models.internal.NagiosExtension; -import models.internal.Organization; -import models.internal.QueryResult; -import models.internal.impl.DefaultAlertQuery; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.time.Duration; -import java.util.Optional; -import java.util.UUID; -import javax.annotation.Nullable; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -/** - * Integration tests for {@code DatabaseAlertRepository}. - * - * @author Deepika Misra (deepika at groupon dot com) - */ -public final class DatabaseAlertRepositoryIT { - - @Before - public void setUp() { - _server = EbeanServerHelper.getMetricsDatabase(); - _alertRepo = new DatabaseAlertRepository(_server); - _alertRepo.open(); - - _ebeanOrganization = TestBeanFactory.createEbeanOrganization(); - _server.save(_ebeanOrganization); - _organization = TestBeanFactory.organizationFrom(_ebeanOrganization); - } - - @After - public void tearDown() { - _alertRepo.close(); - } - - @Test - public void testGetForNonexistentAlertAndOrganizationId() { - assertFalse(_alertRepo.getAlert(UUID.randomUUID(), TestBeanFactory.organizationFrom(UUID.randomUUID())).isPresent()); - } - - @Test - public void testGetForNonexistentOrganizationId() { - final models.ebean.Alert alert = TestBeanFactory.createEbeanAlert(_ebeanOrganization); - _server.save(alert); - - assertFalse(_alertRepo.getAlert(alert.getUuid(), TestBeanFactory.organizationFrom(UUID.randomUUID())).isPresent()); - } - - @Test - public void testGetForNonexistentAlertId() { - assertFalse(_alertRepo.getAlert(UUID.randomUUID(), TestBeanFactory.organizationFrom(_ebeanOrganization)).isPresent()); - } - - @Test - public void testGetForValidId() { - final models.ebean.Alert alert = TestBeanFactory.createEbeanAlert(_ebeanOrganization); - _server.save(alert); - - final Optional actual = _alertRepo.getAlert(alert.getUuid(), _organization); - assertTrue(actual.isPresent()); - assertAlertEbeanEquivalent(actual.get(), alert); - } - - @Test - public void testGetAlertCount() { - assertEquals(0, _alertRepo.getAlertCount(_organization)); - - try (Transaction transaction = _server.beginTransaction()) { - _server.save(TestBeanFactory.createEbeanAlert(_ebeanOrganization)); - _server.save(TestBeanFactory.createEbeanAlert(_ebeanOrganization)); - transaction.commit(); - } - - assertEquals(2, _alertRepo.getAlertCount(_organization)); - } - - @Test - public void testAddOrUpdateAlertAddCase() { - final UUID uuid = UUID.randomUUID(); - assertFalse(_alertRepo.getAlert(uuid, _organization).isPresent()); - - final Alert actualAlert = TestBeanFactory.createAlertBuilder().setId(uuid).build(); - _alertRepo.addOrUpdateAlert(actualAlert, _organization); - - final Optional expected = _alertRepo.getAlert(uuid, _organization); - assertTrue(expected.isPresent()); - assertEquals(expected.get(), actualAlert); - } - - @Test - public void testAddAlertWithNoExtension() { - final UUID uuid = UUID.randomUUID(); - final Alert alert = TestBeanFactory.createAlertBuilder() - .setId(uuid) - .setNagiosExtension(null) - .build(); - _alertRepo.addOrUpdateAlert(alert, _organization); - final Optional expectedAlert = _alertRepo.getAlert(uuid, _organization); - assertTrue(expectedAlert.isPresent()); - assertNull(expectedAlert.get().getNagiosExtension()); - } - - @Test - public void testQueryClauseWithClusterOnly() { - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setCluster("my-test-cluster") - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .build(); - - _alertRepo.addOrUpdateAlert(alert1, _organization); - _alertRepo.addOrUpdateAlert(alert2, _organization); - - final AlertQuery successQuery = new DefaultAlertQuery(_alertRepo, _organization); - successQuery.cluster(Optional.of("my-test-cluster")); - - final QueryResult successResult = _alertRepo.queryAlerts(successQuery); - assertEquals(1, successResult.total()); - - final AlertQuery failQuery = new DefaultAlertQuery(_alertRepo, _organization); - failQuery.cluster(Optional.of("some-random-cluster")); - - final QueryResult failResult = _alertRepo.queryAlerts(failQuery); - assertEquals(0, failResult.total()); - } - - @Test - public void testQueryClauseWithContextOnly() { - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setContext(Context.CLUSTER) - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .setContext(Context.HOST) - .build(); - - _alertRepo.addOrUpdateAlert(alert1, _organization); - _alertRepo.addOrUpdateAlert(alert2, _organization); - - final AlertQuery successQuery = new DefaultAlertQuery(_alertRepo, _organization); - successQuery.context(Optional.of(Context.CLUSTER)); - final QueryResult successResult = _alertRepo.queryAlerts(successQuery); - assertEquals(1, successResult.total()); - } - - @Test - public void testQueryClauseWithServiceOnly() { - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setService("my-test-service") - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .build(); - - _alertRepo.addOrUpdateAlert(alert1, _organization); - _alertRepo.addOrUpdateAlert(alert2, _organization); - - final AlertQuery successQuery = new DefaultAlertQuery(_alertRepo, _organization); - successQuery.service(Optional.of("my-test-service")); - - final QueryResult successResult = _alertRepo.queryAlerts(successQuery); - assertEquals(1, successResult.total()); - - final AlertQuery failQuery = new DefaultAlertQuery(_alertRepo, _organization); - failQuery.service(Optional.of("some-random-service")); - - final QueryResult failResult = _alertRepo.queryAlerts(failQuery); - assertEquals(0, failResult.total()); - } - - @Test - public void testQueryClauseWithContainsOnly() { - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setService("my-contained-service") - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .setCluster("my-cluster") - .build(); - final Alert alert3 = TestBeanFactory.createAlertBuilder() - .setName("my-contained-name") - .build(); - final Alert alert4 = TestBeanFactory.createAlertBuilder() - .setMetric("my-contained-metric") - .build(); - final Alert alert5 = TestBeanFactory.createAlertBuilder() - .build(); - - _alertRepo.addOrUpdateAlert(alert1, _organization); - _alertRepo.addOrUpdateAlert(alert2, _organization); - _alertRepo.addOrUpdateAlert(alert3, _organization); - _alertRepo.addOrUpdateAlert(alert4, _organization); - _alertRepo.addOrUpdateAlert(alert5, _organization); - - final AlertQuery successQuery = new DefaultAlertQuery(_alertRepo, _organization); - successQuery.contains(Optional.of("contained")); - - final QueryResult successResult = _alertRepo.queryAlerts(successQuery); - assertEquals(3, successResult.total()); - } - - @Test - public void testQueryClauseWithLimit() { - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setService("my-test-service") - .setCluster("my-test-cluster") - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .setService("my-test-service") - .setCluster("my-test-cluster") - .build(); - - _alertRepo.addOrUpdateAlert(alert1, _organization); - _alertRepo.addOrUpdateAlert(alert2, _organization); - - final AlertQuery query1 = new DefaultAlertQuery(_alertRepo, _organization); - query1.service(Optional.of("my-test-service")); - query1.cluster(Optional.of("my-test-cluster")); - query1.limit(1); - - final QueryResult result1 = _alertRepo.queryAlerts(query1); - assertEquals(1, result1.values().size()); - - final AlertQuery query2 = new DefaultAlertQuery(_alertRepo, _organization); - query2.service(Optional.of("my-test-service")); - query2.cluster(Optional.of("my-test-cluster")); - query2.limit(2); - - final QueryResult result2 = _alertRepo.queryAlerts(query2); - assertEquals(2, result2.values().size()); - } - - @Test - public void testQueryClauseWithOffsetAndLimit() { - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setService("my-test-service") - .setCluster("my-test-cluster") - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .setService("my-test-service") - .setCluster("my-test-cluster") - .build(); - final Alert alert3 = TestBeanFactory.createAlertBuilder() - .setService("my-test-service") - .setCluster("my-test-cluster") - .build(); - - _alertRepo.addOrUpdateAlert(alert1, _organization); - _alertRepo.addOrUpdateAlert(alert2, _organization); - _alertRepo.addOrUpdateAlert(alert3, _organization); - - final AlertQuery query = new DefaultAlertQuery(_alertRepo, _organization); - query.service(Optional.of("my-test-service")); - query.cluster(Optional.of("my-test-cluster")); - query.offset(Optional.of(2)); - query.limit(2); - - final QueryResult result = _alertRepo.queryAlerts(query); - assertEquals(1, result.values().size()); - assertEquals(alert3.getId(), result.values().get(0).getId()); - } - - @Test - public void testQueryWithContainsAndClusterClause() { - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setMetric("my-contained-metric") - .setCluster("my-cluster") - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .setService("my-contained-service") - .setCluster("my-cluster") - .build(); - final Alert alert3 = TestBeanFactory.createAlertBuilder() - .build(); - - _alertRepo.addOrUpdateAlert(alert1, _organization); - _alertRepo.addOrUpdateAlert(alert2, _organization); - _alertRepo.addOrUpdateAlert(alert3, _organization); - - final AlertQuery query = new DefaultAlertQuery(_alertRepo, _organization); - query.contains(Optional.of("contained")); - query.cluster(Optional.of("my-cluster")); - - final QueryResult result = _alertRepo.queryAlerts(query); - assertEquals(2, result.values().size()); - assertEquals(alert1.getId(), result.values().get(0).getId()); - assertEquals(alert2.getId(), result.values().get(1).getId()); - } - - @Test - public void testQueryWithContainsAndServiceClause() { - final Alert alert1 = TestBeanFactory.createAlertBuilder() - .setMetric("my-contained-metric") - .setService("my-service") - .build(); - final Alert alert2 = TestBeanFactory.createAlertBuilder() - .setCluster("my-contained-cluster") - .setService("my-service") - .build(); - final Alert alert3 = TestBeanFactory.createAlertBuilder() - .build(); - - _alertRepo.addOrUpdateAlert(alert1, _organization); - _alertRepo.addOrUpdateAlert(alert2, _organization); - _alertRepo.addOrUpdateAlert(alert3, _organization); - - final AlertQuery query = new DefaultAlertQuery(_alertRepo, _organization); - query.contains(Optional.of("contained")); - query.service(Optional.of("my-service")); - - final QueryResult result = _alertRepo.queryAlerts(query); - assertEquals(2, result.values().size()); - assertEquals(alert1.getId(), result.values().get(0).getId()); - assertEquals(alert2.getId(), result.values().get(1).getId()); - } - - private void assertAlertEbeanEquivalent(final Alert alert, final models.ebean.Alert ebeanAlert) { - assertEquals(alert.getId(), ebeanAlert.getUuid()); - assertEquals(alert.getCluster(), ebeanAlert.getCluster()); - assertEquals(alert.getMetric(), ebeanAlert.getMetric()); - assertNagiosExtensionEbeanEquivalent(alert.getNagiosExtension(), ebeanAlert.getNagiosExtension()); - assertEquals(alert.getService(), ebeanAlert.getService()); - assertEquals(alert.getName(), ebeanAlert.getName()); - assertEquals(alert.getOperator(), ebeanAlert.getOperator()); - assertEquals(alert.getPeriod(), Duration.ofSeconds(ebeanAlert.getPeriod())); - assertEquals(alert.getStatistic(), ebeanAlert.getStatistic()); - assertEquals(alert.getValue().getUnit(), Optional.of(ebeanAlert.getQuantityUnit())); - assertEquals(alert.getValue().getValue(), ebeanAlert.getQuantityValue(), 0.001); - assertEquals(alert.getContext(), ebeanAlert.getContext()); - } - - private static void assertNagiosExtensionEbeanEquivalent( - @Nullable final NagiosExtension extension, - @Nullable final models.ebean.NagiosExtension ebeanExtension) { - if (extension == null && ebeanExtension == null) { - return; - } else if (extension == null || ebeanExtension == null) { - fail("One of extension or ebeanExtension is null while the other is not"); - } - assertEquals(extension.getSeverity(), ebeanExtension.getSeverity()); - assertEquals(extension.getNotify(), ebeanExtension.getNotify()); - assertEquals(extension.getMaxCheckAttempts(), ebeanExtension.getMaxCheckAttempts()); - assertEquals(extension.getFreshnessThreshold().getSeconds(), ebeanExtension.getFreshnessThreshold()); - } - - private EbeanServer _server; - private DatabaseAlertRepository _alertRepo; - private Organization _organization; - private models.ebean.Organization _ebeanOrganization; -} diff --git a/test/java/com/arpnetworking/metrics/portal/integration/test/CassandraServerHelper.java b/test/java/com/arpnetworking/metrics/portal/integration/test/CassandraServerHelper.java index 8b71327bf..388431d62 100644 --- a/test/java/com/arpnetworking/metrics/portal/integration/test/CassandraServerHelper.java +++ b/test/java/com/arpnetworking/metrics/portal/integration/test/CassandraServerHelper.java @@ -24,14 +24,11 @@ import com.datastax.driver.core.Cluster; import com.datastax.driver.core.CodecRegistry; import com.datastax.driver.core.Session; -import com.datastax.driver.extras.codecs.enums.EnumNameCodec; import com.datastax.driver.extras.codecs.jdk8.InstantCodec; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.Maps; -import models.internal.Context; -import models.internal.Operator; import scala.Predef; import scala.collection.JavaConverters; @@ -86,8 +83,6 @@ private static Cluster initializeCassandraServer( throw new RuntimeException(e); } cluster.getConfiguration().getCodecRegistry().register(InstantCodec.instance); - cluster.getConfiguration().getCodecRegistry().register(new EnumNameCodec<>(Operator.class)); - cluster.getConfiguration().getCodecRegistry().register(new EnumNameCodec<>(Context.class)); final Session session = cluster.newSession();