WIP - Alarmed metrics API changes

- Changed alarms resource to alarm-definitions
- Added new alarms resource that tracks alarm/metric associations

Change-Id: Ib660eabd2c34001d165bf22bc9941b6a1cb4fcef
This commit is contained in:
Jonathan Halterman
2014-09-19 09:55:59 -07:00
parent c5ecbc28c2
commit 2e44111d90
37 changed files with 2822 additions and 1759 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,6 @@
*/
package com.hpcloud.mon;
import com.hpcloud.mon.infrastructure.servlet.RoleAuthorizationFilter;
import io.dropwizard.Application;
import io.dropwizard.jdbi.bundles.DBIExceptionsBundle;
import io.dropwizard.setup.Bootstrap;
@@ -39,6 +38,8 @@ import com.hpcloud.mon.bundle.SwaggerBundle;
import com.hpcloud.mon.infrastructure.servlet.MockAuthenticationFilter;
import com.hpcloud.mon.infrastructure.servlet.PostAuthenticationFilter;
import com.hpcloud.mon.infrastructure.servlet.PreAuthenticationFilter;
import com.hpcloud.mon.infrastructure.servlet.RoleAuthorizationFilter;
import com.hpcloud.mon.resource.AlarmDefinitionResource;
import com.hpcloud.mon.resource.AlarmResource;
import com.hpcloud.mon.resource.MeasurementResource;
import com.hpcloud.mon.resource.MetricResource;
@@ -77,12 +78,14 @@ public class MonApiApplication extends Application<MonApiConfiguration> {
}
@Override
@SuppressWarnings("unchecked")
public void run(MonApiConfiguration config, Environment environment) throws Exception {
/** Wire services */
Injector.registerModules(new MonApiModule(environment, config));
/** Configure resources */
environment.jersey().register(Injector.getInstance(VersionResource.class));
environment.jersey().register(Injector.getInstance(AlarmDefinitionResource.class));
environment.jersey().register(Injector.getInstance(AlarmResource.class));
environment.jersey().register(Injector.getInstance(MetricResource.class));
environment.jersey().register(Injector.getInstance(MeasurementResource.class));
@@ -153,15 +156,18 @@ public class MonApiApplication extends Application<MonApiConfiguration> {
tokenAuthFilter.setInitParameters(authInitParams);
Dynamic postAuthenticationFilter =
environment.servlets().addFilter("post-auth",
new PostAuthenticationFilter(config.middleware.defaultAuthorizedRoles, config.middleware.agentAuthorizedRoles));
environment.servlets().addFilter(
"post-auth",
new PostAuthenticationFilter(config.middleware.defaultAuthorizedRoles,
config.middleware.agentAuthorizedRoles));
postAuthenticationFilter.addMappingForUrlPatterns(null, true, "/");
postAuthenticationFilter.addMappingForUrlPatterns(null, true, "/v2.0/*");
environment.jersey().getResourceConfig().getContainerRequestFilters().add(new RoleAuthorizationFilter());
environment.jersey().getResourceConfig().getContainerRequestFilters()
.add(new RoleAuthorizationFilter());
} else {
Dynamic mockAuthenticationFilter =
environment.servlets().addFilter("mock-auth", new MockAuthenticationFilter());
Dynamic mockAuthenticationFilter =
environment.servlets().addFilter("mock-auth", new MockAuthenticationFilter());
mockAuthenticationFilter.addMappingForUrlPatterns(null, true, "/");
mockAuthenticationFilter.addMappingForUrlPatterns(null, true, "/v2.0/*");
}

View File

@@ -0,0 +1,315 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.hpcloud.mon.app;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.inject.Inject;
import kafka.javaapi.producer.Producer;
import kafka.producer.KeyedMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Sets;
import com.hpcloud.mon.MonApiConfiguration;
import com.hpcloud.mon.app.command.UpdateAlarmDefinitionCommand;
import com.hpcloud.mon.common.event.AlarmDefinitionCreatedEvent;
import com.hpcloud.mon.common.event.AlarmDefinitionDeletedEvent;
import com.hpcloud.mon.common.event.AlarmDefinitionUpdatedEvent;
import com.hpcloud.mon.common.model.alarm.AlarmExpression;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.common.model.alarm.AlarmSubExpression;
import com.hpcloud.mon.common.model.metric.MetricDefinition;
import com.hpcloud.mon.domain.exception.EntityExistsException;
import com.hpcloud.mon.domain.exception.EntityNotFoundException;
import com.hpcloud.mon.domain.exception.InvalidEntityException;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinition;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinitionRepository;
import com.hpcloud.mon.domain.model.notificationmethod.NotificationMethodRepository;
import com.hpcloud.util.Exceptions;
import com.hpcloud.util.Serialization;
/**
* Services alarm definition related requests.
*/
public class AlarmDefinitionService {
private static final Logger LOG = LoggerFactory.getLogger(AlarmService.class);
private final MonApiConfiguration config;
private final Producer<String, String> producer;
private final AlarmDefinitionRepository repo;
private final NotificationMethodRepository notificationMethodRepo;
@Inject
public AlarmDefinitionService(MonApiConfiguration config, Producer<String, String> producer,
AlarmDefinitionRepository repo, NotificationMethodRepository notificationMethodRepo) {
this.config = config;
this.producer = producer;
this.repo = repo;
this.notificationMethodRepo = notificationMethodRepo;
}
static class SubExpressions {
/** Sub expressions which have been removed from an updated alarm expression. */
Map<String, AlarmSubExpression> oldAlarmSubExpressions;
/** Sub expressions which have had their operator or threshold changed. */
Map<String, AlarmSubExpression> changedSubExpressions;
/** Sub expressions which have not changed. */
Map<String, AlarmSubExpression> unchangedSubExpressions;
/** Sub expressions which have been added to an updated alarm expression. */
Map<String, AlarmSubExpression> newAlarmSubExpressions;
}
/**
* Creates an alarm definition and publishes an AlarmDefinitionCreatedEvent. Note, the event is
* published first since chances of failure are higher.
*
* @throws EntityExistsException if an alarm already exists for the name
* @throws InvalidEntityException if one of the actions cannot be found
*/
public AlarmDefinition create(String tenantId, String name, @Nullable String description,
String severity, String expression, AlarmExpression alarmExpression, List<String> matchBy,
List<String> alarmActions, @Nullable List<String> okActions,
@Nullable List<String> undeterminedActions) {
// Assert no alarm exists by the name
if (repo.exists(tenantId, name))
throw new EntityExistsException(
"An alarm definition already exists for project / tenant: %s named: %s", tenantId, name);
assertActionsExist(tenantId, alarmActions, okActions, undeterminedActions);
Map<String, AlarmSubExpression> subAlarms = new HashMap<String, AlarmSubExpression>();
for (AlarmSubExpression subExpression : alarmExpression.getSubExpressions())
subAlarms.put(UUID.randomUUID().toString(), subExpression);
String alarmDefId = UUID.randomUUID().toString();
AlarmDefinition alarm = null;
try {
LOG.debug("Creating alarm definition {} for tenant {}", name, tenantId);
alarm =
repo.create(tenantId, alarmDefId, name, description, severity, expression, subAlarms,
matchBy, alarmActions, okActions, undeterminedActions);
// Notify interested parties of new alarm
String event =
Serialization.toJson(new AlarmDefinitionCreatedEvent(tenantId, alarmDefId, name,
description, expression, subAlarms, matchBy));
producer.send(new KeyedMessage<>(config.eventsTopic, tenantId, event));
return alarm;
} catch (Exception e) {
if (alarm != null)
try {
repo.deleteById(tenantId, alarm.getId());
} catch (Exception ignore) {
}
throw Exceptions.uncheck(e, "Error creating alarm definition for project / tenant %s",
tenantId);
}
}
/**
* Deletes the alarm definition identified by the {@code alarmDefId}.
*
* @throws EntityNotFoundException if the alarm cannot be found
*/
public void delete(String tenantId, String alarmDefId) {
Map<String, MetricDefinition> subAlarmMetricDefs =
repo.findSubAlarmMetricDefinitions(alarmDefId);
repo.deleteById(tenantId, alarmDefId);
// Notify interested parties of alarm definition deletion
String event =
Serialization.toJson(new AlarmDefinitionDeletedEvent(alarmDefId, subAlarmMetricDefs));
producer.send(new KeyedMessage<>(config.eventsTopic, tenantId, event));
}
/**
* Updates the alarm definition for the {@code tenantId} and {@code alarmDefId} to the state of
* the {@code command}.
*
* @throws EntityNotFoundException if the alarm cannot be found
* @throws InvalidEntityException if one of the actions cannot be found
*/
public AlarmDefinition update(String tenantId, String alarmDefId,
AlarmExpression alarmExpression, UpdateAlarmDefinitionCommand command) {
assertAlarmDefinitionExists(tenantId, alarmDefId, command.alarmActions, command.okActions,
command.undeterminedActions);
updateInternal(tenantId, alarmDefId, false, command.name, command.description,
command.expression, command.matchBy, command.severity, alarmExpression,
command.actionsEnabled, command.alarmActions, command.okActions,
command.undeterminedActions);
return new AlarmDefinition(alarmDefId, command.name, command.description, command.severity,
command.expression, command.matchBy, command.actionsEnabled, command.alarmActions,
command.okActions, command.undeterminedActions);
}
/**
* Patches the alarm definition for the {@code tenantId} and {@code alarmDefId} to the state of
* the {@code fields}.
*
* @throws EntityNotFoundException if the alarm cannot be found
* @throws InvalidEntityException if one of the actions cannot be found
*/
public AlarmDefinition patch(String tenantId, String alarmDefId, String name, String description,
String severity, String expression, AlarmExpression alarmExpression, List<String> matchBy,
AlarmState state, Boolean enabled, List<String> alarmActions, List<String> okActions,
List<String> undeterminedActions) {
AlarmDefinition alarm =
assertAlarmDefinitionExists(tenantId, alarmDefId, alarmActions, okActions,
undeterminedActions);
name = name == null ? alarm.getName() : name;
description = description == null ? alarm.getDescription() : description;
expression = expression == null ? alarm.getExpression() : expression;
severity = severity == null ? alarm.getSeverity() : severity;
alarmExpression = alarmExpression == null ? AlarmExpression.of(expression) : alarmExpression;
enabled = enabled == null ? alarm.isActionsEnabled() : enabled;
updateInternal(tenantId, alarmDefId, true, name, description, expression, matchBy, severity,
alarmExpression, enabled, alarmActions, okActions, undeterminedActions);
return new AlarmDefinition(alarmDefId, name, description, severity, expression, matchBy,
enabled, alarmActions == null ? alarm.getAlarmActions() : alarmActions,
okActions == null ? alarm.getOkActions() : okActions,
undeterminedActions == null ? alarm.getUndeterminedActions() : undeterminedActions);
}
private void updateInternal(String tenantId, String alarmDefId, boolean patch, String name,
String description, String expression, List<String> matchBy, String severity,
AlarmExpression alarmExpression, Boolean enabled, List<String> alarmActions,
List<String> okActions, List<String> undeterminedActions) {
SubExpressions subExpressions = subExpressionsFor(alarmDefId, alarmExpression);
try {
LOG.debug("Updating alarm definition {} for tenant {}", name, tenantId);
repo.update(tenantId, alarmDefId, patch, name, description, expression, matchBy, severity,
enabled, subExpressions.oldAlarmSubExpressions.keySet(),
subExpressions.changedSubExpressions, subExpressions.newAlarmSubExpressions,
alarmActions, okActions, undeterminedActions);
// Notify interested parties of updated alarm
String event =
Serialization.toJson(new AlarmDefinitionUpdatedEvent(tenantId, alarmDefId, name,
description, expression, matchBy, enabled, subExpressions.oldAlarmSubExpressions,
subExpressions.changedSubExpressions, subExpressions.unchangedSubExpressions,
subExpressions.newAlarmSubExpressions));
producer.send(new KeyedMessage<>(config.eventsTopic, tenantId, event));
} catch (Exception e) {
throw Exceptions.uncheck(e, "Error updating alarm definition for project / tenant %s",
tenantId);
}
}
/**
* Returns an entry containing Maps of old, changed, and new sub expressions by comparing the
* {@code alarmExpression} to the existing sub expressions for the {@code alarmDefId}.
*/
SubExpressions subExpressionsFor(String alarmDefId, AlarmExpression alarmExpression) {
BiMap<String, AlarmSubExpression> oldExpressions =
HashBiMap.create(repo.findSubExpressions(alarmDefId));
Set<AlarmSubExpression> oldSet = oldExpressions.inverse().keySet();
Set<AlarmSubExpression> newSet = new HashSet<>(alarmExpression.getSubExpressions());
// Identify old or changed expressions
Set<AlarmSubExpression> oldOrChangedExpressions =
new HashSet<>(Sets.difference(oldSet, newSet));
// Identify new or changed expressions
Set<AlarmSubExpression> newOrChangedExpressions =
new HashSet<>(Sets.difference(newSet, oldSet));
// Find changed expressions
Map<String, AlarmSubExpression> changedExpressions = new HashMap<>();
for (Iterator<AlarmSubExpression> oldIt = oldOrChangedExpressions.iterator(); oldIt.hasNext();) {
AlarmSubExpression oldExpr = oldIt.next();
for (Iterator<AlarmSubExpression> newIt = newOrChangedExpressions.iterator(); newIt.hasNext();) {
AlarmSubExpression newExpr = newIt.next();
if (sameKeyFields(oldExpr, newExpr)) {
oldIt.remove();
newIt.remove();
changedExpressions.put(oldExpressions.inverse().get(oldExpr), newExpr);
}
}
}
// Create the list of unchanged expressions
BiMap<String, AlarmSubExpression> unchangedExpressions = HashBiMap.create(oldExpressions);
unchangedExpressions.values().removeAll(oldOrChangedExpressions);
unchangedExpressions.keySet().removeAll(changedExpressions.keySet());
// Remove old sub expressions
oldExpressions.values().retainAll(oldOrChangedExpressions);
// Create IDs for new expressions
Map<String, AlarmSubExpression> newExpressions = new HashMap<>();
for (AlarmSubExpression expression : newOrChangedExpressions)
newExpressions.put(UUID.randomUUID().toString(), expression);
SubExpressions subExpressions = new SubExpressions();
subExpressions.oldAlarmSubExpressions = oldExpressions;
subExpressions.changedSubExpressions = changedExpressions;
subExpressions.unchangedSubExpressions = unchangedExpressions;
subExpressions.newAlarmSubExpressions = newExpressions;
return subExpressions;
}
/**
* Returns whether all of the fields of {@code a} and {@code b} are the same except the operator
* and threshold.
*/
private boolean sameKeyFields(AlarmSubExpression a, AlarmSubExpression b) {
return a.getMetricDefinition().equals(b.getMetricDefinition())
&& a.getFunction().equals(b.getFunction()) && a.getPeriod() == b.getPeriod()
&& a.getPeriods() == b.getPeriods();
}
/**
* Asserts an alarm definition exists for the {@code alarmDefId} as well as the actions.
*
* @throws EntityNotFoundException if the alarm cannot be found
*/
private AlarmDefinition assertAlarmDefinitionExists(String tenantId, String alarmDefId,
List<String> alarmActions, List<String> okActions, List<String> undeterminedActions) {
AlarmDefinition alarm = repo.findById(tenantId, alarmDefId);
assertActionsExist(tenantId, alarmActions, okActions, undeterminedActions);
return alarm;
}
private void assertActionsExist(String tenantId, List<String> alarmActions,
List<String> okActions, List<String> undeterminedActions) {
Set<String> actions = new HashSet<>();
if (alarmActions != null)
actions.addAll(alarmActions);
if (okActions != null)
actions.addAll(okActions);
if (undeterminedActions != null)
actions.addAll(undeterminedActions);
if (!actions.isEmpty())
for (String action : actions)
if (!notificationMethodRepo.exists(tenantId, action))
throw new InvalidEntityException("No notification method exists for action %s", action);
}
}

View File

@@ -13,15 +13,9 @@
*/
package com.hpcloud.mon.app;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.inject.Inject;
import kafka.javaapi.producer.Producer;
@@ -30,30 +24,24 @@ import kafka.producer.KeyedMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Sets;
import com.hpcloud.mon.MonApiConfiguration;
import com.hpcloud.mon.app.command.UpdateAlarmCommand;
import com.hpcloud.mon.common.event.AlarmCreatedEvent;
import com.hpcloud.mon.common.event.AlarmDeletedEvent;
import com.hpcloud.mon.common.event.AlarmStateTransitionedEvent;
import com.hpcloud.mon.common.event.AlarmUpdatedEvent;
import com.hpcloud.mon.common.model.alarm.AlarmExpression;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.common.model.alarm.AlarmSubExpression;
import com.hpcloud.mon.common.model.metric.MetricDefinition;
import com.hpcloud.mon.domain.exception.EntityExistsException;
import com.hpcloud.mon.domain.exception.EntityNotFoundException;
import com.hpcloud.mon.domain.exception.InvalidEntityException;
import com.hpcloud.mon.domain.model.alarm.Alarm;
import com.hpcloud.mon.domain.model.alarm.AlarmRepository;
import com.hpcloud.mon.domain.model.notificationmethod.NotificationMethodRepository;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinition;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinitionRepository;
import com.hpcloud.util.Exceptions;
import com.hpcloud.util.Serialization;
/**
* Services alarm related requests.
* Services alarmed metric related requests.
*/
public class AlarmService {
private static final Logger LOG = LoggerFactory.getLogger(AlarmService.class);
@@ -61,110 +49,37 @@ public class AlarmService {
private final MonApiConfiguration config;
private final Producer<String, String> producer;
private final AlarmRepository repo;
private final NotificationMethodRepository notificationMethodRepo;
private final AlarmDefinitionRepository alarmDefRepo;
@Inject
public AlarmService(MonApiConfiguration config, Producer<String, String> producer,
AlarmRepository repo, NotificationMethodRepository notificationMethodRepo) {
AlarmRepository repo, AlarmDefinitionRepository alarmDefRepo) {
this.config = config;
this.producer = producer;
this.repo = repo;
this.notificationMethodRepo = notificationMethodRepo;
}
static class SubExpressions {
/** Sub expressions which have been removed from an updated alarm expression. */
Map<String, AlarmSubExpression> oldAlarmSubExpressions;
/** Sub expressions which have had their operator or threshold changed. */
Map<String, AlarmSubExpression> changedSubExpressions;
/** Sub expressions which have not changed. */
Map<String, AlarmSubExpression> unchangedSubExpressions;
/** Sub expressions which have been added to an updated alarm expression. */
Map<String, AlarmSubExpression> newAlarmSubExpressions;
this.alarmDefRepo = alarmDefRepo;
}
/**
* Creates an alarm and publishes an AlarmCreatedEvent. Note, the event is published first since
* chances of failure are higher.
*
* @throws EntityExistsException if an alarm already exists for the name
* @throws InvalidEntityException if one of the actions cannot be found
*/
public Alarm create(String tenantId, String name, @Nullable String description, String severity,
String expression, AlarmExpression alarmExpression, List<String> alarmActions,
@Nullable List<String> okActions, @Nullable List<String> undeterminedActions) {
// Assert no alarm exists by the name
if (repo.exists(tenantId, name))
throw new EntityExistsException("An alarm already exists for project / tenant: %s named: %s",
tenantId, name);
assertActionsExist(tenantId, alarmActions, okActions, undeterminedActions);
Map<String, AlarmSubExpression> subAlarms = new HashMap<String, AlarmSubExpression>();
for (AlarmSubExpression subExpression : alarmExpression.getSubExpressions())
subAlarms.put(UUID.randomUUID().toString(), subExpression);
String alarmId = UUID.randomUUID().toString();
Alarm alarm = null;
try {
LOG.debug("Creating alarm {} for tenant {}", name, tenantId);
alarm =
repo.create(tenantId, alarmId, name, description, severity, expression, subAlarms,
alarmActions, okActions, undeterminedActions);
// Notify interested parties of new alarm
String event =
Serialization
.toJson(new AlarmCreatedEvent(tenantId, alarmId, name, expression, subAlarms));
producer.send(new KeyedMessage<>(config.eventsTopic, tenantId, event));
return alarm;
} catch (Exception e) {
if (alarm != null)
try {
repo.deleteById(tenantId, alarm.getId());
} catch (Exception ignore) {
}
throw Exceptions.uncheck(e, "Error creating alarm for project / tenant %s", tenantId);
}
}
/**
* Deletes the alarm identified by the {@code alarmId}.
* Deletes the alarm identified by the {@code alarmId
* }.
*
* @throws EntityNotFoundException if the alarm cannot be found
*/
public void delete(String tenantId, String alarmId) {
Map<String, MetricDefinition> subAlarmMetricDefs = repo.findSubAlarmMetricDefinitions(alarmId);
repo.deleteById(tenantId, alarmId);
Alarm alarm = repo.findById(alarmId);
Map<String, MetricDefinition> subAlarmMetricDefs =
alarmDefRepo.findSubAlarmMetricDefinitions(alarm.getAlarmDefinitionId());
List<MetricDefinition> metrics = repo.findMetrics(alarmId);
repo.deleteById(alarmId);
// Notify interested parties of alarm deletion
String event =
Serialization.toJson(new AlarmDeletedEvent(tenantId, alarmId, subAlarmMetricDefs));
Serialization.toJson(new AlarmDeletedEvent(tenantId, alarmId, metrics, alarm
.getAlarmDefinitionId(), subAlarmMetricDefs));
producer.send(new KeyedMessage<>(config.eventsTopic, tenantId, event));
}
/**
* Updates the alarm for the {@code tenantId} and {@code alarmId} to the state of the
* {@code command}.
*
* @throws EntityNotFoundException if the alarm cannot be found
* @throws InvalidEntityException if one of the actions cannot be found
*/
public Alarm update(String tenantId, String alarmId, AlarmExpression alarmExpression,
UpdateAlarmCommand command) {
Alarm alarm =
assertAlarmExists(tenantId, alarmId, command.alarmActions, command.okActions,
command.undeterminedActions);
updateInternal(tenantId, alarmId, false, command.name, command.description, command.expression,
command.severity, alarmExpression, alarm.getState(), command.state, command.actionsEnabled,
command.alarmActions, command.okActions, command.undeterminedActions);
return new Alarm(alarmId, command.name, command.description, command.severity,
command.expression, command.state, command.actionsEnabled, command.alarmActions,
command.okActions, command.undeterminedActions);
}
/**
* Patches the alarm for the {@code tenantId} and {@code alarmId} to the state of the
* {@code fields}.
@@ -172,155 +87,54 @@ public class AlarmService {
* @throws EntityNotFoundException if the alarm cannot be found
* @throws InvalidEntityException if one of the actions cannot be found
*/
public Alarm patch(String tenantId, String alarmId, String name, String description,
String severity, String expression, AlarmExpression alarmExpression, AlarmState state,
Boolean enabled, List<String> alarmActions, List<String> okActions,
List<String> undeterminedActions) {
Alarm alarm =
assertAlarmExists(tenantId, alarmId, alarmActions, okActions, undeterminedActions);
name = name == null ? alarm.getName() : name;
description = description == null ? alarm.getDescription() : description;
expression = expression == null ? alarm.getExpression() : expression;
severity = severity == null ? alarm.getSeverity() : severity;
alarmExpression = alarmExpression == null ? AlarmExpression.of(expression) : alarmExpression;
public Alarm patch(String tenantId, String alarmId, AlarmState state) {
Alarm alarm = repo.findById(alarmId);
state = state == null ? alarm.getState() : state;
enabled = enabled == null ? alarm.isActionsEnabled() : enabled;
updateInternal(tenantId, alarmId, true, name, description, expression, severity,
alarmExpression, alarm.getState(), state, enabled, alarmActions, okActions,
undeterminedActions);
return new Alarm(alarmId, name, description, severity, expression, state, enabled,
alarmActions == null ? alarm.getAlarmActions() : alarmActions,
okActions == null ? alarm.getOkActions() : okActions,
undeterminedActions == null ? alarm.getUndeterminedActions() : undeterminedActions);
}
private void updateInternal(String tenantId, String alarmId, boolean patch, String name,
String description, String expression, String severity, AlarmExpression alarmExpression,
AlarmState oldState, AlarmState newState, Boolean enabled, List<String> alarmActions,
List<String> okActions, List<String> undeterminedActions) {
SubExpressions subExpressions = subExpressionsFor(alarmId, alarmExpression);
try {
LOG.debug("Updating alarm {} for tenant {}", name, tenantId);
repo.update(tenantId, alarmId, patch, name, description, expression, severity, newState,
enabled, subExpressions.oldAlarmSubExpressions.keySet(),
subExpressions.changedSubExpressions, subExpressions.newAlarmSubExpressions,
alarmActions, okActions, undeterminedActions);
// Notify interested parties of updated alarm
String event =
Serialization.toJson(new AlarmUpdatedEvent(tenantId, alarmId, name, description,
expression, newState, oldState, enabled, subExpressions.oldAlarmSubExpressions,
subExpressions.changedSubExpressions, subExpressions.unchangedSubExpressions,
subExpressions.newAlarmSubExpressions));
producer.send(new KeyedMessage<>(config.eventsTopic, tenantId, event));
// Notify interested parties of transitioned alarm state
if (!oldState.equals(newState)) {
event =
Serialization.toJson(new AlarmStateTransitionedEvent(tenantId, alarmId, name,
description, oldState, newState, enabled, stateChangeReasonFor(oldState, newState),
System.currentTimeMillis() / 1000));
producer.send(new KeyedMessage<>(config.alarmStateTransitionsTopic, tenantId, event));
}
} catch (Exception e) {
throw Exceptions.uncheck(e, "Error updating alarm for project / tenant %s", tenantId);
}
updateInternal(tenantId, alarm, alarm.getState(), state);
alarm.setState(state);
return alarm;
}
/**
* Returns an entry containing Maps of old, changed, and new sub expressions by comparing the
* {@code alarmExpression} to the existing sub expressions for the {@code alarmId}.
* Updates the alarmed metric for the {@code tenantId} and {@code alarmedMetricId} to the state of
* the {@code command}.
*
* @throws EntityNotFoundException if the alarmed metric cannot be found
*/
SubExpressions subExpressionsFor(String alarmId, AlarmExpression alarmExpression) {
BiMap<String, AlarmSubExpression> oldExpressions =
HashBiMap.create(repo.findSubExpressions(alarmId));
Set<AlarmSubExpression> oldSet = oldExpressions.inverse().keySet();
Set<AlarmSubExpression> newSet = new HashSet<>(alarmExpression.getSubExpressions());
// Identify old or changed expressions
Set<AlarmSubExpression> oldOrChangedExpressions =
new HashSet<>(Sets.difference(oldSet, newSet));
// Identify new or changed expressions
Set<AlarmSubExpression> newOrChangedExpressions =
new HashSet<>(Sets.difference(newSet, oldSet));
// Find changed expressions
Map<String, AlarmSubExpression> changedExpressions = new HashMap<>();
for (Iterator<AlarmSubExpression> oldIt = oldOrChangedExpressions.iterator(); oldIt.hasNext();) {
AlarmSubExpression oldExpr = oldIt.next();
for (Iterator<AlarmSubExpression> newIt = newOrChangedExpressions.iterator(); newIt.hasNext();) {
AlarmSubExpression newExpr = newIt.next();
if (sameKeyFields(oldExpr, newExpr)) {
oldIt.remove();
newIt.remove();
changedExpressions.put(oldExpressions.inverse().get(oldExpr), newExpr);
}
}
}
// Create the list of unchanged expressions
BiMap<String, AlarmSubExpression> unchangedExpressions = HashBiMap.create(oldExpressions);
unchangedExpressions.values().removeAll(oldOrChangedExpressions);
unchangedExpressions.keySet().removeAll(changedExpressions.keySet());
// Remove old sub expressions
oldExpressions.values().retainAll(oldOrChangedExpressions);
// Create IDs for new expressions
Map<String, AlarmSubExpression> newExpressions = new HashMap<>();
for (AlarmSubExpression expression : newOrChangedExpressions)
newExpressions.put(UUID.randomUUID().toString(), expression);
SubExpressions subExpressions = new SubExpressions();
subExpressions.oldAlarmSubExpressions = oldExpressions;
subExpressions.changedSubExpressions = changedExpressions;
subExpressions.unchangedSubExpressions = unchangedExpressions;
subExpressions.newAlarmSubExpressions = newExpressions;
return subExpressions;
public Alarm update(String tenantId, String alarmId, UpdateAlarmCommand command) {
Alarm alarm = repo.findById(alarmId);
updateInternal(tenantId, alarm, alarm.getState(), command.state);
alarm.setState(command.state);
return alarm;
}
private String stateChangeReasonFor(AlarmState oldState, AlarmState newState) {
return "Alarm state updated via API";
}
/**
* Returns whether all of the fields of {@code a} and {@code b} are the same except the operator
* and threshold.
*/
private boolean sameKeyFields(AlarmSubExpression a, AlarmSubExpression b) {
return a.getMetricDefinition().equals(b.getMetricDefinition())
&& a.getFunction().equals(b.getFunction()) && a.getPeriod() == b.getPeriod()
&& a.getPeriods() == b.getPeriods();
}
private void updateInternal(String tenantId, Alarm alarm, AlarmState oldState, AlarmState newState) {
try {
LOG.debug("Updating alarm {} for tenant {}", alarm.getId(), tenantId);
repo.update(tenantId, alarm.getId(), newState);
/**
* Asserts an alarm exists for the {@code alarmId} as well as the actions.
*
* @throws EntityNotFoundException if the alarm cannot be found
*/
private Alarm assertAlarmExists(String tenantId, String alarmId, List<String> alarmActions,
List<String> okActions, List<String> undeterminedActions) {
Alarm alarm = repo.findById(tenantId, alarmId);
assertActionsExist(tenantId, alarmActions, okActions, undeterminedActions);
return alarm;
}
// Notify interested parties of updated alarm
AlarmDefinition alarmDef = alarmDefRepo.findById(tenantId, alarm.getAlarmDefinitionId());
String event =
Serialization.toJson(new AlarmUpdatedEvent(alarm.getId(), alarm.getAlarmDefinitionId(),
newState, oldState));
producer.send(new KeyedMessage<>(config.eventsTopic, tenantId, event));
private void assertActionsExist(String tenantId, List<String> alarmActions,
List<String> okActions, List<String> undeterminedActions) {
Set<String> actions = new HashSet<>();
if (alarmActions != null)
actions.addAll(alarmActions);
if (okActions != null)
actions.addAll(okActions);
if (undeterminedActions != null)
actions.addAll(undeterminedActions);
if (!actions.isEmpty())
for (String action : actions)
if (!notificationMethodRepo.exists(tenantId, action))
throw new InvalidEntityException("No notification method exists for action %s", action);
// Notify interested parties of transitioned alarm state
if (!oldState.equals(newState)) {
event =
Serialization.toJson(new AlarmStateTransitionedEvent(tenantId, alarm.getId(), alarmDef
.getId(), alarm.getMetrics(), alarmDef.getName(), alarmDef.getDescription(),
oldState, newState, alarmDef.isActionsEnabled(), stateChangeReasonFor(oldState,
newState), System.currentTimeMillis() / 1000));
producer.send(new KeyedMessage<>(config.alarmStateTransitionsTopic, tenantId, event));
}
} catch (Exception e) {
throw Exceptions.uncheck(e, "Error updating alarm for project / tenant %s", tenantId);
}
}
}

View File

@@ -24,6 +24,7 @@ public class ApplicationModule extends AbstractModule {
@Override
protected void configure() {
bind(MetricService.class).in(Singleton.class);
bind(AlarmDefinitionService.class).in(Singleton.class);
bind(AlarmService.class).in(Singleton.class);
}
}

View File

@@ -1,80 +0,0 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.hpcloud.mon.app.command;
import java.util.List;
import javax.annotation.Nullable;
import org.hibernate.validator.constraints.NotEmpty;
import com.hpcloud.mon.app.validation.AlarmValidation;
public class CreateAlarmCommand {
@NotEmpty
public String name;
public String description;
@NotEmpty
public String expression;
public String severity;
public List<String> alarmActions;
public List<String> okActions;
public List<String> undeterminedActions;
public CreateAlarmCommand() {
this.severity = "LOW";
}
public CreateAlarmCommand(String name, @Nullable String description, String expression,
String severity, List<String> alarmActions, List<String> okActions,
List<String> undeterminedActions) {
this.name = name;
this.description = description;
this.expression = expression;
this.alarmActions = alarmActions;
this.okActions = okActions;
this.undeterminedActions = undeterminedActions;
this.severity = severity == null ? "LOW" : severity;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CreateAlarmCommand other = (CreateAlarmCommand) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
public void validate() {
AlarmValidation.validate(name, description, severity, alarmActions, okActions,
undeterminedActions);
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.hpcloud.mon.app.command;
import java.util.List;
import javax.annotation.Nullable;
import org.hibernate.validator.constraints.NotEmpty;
import com.hpcloud.mon.app.validation.AlarmValidation;
public class CreateAlarmDefinitionCommand {
@NotEmpty
public String name;
public String description;
@NotEmpty
public String expression;
public List<String> matchBy;
public String severity;
public List<String> alarmActions;
public List<String> okActions;
public List<String> undeterminedActions;
public CreateAlarmDefinitionCommand() {
this.severity = "LOW";
}
public CreateAlarmDefinitionCommand(String name, @Nullable String description, String expression,
List<String> matchBy, String severity, List<String> alarmActions, List<String> okActions,
List<String> undeterminedActions) {
this.name = name;
this.description = description;
this.expression = expression;
this.matchBy = matchBy;
this.alarmActions = alarmActions;
this.okActions = okActions;
this.undeterminedActions = undeterminedActions;
this.severity = severity == null ? "LOW" : severity;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof CreateAlarmDefinitionCommand))
return false;
CreateAlarmDefinitionCommand other = (CreateAlarmDefinitionCommand) obj;
if (alarmActions == null) {
if (other.alarmActions != null)
return false;
} else if (!alarmActions.equals(other.alarmActions))
return false;
if (description == null) {
if (other.description != null)
return false;
} else if (!description.equals(other.description))
return false;
if (expression == null) {
if (other.expression != null)
return false;
} else if (!expression.equals(other.expression))
return false;
if (matchBy == null) {
if (other.matchBy != null)
return false;
} else if (!matchBy.equals(other.matchBy))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (okActions == null) {
if (other.okActions != null)
return false;
} else if (!okActions.equals(other.okActions))
return false;
if (severity == null) {
if (other.severity != null)
return false;
} else if (!severity.equals(other.severity))
return false;
if (undeterminedActions == null) {
if (other.undeterminedActions != null)
return false;
} else if (!undeterminedActions.equals(other.undeterminedActions))
return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((alarmActions == null) ? 0 : alarmActions.hashCode());
result = prime * result + ((description == null) ? 0 : description.hashCode());
result = prime * result + ((expression == null) ? 0 : expression.hashCode());
result = prime * result + ((matchBy == null) ? 0 : matchBy.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((okActions == null) ? 0 : okActions.hashCode());
result = prime * result + ((severity == null) ? 0 : severity.hashCode());
result = prime * result + ((undeterminedActions == null) ? 0 : undeterminedActions.hashCode());
return result;
}
public void validate() {
AlarmValidation.validate(name, description, severity, alarmActions, okActions,
undeterminedActions);
}
}

View File

@@ -13,26 +13,17 @@
*/
package com.hpcloud.mon.app.command;
import java.util.List;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import com.hpcloud.mon.common.model.alarm.AlarmState;
public class UpdateAlarmCommand extends CreateAlarmCommand {
public class UpdateAlarmCommand {
@NotNull
public AlarmState state;
@NotNull
public Boolean actionsEnabled;
public UpdateAlarmCommand() {}
public UpdateAlarmCommand(String name, @Nullable String description, String severity,
String expression, AlarmState state, boolean enabled, List<String> alarmActions,
List<String> okActions, List<String> undeterminedActions) {
super(name, description, severity, expression, alarmActions, okActions, undeterminedActions);
public UpdateAlarmCommand(AlarmState state) {
this.state = state;
this.actionsEnabled = enabled;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.hpcloud.mon.app.command;
import java.util.List;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
public class UpdateAlarmDefinitionCommand extends CreateAlarmDefinitionCommand {
@NotNull
public Boolean actionsEnabled;
public UpdateAlarmDefinitionCommand() {}
public UpdateAlarmDefinitionCommand(String name, @Nullable String description, String expression,
List<String> matchBy, String severity, boolean enabled, List<String> alarmActions,
List<String> okActions, List<String> undeterminedActions) {
super(name, description, expression, matchBy, severity, alarmActions, okActions,
undeterminedActions);
this.actionsEnabled = enabled;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (!(obj instanceof UpdateAlarmDefinitionCommand))
return false;
UpdateAlarmDefinitionCommand other = (UpdateAlarmDefinitionCommand) obj;
if (actionsEnabled == null) {
if (other.actionsEnabled != null)
return false;
} else if (!actionsEnabled.equals(other.actionsEnabled))
return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((actionsEnabled == null) ? 0 : actionsEnabled.hashCode());
return result;
}
}

View File

@@ -18,120 +18,31 @@ import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import com.hpcloud.mon.common.model.alarm.AlarmExpression;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.common.model.metric.MetricDefinition;
import com.hpcloud.mon.domain.common.AbstractEntity;
import com.hpcloud.mon.domain.model.common.Link;
import com.hpcloud.mon.domain.model.common.Linked;
import com.wordnik.swagger.annotations.ApiModel;
import com.wordnik.swagger.annotations.ApiModelProperty;
@ApiModel(value = "An alarm is a devops's best friend")
@XmlRootElement(name = "Alarm")
public class Alarm extends AbstractEntity implements Linked {
private List<Link> links;
private String name;
private String description = "";
private String expression;
private Object expressionData;
private String alarmDefinitionId;
private List<MetricDefinition> metrics;
private AlarmState state;
private String severity;
private boolean actionsEnabled;
private List<String> alarmActions;
private List<String> okActions;
private List<String> undeterminedActions;
public Alarm() {}
public Alarm(String id, String name, String description, String severity, String expression,
AlarmState state, boolean actionsEnabled, List<String> alarmActions, List<String> okActions,
List<String> undeterminedActions) {
public Alarm(String id, String alarmDefinitionId, String metricName,
List<MetricDefinition> metrics, AlarmState state) {
this.id = id;
this.name = name;
setDescription(description);
setSeverity(severity);
setExpression(expression);
setMetrics(metrics);
setState(state);
setActionsEnabled(actionsEnabled);
setAlarmActions(alarmActions);
setOkActions(okActions);
setUndeterminedActions(undeterminedActions);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
Alarm other = (Alarm) obj;
if (alarmActions == null) {
if (other.alarmActions != null)
return false;
} else if (!alarmActions.equals(other.alarmActions))
return false;
if (description == null) {
if (other.description != null)
return false;
} else if (!description.equals(other.description))
return false;
if (severity == null) {
if (other.severity != null)
return false;
} else if (!severity.equals(other.severity))
return false;
if (actionsEnabled != other.actionsEnabled)
return false;
if (expression == null) {
if (other.expression != null)
return false;
} else if (!expression.equals(other.expression))
return false;
if (links == null) {
if (other.links != null)
return false;
} else if (!links.equals(other.links))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (okActions == null) {
if (other.okActions != null)
return false;
} else if (!okActions.equals(other.okActions))
return false;
if (state != other.state)
return false;
if (undeterminedActions == null) {
if (other.undeterminedActions != null)
return false;
} else if (!undeterminedActions.equals(other.undeterminedActions))
return false;
return true;
}
public List<String> getAlarmActions() {
return alarmActions;
}
public String getDescription() {
return description;
}
public String getSeverity() {
return severity;
}
public String getExpression() {
return expression;
}
public Object getExpressionData() {
return expressionData;
public String getAlarmDefinitionId() {
return alarmDefinitionId;
}
public String getId() {
@@ -142,62 +53,12 @@ public class Alarm extends AbstractEntity implements Linked {
return links;
}
public String getName() {
return name;
}
public List<String> getOkActions() {
return okActions;
}
public AlarmState getState() {
return state;
}
public List<String> getUndeterminedActions() {
return undeterminedActions;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((alarmActions == null) ? 0 : alarmActions.hashCode());
result = prime * result + ((description == null) ? 0 : description.hashCode());
result = prime * result + ((severity == null) ? 0 : severity.hashCode());
result = prime * result + (actionsEnabled ? 1231 : 1237);
result = prime * result + ((expression == null) ? 0 : expression.hashCode());
result = prime * result + ((links == null) ? 0 : links.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((okActions == null) ? 0 : okActions.hashCode());
result = prime * result + ((state == null) ? 0 : state.hashCode());
result = prime * result + ((undeterminedActions == null) ? 0 : undeterminedActions.hashCode());
return result;
}
public boolean isActionsEnabled() {
return actionsEnabled;
}
public void setActionsEnabled(boolean actionsEnabled) {
this.actionsEnabled = actionsEnabled;
}
public void setAlarmActions(List<String> alarmActions) {
this.alarmActions = alarmActions;
}
public void setDescription(String description) {
this.description = description == null ? "" : description;
}
public void setExpression(String expression) {
this.expression = expression;
setExpressionData(AlarmExpression.of(expression).getExpressionTree());
}
public void setExpressionData(Object expressionData) {
this.expressionData = expressionData;
public void setAlarmDefinitionId(String alarmDefinitionId) {
this.alarmDefinitionId = alarmDefinitionId;
}
@XmlElement(name = "id")
@@ -211,28 +72,15 @@ public class Alarm extends AbstractEntity implements Linked {
this.links = links;
}
public void setName(String name) {
this.name = name;
}
public void setOkActions(List<String> okActions) {
this.okActions = okActions;
}
public void setState(AlarmState state) {
this.state = state;
}
public void setSeverity(String severity) {
this.severity = severity;
public List<MetricDefinition> getMetrics() {
return metrics;
}
public void setUndeterminedActions(List<String> undeterminedActions) {
this.undeterminedActions = undeterminedActions;
}
@Override
public String toString() {
return String.format("Alarm [name=%s]", name);
public void setMetrics(List<MetricDefinition> metrics) {
this.metrics = metrics;
}
}

View File

@@ -1,74 +1,33 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.hpcloud.mon.domain.model.alarm;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.common.model.alarm.AlarmSubExpression;
import com.hpcloud.mon.common.model.metric.MetricDefinition;
import com.hpcloud.mon.domain.exception.EntityNotFoundException;
/**
* Repository for alarms.
*/
public interface AlarmRepository {
/**
* Creates and returns a new alarm for the criteria.
* Deletes all alarms associated with the {@code id}.
*/
Alarm create(String tenantId, String id, String name, String description, String severity,
String expression, Map<String, AlarmSubExpression> subExpressions, List<String> alarmActions,
List<String> okActions, List<String> undeterminedActions);
/**
* @throws EntityNotFoundException if an alarm cannot be found for the {@code alarmId}
*/
void deleteById(String tenantId, String alarmId);
/**
* Returns true if an alarm exists for the given criteria, else false.
*/
boolean exists(String tenantId, String name);
void deleteById(String id);
/**
* Returns alarms for the given criteria.
*/
List<Alarm> find(String tenantId, String name, Map<String, String> dimensions, String state);
List<Alarm> find(String tenantId, String alarmDefId, String metricName,
Map<String, String> metricDimensions, AlarmState state);
/**
* @throws EntityNotFoundException if an alarm cannot be found for the {@code alarmId}
* @throws EntityNotFoundException if an alarm cannot be found for the {@code id}
*/
Alarm findById(String tenantId, String alarmId);
Alarm findById(String id);
/**
* Returns the sub-alarm Ids for the {@code alarmId}.
*/
Map<String, MetricDefinition> findSubAlarmMetricDefinitions(String alarmId);
/**
* Returns the sub expressions for the {@code alarmId}.
*/
Map<String, AlarmSubExpression> findSubExpressions(String alarmId);
List<MetricDefinition> findMetrics(String alarmId);
/**
* Updates and returns an alarm for the criteria.
*/
void update(String tenantId, String id, boolean patch, String name, String description,
String expression, String severity, AlarmState state, boolean enabled,
Collection<String> oldSubAlarmIds, Map<String, AlarmSubExpression> changedSubAlarms,
Map<String, AlarmSubExpression> newSubAlarms, List<String> alarmActions,
List<String> okActions, List<String> undeterminedActions);
void update(String tenantId, String id, AlarmState state);
}

View File

@@ -0,0 +1,249 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.hpcloud.mon.domain.model.alarmdefinition;
import java.util.Collections;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.hpcloud.mon.common.model.alarm.AlarmExpression;
import com.hpcloud.mon.domain.common.AbstractEntity;
import com.hpcloud.mon.domain.model.common.Link;
import com.hpcloud.mon.domain.model.common.Linked;
import com.wordnik.swagger.annotations.ApiModel;
import com.wordnik.swagger.annotations.ApiModelProperty;
@ApiModel(value = "Defines an alarm")
@XmlRootElement(name = "Alarm definition")
public class AlarmDefinition extends AbstractEntity implements Linked {
private List<Link> links;
private String name;
private String description = "";
private String expression;
private Object expressionData;
private List<String> matchBy;
private String severity;
private boolean actionsEnabled;
private List<String> alarmActions;
private List<String> okActions;
private List<String> undeterminedActions;
public AlarmDefinition() {}
public AlarmDefinition(String id, String name, String description, String severity,
String expression, List<String> matchBy, boolean actionsEnabled, List<String> alarmActions,
List<String> okActions, List<String> undeterminedActions) {
this.id = id;
this.name = name;
setDescription(description);
setSeverity(severity);
setExpression(expression);
setMatchBy(matchBy);
setActionsEnabled(actionsEnabled);
setAlarmActions(alarmActions);
setOkActions(okActions);
setUndeterminedActions(undeterminedActions);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (!(obj instanceof AlarmDefinition))
return false;
AlarmDefinition other = (AlarmDefinition) obj;
if (actionsEnabled != other.actionsEnabled)
return false;
if (alarmActions == null) {
if (other.alarmActions != null)
return false;
} else if (!alarmActions.equals(other.alarmActions))
return false;
if (description == null) {
if (other.description != null)
return false;
} else if (!description.equals(other.description))
return false;
if (expression == null) {
if (other.expression != null)
return false;
} else if (!expression.equals(other.expression))
return false;
if (expressionData == null) {
if (other.expressionData != null)
return false;
} else if (!expressionData.equals(other.expressionData))
return false;
if (links == null) {
if (other.links != null)
return false;
} else if (!links.equals(other.links))
return false;
if (matchBy == null) {
if (other.matchBy != null)
return false;
} else if (!matchBy.equals(other.matchBy))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (okActions == null) {
if (other.okActions != null)
return false;
} else if (!okActions.equals(other.okActions))
return false;
if (severity == null) {
if (other.severity != null)
return false;
} else if (!severity.equals(other.severity))
return false;
if (undeterminedActions == null) {
if (other.undeterminedActions != null)
return false;
} else if (!undeterminedActions.equals(other.undeterminedActions))
return false;
return true;
}
public List<String> getAlarmActions() {
return alarmActions;
}
public String getDescription() {
return description;
}
public String getExpression() {
return expression;
}
public Object getExpressionData() {
return expressionData;
}
public String getId() {
return id;
}
public List<Link> getLinks() {
return links;
}
public List<String> getMatchBy() {
return matchBy;
}
public String getName() {
return name;
}
public List<String> getOkActions() {
return okActions;
}
public String getSeverity() {
return severity;
}
public List<String> getUndeterminedActions() {
return undeterminedActions;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + (actionsEnabled ? 1231 : 1237);
result = prime * result + ((alarmActions == null) ? 0 : alarmActions.hashCode());
result = prime * result + ((description == null) ? 0 : description.hashCode());
result = prime * result + ((expression == null) ? 0 : expression.hashCode());
result = prime * result + ((expressionData == null) ? 0 : expressionData.hashCode());
result = prime * result + ((links == null) ? 0 : links.hashCode());
result = prime * result + ((matchBy == null) ? 0 : matchBy.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((okActions == null) ? 0 : okActions.hashCode());
result = prime * result + ((severity == null) ? 0 : severity.hashCode());
result = prime * result + ((undeterminedActions == null) ? 0 : undeterminedActions.hashCode());
return result;
}
public boolean isActionsEnabled() {
return actionsEnabled;
}
public void setActionsEnabled(boolean actionsEnabled) {
this.actionsEnabled = actionsEnabled;
}
public void setAlarmActions(List<String> alarmActions) {
this.alarmActions = alarmActions;
}
public void setDescription(String description) {
this.description = description == null ? "" : description;
}
public void setExpression(String expression) {
this.expression = expression;
setExpressionData(AlarmExpression.of(expression).getExpressionTree());
}
@JsonIgnore
public void setExpressionData(Object expressionData) {
this.expressionData = expressionData;
}
@XmlElement(name = "id")
@ApiModelProperty(value = "Alarm definition ID")
public void setId(String id) {
this.id = id;
}
@Override
public void setLinks(List<Link> links) {
this.links = links;
}
public void setMatchBy(List<String> matchBy) {
this.matchBy = matchBy == null ? Collections.<String>emptyList() : matchBy;
}
public void setName(String name) {
this.name = name;
}
public void setOkActions(List<String> okActions) {
this.okActions = okActions;
}
public void setSeverity(String severity) {
this.severity = severity;
}
public void setUndeterminedActions(List<String> undeterminedActions) {
this.undeterminedActions = undeterminedActions;
}
@Override
public String toString() {
return String.format("AlarmDefinition [name=%s]", name);
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.hpcloud.mon.domain.model.alarmdefinition;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.hpcloud.mon.common.model.alarm.AlarmSubExpression;
import com.hpcloud.mon.common.model.metric.MetricDefinition;
import com.hpcloud.mon.domain.exception.EntityNotFoundException;
/**
* Repository for alarm definitions.
*/
public interface AlarmDefinitionRepository {
/**
* Creates and returns a new alarm definition for the criteria.
*/
AlarmDefinition create(String tenantId, String id, String name, String description,
String severity, String expression, Map<String, AlarmSubExpression> subExpressions,
List<String> matchBy, List<String> alarmActions, List<String> okActions,
List<String> undeterminedActions);
/**
* @throws EntityNotFoundException if an alarm definition cannot be found for the
* {@code alarmDefId}
*/
void deleteById(String tenantId, String alarmDefId);
/**
* Returns true if an alarm exists for the given criteria, else false.
*/
boolean exists(String tenantId, String name);
/**
* Returns alarms for the given criteria.
*/
List<AlarmDefinition> find(String tenantId, String name, Map<String, String> dimensions);
/**
* @throws EntityNotFoundException if an alarm cannot be found for the {@code alarmDefId}
*/
AlarmDefinition findById(String tenantId, String alarmDefId);
/**
* Returns the sub-alarm Ids for the {@code alarmDefId}.
*/
Map<String, MetricDefinition> findSubAlarmMetricDefinitions(String alarmDefId);
/**
* Returns the sub expressions for the {@code alarmDefId}.
*/
Map<String, AlarmSubExpression> findSubExpressions(String alarmDefId);
/**
* Updates and returns an alarm definition for the criteria.
*/
void update(String tenantId, String id, boolean patch, String name, String description,
String expression, List<String> matchBy, String severity, boolean actionsEnabled,
Collection<String> oldSubAlarmIds, Map<String, AlarmSubExpression> changedSubAlarms,
Map<String, AlarmSubExpression> newSubAlarms, List<String> alarmActions,
List<String> okActions, List<String> undeterminedActions);
}

View File

@@ -13,12 +13,16 @@
*/
package com.hpcloud.mon.domain.model.alarmstatehistory;
import java.util.List;
import org.joda.time.DateTime;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.common.model.metric.MetricDefinition;
public class AlarmStateHistory {
private String alarmId;
private List<MetricDefinition> metrics;
private AlarmState oldState;
private AlarmState newState;
private String reason;
@@ -27,9 +31,10 @@ public class AlarmStateHistory {
public AlarmStateHistory() {}
public AlarmStateHistory(String alarmId, AlarmState oldState, AlarmState newState, String reason,
String reasonData, DateTime timestamp) {
public AlarmStateHistory(String alarmId, List<MetricDefinition> metrics, AlarmState oldState,
AlarmState newState, String reason, String reasonData, DateTime timestamp) {
this.alarmId = alarmId;
this.setMetrics(metrics);
this.oldState = oldState;
this.newState = newState;
this.reason = reason;
@@ -43,7 +48,7 @@ public class AlarmStateHistory {
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
if (!(obj instanceof AlarmStateHistory))
return false;
AlarmStateHistory other = (AlarmStateHistory) obj;
if (alarmId == null) {
@@ -51,6 +56,11 @@ public class AlarmStateHistory {
return false;
} else if (!alarmId.equals(other.alarmId))
return false;
if (metrics == null) {
if (other.metrics != null)
return false;
} else if (!metrics.equals(other.metrics))
return false;
if (newState != other.newState)
return false;
if (oldState != other.oldState)
@@ -77,6 +87,10 @@ public class AlarmStateHistory {
return alarmId;
}
public List<MetricDefinition> getMetrics() {
return metrics;
}
public AlarmState getNewState() {
return newState;
}
@@ -102,6 +116,7 @@ public class AlarmStateHistory {
final int prime = 31;
int result = 1;
result = prime * result + ((alarmId == null) ? 0 : alarmId.hashCode());
result = prime * result + ((metrics == null) ? 0 : metrics.hashCode());
result = prime * result + ((newState == null) ? 0 : newState.hashCode());
result = prime * result + ((oldState == null) ? 0 : oldState.hashCode());
result = prime * result + ((reason == null) ? 0 : reason.hashCode());
@@ -114,6 +129,10 @@ public class AlarmStateHistory {
this.alarmId = alarmId;
}
public void setMetrics(List<MetricDefinition> metrics) {
this.metrics = metrics;
}
public void setNewState(AlarmState newState) {
this.newState = newState;
}
@@ -136,9 +155,8 @@ public class AlarmStateHistory {
@Override
public String toString() {
return String
.format(
"AlarmStateHistory [alarmId=%s, oldState=%s, newState=%s, reason=%s, reasonData=%s, timestamp=%s]",
alarmId, oldState, newState, reason, reasonData, timestamp);
return "AlarmStateHistory [alarmId=" + alarmId + ", metrics=" + metrics + ", oldState="
+ oldState + ", newState=" + newState + ", reason=" + reason + ", reasonData=" + reasonData
+ ", timestamp=" + timestamp + "]";
}
}

View File

@@ -23,6 +23,7 @@ import com.google.inject.Provides;
import com.google.inject.ProvisionException;
import com.hpcloud.mon.MonApiConfiguration;
import com.hpcloud.mon.domain.model.alarm.AlarmRepository;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinitionRepository;
import com.hpcloud.mon.domain.model.alarmstatehistory.AlarmStateHistoryRepository;
import com.hpcloud.mon.domain.model.measurement.MeasurementRepository;
import com.hpcloud.mon.domain.model.metric.MetricDefinitionRepository;
@@ -32,6 +33,7 @@ import com.hpcloud.mon.infrastructure.persistence.influxdb.AlarmStateHistoryInfl
import com.hpcloud.mon.infrastructure.persistence.influxdb.MeasurementInfluxDbRepositoryImpl;
import com.hpcloud.mon.infrastructure.persistence.influxdb.MetricDefinitionInfluxDbRepositoryImpl;
import com.hpcloud.mon.infrastructure.persistence.influxdb.StatisticInfluxDbRepositoryImpl;
import com.hpcloud.mon.infrastructure.persistence.mysql.AlarmDefinitionMySqlRepositoryImpl;
import com.hpcloud.mon.infrastructure.persistence.mysql.AlarmMySqlRepositoryImpl;
import com.hpcloud.mon.infrastructure.persistence.mysql.NotificationMethodMySqlRepositoryImpl;
import com.hpcloud.mon.infrastructure.persistence.vertica.AlarmStateHistoryVerticaRepositoryImpl;
@@ -53,6 +55,8 @@ public class InfrastructureModule extends AbstractModule {
protected void configure() {
// Bind repositories
bind(AlarmRepository.class).to(AlarmMySqlRepositoryImpl.class).in(Singleton.class);
bind(AlarmDefinitionRepository.class).to(AlarmDefinitionMySqlRepositoryImpl.class).in(
Singleton.class);
if (config.databaseConfiguration.getDatabaseType().trim().toLowerCase().equals("vertica")) {
bind(AlarmStateHistoryRepository.class).to(AlarmStateHistoryVerticaRepositoryImpl.class).in(
Singleton.class);

View File

@@ -13,6 +13,8 @@
*/
package com.hpcloud.mon.infrastructure.persistence;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@@ -36,4 +38,19 @@ public final class DimensionQueries {
}
}
}
public static Map<String, String> dimensionsFor(String dimensionSet) {
Map<String, String> dimensions = Collections.emptyMap();
if (dimensionSet != null) {
dimensions = new HashMap<String, String>();
for (String kvStr : dimensionSet.split(",")) {
String[] kv = kvStr.split("=");
if (kv.length > 1)
dimensions.put(kv[0], kv[1]);
}
}
return dimensions;
}
}

View File

@@ -13,14 +13,15 @@
*/
package com.hpcloud.mon.infrastructure.persistence.influxdb;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.hpcloud.mon.MonApiConfiguration;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.domain.model.alarmstatehistory.AlarmStateHistory;
import com.hpcloud.mon.domain.model.alarmstatehistory.AlarmStateHistoryRepository;
import com.hpcloud.mon.infrastructure.persistence.DimensionQueries;
import com.hpcloud.mon.infrastructure.persistence.SubAlarmQueries;
import javax.annotation.Nullable;
import javax.inject.Named;
import org.influxdb.InfluxDB;
import org.influxdb.dto.Serie;
@@ -33,35 +34,42 @@ import org.skife.jdbi.v2.util.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.inject.Named;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.google.inject.Inject;
import com.hpcloud.mon.MonApiConfiguration;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.common.model.metric.MetricDefinition;
import com.hpcloud.mon.domain.model.alarmstatehistory.AlarmStateHistory;
import com.hpcloud.mon.domain.model.alarmstatehistory.AlarmStateHistoryRepository;
import com.hpcloud.mon.infrastructure.persistence.DimensionQueries;
import com.hpcloud.mon.infrastructure.persistence.SubAlarmQueries;
public class AlarmStateHistoryInfluxDbRepositoryImpl implements AlarmStateHistoryRepository {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final TypeReference<List<MetricDefinition>> METRICS_TYPE =
new TypeReference<List<MetricDefinition>>() {};
private static final Logger logger = LoggerFactory
.getLogger(AlarmStateHistoryInfluxDbRepositoryImpl.class);
private static final String FIND_ALARMS_SQL =
"select distinct ad.id from alarm_definition as ad "
+ "join sub_alarm_definition sad on ad.id = sad.alarm_definition_id "
+ "left outer join sub_alarm_definition_dimension dim on sad.id = dim.sub_alarm_definition_id%s "
+ "where ad.tenant_id = :tenantId and ad.deleted_at is NULL";
private final MonApiConfiguration config;
private final InfluxDB influxDB;
private final DBI mysql;
private static final String FIND_ALARMS_SQL = "select distinct a.id from alarm as a " + "join"
+ " sub_alarm sa on a.id = sa.alarm_id "
+ "left outer join sub_alarm_dimension dim on "
+ "sa.id = dim.sub_alarm_id%s "
+ "where a.tenant_id = :tenantId and a.deleted_at is "
+ "NULL";
static {
OBJECT_MAPPER
.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
}
@Inject
public AlarmStateHistoryInfluxDbRepositoryImpl(@Named("mysql") DBI mysql,
MonApiConfiguration config, InfluxDB influxDB) {
MonApiConfiguration config, InfluxDB influxDB) {
this.mysql = mysql;
this.config = config;
this.influxDB = influxDB;
@@ -69,25 +77,20 @@ public class AlarmStateHistoryInfluxDbRepositoryImpl implements AlarmStateHistor
@Override
public List<AlarmStateHistory> findById(String tenantId, String alarmId) throws Exception {
// InfluxDB orders queries by time stamp desc by default.
String query = buildQueryForFindById(tenantId, alarmId);
return queryInfluxDBForAlarmStateHistory(query);
}
String buildQueryForFindById(String tenantId, String alarmId) throws Exception {
return String.format("select alarm_id, old_state, new_state, reason, reason_data "
+ "from alarm_state_history "
+ "where tenant_id = '%1$s' and alarm_id = '%2$s'",
Utils.SQLSanitizer.sanitize(tenantId),
Utils.SQLSanitizer.sanitize(alarmId));
return String.format("select alarm_id, metrics, old_state, new_state, reason, reason_data "
+ "from alarm_state_history where tenant_id = '%1$s' and alarm_id = '%2$s'",
Utils.SQLSanitizer.sanitize(tenantId), Utils.SQLSanitizer.sanitize(alarmId));
}
@Override
public Collection<AlarmStateHistory> find(String tenantId, Map<String, String> dimensions,
DateTime startTime, @Nullable DateTime endTime)
throws Exception {
DateTime startTime, @Nullable DateTime endTime) throws Exception {
List<String> alarmIds = null;
// Find alarm Ids for dimensions
@@ -116,9 +119,9 @@ public class AlarmStateHistoryInfluxDbRepositoryImpl implements AlarmStateHistor
}
String buildQueryForFind(String tenantId, String timePart, String alarmsPart) throws Exception {
return String.format("select alarm_id, old_state, new_state, reason, reason_data "
+ "from alarm_state_history " + "where tenant_id = '%1$s' %2$s %3$s",
Utils.SQLSanitizer.sanitize(tenantId), timePart, alarmsPart);
return String.format("select alarm_id, metrics, old_state, new_state, reason, reason_data "
+ "from alarm_state_history where tenant_id = '%1$s' %2$s %3$s",
Utils.SQLSanitizer.sanitize(tenantId), timePart, alarmsPart);
}
String buildAlarmsPart(List<String> alarmIds) {
@@ -138,17 +141,16 @@ public class AlarmStateHistoryInfluxDbRepositoryImpl implements AlarmStateHistor
return sb.toString();
}
@SuppressWarnings("unchecked")
private List<AlarmStateHistory> queryInfluxDBForAlarmStateHistory(String query) {
logger.debug("Query string: {}", query);
List<Serie> result;
try {
result =
this.influxDB.Query(this.config.influxDB.getName(), query, TimeUnit.MILLISECONDS);
result = this.influxDB.Query(this.config.influxDB.getName(), query, TimeUnit.MILLISECONDS);
} catch (Exception e) {
if (e.getMessage().startsWith(Utils.COULD_NOT_LOOK_UP_COLUMNS_EXC_MSG)) {
return new LinkedList();
return new LinkedList<>();
} else {
logger.error("Failed to get data from InfluxDB", e);
throw e;
@@ -161,18 +163,25 @@ public class AlarmStateHistoryInfluxDbRepositoryImpl implements AlarmStateHistor
for (Serie serie : result) {
final String[] colNames = serie.getColumns();
final List<Map<String, Object>> rows = serie.getRows();
for (Map<String, Object> row : rows) {
for (Map<String, Object> row : rows) {
AlarmStateHistory alarmStateHistory = new AlarmStateHistory();
// Time is always in position 0.
Double timeDouble = (Double) row.get(colNames[0]);
alarmStateHistory.setTimestamp(new DateTime(timeDouble.longValue(), DateTimeZone.UTC));
// Sequence_number is always in position 1.
alarmStateHistory.setAlarmId((String) row.get(colNames[2]));
alarmStateHistory.setNewState(AlarmState.valueOf((String) row.get(colNames[3])));
alarmStateHistory.setOldState(AlarmState.valueOf((String) row.get(colNames[4])));
alarmStateHistory.setReason((String) row.get(colNames[5]));
alarmStateHistory.setReasonData((String) row.get(colNames[6]));
try {
alarmStateHistory.setMetrics((List<MetricDefinition>) OBJECT_MAPPER.readValue(
(String) row.get(colNames[3]), METRICS_TYPE));
} catch (Exception ignore) {
alarmStateHistory.setMetrics(Collections.<MetricDefinition>emptyList());
}
alarmStateHistory.setNewState(AlarmState.valueOf((String) row.get(colNames[4])));
alarmStateHistory.setOldState(AlarmState.valueOf((String) row.get(colNames[5])));
alarmStateHistory.setReason((String) row.get(colNames[6]));
alarmStateHistory.setReasonData((String) row.get(colNames[7]));
alarmStateHistoryList.add(alarmStateHistory);
}

View File

@@ -62,7 +62,7 @@ public class MetricDefinitionInfluxDbRepositoryImpl implements MetricDefinitionR
private List<MetricDefinition> buildMetricDefList(List<Serie> result) throws Exception {
List<MetricDefinition> metricDefinitionList = new ArrayList<>();
for (Serie serie : result) {
for (Map point : serie.getRows()) {
for (Map<String,Object> point : serie.getRows()) {
Utils.SerieNameDecoder serieNameDecoder;
try {

View File

@@ -176,7 +176,7 @@ final class Utils {
}
// It's possible to have no dimensions.
this.dimensions = new HashMap();
this.dimensions = new HashMap<>();
while (rest != null) {
final String nameValPair;
if (rest.contains("&")) {
@@ -216,11 +216,11 @@ final class Utils {
}
static class SerieNameDecodeException extends Exception {
private static final long serialVersionUID = 1L;
public SerieNameDecodeException(String s) {
super(s);
}
}
/**

View File

@@ -0,0 +1,315 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.hpcloud.mon.infrastructure.persistence.mysql;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.Query;
import org.skife.jdbi.v2.util.StringMapper;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.hpcloud.mon.common.model.alarm.AggregateFunction;
import com.hpcloud.mon.common.model.alarm.AlarmOperator;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.common.model.alarm.AlarmSubExpression;
import com.hpcloud.mon.common.model.metric.MetricDefinition;
import com.hpcloud.mon.domain.exception.EntityNotFoundException;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinition;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinitionRepository;
import com.hpcloud.mon.infrastructure.persistence.DimensionQueries;
import com.hpcloud.mon.infrastructure.persistence.SubAlarmQueries;
import com.hpcloud.persistence.BeanMapper;
/**
* Alarm repository implementation.
*/
public class AlarmDefinitionMySqlRepositoryImpl implements AlarmDefinitionRepository {
private static final Joiner COMMA_JOINER = Joiner.on(',');
private static final String SUB_ALARM_SQL =
"select sa.*, sad.dimensions from sub_alarm_definition as sa "
+ "left join (select sub_alarm_definition_id, group_concat(dimension_name, '=', value) as dimensions from sub_alarm_definition_dimension group by sub_alarm_definition_id ) as sad "
+ "on sad.sub_alarm_definition_id = sa.id where sa.alarm_definition_id = :alarmDefId";
private final DBI db;
@Inject
public AlarmDefinitionMySqlRepositoryImpl(@Named("mysql") DBI db) {
this.db = db;
}
@Override
public AlarmDefinition create(String tenantId, String id, String name, String description,
String severity, String expression, Map<String, AlarmSubExpression> subExpressions,
List<String> matchBy, List<String> alarmActions, List<String> okActions,
List<String> undeterminedActions) {
Handle h = db.open();
try {
h.begin();
h.insert(
"insert into alarm_definition (id, tenant_id, name, description, severity, expression, match_by, actions_enabled, created_at, updated_at, deleted_at) values (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW(), NULL)",
id, tenantId, name, description, severity, expression,
matchBy == null || Iterables.isEmpty(matchBy) ? null : COMMA_JOINER.join(matchBy), true);
// Persist sub-alarms
createSubExpressions(h, id, subExpressions);
// Persist actions
persistActions(h, id, AlarmState.ALARM, alarmActions);
persistActions(h, id, AlarmState.OK, okActions);
persistActions(h, id, AlarmState.UNDETERMINED, undeterminedActions);
h.commit();
return new AlarmDefinition(id, name, description, severity, expression, matchBy, true,
alarmActions, okActions == null ? Collections.<String>emptyList() : okActions,
undeterminedActions == null ? Collections.<String>emptyList() : undeterminedActions);
} catch (RuntimeException e) {
h.rollback();
throw e;
} finally {
h.close();
}
}
@Override
public void deleteById(String tenantId, String alarmDefId) {
try (Handle h = db.open()) {
if (h
.update(
"update alarm_definition set deleted_at = NOW() where tenant_id = ? and id = ? and deleted_at is NULL",
tenantId, alarmDefId) == 0)
throw new EntityNotFoundException("No alarm definition exists for %s", alarmDefId);
// Cascade soft delete to alarms
h.execute("delete from alarm where alarm_definition_id = :id", alarmDefId);
}
}
@Override
public boolean exists(String tenantId, String name) {
try (Handle h = db.open()) {
return h
.createQuery(
"select exists(select 1 from alarm_definition where tenant_id = :tenantId and name = :name and deleted_at is NULL)")
.bind("tenantId", tenantId).bind("name", name).mapTo(Boolean.TYPE).first();
}
}
@Override
@SuppressWarnings("unchecked")
public List<AlarmDefinition> find(String tenantId, String name, Map<String, String> dimensions) {
try (Handle h = db.open()) {
String query =
"select distinct ad.id, ad.description, ad.tenant_id, ad.severity, ad.expression, ad.match_by, ad.name, ad.actions_enabled, ad.created_at, ad.updated_at, ad.deleted_at "
+ "from alarm_definition ad join sub_alarm_definition sub on ad.id = sub.alarm_definition_id "
+ "left outer join sub_alarm_definition_dimension dim on sub.id = dim.sub_alarm_definition_id%s "
+ "where tenant_id = :tenantId and deleted_at is NULL %s order by ad.created_at";
StringBuilder sbWhere = new StringBuilder();
if (name != null) {
sbWhere.append(" and ad.name = :name");
}
String sql = String.format(query, SubAlarmQueries.buildJoinClauseFor(dimensions), sbWhere);
Query<?> q = h.createQuery(sql).bind("tenantId", tenantId);
if (name != null) {
q.bind("name", name);
}
q = q.map(new BeanMapper<AlarmDefinition>(AlarmDefinition.class));
DimensionQueries.bindDimensionsToQuery(q, dimensions);
List<AlarmDefinition> alarms = (List<AlarmDefinition>) q.list();
for (AlarmDefinition alarm : alarms)
hydrateRelationships(h, alarm);
return alarms;
}
}
@Override
public AlarmDefinition findById(String tenantId, String alarmDefId) {
try (Handle h = db.open()) {
AlarmDefinition alarm =
h.createQuery(
"select * from alarm_definition where tenant_id = :tenantId and id = :id and deleted_at is NULL")
.bind("tenantId", tenantId).bind("id", alarmDefId)
.map(new BeanMapper<AlarmDefinition>(AlarmDefinition.class)).first();
if (alarm == null)
throw new EntityNotFoundException("No alarm definition exists for %s", alarmDefId);
hydrateRelationships(h, alarm);
return alarm;
}
}
@Override
public Map<String, MetricDefinition> findSubAlarmMetricDefinitions(String alarmDefId) {
try (Handle h = db.open()) {
List<Map<String, Object>> rows =
h.createQuery(SUB_ALARM_SQL).bind("alarmDefId", alarmDefId).list();
Map<String, MetricDefinition> subAlarmMetricDefs = new HashMap<>();
for (Map<String, Object> row : rows) {
String id = (String) row.get("id");
String metricName = (String) row.get("metric_name");
Map<String, String> dimensions =
DimensionQueries.dimensionsFor((String) row.get("dimensions"));
subAlarmMetricDefs.put(id, new MetricDefinition(metricName, dimensions));
}
return subAlarmMetricDefs;
}
}
@Override
public Map<String, AlarmSubExpression> findSubExpressions(String alarmDefId) {
try (Handle h = db.open()) {
List<Map<String, Object>> rows =
h.createQuery(SUB_ALARM_SQL).bind("alarmDefId", alarmDefId).list();
Map<String, AlarmSubExpression> subExpressions = new HashMap<>();
for (Map<String, Object> row : rows) {
String id = (String) row.get("id");
AggregateFunction function = AggregateFunction.fromJson((String) row.get("function"));
String metricName = (String) row.get("metric_name");
AlarmOperator operator = AlarmOperator.fromJson((String) row.get("operator"));
Double threshold = (Double) row.get("threshold");
Integer period = (Integer) row.get("period");
Integer periods = (Integer) row.get("periods");
Map<String, String> dimensions =
DimensionQueries.dimensionsFor((String) row.get("dimensions"));
subExpressions.put(id, new AlarmSubExpression(function, new MetricDefinition(metricName,
dimensions), operator, threshold, period, periods));
}
return subExpressions;
}
}
@Override
public void update(String tenantId, String id, boolean patch, String name, String description,
String expression, List<String> matchBy, String severity, boolean actionsEnabled,
Collection<String> oldSubAlarmIds, Map<String, AlarmSubExpression> changedSubAlarms,
Map<String, AlarmSubExpression> newSubAlarms, List<String> alarmActions,
List<String> okActions, List<String> undeterminedActions) {
Handle h = db.open();
try {
h.begin();
h.insert(
"update alarm_definition set name = ?, description = ?, expression = ?, match_by = ?, severity = ?, actions_enabled = ?, updated_at = NOW() where tenant_id = ? and id = ?",
name, description, expression, matchBy == null || Iterables.isEmpty(matchBy) ? null
: COMMA_JOINER.join(matchBy), severity, actionsEnabled, tenantId, id);
// Delete old sub-alarms
if (oldSubAlarmIds != null)
for (String oldSubAlarmId : oldSubAlarmIds)
h.execute("delete from sub_alarm_definition where id = ?", oldSubAlarmId);
// Update changed sub-alarms
if (changedSubAlarms != null)
for (Map.Entry<String, AlarmSubExpression> entry : changedSubAlarms.entrySet()) {
AlarmSubExpression sa = entry.getValue();
h.execute(
"update sub_alarm_definition set operator = ?, threshold = ?, updated_at = NOW() where id = ?",
sa.getOperator().name(), sa.getThreshold(), entry.getKey());
}
// Insert new sub-alarms
createSubExpressions(h, id, newSubAlarms);
// Delete old actions
if (patch) {
deleteActions(h, id, AlarmState.ALARM, alarmActions);
deleteActions(h, id, AlarmState.OK, okActions);
deleteActions(h, id, AlarmState.UNDETERMINED, undeterminedActions);
} else
h.execute("delete from alarm_action where alarm_id = ?", id);
// Insert new actions
persistActions(h, id, AlarmState.ALARM, alarmActions);
persistActions(h, id, AlarmState.OK, okActions);
persistActions(h, id, AlarmState.UNDETERMINED, undeterminedActions);
h.commit();
} catch (RuntimeException e) {
h.rollback();
throw e;
} finally {
h.close();
}
}
private void deleteActions(Handle handle, String id, AlarmState alarmState, List<String> actions) {
if (actions != null)
handle.execute("delete from alarm_action where alarm_id = ? and alarm_state = ?", id,
alarmState.name());
}
private List<String> findActionsById(Handle handle, String alarmDefId, AlarmState state) {
return handle
.createQuery(
"select action_id from alarm_action where alarm_id = :alarmDefId and alarm_state = :alarmState")
.bind("alarmDefId", alarmDefId).bind("alarmState", state.name()).map(StringMapper.FIRST)
.list();
}
private void persistActions(Handle handle, String id, AlarmState alarmState, List<String> actions) {
if (actions != null)
for (String action : actions)
handle.insert("insert into alarm_action values (?, ?, ?)", id, alarmState.name(), action);
}
private void hydrateRelationships(Handle handle, AlarmDefinition alarm) {
alarm.setAlarmActions(findActionsById(handle, alarm.getId(), AlarmState.ALARM));
alarm.setOkActions(findActionsById(handle, alarm.getId(), AlarmState.OK));
alarm.setUndeterminedActions(findActionsById(handle, alarm.getId(), AlarmState.UNDETERMINED));
}
private void createSubExpressions(Handle handle, String id,
Map<String, AlarmSubExpression> alarmSubExpressions) {
if (alarmSubExpressions != null) {
for (Map.Entry<String, AlarmSubExpression> subEntry : alarmSubExpressions.entrySet()) {
String subAlarmId = subEntry.getKey();
AlarmSubExpression subExpr = subEntry.getValue();
MetricDefinition metricDef = subExpr.getMetricDefinition();
// Persist sub-alarm
handle
.insert(
"insert into sub_alarm_definition (id, alarm_definition_id, function, metric_name, operator, threshold, period, periods, created_at, updated_at) "
+ "values (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())", subAlarmId, id, subExpr
.getFunction().name(), metricDef.name, subExpr.getOperator().name(), subExpr
.getThreshold(), subExpr.getPeriod(), subExpr.getPeriods());
// Persist sub-alarm dimensions
if (metricDef.dimensions != null && !metricDef.dimensions.isEmpty())
for (Map.Entry<String, String> dimEntry : metricDef.dimensions.entrySet())
handle.insert("insert into sub_alarm_definition_dimension values (?, ?, ?)", subAlarmId,
dimEntry.getKey(), dimEntry.getValue());
}
}
}
}

View File

@@ -13,9 +13,7 @@
*/
package com.hpcloud.mon.infrastructure.persistence.mysql;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -25,228 +23,150 @@ import javax.inject.Named;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.Query;
import org.skife.jdbi.v2.util.StringMapper;
import com.hpcloud.mon.common.model.alarm.AggregateFunction;
import com.hpcloud.mon.common.model.alarm.AlarmOperator;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.common.model.alarm.AlarmSubExpression;
import com.hpcloud.mon.common.model.metric.MetricDefinition;
import com.hpcloud.mon.domain.exception.EntityNotFoundException;
import com.hpcloud.mon.domain.model.alarm.Alarm;
import com.hpcloud.mon.domain.model.alarm.AlarmRepository;
import com.hpcloud.mon.infrastructure.persistence.DimensionQueries;
import com.hpcloud.mon.infrastructure.persistence.SubAlarmQueries;
import com.hpcloud.persistence.BeanMapper;
import com.hpcloud.persistence.SqlQueries;
/**
* Alarm repository implementation.
* Alarmed metric repository implementation.
*/
public class AlarmMySqlRepositoryImpl implements AlarmRepository {
private static final String SUB_ALARM_SQL =
"select sa.*, sad.dimensions from sub_alarm as sa "
+ "left join (select sub_alarm_id, group_concat(dimension_name, '=', value) as dimensions from sub_alarm_dimension group by sub_alarm_id ) as sad "
+ "on sad.sub_alarm_id = sa.id where sa.alarm_id = :alarmId";
private final DBI db;
private static final String METRIC_DEFS_FOR_ALARM_SQL =
"select md.name, mdg.dimensions from metric_definition as md "
+ "inner join metric_definition_dimensions as mdd on mdd.metric_definition_id = md.id "
+ "inner join alarm_metric as am on am.metric_definition_dimensions_id = mdd.id "
+ "left join (select dimension_set_id, group_concat(name, '=', value) as dimensions from metric_dimension group by dimension_set_id) as mdg on mdg.dimension_set_id = mdd.metric_dimension_set_id "
+ "where am.alarm_id = :alarmId";
private static final String ALARM_SQL =
"select distinct a.id, a.alarm_definition_id, a.state from alarm a "
+ "inner join alarm_metric am on am.alarm_id = a.id "
+ "inner join metric_definition_dimensions mdd on mdd.id = am.metric_definition_dimensions_id "
+ "inner join metric_definition md on md.id = mdd.metric_definition_id%s "
+ "inner join alarm_definition ad on ad.id = a.alarm_definition_id "
+ "where ad.tenant_id = :tenantId%s order by a.created_at";
@Inject
public AlarmMySqlRepositoryImpl(@Named("mysql") DBI db) {
this.db = db;
}
static String buildJoinClauseFor(Map<String, String> dimensions) {
StringBuilder sbJoin = null;
if (dimensions != null) {
sbJoin = new StringBuilder();
for (int i = 0; i < dimensions.size(); i++) {
sbJoin.append(" inner join metric_dimension d").append(i).append(" on d").append(i)
.append(".name = :dname").append(i).append(" and d").append(i)
.append(".value = :dvalue").append(i).append(" and mdd.metric_dimension_set_id = d")
.append(i).append(".dimension_set_id");
}
}
return sbJoin == null ? "" : sbJoin.toString();
}
static Map<String, String> dimensionsFor(Handle handle, byte[] dimensionSetId) {
return SqlQueries.keyValuesFor(handle, "select name, value from metric_dimension " + "where"
+ " dimension_set_id = ?", dimensionSetId);
}
private static List<MetricDefinition> findMetrics(Handle handle, String alarmId) {
List<MetricDefinition> metricDefs = new ArrayList<>();
for (Map<String, Object> row : handle.createQuery(METRIC_DEFS_FOR_ALARM_SQL)
.bind("alarmId", alarmId).list()) {
String metName = (String) row.get("name");
Map<String, String> dimensions =
DimensionQueries.dimensionsFor((String) row.get("dimensions"));
metricDefs.add(new MetricDefinition(metName, dimensions));
}
return metricDefs;
}
@Override
public Alarm create(String tenantId, String id, String name, String description, String severity,
String expression, Map<String, AlarmSubExpression> subExpressions, List<String> alarmActions,
List<String> okActions, List<String> undeterminedActions) {
Handle h = db.open();
try {
h.begin();
h.insert(
"insert into alarm (id, tenant_id, name, description, severity, expression, state, actions_enabled, created_at, updated_at, deleted_at) values (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW(), NULL)",
id, tenantId, name, description, severity, expression,
AlarmState.UNDETERMINED.toString(), true);
// Persist sub-alarms
createSubExpressions(h, id, subExpressions);
// Persist actions
persistActions(h, id, AlarmState.ALARM, alarmActions);
persistActions(h, id, AlarmState.OK, okActions);
persistActions(h, id, AlarmState.UNDETERMINED, undeterminedActions);
h.commit();
return new Alarm(id, name, description, severity, expression, AlarmState.UNDETERMINED, true,
alarmActions, okActions == null ? Collections.<String>emptyList() : okActions,
undeterminedActions == null ? Collections.<String>emptyList() : undeterminedActions);
} catch (RuntimeException e) {
h.rollback();
throw e;
} finally {
h.close();
public void deleteById(String id) {
try (Handle h = db.open()) {
h.execute("delete from alarm where id = :id", id);
}
}
@Override
public void deleteById(String tenantId, String alarmId) {
public List<Alarm> find(String tenantId, String alarmDefId, String metricName,
Map<String, String> metricDimensions, AlarmState state) {
try (Handle h = db.open()) {
if (h
.update(
"update alarm set deleted_at = NOW() where tenant_id = ? and id = ? and deleted_at is NULL",
tenantId, alarmId) == 0)
throw new EntityNotFoundException("No alarm exists for %s", alarmId);
}
}
@Override
public boolean exists(String tenantId, String name) {
try (Handle h = db.open()) {
return h
.createQuery(
"select exists(select 1 from alarm where tenant_id = :tenantId and name = :name and deleted_at is NULL)")
.bind("tenantId", tenantId).bind("name", name).mapTo(Boolean.TYPE).first();
}
}
@Override
@SuppressWarnings("unchecked")
public List<Alarm> find(String tenantId, String name, Map<String, String> dimensions, String state) {
try (Handle h = db.open()) {
String query =
"select distinct alarm.id, alarm.description, alarm.tenant_id, alarm.severity, alarm.expression, alarm.state, alarm.name, alarm.actions_enabled, alarm.created_at, alarm.updated_at, alarm.deleted_at "
+ "from alarm join sub_alarm sub on alarm.id = sub.alarm_id "
+ "left outer join sub_alarm_dimension dim on sub.id = dim.sub_alarm_id%s "
+ "where tenant_id = :tenantId and deleted_at is NULL %s";
StringBuilder sbWhere = new StringBuilder();
if (name != null) {
sbWhere.append(" and alarm.name = :name");
if (alarmDefId != null) {
sbWhere.append(" and ad.id = :alarmDefId");
}
if (metricName != null) {
sbWhere.append(" and md.name = :metricName");
}
if (state != null) {
sbWhere.append(" and alarm.state = :state");
sbWhere.append(" and a.state = :state");
}
String sql = String.format(query, SubAlarmQueries.buildJoinClauseFor(dimensions), sbWhere);
String sql = String.format(ALARM_SQL, buildJoinClauseFor(metricDimensions), sbWhere);
Query<?> q = h.createQuery(sql).bind("tenantId", tenantId);
if (name != null) {
q.bind("name", name);
if (alarmDefId != null) {
q.bind("alarmDefId", alarmDefId);
}
if (metricName != null) {
q.bind("metricName", metricName);
}
if (state != null) {
q.bind("state", state);
q.bind("state", state.name());
}
q = q.map(new BeanMapper<Alarm>(Alarm.class));
DimensionQueries.bindDimensionsToQuery(q, dimensions);
DimensionQueries.bindDimensionsToQuery(q, metricDimensions);
@SuppressWarnings("unchecked")
List<Alarm> alarms = (List<Alarm>) q.list();
for (Alarm alarm : alarms)
hydrateRelationships(h, alarm);
for (Alarm alarm : alarms) {
alarm.setMetrics(findMetrics(h, alarm.getId()));
}
return alarms;
}
}
@Override
public Alarm findById(String tenantId, String alarmId) {
public Alarm findById(String alarmId) {
try (Handle h = db.open()) {
Alarm alarm =
h.createQuery(
"select * from alarm where tenant_id = :tenantId and id = :id and deleted_at is NULL")
.bind("tenantId", tenantId).bind("id", alarmId)
h.createQuery("select * from alarm where id = :id").bind("id", alarmId)
.map(new BeanMapper<Alarm>(Alarm.class)).first();
if (alarm == null)
throw new EntityNotFoundException("No alarm exists for %s", alarmId);
hydrateRelationships(h, alarm);
// Hydrate metrics
alarm.setMetrics(findMetrics(h, alarm.getId()));
return alarm;
}
}
@Override
public Map<String, MetricDefinition> findSubAlarmMetricDefinitions(String alarmId) {
public List<MetricDefinition> findMetrics(String alarmId) {
try (Handle h = db.open()) {
List<Map<String, Object>> rows = h.createQuery(SUB_ALARM_SQL).bind("alarmId", alarmId).list();
Map<String, MetricDefinition> subAlarmMetricDefs = new HashMap<>();
for (Map<String, Object> row : rows) {
String id = (String) row.get("id");
String metricName = (String) row.get("metric_name");
Map<String, String> dimensions = dimensionsFor((String) row.get("dimensions"));
subAlarmMetricDefs.put(id, new MetricDefinition(metricName, dimensions));
}
return subAlarmMetricDefs;
return findMetrics(h, alarmId);
}
}
@Override
public Map<String, AlarmSubExpression> findSubExpressions(String alarmId) {
try (Handle h = db.open()) {
List<Map<String, Object>> rows = h.createQuery(SUB_ALARM_SQL).bind("alarmId", alarmId).list();
Map<String, AlarmSubExpression> subExpressions = new HashMap<>();
for (Map<String, Object> row : rows) {
String id = (String) row.get("id");
AggregateFunction function = AggregateFunction.fromJson((String) row.get("function"));
String metricName = (String) row.get("metric_name");
AlarmOperator operator = AlarmOperator.fromJson((String) row.get("operator"));
Double threshold = (Double) row.get("threshold");
Integer period = (Integer) row.get("period");
Integer periods = (Integer) row.get("periods");
Map<String, String> dimensions = dimensionsFor((String) row.get("dimensions"));
subExpressions.put(id, new AlarmSubExpression(function, new MetricDefinition(metricName,
dimensions), operator, threshold, period, periods));
}
return subExpressions;
}
}
@Override
public void update(String tenantId, String id, boolean patch, String name, String description,
String expression, String severity, AlarmState state, boolean actionsEnabled,
Collection<String> oldSubAlarmIds, Map<String, AlarmSubExpression> changedSubAlarms,
Map<String, AlarmSubExpression> newSubAlarms, List<String> alarmActions,
List<String> okActions, List<String> undeterminedActions) {
public void update(String tenantId, String id, AlarmState state) {
Handle h = db.open();
try {
h.begin();
h.insert(
"update alarm set name = ?, description = ?, expression = ?, severity = ?, state = ?, actions_enabled = ?, updated_at = NOW() where tenant_id = ? and id = ?",
name, description, expression, severity, state.name(), actionsEnabled, tenantId, id);
// Delete old sub-alarms
if (oldSubAlarmIds != null)
for (String oldSubAlarmId : oldSubAlarmIds)
h.execute("delete from sub_alarm where id = ?", oldSubAlarmId);
// Update changed sub-alarms
if (changedSubAlarms != null)
for (Map.Entry<String, AlarmSubExpression> entry : changedSubAlarms.entrySet()) {
AlarmSubExpression sa = entry.getValue();
h.execute(
"update sub_alarm set operator = ?, threshold = ?, updated_at = NOW() where id = ?",
sa.getOperator().name(), sa.getThreshold(), entry.getKey());
}
// Insert new sub-alarms
createSubExpressions(h, id, newSubAlarms);
// Delete old actions
if (patch) {
deleteActions(h, id, AlarmState.ALARM, alarmActions);
deleteActions(h, id, AlarmState.OK, okActions);
deleteActions(h, id, AlarmState.UNDETERMINED, undeterminedActions);
} else
h.execute("delete from alarm_action where alarm_id = ?", id);
// Insert new actions
persistActions(h, id, AlarmState.ALARM, alarmActions);
persistActions(h, id, AlarmState.OK, okActions);
persistActions(h, id, AlarmState.UNDETERMINED, undeterminedActions);
h.insert("update alarm set state = ?, updated_at = NOW() where id = ?", state.name(), id);
h.commit();
} catch (RuntimeException e) {
h.rollback();
@@ -255,69 +175,4 @@ public class AlarmMySqlRepositoryImpl implements AlarmRepository {
h.close();
}
}
private void deleteActions(Handle handle, String id, AlarmState alarmState, List<String> actions) {
if (actions != null)
handle.execute("delete from alarm_action where alarm_id = ? and alarm_state = ?", id,
alarmState.name());
}
private Map<String, String> dimensionsFor(String dimensionSet) {
Map<String, String> dimensions = null;
if (dimensionSet != null) {
dimensions = new HashMap<String, String>();
for (String kvStr : dimensionSet.split(",")) {
String[] kv = kvStr.split("=");
if (kv.length > 1)
dimensions.put(kv[0], kv[1]);
}
}
return dimensions;
}
private List<String> findActionsById(Handle handle, String alarmId, AlarmState state) {
return handle
.createQuery(
"select action_id from alarm_action where alarm_id = :alarmId and alarm_state = :alarmState")
.bind("alarmId", alarmId).bind("alarmState", state.name()).map(StringMapper.FIRST).list();
}
private void persistActions(Handle handle, String id, AlarmState alarmState, List<String> actions) {
if (actions != null)
for (String action : actions)
handle.insert("insert into alarm_action values (?, ?, ?)", id, alarmState.name(), action);
}
private void hydrateRelationships(Handle handle, Alarm alarm) {
alarm.setAlarmActions(findActionsById(handle, alarm.getId(), AlarmState.ALARM));
alarm.setOkActions(findActionsById(handle, alarm.getId(), AlarmState.OK));
alarm.setUndeterminedActions(findActionsById(handle, alarm.getId(), AlarmState.UNDETERMINED));
}
private void createSubExpressions(Handle handle, String id,
Map<String, AlarmSubExpression> alarmSubExpressions) {
if (alarmSubExpressions != null)
for (Map.Entry<String, AlarmSubExpression> subEntry : alarmSubExpressions.entrySet()) {
String subAlarmId = subEntry.getKey();
AlarmSubExpression subExpr = subEntry.getValue();
MetricDefinition metricDef = subExpr.getMetricDefinition();
// Persist sub-alarm
handle
.insert(
"insert into sub_alarm (id, alarm_id, function, metric_name, operator, threshold, period, periods, state, created_at, updated_at) "
+ "values (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())", subAlarmId, id, subExpr
.getFunction().name(), metricDef.name, subExpr.getOperator().name(), subExpr
.getThreshold(), subExpr.getPeriod(), subExpr.getPeriods(),
AlarmState.UNDETERMINED.toString());
// Persist sub-alarm dimensions
if (metricDef.dimensions != null && !metricDef.dimensions.isEmpty())
for (Map.Entry<String, String> dimEntry : metricDef.dimensions.entrySet())
handle.insert("insert into sub_alarm_dimension values (?, ?, ?)", subAlarmId,
dimEntry.getKey(), dimEntry.getValue());
}
}
}

View File

@@ -43,10 +43,11 @@ import com.hpcloud.persistence.BeanMapper;
public class AlarmStateHistoryVerticaRepositoryImpl implements AlarmStateHistoryRepository {
public static final DateTimeFormatter DATETIME_FORMATTER = ISODateTimeFormat.dateTimeNoMillis()
.withZoneUTC();
private static final String FIND_ALARMS_SQL = "select distinct a.id from alarm as a "
+ "join sub_alarm sa on a.id = sa.alarm_id "
+ "left outer join sub_alarm_dimension dim on sa.id = dim.sub_alarm_id%s "
+ "where a.tenant_id = :tenantId and a.deleted_at is NULL";
private static final String FIND_ALARMS_SQL =
"select distinct ad.id from alarm_definition as ad "
+ "join sub_alarm_definition sad on ad.id = sad.alarm_definition_id "
+ "left outer join sub_alarm_definition_dimension dim on sad.id = dim.sub_alarm_definition_id%s "
+ "where ad.tenant_id = :tenantId and ad.deleted_at is NULL";
private static final String FIND_BY_ALARM_DEF_SQL =
"select *, time_stamp as timestamp from MonAlarms.StateHistory "
+ "where tenant_id = :tenantId%s order by time_stamp desc";

View File

@@ -15,7 +15,6 @@ package com.hpcloud.mon.infrastructure.servlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

View File

@@ -0,0 +1,174 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.hpcloud.mon.resource;
import java.net.URI;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.hibernate.validator.constraints.NotEmpty;
import com.codahale.metrics.annotation.Timed;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.google.common.base.Strings;
import com.hpcloud.mon.app.AlarmDefinitionService;
import com.hpcloud.mon.app.command.CreateAlarmDefinitionCommand;
import com.hpcloud.mon.app.command.UpdateAlarmDefinitionCommand;
import com.hpcloud.mon.app.validation.AlarmValidation;
import com.hpcloud.mon.app.validation.Validation;
import com.hpcloud.mon.common.model.alarm.AlarmExpression;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinition;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinitionRepository;
import com.hpcloud.mon.resource.annotation.PATCH;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
import com.wordnik.swagger.annotations.ApiResponse;
import com.wordnik.swagger.annotations.ApiResponses;
/**
* Alarm definition resource implementation.
*/
@Path("/v2.0/alarm-definitions")
@Api(value = "/v2.0/alarm-definitions",
description = "Operations for working with alarm definitions")
public class AlarmDefinitionResource {
private final AlarmDefinitionService service;
private final AlarmDefinitionRepository repo;
@Inject
public AlarmDefinitionResource(AlarmDefinitionService service, AlarmDefinitionRepository repo) {
this.service = service;
this.repo = repo;
}
@POST
@Timed
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Create alarm definition", response = AlarmDefinition.class)
public Response create(@Context UriInfo uriInfo, @HeaderParam("X-Tenant-Id") String tenantId,
@Valid CreateAlarmDefinitionCommand command) {
command.validate();
AlarmExpression alarmExpression = AlarmValidation.validateNormalizeAndGet(command.expression);
AlarmDefinition alarm =
Links.hydrate(service.create(tenantId, command.name, command.description, command.severity,
command.expression, alarmExpression, command.matchBy, command.alarmActions,
command.okActions, command.undeterminedActions), uriInfo, false);
return Response.created(URI.create(alarm.getId())).entity(alarm).build();
}
@GET
@Timed
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "List alarm definitions", response = AlarmDefinition.class,
responseContainer = "List")
public List<AlarmDefinition> list(@Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId, @QueryParam("name") String name,
@QueryParam("dimensions") String dimensionsStr) {
Map<String, String> dimensions =
Strings.isNullOrEmpty(dimensionsStr) ? null : Validation
.parseAndValidateDimensions(dimensionsStr);
return Links.hydrate(repo.find(tenantId, name, dimensions), uriInfo);
}
@GET
@Timed
@Path("/{alarm_definition_id}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get alarm definition", response = AlarmDefinition.class)
@ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid ID supplied"),
@ApiResponse(code = 404, message = "Alarm definition not found")})
public AlarmDefinition get(
@ApiParam(value = "ID of alarm definition to fetch", required = true) @Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId,
@PathParam("alarm_definition_id") String alarmDefinitionId) {
return Links.hydrate(repo.findById(tenantId, alarmDefinitionId), uriInfo, true);
}
@PUT
@Timed
@Path("/{alarm_definition_id}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Update alarm definition", response = AlarmDefinition.class)
public AlarmDefinition update(@Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId,
@PathParam("alarm_definition_id") String alarmDefinitionId,
@Valid UpdateAlarmDefinitionCommand command) {
command.validate();
AlarmExpression alarmExpression = AlarmValidation.validateNormalizeAndGet(command.expression);
return Links.hydrate(service.update(tenantId, alarmDefinitionId, alarmExpression, command),
uriInfo, true);
}
@PATCH
@Timed
@Path("/{alarm_definition_id}")
@Consumes("application/json-patch+json")
@Produces(MediaType.APPLICATION_JSON)
@SuppressWarnings("unchecked")
public AlarmDefinition patch(@Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId,
@PathParam("alarm_definition_id") String alarmDefinitionId,
@NotEmpty Map<String, Object> fields) throws JsonMappingException {
String name = (String) fields.get("name");
String description = (String) fields.get("description");
String severity = (String) fields.get("severity");
String expression = (String) fields.get("expression");
List<String> matchBy = (List<String>) fields.get("match_by");
String stateStr = (String) fields.get("state");
AlarmState state =
stateStr == null ? null : Validation.parseAndValidate(AlarmState.class, stateStr);
Boolean enabled = (Boolean) fields.get("actions_enabled");
List<String> alarmActions = (List<String>) fields.get("alarm_actions");
List<String> okActions = (List<String>) fields.get("ok_actions");
List<String> undeterminedActions = (List<String>) fields.get("undetermined_actions");
AlarmValidation.validate(name, description, severity, alarmActions, okActions,
undeterminedActions);
AlarmExpression alarmExpression =
expression == null ? null : AlarmValidation.validateNormalizeAndGet(expression);
return Links.hydrate(service.patch(tenantId, alarmDefinitionId, name, description, severity,
expression, alarmExpression, matchBy, state, enabled, alarmActions, okActions,
undeterminedActions), uriInfo, true);
}
@DELETE
@Timed
@Path("/{alarm_definition_id}")
@ApiOperation(value = "Delete alarm definition")
public void delete(@HeaderParam("X-Tenant-Id") String tenantId,
@PathParam("alarm_definition_id") String alarmDefinitionId) {
service.delete(tenantId, alarmDefinitionId);
}
}

View File

@@ -13,7 +13,6 @@
*/
package com.hpcloud.mon.resource;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -24,7 +23,6 @@ import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@@ -32,7 +30,6 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.hibernate.validator.constraints.NotEmpty;
@@ -42,14 +39,12 @@ import com.codahale.metrics.annotation.Timed;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.google.common.base.Strings;
import com.hpcloud.mon.app.AlarmService;
import com.hpcloud.mon.app.command.CreateAlarmCommand;
import com.hpcloud.mon.app.command.UpdateAlarmCommand;
import com.hpcloud.mon.app.validation.AlarmValidation;
import com.hpcloud.mon.app.validation.Validation;
import com.hpcloud.mon.common.model.alarm.AlarmExpression;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.domain.model.alarm.Alarm;
import com.hpcloud.mon.domain.model.alarm.AlarmRepository;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinition;
import com.hpcloud.mon.domain.model.alarmstatehistory.AlarmStateHistory;
import com.hpcloud.mon.domain.model.alarmstatehistory.AlarmStateHistoryRepository;
import com.hpcloud.mon.resource.annotation.PATCH;
@@ -63,7 +58,7 @@ import com.wordnik.swagger.annotations.ApiResponses;
* Alarm resource implementation.
*/
@Path("/v2.0/alarms")
@Api(value = "/v2.0/alarms", description = "Operations for working with alarms")
@Api(value = "/v2.0/alarms", description = "Operations for accessing alarms")
public class AlarmResource {
private final AlarmService service;
private final AlarmRepository repo;
@@ -77,20 +72,38 @@ public class AlarmResource {
this.stateHistoryRepo = stateHistoryRepo;
}
@POST
@DELETE
@Timed
@Consumes(MediaType.APPLICATION_JSON)
@Path("/{alarm_id}")
@ApiOperation(value = "Delete alarm")
public void delete(@HeaderParam("X-Tenant-Id") String tenantId,
@PathParam("alarm_id") String alarmId) {
service.delete(tenantId, alarmId);
}
@GET
@Timed
@Path("/{alarm_id}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Create alarm", response = Alarm.class)
public Response create(@Context UriInfo uriInfo, @HeaderParam("X-Tenant-Id") String tenantId,
@Valid CreateAlarmCommand command) {
command.validate();
AlarmExpression alarmExpression = AlarmValidation.validateNormalizeAndGet(command.expression);
Alarm alarm =
Links.hydrate(service.create(tenantId, command.name, command.description, command.severity,
command.expression, alarmExpression, command.alarmActions, command.okActions,
command.undeterminedActions), uriInfo, false, "history");
return Response.created(URI.create(alarm.getId())).entity(alarm).build();
@ApiOperation(value = "Get alarm", response = Alarm.class)
@ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid ID supplied"),
@ApiResponse(code = 404, message = "Alarm not found")})
public Alarm get(
@ApiParam(value = "ID of alarm to fetch", required = true) @Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId, @PathParam("alarm_id") String alarm_id) {
return Links.hydrate(repo.findById(alarm_id), uriInfo, true);
}
@GET
@Timed
@Path("/{alarm_id}/state-history")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get alarm state history", response = AlarmStateHistory.class,
responseContainer = "List")
public List<AlarmStateHistory> getStateHistory(@Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId, @PathParam("alarm_id") String alarmId)
throws Exception {
return stateHistoryRepo.findById(tenantId, alarmId);
}
@GET
@@ -98,23 +111,22 @@ public class AlarmResource {
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "List alarms", response = Alarm.class, responseContainer = "List")
public List<Alarm> list(@Context UriInfo uriInfo, @HeaderParam("X-Tenant-Id") String tenantId,
@QueryParam("name") String name, @QueryParam("dimensions") String dimensionsStr,
@QueryParam("state") String state) {
Map<String, String> dimensions =
Strings.isNullOrEmpty(dimensionsStr) ? null : Validation
.parseAndValidateDimensions(dimensionsStr);
if (state != null) {
Validation.validateAlarmState(state);
}
return Links.hydrate(repo.find(tenantId, name, dimensions, state), uriInfo, "history");
@QueryParam("alarm_definition_id") String alarmDefId,
@QueryParam("metric_name") String metricName,
@QueryParam("metric_dimensions") String metricDimensionsStr,
@QueryParam("state") AlarmState state) throws Exception {
Map<String, String> metricDimensions =
Strings.isNullOrEmpty(metricDimensionsStr) ? null : Validation
.parseAndValidateNameAndDimensions(metricName, metricDimensionsStr);
return Links.hydrate(repo.find(tenantId, alarmDefId, metricName, metricDimensions, state),
uriInfo);
}
@GET
@Timed
@Path("/state-history")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "List alarm state history", response = Alarm.class,
@ApiOperation(value = "List alarm state history", response = AlarmDefinition.class,
responseContainer = "List")
public Collection<AlarmStateHistory> listStateHistory(
@HeaderParam("X-Tenant-Id") String tenantId, @QueryParam("dimensions") String dimensionsStr,
@@ -133,17 +145,19 @@ public class AlarmResource {
return stateHistoryRepo.find(tenantId, dimensions, startTime, endTime);
}
@GET
@PATCH
@Timed
@Path("/{alarm_id}")
@Consumes("application/json-patch+json")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get alarm", response = Alarm.class)
@ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid ID supplied"),
@ApiResponse(code = 404, message = "Alarm not found")})
public Alarm get(
@ApiParam(value = "ID of alarm to fetch", required = true) @Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId, @PathParam("alarm_id") String alarmId) {
return Links.hydrate(repo.findById(tenantId, alarmId), uriInfo, true, "history");
public Alarm patch(@Context UriInfo uriInfo, @HeaderParam("X-Tenant-Id") String tenantId,
@PathParam("alarm_id") String alarmId, @NotEmpty Map<String, Object> fields)
throws JsonMappingException {
String stateStr = (String) fields.get("state");
AlarmState state =
stateStr == null ? null : Validation.parseAndValidate(AlarmState.class, stateStr);
return Links.hydrate((Alarm) service.patch(tenantId, alarmId, state), uriInfo, true);
}
@PUT
@@ -154,60 +168,6 @@ public class AlarmResource {
@ApiOperation(value = "Update alarm", response = Alarm.class)
public Alarm update(@Context UriInfo uriInfo, @HeaderParam("X-Tenant-Id") String tenantId,
@PathParam("alarm_id") String alarmId, @Valid UpdateAlarmCommand command) {
command.validate();
AlarmExpression alarmExpression = AlarmValidation.validateNormalizeAndGet(command.expression);
return Links.hydrate(service.update(tenantId, alarmId, alarmExpression, command), uriInfo,
true, "history");
}
@PATCH
@Timed
@Path("/{alarm_id}")
@Consumes("application/json-patch+json")
@Produces(MediaType.APPLICATION_JSON)
@SuppressWarnings("unchecked")
public Alarm patch(@Context UriInfo uriInfo, @HeaderParam("X-Tenant-Id") String tenantId,
@PathParam("alarm_id") String alarmId, @NotEmpty Map<String, Object> fields)
throws JsonMappingException {
String name = (String) fields.get("name");
String description = (String) fields.get("description");
String severity = (String) fields.get("severity");
String expression = (String) fields.get("expression");
String stateStr = (String) fields.get("state");
AlarmState state =
stateStr == null ? null : Validation.parseAndValidate(AlarmState.class, stateStr);
Boolean enabled = (Boolean) fields.get("actions_enabled");
List<String> alarmActions = (List<String>) fields.get("alarm_actions");
List<String> okActions = (List<String>) fields.get("ok_actions");
List<String> undeterminedActions = (List<String>) fields.get("undetermined_actions");
AlarmValidation.validate(name, description, severity, alarmActions, okActions,
undeterminedActions);
AlarmExpression alarmExpression =
expression == null ? null : AlarmValidation.validateNormalizeAndGet(expression);
return Links.hydrate(service.patch(tenantId, alarmId, name, description, severity, expression,
alarmExpression, state, enabled, alarmActions, okActions, undeterminedActions), uriInfo,
true, "history");
}
@DELETE
@Timed
@Path("/{alarm_id}")
@ApiOperation(value = "Delete alarm")
public void delete(@HeaderParam("X-Tenant-Id") String tenantId,
@PathParam("alarm_id") String alarmId) {
service.delete(tenantId, alarmId);
}
@GET
@Timed
@Path("/{alarm_id}/state-history")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get alarm state history", response = AlarmStateHistory.class,
responseContainer = "List")
public List<AlarmStateHistory> getStateHistory(@Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId, @PathParam("alarm_id") String alarmId)
throws Exception {
return stateHistoryRepo.findById(tenantId, alarmId);
return Links.hydrate(service.update(tenantId, alarmId, command), uriInfo, true);
}
}

View File

@@ -53,6 +53,17 @@ public final class Links {
return resources;
}
/**
* Hydrates the {@code resource} with links for the {@code uriInfo}.
*
* @param resource to obtain id from
* @param uriInfo to obtain path from
* @throws NullPointerException if {@code resource} is null
*/
public static <T extends AbstractEntity & Linked> T hydrate(T resource, UriInfo uriInfo) {
return hydrate(resource, prefixForHttps(uriInfo.getAbsolutePath().toString()), false);
}
/**
* Hydrates the {@code resource} with links for the {@code uriInfo}.
*

View File

@@ -18,7 +18,6 @@ import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
@@ -39,21 +38,20 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.hpcloud.mon.MonApiConfiguration;
import com.hpcloud.mon.app.AlarmService.SubExpressions;
import com.hpcloud.mon.app.command.UpdateAlarmCommand;
import com.hpcloud.mon.app.AlarmDefinitionService.SubExpressions;
import com.hpcloud.mon.app.command.UpdateAlarmDefinitionCommand;
import com.hpcloud.mon.common.model.alarm.AlarmExpression;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.common.model.alarm.AlarmSubExpression;
import com.hpcloud.mon.domain.model.alarm.Alarm;
import com.hpcloud.mon.domain.model.alarm.AlarmRepository;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinition;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinitionRepository;
import com.hpcloud.mon.domain.model.notificationmethod.NotificationMethodRepository;
@Test
public class AlarmServiceTest {
AlarmService service;
public class AlarmDefinitionServiceTest {
AlarmDefinitionService service;
MonApiConfiguration config;
Producer<String, String> producer;
AlarmRepository repo;
AlarmDefinitionRepository repo;
NotificationMethodRepository notificationMethodRepo;
@BeforeMethod
@@ -61,20 +59,20 @@ public class AlarmServiceTest {
protected void beforeMethod() {
config = new MonApiConfiguration();
producer = mock(Producer.class);
repo = mock(AlarmRepository.class);
repo = mock(AlarmDefinitionRepository.class);
notificationMethodRepo = mock(NotificationMethodRepository.class);
service = new AlarmService(config, producer, repo, notificationMethodRepo);
service = new AlarmDefinitionService(config, producer, repo, notificationMethodRepo);
when(
repo.create(anyString(), anyString(), anyString(), anyString(), anyString(), anyString(),
any(Map.class), any(List.class), any(List.class), any(List.class))).thenAnswer(
new Answer<Alarm>() {
any(Map.class), any(List.class), any(List.class), any(List.class), any(List.class)))
.thenAnswer(new Answer<AlarmDefinition>() {
@Override
public Alarm answer(InvocationOnMock invocation) throws Throwable {
public AlarmDefinition answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return new Alarm((String) args[0], (String) args[2], (String) args[3],
(String) args[4], (String) args[5], AlarmState.UNDETERMINED, true,
(List<String>) args[7], (List<String>) args[8], (List<String>) args[9]);
return new AlarmDefinition((String) args[0], (String) args[2], (String) args[3],
(String) args[4], (String) args[5], (List<String>) args[7], true,
(List<String>) args[8], (List<String>) args[9], (List<String>) args[10]);
}
});
}
@@ -82,22 +80,23 @@ public class AlarmServiceTest {
@SuppressWarnings("unchecked")
public void shouldCreate() {
String exprStr = "avg(cpu_utilization{service=hpcs.compute, instance_id=123}) > 90";
List<String> matchBy = Arrays.asList("service", "instance_id");
List<String> alarmActions = Arrays.asList("1", "2", "3");
List<String> okActions = Arrays.asList("2", "3");
List<String> undeterminedActions = Arrays.asList("3");
when(notificationMethodRepo.exists(eq("bob"), anyString())).thenReturn(true);
Alarm alarm =
AlarmDefinition alarm =
service.create("bob", "90% CPU", "foo", "LOW", exprStr, AlarmExpression.of(exprStr),
alarmActions, okActions, undeterminedActions);
matchBy, alarmActions, okActions, undeterminedActions);
Alarm expected =
new Alarm(alarm.getId(), "90% CPU", "foo", "LOW", exprStr, AlarmState.UNDETERMINED, true,
AlarmDefinition expected =
new AlarmDefinition(alarm.getId(), "90% CPU", "foo", "LOW", exprStr, matchBy, true,
alarmActions, okActions, undeterminedActions);
assertEquals(expected, alarm);
verify(repo).create(eq("bob"), anyString(), eq("90% CPU"), eq("foo"), eq("LOW"), eq(exprStr),
any(Map.class), eq(alarmActions), eq(okActions), eq(undeterminedActions));
any(Map.class), eq(matchBy), eq(alarmActions), eq(okActions), eq(undeterminedActions));
verify(producer).send(any(KeyedMessage.class));
}
@@ -108,8 +107,8 @@ public class AlarmServiceTest {
List<String> okActions = Arrays.asList("2", "3");
List<String> undeterminedActions = Arrays.asList("3");
Alarm oldAlarm =
new Alarm("123", "foo bar", "foo bar", "LOW", exprStr, AlarmState.OK, true, alarmActions,
AlarmDefinition oldAlarm =
new AlarmDefinition("123", "foo bar", "foo bar", "LOW", exprStr, null, true, alarmActions,
okActions, undeterminedActions);
Map<String, AlarmSubExpression> oldSubExpressions = new HashMap<>();
oldSubExpressions.put("444", AlarmSubExpression.of("avg(foo{instance_id=123}) > 90"));
@@ -124,17 +123,17 @@ public class AlarmServiceTest {
List<String> newAlarmActions = Arrays.asList("5", "6", "7");
List<String> newOkActions = Arrays.asList("6", "7");
List<String> newUndeterminedActions = Arrays.asList("7");
UpdateAlarmCommand command =
new UpdateAlarmCommand("foo bar baz", "foo bar baz", newExprStr, "LOW", AlarmState.ALARM,
UpdateAlarmDefinitionCommand command =
new UpdateAlarmDefinitionCommand("foo bar baz", "foo bar baz", newExprStr, null, "LOW",
false, newAlarmActions, newOkActions, newUndeterminedActions);
Alarm alarm = service.update("bob", "123", AlarmExpression.of(newExprStr), command);
AlarmDefinition alarm = service.update("bob", "123", AlarmExpression.of(newExprStr), command);
Alarm expected =
new Alarm(alarm.getId(), "foo bar baz", "foo bar baz", "LOW", newExprStr, AlarmState.ALARM,
AlarmDefinition expected =
new AlarmDefinition(alarm.getId(), "foo bar baz", "foo bar baz", "LOW", newExprStr, null,
false, newAlarmActions, newOkActions, newUndeterminedActions);
assertEquals(expected, alarm);
verify(producer, times(2)).send(any(KeyedMessage.class));
verify(producer).send(any(KeyedMessage.class));
}
public void testOldAndNewSubExpressionsFor() {

View File

@@ -23,22 +23,21 @@ import java.util.Map;
import org.testng.annotations.Test;
import com.hpcloud.mon.app.command.CreateAlarmCommand;
import com.hpcloud.mon.domain.model.AbstractModelTest;
@Test
public class CreateAlarmCommandTest extends AbstractModelTest {
public class CreateAlarmDefinitionCommandTest extends AbstractModelTest {
public void shouldDeserializeFromJson() throws Exception {
Map<String, String> dimensions = new HashMap<String, String>();
dimensions.put("instanceId", "392633");
/** todo: Check the null value to get works **/
CreateAlarmCommand newAlarm =
new CreateAlarmCommand("Disk Exceeds 1k Operations", null, null,
"avg(hpcs.compute:cpu:1:{instance_id=5}) > 5", Arrays.asList("123345345", "23423"),
null, null);
CreateAlarmDefinitionCommand newAlarm =
new CreateAlarmDefinitionCommand("Disk Exceeds 1k Operations", null,
"avg(hpcs.compute:cpu:1:{instance_id=5}) > 5", null, null, Arrays.asList("123345345",
"23423"), null, null);
String json = jsonFixture("fixtures/newAlarm.json");
CreateAlarmCommand alarm = fromJson(json, CreateAlarmCommand.class);
CreateAlarmDefinitionCommand alarm = fromJson(json, CreateAlarmDefinitionCommand.class);
assertEquals(alarm, newAlarm);
}
}

View File

@@ -18,28 +18,28 @@ import static com.hpcloud.dropwizard.JsonHelpers.jsonFixture;
import static org.testng.Assert.assertEquals;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.testng.annotations.Test;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.domain.model.alarm.Alarm;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinition;
import com.hpcloud.mon.domain.model.common.Link;
@Test
public class AlarmTest extends AbstractModelTest {
private final Alarm alarm;
public class AlarmDefinitionTest extends AbstractModelTest {
private final AlarmDefinition alarm;
private final Map<String, String> dimensions;
public AlarmTest() {
public AlarmDefinitionTest() {
dimensions = new HashMap<String, String>();
dimensions.put("instance_id", "666");
dimensions.put("image_id", "345");
alarm =
new Alarm("123", "90% CPU", null, "LOW",
"avg(hpcs.compute{instance_id=666, image_id=345}) >= 90", AlarmState.OK, false,
Arrays.asList("123345345", "23423"), null, null);
new AlarmDefinition("123", "90% CPU", null, "LOW",
"avg(hpcs.compute{instance_id=666, image_id=345}) >= 90",
Collections.<String>emptyList(), false, Arrays.asList("123345345", "23423"), null, null);
alarm.setLinks(Arrays
.asList(new Link("self", "https://region-a.geo-1.maas.hpcloudsvc.com/v1.0")));
}
@@ -48,10 +48,4 @@ public class AlarmTest extends AbstractModelTest {
String json = toJson(alarm);
assertEquals(json, jsonFixture("fixtures/alarm.json"));
}
public void shouldDeserializeFromJson() throws Exception {
String json = jsonFixture("fixtures/alarm.json");
Alarm detail = fromJson(json, Alarm.class);
assertEquals(alarm, detail);
}
}

View File

@@ -48,7 +48,7 @@ public class AlarmStateHistoryInfluxDbRepositoryImplTest {
public void buildQueryForFindByIdTest() throws Exception {
String er = "select alarm_id, old_state, new_state, reason, " +
String er = "select alarm_id, metrics, old_state, new_state, reason, " +
"" + "reason_data from alarm_state_history where tenant_id = 'tenant-id' and alarm_id = "
+ "'alarm-id'";
String r = this.alarmStateHistoryInfluxDBRepository.buildQueryForFindById("tenant-id",
@@ -75,7 +75,7 @@ public class AlarmStateHistoryInfluxDbRepositoryImplTest {
}
public void buildQueryForFindTest() throws Exception {
String er = "select alarm_id, old_state, new_state, reason, " +
String er = "select alarm_id, metrics, old_state, new_state, reason, " +
"" + "reason_data from alarm_state_history where tenant_id = 'tenant-id' and time > " +
"1388559600s and time < 1388559601s and ( alarm_id = 'id-1' or alarm_id = 'id-2' )";
String r = this.alarmStateHistoryInfluxDBRepository.buildQueryForFind("tenant-id",

View File

@@ -0,0 +1,271 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.hpcloud.mon.infrastructure.persistence.mysql;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.util.StringMapper;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Resources;
import com.hpcloud.mon.common.model.alarm.AggregateFunction;
import com.hpcloud.mon.common.model.alarm.AlarmOperator;
import com.hpcloud.mon.common.model.alarm.AlarmSubExpression;
import com.hpcloud.mon.common.model.metric.MetricDefinition;
import com.hpcloud.mon.domain.exception.EntityNotFoundException;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinition;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinitionRepository;
@Test(groups = "database")
public class AlarmDefinitionMySqlRepositoryImplTest {
private DBI db;
private Handle handle;
private AlarmDefinitionRepository repo;
private List<String> alarmActions;
@BeforeClass
protected void setupClass() throws Exception {
db = new DBI("jdbc:h2:mem:test;MODE=MySQL");
handle = db.open();
handle
.execute(Resources.toString(getClass().getResource("alarm.sql"), Charset.defaultCharset()));
repo = new AlarmDefinitionMySqlRepositoryImpl(db);
alarmActions = new ArrayList<String>();
alarmActions.add("29387234");
alarmActions.add("77778687");
}
@AfterClass
protected void afterClass() {
handle.close();
}
@BeforeMethod
protected void beforeMethod() {
handle.execute("SET foreign_key_checks = 0;");
handle.execute("truncate table sub_alarm");
handle.execute("truncate table alarm_action");
handle.execute("truncate table sub_alarm_dimension");
handle.execute("truncate table alarm_definition");
handle
.execute("insert into alarm_definition (id, tenant_id, name, severity, expression, match_by, actions_enabled, created_at, updated_at, deleted_at) "
+ "values ('123', 'bob', '90% CPU', 'LOW', 'avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=cpu, device=1}) > 10', 'flavor_id,image_id', 1, NOW(), NOW(), NULL)");
handle
.execute("insert into sub_alarm (id, alarm_definition_id, function, metric_name, operator, threshold, period, periods, created_at, updated_at) "
+ "values ('111', '123', 'avg', 'hpcs.compute', 'GT', 10, 60, 1, NOW(), NOW())");
handle.execute("insert into sub_alarm_dimension values ('111', 'flavor_id', '777')");
handle.execute("insert into sub_alarm_dimension values ('111', 'image_id', '888')");
handle.execute("insert into sub_alarm_dimension values ('111', 'metric_name', 'cpu')");
handle.execute("insert into sub_alarm_dimension values ('111', 'device', '1')");
handle.execute("insert into alarm_action values ('123', 'ALARM', '29387234')");
handle.execute("insert into alarm_action values ('123', 'ALARM', '77778687')");
handle
.execute("insert into alarm_definition (id, tenant_id, name, severity, expression, match_by, actions_enabled, created_at, updated_at, deleted_at) "
+ "values ('234', 'bob', '50% CPU', 'LOW', 'avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=mem}) > 20 and avg(hpcs.compute) < 100', 'flavor_id,image_id', 1, NOW(), NOW(), NULL)");
handle
.execute("insert into sub_alarm (id, alarm_definition_id, function, metric_name, operator, threshold, period, periods, created_at, updated_at) "
+ "values ('222', '234', 'avg', 'hpcs.compute', 'GT', 20, 60, 1, NOW(), NOW())");
handle
.execute("insert into sub_alarm (id, alarm_definition_id, function, metric_name, operator, threshold, period, periods, created_at, updated_at) "
+ "values ('223', '234', 'avg', 'hpcs.compute', 'LT', 100, 60, 1, NOW(), NOW())");
handle.execute("insert into sub_alarm_dimension values ('222', 'flavor_id', '777')");
handle.execute("insert into sub_alarm_dimension values ('222', 'image_id', '888')");
handle.execute("insert into sub_alarm_dimension values ('222', 'metric_name', 'mem')");
handle.execute("insert into alarm_action values ('234', 'ALARM', '29387234')");
handle.execute("insert into alarm_action values ('234', 'ALARM', '77778687')");
}
public void shouldCreate() {
Map<String, AlarmSubExpression> subExpressions =
ImmutableMap
.<String, AlarmSubExpression>builder()
.put(
"4433",
AlarmSubExpression
.of("avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=cpu}) > 10"))
.build();
AlarmDefinition alarmA =
repo.create("555", "2345", "90% CPU", null, "LOW",
"avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=cpu}) > 10", subExpressions,
Arrays.asList("flavor_id", "image_id"), alarmActions, null, null);
AlarmDefinition alarmB = repo.findById("555", alarmA.getId());
assertEquals(alarmA, alarmB);
// Assert that sub-alarm and sub-alarm-dimensions made it to the db
assertEquals(
handle.createQuery("select count(*) from sub_alarm where id = 4433")
.map(StringMapper.FIRST).first(), "1");
assertEquals(
handle.createQuery("select count(*) from sub_alarm_dimension where sub_alarm_id = 4433")
.map(StringMapper.FIRST).first(), "3");
}
@Test(groups = "database")
public void shouldUpdate() {
db = new DBI("jdbc:mysql://192.168.10.4/mon", "monapi", "password");
handle = db.open();
repo = new AlarmDefinitionMySqlRepositoryImpl(db);
beforeMethod();
List<String> oldSubAlarmIds = Arrays.asList("222");
AlarmSubExpression changedSubExpression = AlarmSubExpression.of("avg(hpcs.compute) <= 200");
Map<String, AlarmSubExpression> changedSubExpressions =
ImmutableMap.<String, AlarmSubExpression>builder().put("223", changedSubExpression).build();
AlarmSubExpression newSubExpression = AlarmSubExpression.of("avg(foo{flavor_id=777}) > 333");
Map<String, AlarmSubExpression> newSubExpressions =
ImmutableMap.<String, AlarmSubExpression>builder().put("555", newSubExpression).build();
repo.update("bob", "234", false, "90% CPU", null,
"avg(foo{flavor_id=777}) > 333 and avg(hpcs.compute) <= 200",
Arrays.asList("flavor_id", "image_id"), "LOW", false, oldSubAlarmIds,
changedSubExpressions, newSubExpressions, alarmActions, null, null);
AlarmDefinition alarm = repo.findById("bob", "234");
AlarmDefinition expected =
new AlarmDefinition("234", "90% CPU", null, "LOW",
"avg(foo{flavor_id=777}) > 333 and avg(hpcs.compute) <= 200", Arrays.asList(
"flavor_id", "image_id"), false, alarmActions, Collections.<String>emptyList(),
Collections.<String>emptyList());
assertEquals(expected, alarm);
Map<String, AlarmSubExpression> subExpressions = repo.findSubExpressions("234");
assertEquals(subExpressions.get("223"), changedSubExpression);
assertEquals(subExpressions.get("555"), newSubExpression);
}
public void shouldFindById() {
AlarmDefinition alarm = repo.findById("bob", "123");
assertEquals(alarm.getId(), "123");
assertEquals(alarm.getName(), "90% CPU");
assertEquals(alarm.getSeverity(), "LOW");
assertEquals(alarm.getExpression(),
"avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=cpu, device=1}) > 10");
assertEquals(alarm.getMatchBy(), Arrays.asList("flavor_id", "image_id"));
assertEquals(alarm.isActionsEnabled(), true);
assertEquals(alarm.getAlarmActions(), alarmActions);
}
@Test(groups = "database")
public void shouldFindSubAlarmMetricDefinitions() {
db = new DBI("jdbc:mysql://192.168.10.4/mon", "monapi", "password");
handle = db.open();
repo = new AlarmDefinitionMySqlRepositoryImpl(db);
beforeMethod();
assertEquals(
repo.findSubAlarmMetricDefinitions("123").get("111"),
new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
.put("flavor_id", "777").put("image_id", "888").put("metric_name", "cpu")
.put("device", "1").build()));
assertEquals(
repo.findSubAlarmMetricDefinitions("234").get("222"),
new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
.put("flavor_id", "777").put("image_id", "888").put("metric_name", "mem").build()));
assertTrue(repo.findSubAlarmMetricDefinitions("asdfasdf").isEmpty());
}
@Test(groups = "database")
public void shouldFindSubExpressions() {
db = new DBI("jdbc:mysql://192.168.10.4/mon", "monapi", "password");
handle = db.open();
repo = new AlarmDefinitionMySqlRepositoryImpl(db);
beforeMethod();
assertEquals(
repo.findSubExpressions("123").get("111"),
new AlarmSubExpression(AggregateFunction.AVG, new MetricDefinition("hpcs.compute",
ImmutableMap.<String, String>builder().put("flavor_id", "777").put("image_id", "888")
.put("metric_name", "cpu").put("device", "1").build()), AlarmOperator.GT, 10, 60, 1));
assertEquals(repo.findSubExpressions("234").get("223"), new AlarmSubExpression(
AggregateFunction.AVG, new MetricDefinition("hpcs.compute", null), AlarmOperator.LT, 100,
60, 1));
assertTrue(repo.findSubAlarmMetricDefinitions("asdfasdf").isEmpty());
}
public void testExists() {
assertTrue(repo.exists("bob", "90% CPU"));
// Negative
assertFalse(repo.exists("bob", "999% CPU"));
}
public void shouldFind() {
List<AlarmDefinition> alarms = repo.find("bob", null, null);
assertEquals(
alarms,
Arrays.asList(
new AlarmDefinition("123", "90% CPU", null, "LOW",
"avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=cpu, device=1}) > 10",
Arrays.asList("flavor_id", "image_id"), true,
Arrays.asList("29387234", "77778687"), Collections.<String>emptyList(), Collections
.<String>emptyList()),
new AlarmDefinition(
"234",
"50% CPU",
null,
"LOW",
"avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=mem}) > 20 and avg(hpcs.compute) < 100",
Arrays.asList("flavor_id", "image_id"), true,
Arrays.asList("29387234", "77778687"), Collections.<String>emptyList(), Collections
.<String>emptyList())));
}
public void shouldFindByName() {
List<AlarmDefinition> alarms = repo.find("bob", "90% CPU", null);
assertEquals(alarms, Arrays.asList(new AlarmDefinition("123", "90% CPU", null, "LOW",
"avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=cpu, device=1}) > 10", Arrays
.asList("flavor_id", "image_id"), true, Arrays.asList("29387234", "77778687"),
Collections.<String>emptyList(), Collections.<String>emptyList())));
}
public void shouldDeleteById() {
repo.deleteById("bob", "123");
try {
assertNull(repo.findById("bob", "123"));
fail();
} catch (EntityNotFoundException expected) {
}
}
}

View File

@@ -15,21 +15,16 @@
package com.hpcloud.mon.infrastructure.persistence.mysql;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.util.StringMapper;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
@@ -37,10 +32,7 @@ import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Resources;
import com.hpcloud.mon.common.model.alarm.AggregateFunction;
import com.hpcloud.mon.common.model.alarm.AlarmOperator;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.common.model.alarm.AlarmSubExpression;
import com.hpcloud.mon.common.model.metric.MetricDefinition;
import com.hpcloud.mon.domain.exception.EntityNotFoundException;
import com.hpcloud.mon.domain.model.alarm.Alarm;
@@ -74,195 +66,85 @@ public class AlarmMySqlRepositoryImplTest {
@BeforeMethod
protected void beforeMethod() {
handle.execute("SET foreign_key_checks = 0;");
handle.execute("truncate table sub_alarm");
handle.execute("truncate table alarm_action");
handle.execute("truncate table sub_alarm_dimension");
handle.execute("truncate table alarm");
handle.execute("truncate table alarm_action");
handle.execute("truncate table alarm_definition");
handle.execute("truncate table alarm_metric");
handle.execute("truncate table metric_definition");
handle.execute("truncate table metric_definition_dimensions");
handle.execute("truncate table metric_dimension");
handle
.execute("insert into alarm (id, tenant_id, name, severity, expression, state, actions_enabled, created_at, updated_at, deleted_at) "
+ "values ('123', 'bob', '90% CPU', 'LOW', 'avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=cpu, device=1}) > 10', 'UNDETERMINED', 1, NOW(), NOW(), NULL)");
.execute("insert into alarm_definition (id, tenant_id, name, severity, expression, match_by, actions_enabled, created_at, updated_at, deleted_at) "
+ "values ('1', 'bob', '90% CPU', 'LOW', 'avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=cpu, device=1}) > 10', 'flavor_id,image_id', 1, NOW(), NOW(), NULL)");
handle
.execute("insert into sub_alarm (id, alarm_id, function, metric_name, operator, threshold, period, periods, state, created_at, updated_at) "
+ "values ('111', '123', 'avg', 'hpcs.compute', 'GT', 10, 60, 1, 'UNDETERMINED', NOW(), NOW())");
handle.execute("insert into sub_alarm_dimension values ('111', 'flavor_id', '777')");
handle.execute("insert into sub_alarm_dimension values ('111', 'image_id', '888')");
handle.execute("insert into sub_alarm_dimension values ('111', 'metric_name', 'cpu')");
handle.execute("insert into sub_alarm_dimension values ('111', 'device', '1')");
handle.execute("insert into alarm_action values ('123', 'ALARM', '29387234')");
handle.execute("insert into alarm_action values ('123', 'ALARM', '77778687')");
.execute("insert into alarm (id, alarm_definition_id, state, created_at, updated_at) values ('1', '1', 'OK', NOW(), NOW())");
handle
.execute("insert into alarm (id, alarm_definition_id, state, created_at, updated_at) values ('2', '1', 'UNDETERMINED', NOW(), NOW())");
handle
.execute("insert into alarm (id, alarm_definition_id, state, created_at, updated_at) values ('3', '1', 'ALARM', NOW(), NOW())");
handle
.execute("insert into alarm_metric (alarm_id, metric_definition_dimensions_id) values ('1', 11)");
handle
.execute("insert into alarm_metric (alarm_id, metric_definition_dimensions_id) values ('1', 22)");
handle
.execute("insert into alarm_metric (alarm_id, metric_definition_dimensions_id) values ('2', 11)");
handle
.execute("insert into alarm_metric (alarm_id, metric_definition_dimensions_id) values ('3', 22)");
handle
.execute("insert into metric_definition (id, name, tenant_id, region) values (1, 'cpu', 'bob', 'west')");
handle
.execute("insert into metric_definition (id, name, tenant_id, region) values (2, 'mem', 'bob', 'west')");
handle
.execute("insert into metric_definition_dimensions (id, metric_definition_id, metric_dimension_set_id) values (11, 1, 1)");
handle
.execute("insert into metric_definition_dimensions (id, metric_definition_id, metric_dimension_set_id) values (22, 2, 1)");
handle
.execute("insert into metric_dimension (dimension_set_id, name, value) values (1, 'instance_id', '123')");
handle
.execute("insert into metric_dimension (dimension_set_id, name, value) values (1, 'flavor_id', '222')");
handle
.execute("insert into alarm (id, tenant_id, name, severity, expression, state, actions_enabled, created_at, updated_at, deleted_at) "
+ "values ('234', 'bob', '50% CPU', 'LOW', 'avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=mem}) > 20 and avg(hpcs.compute) < 100', 'UNDETERMINED', 1, NOW(), NOW(), NULL)");
.execute("insert into alarm_definition (id, tenant_id, name, severity, expression, match_by, actions_enabled, created_at, updated_at, deleted_at) "
+ "values ('234', 'bob', '50% CPU', 'LOW', 'avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=mem}) > 20 and avg(hpcs.compute) < 100', 'flavor_id,image_id', 1, NOW(), NOW(), NULL)");
handle
.execute("insert into sub_alarm (id, alarm_id, function, metric_name, operator, threshold, period, periods, state, created_at, updated_at) "
+ "values ('222', '234', 'avg', 'hpcs.compute', 'GT', 20, 60, 1, 'UNDETERMINED', NOW(), NOW())");
.execute("insert into alarm (id, alarm_definition_id, state, created_at, updated_at) values ('234111', '234', 'UNDETERMINED', NOW(), NOW())");
handle
.execute("insert into sub_alarm (id, alarm_id, function, metric_name, operator, threshold, period, periods, state, created_at, updated_at) "
+ "values ('223', '234', 'avg', 'hpcs.compute', 'LT', 100, 60, 1, 'UNDETERMINED', NOW(), NOW())");
handle.execute("insert into sub_alarm_dimension values ('222', 'flavor_id', '777')");
handle.execute("insert into sub_alarm_dimension values ('222', 'image_id', '888')");
handle.execute("insert into sub_alarm_dimension values ('222', 'metric_name', 'mem')");
handle.execute("insert into alarm_action values ('234', 'ALARM', '29387234')");
handle.execute("insert into alarm_action values ('234', 'ALARM', '77778687')");
}
public void shouldCreate() {
Map<String, AlarmSubExpression> subExpressions =
ImmutableMap
.<String, AlarmSubExpression>builder()
.put(
"4433",
AlarmSubExpression
.of("avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=cpu}) > 10"))
.build();
Alarm alarmA =
repo.create("555", "2345", "90% CPU", null, "LOW",
"avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=cpu}) > 10", subExpressions,
alarmActions, null, null);
Alarm alarmB = repo.findById("555", alarmA.getId());
assertEquals(alarmA, alarmB);
// Assert that sub-alarm and sub-alarm-dimensions made it to the db
assertEquals(
handle.createQuery("select count(*) from sub_alarm where id = 4433")
.map(StringMapper.FIRST).first(), "1");
assertEquals(
handle.createQuery("select count(*) from sub_alarm_dimension where sub_alarm_id = 4433")
.map(StringMapper.FIRST).first(), "3");
.execute("insert into alarm (id, alarm_definition_id, state, created_at, updated_at) values ('234222', '234', 'ALARM', NOW(), NOW())");
}
@Test(groups = "database")
public void shouldUpdate() {
db = new DBI("jdbc:mysql://192.168.10.4/mon", "monapi", "password");
handle = db.open();
repo = new AlarmMySqlRepositoryImpl(db);
beforeMethod();
List<String> oldSubAlarmIds = Arrays.asList("222");
AlarmSubExpression changedSubExpression = AlarmSubExpression.of("avg(hpcs.compute) <= 200");
Map<String, AlarmSubExpression> changedSubExpressions =
ImmutableMap.<String, AlarmSubExpression>builder().put("223", changedSubExpression).build();
AlarmSubExpression newSubExpression = AlarmSubExpression.of("avg(foo{flavor_id=777}) > 333");
Map<String, AlarmSubExpression> newSubExpressions =
ImmutableMap.<String, AlarmSubExpression>builder().put("555", newSubExpression).build();
repo.update("bob", "234", false, "90% CPU", null,
"avg(foo{flavor_id=777}) > 333 and avg(hpcs.compute) <= 200", "LOW", AlarmState.ALARM,
false, oldSubAlarmIds, changedSubExpressions, newSubExpressions, alarmActions, null, null);
Alarm alarm = repo.findById("bob", "234");
Alarm expected =
new Alarm("234", "90% CPU", null, "LOW",
"avg(foo{flavor_id=777}) > 333 and avg(hpcs.compute) <= 200", AlarmState.ALARM, false,
alarmActions, Collections.<String>emptyList(), Collections.<String>emptyList());
assertEquals(expected, alarm);
Map<String, AlarmSubExpression> subExpressions = repo.findSubExpressions("234");
assertEquals(subExpressions.get("223"), changedSubExpression);
assertEquals(subExpressions.get("555"), newSubExpression);
}
public void shouldFindById() {
Alarm alarm = repo.findById("bob", "123");
assertEquals(alarm.getId(), "123");
assertEquals(alarm.getName(), "90% CPU");
assertEquals(alarm.getSeverity(), "LOW");
assertEquals(alarm.getExpression(),
"avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=cpu, device=1}) > 10");
assertEquals(alarm.getState(), AlarmState.UNDETERMINED);
assertEquals(alarm.isActionsEnabled(), true);
assertEquals(alarm.getAlarmActions(), alarmActions);
}
@Test(groups = "database")
public void shouldFindSubAlarmMetricDefinitions() {
db = new DBI("jdbc:mysql://192.168.10.4/mon", "monapi", "password");
handle = db.open();
repo = new AlarmMySqlRepositoryImpl(db);
beforeMethod();
assertEquals(
repo.findSubAlarmMetricDefinitions("123").get("111"),
new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
.put("flavor_id", "777").put("image_id", "888").put("metric_name", "cpu")
.put("device", "1").build()));
assertEquals(
repo.findSubAlarmMetricDefinitions("234").get("222"),
new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
.put("flavor_id", "777").put("image_id", "888").put("metric_name", "mem").build()));
assertTrue(repo.findSubAlarmMetricDefinitions("asdfasdf").isEmpty());
}
@Test(groups = "database")
public void shouldFindSubExpressions() {
db = new DBI("jdbc:mysql://192.168.10.4/mon", "monapi", "password");
handle = db.open();
repo = new AlarmMySqlRepositoryImpl(db);
beforeMethod();
assertEquals(
repo.findSubExpressions("123").get("111"),
new AlarmSubExpression(AggregateFunction.AVG, new MetricDefinition("hpcs.compute",
ImmutableMap.<String, String>builder().put("flavor_id", "777").put("image_id", "888")
.put("metric_name", "cpu").put("device", "1").build()), AlarmOperator.GT, 10, 60, 1));
assertEquals(repo.findSubExpressions("234").get("223"), new AlarmSubExpression(
AggregateFunction.AVG, new MetricDefinition("hpcs.compute", null), AlarmOperator.LT, 100,
60, 1));
assertTrue(repo.findSubAlarmMetricDefinitions("asdfasdf").isEmpty());
}
public void testExists() {
assertTrue(repo.exists("bob", "90% CPU"));
// Negative
assertFalse(repo.exists("bob", "999% CPU"));
}
public void shouldFind() {
List<Alarm> alarms = repo.find("bob", null, null, null);
assertEquals(
alarms,
Arrays.asList(
new Alarm("123", "90% CPU", null, "LOW",
"avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=cpu, device=1}) > 10",
AlarmState.UNDETERMINED, true, Arrays.asList("29387234", "77778687"), Collections
.<String>emptyList(), Collections.<String>emptyList()),
new Alarm(
"234",
"50% CPU",
null,
"LOW",
"avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=mem}) > 20 and avg(hpcs.compute) < 100",
AlarmState.UNDETERMINED, true, Arrays.asList("29387234", "77778687"), Collections
.<String>emptyList(), Collections.<String>emptyList())));
}
public void shouldFindByName() {
List<Alarm> alarms = repo.find("bob", "90% CPU", null, null);
assertEquals(alarms, Arrays.asList(new Alarm("123", "90% CPU", null, "LOW",
"avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=cpu, device=1}) > 10",
AlarmState.UNDETERMINED, true, Arrays.asList("29387234", "77778687"), Collections
.<String>emptyList(), Collections.<String>emptyList())));
}
public void shouldDeleteById() {
repo.deleteById("bob", "123");
public void shouldDelete() {
repo.deleteById("123111");
try {
assertNull(repo.findById("bob", "123"));
assertNull(repo.findById("123111"));
fail();
} catch (EntityNotFoundException expected) {
}
}
@Test(groups = "database")
public void shouldFind() {
List<Alarm> alarms =
repo.find("bob", "1", "cpu",
ImmutableMap.<String, String>builder().put("instance_id", "123").build(), null);
assertEquals(alarms, Arrays.asList(new Alarm("123111", "123", "90% CPU", Arrays
.asList(new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
.put("flavor_id", "777").put("image_id", "888").put("metric_name", "cpu").build())),
AlarmState.ALARM)));
}
@Test(groups = "database")
public void shouldFindById() {
Alarm alarm = repo.findById("123111");
assertEquals(alarm.getId(), "123111");
assertEquals(alarm.getAlarmDefinitionId(), "123");
assertEquals(alarm.getState(), "90% CPU");
assertEquals(
alarm.getMetrics(),
Arrays.asList(new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
.put("flavor_id", "777").put("image_id", "888").put("metric_name", "cpu").build())));
}
}

View File

@@ -21,6 +21,7 @@ import static org.testng.Assert.fail;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -43,28 +44,27 @@ import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
import com.hpcloud.mon.MonApiConfiguration;
import com.hpcloud.mon.MonApiModule;
import com.hpcloud.mon.app.AlarmService;
import com.hpcloud.mon.app.command.CreateAlarmCommand;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.app.AlarmDefinitionService;
import com.hpcloud.mon.app.command.CreateAlarmDefinitionCommand;
import com.hpcloud.mon.domain.exception.EntityNotFoundException;
import com.hpcloud.mon.domain.model.alarm.Alarm;
import com.hpcloud.mon.domain.model.alarm.AlarmRepository;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinition;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinitionRepository;
import com.hpcloud.mon.domain.model.alarmstatehistory.AlarmStateHistoryRepository;
import com.hpcloud.mon.infrastructure.persistence.mysql.AlarmMySqlRepositoryImpl;
import com.hpcloud.mon.infrastructure.persistence.mysql.AlarmDefinitionMySqlRepositoryImpl;
import com.hpcloud.mon.infrastructure.persistence.mysql.NotificationMethodMySqlRepositoryImpl;
import com.hpcloud.mon.resource.AbstractMonApiResourceTest;
import com.hpcloud.mon.resource.AlarmResource;
import com.hpcloud.mon.resource.AlarmDefinitionResource;
import com.sun.jersey.api.client.ClientResponse;
@Test(groups = "integration", enabled = false)
public class AlarmIntegrationTest extends AbstractMonApiResourceTest {
private static final String TENANT_ID = "alarm-test";
private DBI mysqlDb;
private Alarm alarm;
private AlarmService service;
private AlarmDefinition alarm;
private AlarmDefinitionService service;
private MonApiConfiguration config;
private Producer<String, String> producer;
private AlarmRepository repo;
private AlarmDefinitionRepository repo;
AlarmStateHistoryRepository stateHistoryRepo;
private Map<String, String> dimensions;
private List<String> alarmActions;
@@ -82,10 +82,11 @@ public class AlarmIntegrationTest extends AbstractMonApiResourceTest {
.execute("insert into notification_method (id, tenant_id, name, type, address, created_at, updated_at) values ('77778687', 'alarm-test', 'MySMS', 'SMS', '8675309', NOW(), NOW())");
mysqlDb.close(handle);
repo = new AlarmMySqlRepositoryImpl(mysqlDb);
repo = new AlarmDefinitionMySqlRepositoryImpl(mysqlDb);
service =
new AlarmService(config, producer, repo, new NotificationMethodMySqlRepositoryImpl(mysqlDb));
addResources(new AlarmResource(service, repo, null));
new AlarmDefinitionService(config, producer, repo,
new NotificationMethodMySqlRepositoryImpl(mysqlDb));
addResources(new AlarmDefinitionResource(service, repo));
}
@BeforeTest
@@ -110,8 +111,9 @@ public class AlarmIntegrationTest extends AbstractMonApiResourceTest {
alarmActions.add("29387234");
alarmActions.add("77778687");
alarm =
new Alarm("123", "90% CPU", null, null, "avg(hpcs.compute:cpu:{instance_id=123} > 10",
AlarmState.OK, true, alarmActions, null, null);
new AlarmDefinition("123", "90% CPU", null, null,
"avg(hpcs.compute:cpu:{instance_id=123} > 10", Arrays.asList("instance_id"), true,
alarmActions, null, null);
}
@AfterTest
@@ -127,10 +129,11 @@ public class AlarmIntegrationTest extends AbstractMonApiResourceTest {
.header("Content-Type", MediaType.APPLICATION_JSON)
.post(
ClientResponse.class,
new CreateAlarmCommand("90% CPU", null, null,
"avg(hpcs.compute:cpu:{instance_id=123} > 10", alarmActions, null, null));
new CreateAlarmDefinitionCommand("90% CPU", null,
"avg(hpcs.compute:cpu:{instance_id=123} > 10", Arrays.asList("instance_id"),
null, alarmActions, null, null));
Alarm newAlarm = response.getEntity(Alarm.class);
AlarmDefinition newAlarm = response.getEntity(AlarmDefinition.class);
String location = response.getHeaders().get("Location").get(0);
assertEquals(response.getStatus(), 201);
assertEquals(location, "/v2.0/alarms/" + newAlarm.getId());
@@ -139,10 +142,10 @@ public class AlarmIntegrationTest extends AbstractMonApiResourceTest {
}
public void shouldCreateCaseInsensitiveAndKeywords() throws Exception {
Alarm alarm_local;
AlarmDefinition alarm_local;
alarm_local =
new Alarm("123", "90% CPU", null, null, "AvG(avg:cpu:{instance_id=123} gT 10",
AlarmState.OK, true, alarmActions, null, null);
new AlarmDefinition("123", "90% CPU", null, null, "AvG(avg:cpu:{instance_id=123} gT 10",
Arrays.asList("instance_id"), true, alarmActions, null, null);
ClientResponse response =
client()
.resource("/v2.0/alarms")
@@ -150,10 +153,11 @@ public class AlarmIntegrationTest extends AbstractMonApiResourceTest {
.header("Content-Type", MediaType.APPLICATION_JSON)
.post(
ClientResponse.class,
new CreateAlarmCommand("90% CPU", null, null,
"AvG(avg:cpu:{instance_id=123} gT 10", alarmActions, null, null));
new CreateAlarmDefinitionCommand("90% CPU", null,
"AvG(avg:cpu:{instance_id=123} gT 10", Arrays.asList("instance_id"), null,
alarmActions, null, null));
Alarm newAlarm = response.getEntity(Alarm.class);
AlarmDefinition newAlarm = response.getEntity(AlarmDefinition.class);
String location = response.getHeaders().get("Location").get(0);
assertEquals(response.getStatus(), 201);
assertEquals(location, "/v2.0/alarms/" + newAlarm.getId());
@@ -162,10 +166,10 @@ public class AlarmIntegrationTest extends AbstractMonApiResourceTest {
}
public void shouldDelete() {
Alarm newAlarm =
repo.create(TENANT_ID, "123", alarm.getName(), null, alarm.getName(),
alarm.getExpression(), null, alarm.getAlarmActions(), alarm.getOkActions(),
alarm.getUndeterminedActions());
AlarmDefinition newAlarm =
repo.create(TENANT_ID, "123", alarm.getName(), alarm.getName(), alarm.getSeverity(),
alarm.getExpression(), null, alarm.getMatchBy(), alarm.getAlarmActions(),
alarm.getOkActions(), alarm.getUndeterminedActions());
assertNotNull(repo.findById(TENANT_ID, newAlarm.getId()));
ClientResponse response =

View File

@@ -35,33 +35,27 @@ import java.util.Map;
import javax.ws.rs.core.MediaType;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.testng.annotations.Test;
import com.hpcloud.mon.app.AlarmService;
import com.hpcloud.mon.app.command.CreateAlarmCommand;
import com.hpcloud.mon.app.command.UpdateAlarmCommand;
import com.hpcloud.mon.app.AlarmDefinitionService;
import com.hpcloud.mon.app.command.CreateAlarmDefinitionCommand;
import com.hpcloud.mon.app.command.UpdateAlarmDefinitionCommand;
import com.hpcloud.mon.common.model.alarm.AlarmExpression;
import com.hpcloud.mon.common.model.alarm.AlarmState;
import com.hpcloud.mon.domain.exception.EntityNotFoundException;
import com.hpcloud.mon.domain.model.alarm.Alarm;
import com.hpcloud.mon.domain.model.alarm.AlarmRepository;
import com.hpcloud.mon.domain.model.alarmstatehistory.AlarmStateHistory;
import com.hpcloud.mon.domain.model.alarmstatehistory.AlarmStateHistoryRepository;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinition;
import com.hpcloud.mon.domain.model.alarmdefinition.AlarmDefinitionRepository;
import com.hpcloud.mon.domain.model.common.Link;
import com.hpcloud.mon.resource.exception.ErrorMessages;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.GenericType;
@Test
public class AlarmResourceTest extends AbstractMonApiResourceTest {
public class AlarmDefinitionResourceTest extends AbstractMonApiResourceTest {
private String expression;
private Alarm alarm;
private Alarm alarmItem;
private AlarmService service;
private AlarmRepository repo;
private AlarmStateHistoryRepository stateHistoryRepo;
private AlarmDefinition alarm;
private AlarmDefinition alarmItem;
private AlarmDefinitionService service;
private AlarmDefinitionRepository repo;
private List<String> alarmActions;
@Override
@@ -70,72 +64,72 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
super.setupResources();
expression = "avg(disk_read_ops{service=hpcs.compute, instance_id=937}) >= 90";
List<String> matchBy = Arrays.asList("service", "instance_id");
alarmItem =
new Alarm("123", "Disk Exceeds 1k Operations", null, "LOW", expression, AlarmState.OK,
true, null, null, null);
new AlarmDefinition("123", "Disk Exceeds 1k Operations", null, "LOW", expression,
Arrays.asList("service", "instance_id"), true, null, null, null);
alarmActions = new ArrayList<String>();
alarmActions.add("29387234");
alarmActions.add("77778687");
alarm =
new Alarm("123", "Disk Exceeds 1k Operations", null, "LOW", expression, AlarmState.OK,
new AlarmDefinition("123", "Disk Exceeds 1k Operations", null, "LOW", expression, matchBy,
true, alarmActions, null, null);
service = mock(AlarmService.class);
service = mock(AlarmDefinitionService.class);
when(
service.create(eq("abc"), eq("Disk Exceeds 1k Operations"), any(String.class), eq("LOW"),
eq(expression), eq(AlarmExpression.of(expression)), any(List.class), any(List.class),
any(List.class))).thenReturn(alarm);
eq(expression), eq(AlarmExpression.of(expression)), eq(matchBy), any(List.class),
any(List.class), any(List.class))).thenReturn(alarm);
repo = mock(AlarmRepository.class);
repo = mock(AlarmDefinitionRepository.class);
when(repo.findById(eq("abc"), eq("123"))).thenReturn(alarm);
when(repo.find(anyString(), anyString(), (Map<String, String>) anyMap(), any(String.class)))
.thenReturn(Arrays.asList(alarmItem));
when(repo.find(anyString(), anyString(), (Map<String, String>) anyMap())).thenReturn(
Arrays.asList(alarmItem));
stateHistoryRepo = mock(AlarmStateHistoryRepository.class);
addResources(new AlarmResource(service, repo, stateHistoryRepo));
addResources(new AlarmDefinitionResource(service, repo));
}
@SuppressWarnings("unchecked")
public void shouldCreate() {
ClientResponse response =
createResponseFor(new CreateAlarmCommand("Disk Exceeds 1k Operations", null, expression,
"LOW", alarmActions, null, null));
createResponseFor(new CreateAlarmDefinitionCommand("Disk Exceeds 1k Operations", null,
expression, Arrays.asList("service", "instance_id"), "LOW", alarmActions, null, null));
assertEquals(response.getStatus(), 201);
Alarm newAlarm = response.getEntity(Alarm.class);
AlarmDefinition newAlarm = response.getEntity(AlarmDefinition.class);
String location = response.getHeaders().get("Location").get(0);
assertEquals(location, "/v2.0/alarms/" + newAlarm.getId());
assertEquals(location, "/v2.0/alarm-definitions/" + newAlarm.getId());
assertEquals(newAlarm, alarm);
verify(service).create(eq("abc"), eq("Disk Exceeds 1k Operations"), any(String.class),
eq("LOW"), eq(expression), eq(AlarmExpression.of(expression)), any(List.class),
any(List.class), any(List.class));
eq("LOW"), eq(expression), eq(AlarmExpression.of(expression)),
eq(Arrays.asList("service", "instance_id")), any(List.class), any(List.class),
any(List.class));
}
public void shouldUpdate() {
when(
service.update(eq("abc"), eq("123"), any(AlarmExpression.class),
any(UpdateAlarmCommand.class))).thenReturn(alarm);
any(UpdateAlarmDefinitionCommand.class))).thenReturn(alarm);
ClientResponse response =
client()
.resource("/v2.0/alarms/123")
.resource("/v2.0/alarm-definitions/123")
.header("X-Tenant-Id", "abc")
.header("Content-Type", MediaType.APPLICATION_JSON)
.put(
ClientResponse.class,
new UpdateAlarmCommand("Disk Exceeds 1k Operations", null, expression, "LOW",
AlarmState.ALARM, true, alarmActions, null, null));
new UpdateAlarmDefinitionCommand("Disk Exceeds 1k Operations", null, expression,
Arrays.asList("service", "instance_id"), "LOW", true, alarmActions, null, null));
assertEquals(response.getStatus(), 200);
verify(service).update(eq("abc"), eq("123"), any(AlarmExpression.class),
any(UpdateAlarmCommand.class));
any(UpdateAlarmDefinitionCommand.class));
}
public void shouldErrorOnCreateWithInvalidMetricName() {
String expression = "avg(foo{service=hpcs.compute, instance_id=937}) >= 90";
ClientResponse response =
createResponseFor(new CreateAlarmCommand("Disk Exceeds 1k Operations", null, expression,
"LOW", alarmActions, null, null));
createResponseFor(new CreateAlarmDefinitionCommand("Disk Exceeds 1k Operations", null,
expression, Arrays.asList("service", "instance_id"), "LOW", alarmActions, null, null));
ErrorMessages.assertThat(response.getEntity(String.class)).matches("unprocessable_entity", 422,
"foo is not a valid metric name for namespace hpcs.compute");
@@ -144,8 +138,8 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
public void shouldErrorOnCreateWithInvalidDimensions() {
String expression = "avg(disk_read_ops{service=hpcs.compute, instance_id=937, foo=bar}) >= 90";
ClientResponse response =
createResponseFor(new CreateAlarmCommand("Disk Exceeds 1k Operations", null, expression,
"LOW", alarmActions, null, null));
createResponseFor(new CreateAlarmDefinitionCommand("Disk Exceeds 1k Operations", null,
expression, Arrays.asList("service", "instance_id"), "LOW", alarmActions, null, null));
ErrorMessages.assertThat(response.getEntity(String.class)).matches("unprocessable_entity", 422,
"foo is not a valid dimension name for service hpcs.compute");
@@ -155,8 +149,8 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
String expression =
"avg(hpcs.compute{instance_id=937, instance_id=123, az=2, instance_uuid=abc123, metric_name=disk_read_ops}) >= 90";
ClientResponse response =
createResponseFor(new CreateAlarmCommand("Disk Exceeds 1k Operations", null, expression,
"LOW", alarmActions, null, null));
createResponseFor(new CreateAlarmDefinitionCommand("Disk Exceeds 1k Operations", null,
expression, Arrays.asList("service", "instance_id"), "LOW", alarmActions, null, null));
ErrorMessages.assertThat(response.getEntity(String.class)).matches("unprocessable_entity", 422,
"The alarm expression is invalid",
@@ -169,10 +163,10 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
when(
service.create(eq("abc"), eq("Disk Exceeds 1k Operations"), any(String.class), eq("LOW"),
eq(expression), eq(AlarmExpression.of(expression)), any(List.class), any(List.class),
any(List.class))).thenReturn(alarm);
any(List.class), any(List.class))).thenReturn(alarm);
ClientResponse response =
createResponseFor(new CreateAlarmCommand("Disk Exceeds 1k Operations", null, expression,
"LOW", alarmActions, null, null));
createResponseFor(new CreateAlarmDefinitionCommand("Disk Exceeds 1k Operations", null,
expression, Arrays.asList("service", "instance_id"), "LOW", alarmActions, null, null));
assertEquals(response.getStatus(), 201);
}
@@ -188,8 +182,8 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
String expression =
"avg(hpcs.compute{instance_id=937, az=2, instance_uuid=0ff588fc-d298-482f-bb11-4b52d56801a4, metric_name=disk_read_ops}) & 90";
ClientResponse response =
createResponseFor(new CreateAlarmCommand("Disk Exceeds 1k Operations", null, expression,
"LOW", alarmActions, null, null));
createResponseFor(new CreateAlarmDefinitionCommand("Disk Exceeds 1k Operations", null,
expression, Arrays.asList("service", "instance_id"), "LOW", alarmActions, null, null));
ErrorMessages.assertThat(response.getEntity(String.class)).matches("unprocessable_entity", 422,
"The alarm expression is invalid", "Syntax Error");
@@ -199,8 +193,8 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
String expression =
"avg(hpcs.compute{instance_id=937, az=2, instance_uuid=0ff588fc-d298-482f-bb11-4b52d56801a4, metric_name=disk_read_ops},0) >= 90";
ClientResponse response =
createResponseFor(new CreateAlarmCommand("Disk Exceeds 1k Operations", null, expression,
"LOW", alarmActions, null, null));
createResponseFor(new CreateAlarmDefinitionCommand("Disk Exceeds 1k Operations", null,
expression, Arrays.asList("service", "instance_id"), "LOW", alarmActions, null, null));
ErrorMessages.assertThat(response.getEntity(String.class)).matches("unprocessable_entity", 422,
"Period must not be 0");
@@ -210,8 +204,8 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
String expression =
"avg(hpcs.compute{instance_id=937, az=2, instance_uuid=0ff588fc-d298-482f-bb11-4b52d56801a4, metric_name=disk_read_ops},61) >= 90";
ClientResponse response =
createResponseFor(new CreateAlarmCommand("Disk Exceeds 1k Operations", null, expression,
"LOW", alarmActions, null, null));
createResponseFor(new CreateAlarmDefinitionCommand("Disk Exceeds 1k Operations", null,
expression, Arrays.asList("service", "instance_id"), "LOW", alarmActions, null, null));
ErrorMessages.assertThat(response.getEntity(String.class)).matches("unprocessable_entity", 422,
"Period 61 must be a multiple of 60");
@@ -221,8 +215,8 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
String expression =
"avg(hpcs.compute{instance_id=937, az=2, instance_uuid=0ff588fc-d298-482f-bb11-4b52d56801a4, metric_name=disk_read_ops}) >= 90 times 0";
ClientResponse response =
createResponseFor(new CreateAlarmCommand("Disk Exceeds 1k Operations", null, expression,
"LOW", alarmActions, null, null));
createResponseFor(new CreateAlarmDefinitionCommand("Disk Exceeds 1k Operations", null,
expression, Arrays.asList("service", "instance_id"), "LOW", alarmActions, null, null));
ErrorMessages.assertThat(response.getEntity(String.class)).matches("unprocessable_entity", 422,
"Periods 0 must be greater than or equal to 1");
@@ -232,8 +226,8 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
String expression =
"avg(hpcs.compute{instance_id=937, az=2, instance_uuid=0ff588fc-d298-482f-bb11-4b52d56801a4, metric_name=disk_read_ops},60) >= 90 times 20161";
ClientResponse response =
createResponseFor(new CreateAlarmCommand("Disk Exceeds 1k Operations", null, expression,
"LOW", alarmActions, null, null));
createResponseFor(new CreateAlarmDefinitionCommand("Disk Exceeds 1k Operations", null,
expression, Arrays.asList("service", "instance_id"), "LOW", alarmActions, null, null));
ErrorMessages.assertThat(response.getEntity(String.class)).matches("unprocessable_entity", 422,
"Period 60 times 20161 must total less than 2 weeks in seconds (1209600)");
@@ -243,11 +237,12 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
String expression =
"avg(hpcs.compute{instance_id=937, az=2, instance_uuid=0ff588fc-d298-482f-bb11-4b52d56801a4, metric_name=disk_read_ops}) >= 90";
ClientResponse response =
createResponseFor(new CreateAlarmCommand(
createResponseFor(new CreateAlarmDefinitionCommand(
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
null, "LOW", expression, alarmActions, null, null));
null, expression, Arrays.asList("service", "instance_id"), "LOW", alarmActions, null,
null));
ErrorMessages
.assertThat(response.getEntity(String.class))
@@ -261,8 +256,8 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
alarmActions = new ArrayList<String>();
alarmActions.add("012345678901234567890123456789012345678901234567890");
ClientResponse response =
createResponseFor(new CreateAlarmCommand("Disk Exceeds 1k Operations", null, "LOW",
expression, alarmActions, null, null));
createResponseFor(new CreateAlarmDefinitionCommand("Disk Exceeds 1k Operations", null,
expression, Arrays.asList("service", "instance_id"), "LOW", alarmActions, null, null));
ErrorMessages
.assertThat(response.getEntity(String.class))
@@ -272,28 +267,28 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
@SuppressWarnings("unchecked")
public void shouldList() {
List<Alarm> alarms =
client().resource("/v2.0/alarms").header("X-Tenant-Id", "abc")
.get(new GenericType<List<Alarm>>() {});
List<AlarmDefinition> alarms =
client().resource("/v2.0/alarm-definitions").header("X-Tenant-Id", "abc")
.get(new GenericType<List<AlarmDefinition>>() {});
assertEquals(alarms, Arrays.asList(alarmItem));
verify(repo).find(eq("abc"), anyString(), (Map<String, String>) anyMap(), any(String.class));
verify(repo).find(eq("abc"), anyString(), (Map<String, String>) anyMap());
}
@SuppressWarnings("unchecked")
public void shouldListByName() throws Exception {
List<Alarm> alarms =
client().resource("/v2.0/alarms?name=" + URLEncoder.encode("foo bar baz", "UTF-8"))
.header("X-Tenant-Id", "abc").get(new GenericType<List<Alarm>>() {});
List<AlarmDefinition> alarms =
client().resource("/v2.0/alarm-definitions?name=" + URLEncoder.encode("foo bar baz", "UTF-8"))
.header("X-Tenant-Id", "abc").get(new GenericType<List<AlarmDefinition>>() {});
assertEquals(alarms, Arrays.asList(alarmItem));
verify(repo).find(eq("abc"), eq("foo bar baz"), (Map<String, String>) anyMap(),
any(String.class));
verify(repo).find(eq("abc"), eq("foo bar baz"), (Map<String, String>) anyMap());
}
public void shouldGet() {
assertEquals(client().resource("/v2.0/alarms/123").header("X-Tenant-Id", "abc")
.get(Alarm.class), alarm);
assertEquals(
client().resource("/v2.0/alarm-definitions/123").header("X-Tenant-Id", "abc")
.get(AlarmDefinition.class), alarm);
verify(repo).findById(eq("abc"), eq("123"));
}
@@ -301,7 +296,7 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
doThrow(new EntityNotFoundException(null)).when(repo).findById(eq("abc"), eq("999"));
try {
client().resource("/v2.0/alarms/999").header("X-Tenant-Id", "abc").get(Alarm.class);
client().resource("/v2.0/alarm-definitions/999").header("X-Tenant-Id", "abc").get(AlarmDefinition.class);
fail();
} catch (Exception e) {
assertTrue(e.getMessage().contains("404"));
@@ -310,7 +305,7 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
public void shouldDelete() {
ClientResponse response =
client().resource("/v2.0/alarms/123").header("X-Tenant-Id", "abc")
client().resource("/v2.0/alarm-definitions/123").header("X-Tenant-Id", "abc")
.delete(ClientResponse.class);
assertEquals(response.getStatus(), 204);
verify(service).delete(eq("abc"), eq("123"));
@@ -320,7 +315,7 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
doThrow(new EntityNotFoundException(null)).when(service).delete(eq("abc"), eq("999"));
try {
client().resource("/v2.0/alarms/999").header("X-Tenant-Id", "abc").delete();
client().resource("/v2.0/alarm-definitions/999").header("X-Tenant-Id", "abc").delete();
fail();
} catch (Exception e) {
assertTrue(e.getMessage().contains("404"));
@@ -330,10 +325,10 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
@SuppressWarnings("unchecked")
public void should500OnInternalException() {
doThrow(new RuntimeException("")).when(repo).find(anyString(), anyString(),
(Map<String, String>) anyObject(), (String) anyObject());
(Map<String, String>) anyObject());
try {
client().resource("/v2.0/alarms").header("X-Tenant-Id", "abc").get(List.class);
client().resource("/v2.0/alarm-definitions").header("X-Tenant-Id", "abc").get(List.class);
fail();
} catch (Exception e) {
assertTrue(e.getMessage().contains("500"));
@@ -342,39 +337,23 @@ public class AlarmResourceTest extends AbstractMonApiResourceTest {
public void shouldHydateLinksOnList() {
List<Link> expected =
Arrays.asList(new Link("self", "/v2.0/alarms/123"), new Link("history",
"/v2.0/alarms/123/history"));
Arrays.asList(new Link("self", "/v2.0/alarm-definitions/123"));
List<Link> links =
client().resource("/v2.0/alarms").header("X-Tenant-Id", "abc")
.get(new GenericType<List<Alarm>>() {}).get(0).getLinks();
client().resource("/v2.0/alarm-definitions").header("X-Tenant-Id", "abc")
.get(new GenericType<List<AlarmDefinition>>() {}).get(0).getLinks();
assertEquals(links, expected);
}
public void shouldHydateLinksOnGet() {
List<Link> links =
Arrays.asList(new Link("self", "/v2.0/alarms/123"), new Link("history",
"/v2.0/alarms/123/history"));
assertEquals(client().resource("/v2.0/alarms/123").header("X-Tenant-Id", "abc")
.get(Alarm.class).getLinks(), links);
}
public void shouldGetAlarmStateHistory() throws Exception {
AlarmStateHistory history1 =
new AlarmStateHistory("123", AlarmState.OK, AlarmState.ALARM, "foo", "foobar",
new DateTime(2014, 1, 1, 1, 1, DateTimeZone.UTC));
AlarmStateHistory history2 =
new AlarmStateHistory("123", AlarmState.ALARM, AlarmState.OK, "foo", "foobar",
new DateTime(2014, 1, 1, 1, 1, DateTimeZone.UTC));
List<AlarmStateHistory> expected = Arrays.asList(history1, history2);
when(stateHistoryRepo.findById(eq("abc"), eq("123"))).thenReturn(expected);
assertEquals(client().resource("/v2.0/alarms/123/state-history").header("X-Tenant-Id", "abc")
.get(new GenericType<List<AlarmStateHistory>>() {}), expected);
Arrays.asList(new Link("self", "/v2.0/alarm-definitions/123"));
assertEquals(
client().resource("/v2.0/alarm-definitions/123").header("X-Tenant-Id", "abc")
.get(AlarmDefinition.class).getLinks(), links);
}
private ClientResponse createResponseFor(Object request) {
return client().resource("/v2.0/alarms").header("X-Tenant-Id", "abc")
return client().resource("/v2.0/alarm-definitions").header("X-Tenant-Id", "abc")
.header("Content-Type", MediaType.APPLICATION_JSON).post(ClientResponse.class, request);
}
}

View File

@@ -1,11 +1,21 @@
CREATE TABLE `alarm` (
`id` varchar(36) NOT NULL,
`alarm_definition_id` varchar(36) NOT NULL DEFAULT '',
`state` varchar(20) NOT NULL check state in ('UNDETERMINED','OK','ALARM'),
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `tenant_id` (`alarm_definition_id`)
);
CREATE TABLE `alarm_definition` (
`id` varchar(36) NOT NULL,
`tenant_id` varchar(36) NOT NULL,
`name` varchar(250) DEFAULT NULL,
`description` varchar(250) DEFAULT NULL,
`expression` mediumtext,
`severity` varchar(20) NOT NULL check severity in ('LOW','MEDIUM','HIGH','CRITICAL'),
`state` varchar(20) NOT NULL check state in ('UNDETERMINED','OK','ALARM'),
`match_by` varchar(255) DEFAULT '',
`actions_enabled` tinyint(1) NOT NULL DEFAULT '1',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
@@ -13,19 +23,46 @@ CREATE TABLE `alarm` (
PRIMARY KEY (`id`)
);
CREATE TABLE `alarm_metric` (
`alarm_id` varchar(36) NOT NULL,
`metric_definition_dimensions_id` binary(20) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
PRIMARY KEY (`alarm_id`,`metric_definition_dimensions_id`)
);
CREATE TABLE `metric_definition` (
`id` binary(20) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
`name` varchar(255) NOT NULL,
`tenant_id` varchar(36) NOT NULL,
`region` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
);
CREATE TABLE `metric_definition_dimensions` (
`id` binary(20) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
`metric_definition_id` binary(20) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
`metric_dimension_set_id` binary(20) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
PRIMARY KEY (`id`)
);
CREATE TABLE `metric_dimension` (
`dimension_set_id` binary(20) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
`name` varchar(255) NOT NULL DEFAULT '',
`value` varchar(255) NOT NULL DEFAULT ''
);
CREATE TABLE `sub_alarm` (
`id` varchar(36) NOT NULL,
`alarm_id` varchar(36) NOT NULL,
`alarm_definition_id` varchar(36) NOT NULL DEFAULT '',
`function` varchar(10) NOT NULL,
`metric_name` varchar(100) DEFAULT NULL,
`operator` varchar(5) NOT NULL,
`threshold` double NOT NULL,
`period` int(11) NOT NULL,
`periods` int(11) NOT NULL,
`state` varchar(20) NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`)
PRIMARY KEY (`id`),
KEY `fk_sub_alarm` (`alarm_definition_id`)
);
CREATE TABLE `sub_alarm_dimension` (
@@ -39,5 +76,5 @@ CREATE TABLE `alarm_action` (
`alarm_id` varchar(36) NOT NULL,
`alarm_state` varchar(20) NOT NULL check alarm_state in ('UNDETERMINED','OK','ALARM'),
`action_id` varchar(36) NOT NULL DEFAULT '',
PRIMARY KEY (`alarm_id`,`alarm_state`,`action_id`),
PRIMARY KEY (`alarm_id`,`alarm_state`,`action_id`)
);

View File

@@ -1 +1 @@
{"id":"123","links":[{"rel":"self","href":"https://region-a.geo-1.maas.hpcloudsvc.com/v1.0"}],"name":"90% CPU","description":"","expression":"avg(hpcs.compute{instance_id=666, image_id=345}) >= 90","expression_data":{"function":"AVG","metric_name":"hpcs.compute","dimensions":{"image_id":"345","instance_id":"666"},"operator":"GTE","threshold":90.0,"period":60,"periods":1},"state":"OK","severity":"LOW","actions_enabled":false,"alarm_actions":["123345345","23423"],"ok_actions":null,"undetermined_actions":null}
{"id":"123","links":[{"rel":"self","href":"https://region-a.geo-1.maas.hpcloudsvc.com/v1.0"}],"name":"90% CPU","description":"","expression":"avg(hpcs.compute{instance_id=666, image_id=345}) >= 90","match_by":[],"severity":"LOW","actions_enabled":false,"alarm_actions":["123345345","23423"],"ok_actions":null,"undetermined_actions":null}

View File

@@ -24,7 +24,7 @@ kafka:
mysql:
driverClass: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.59.103:3306/mon?connectTimeout=5000&autoReconnect=true
url: jdbc:mysql://localhost:3306/mon?connectTimeout=5000&autoReconnect=true
user: monapi
password: password
maxWaitForConnection: 1s
@@ -34,16 +34,16 @@ mysql:
checkConnectionWhileIdle: false
checkConnectionOnBorrow: true
vertica:
driverClass: com.vertica.jdbc.Driver
url: jdbc:vertica://192.168.10.4/mon
user: mon_api
password: password
maxWaitForConnection: 1s
validationQuery: "/* MyService Health Check */ SELECT 1"
minSize: 4
maxSize: 32
checkConnectionWhileIdle: false
# vertica:
# driverClass: com.vertica.jdbc.Driver
# url: jdbc:vertica://192.168.10.4/mon
# user: mon_api
# password: password
# maxWaitForConnection: 1s
# validationQuery: "/* MyService Health Check */ SELECT 1"
# minSize: 4
# maxSize: 32
# checkConnectionWhileIdle: false
influxDB: