monasca-api/java/src/main/java/monasca/api/infrastructure/persistence/mysql/AlarmDefinitionMySqlRepoImp...

449 lines
18 KiB
Java

/*
* (C) Copyright 2014,2016 Hewlett Packard Enterprise Development Company LP
*
* 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 monasca.api.infrastructure.persistence.mysql;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.sql.SQLException;
import java.sql.ResultSet;
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.tweak.ResultSetMapper;
import org.skife.jdbi.v2.StatementContext;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import monasca.api.infrastructure.persistence.PersistUtils;
import monasca.common.model.alarm.AggregateFunction;
import monasca.common.model.alarm.AlarmOperator;
import monasca.common.model.alarm.AlarmSeverity;
import monasca.common.model.alarm.AlarmState;
import monasca.common.model.alarm.AlarmSubExpression;
import monasca.common.model.metric.MetricDefinition;
import monasca.common.util.Conversions;
import monasca.api.domain.exception.EntityNotFoundException;
import monasca.api.domain.model.alarmdefinition.AlarmDefinition;
import monasca.api.domain.model.alarmdefinition.AlarmDefinitionRepo;
import monasca.api.infrastructure.persistence.DimensionQueries;
import monasca.api.infrastructure.persistence.SubAlarmDefinitionQueries;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
/**
* Alarm repository implementation.
*/
public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo {
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 static final String CREATE_SUB_EXPRESSION_SQL = "insert into sub_alarm_definition "
+ "(id, alarm_definition_id, function, metric_name, "
+ "operator, threshold, period, periods, is_deterministic, "
+ "created_at, updated_at) "
+ "values (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())";
private static final String UPDATE_SUB_ALARM_DEF_SQL = "update sub_alarm_definition set "
+ "operator = ?, threshold = ?, is_deterministic = ?, updated_at = NOW() where id = ?";
private final DBI db;
private final PersistUtils persistUtils;
@Inject
public AlarmDefinitionMySqlRepoImpl(@Named("mysql") DBI db, PersistUtils persistUtils) {
this.db = db;
this.persistUtils = persistUtils;
}
@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 String exists(String tenantId, String name) {
try (Handle h = db.open()) {
Map<String, Object> map = h
.createQuery(
"select id from alarm_definition where tenant_id = :tenantId and name = :name and deleted_at is NULL")
.bind("tenantId", tenantId).bind("name", name).first();
if (map != null) {
if (map.values().size() != 0) {
return map.get("id").toString();
} else {
return null;
}
} else {
return null;
}
}
}
@SuppressWarnings("unchecked")
@Override
public List<AlarmDefinition> find(String tenantId, String name,
Map<String, String> dimensions, List<AlarmSeverity> severities,
List<String> sortBy, String offset, int limit) {
try (Handle h = db.open()) {
String query =
" SELECT t.id, t.tenant_id, t.name, t.description, t.expression, t.severity, t.match_by,"
+ " t.actions_enabled, t.created_at, t.updated_at, t.deleted_at, "
+ " GROUP_CONCAT(aa.alarm_state) AS states, "
+ " GROUP_CONCAT(aa.action_id) AS notificationIds "
+ "FROM (SELECT distinct ad.id, ad.tenant_id, ad.name, ad.description, ad.expression,"
+ " ad.severity, ad.match_by, ad.actions_enabled, ad.created_at, "
+ " ad.updated_at, ad.deleted_at "
+ " FROM alarm_definition AS ad "
+ " LEFT OUTER JOIN sub_alarm_definition AS sad ON ad.id = sad.alarm_definition_id "
+ " LEFT OUTER JOIN sub_alarm_definition_dimension AS dim ON sad.id = dim.sub_alarm_definition_id %1$s "
+ " WHERE ad.tenant_id = :tenantId AND ad.deleted_at IS NULL %2$s) AS t "
+ "LEFT OUTER JOIN alarm_action AS aa ON t.id = aa.alarm_definition_id "
+ "GROUP BY t.id %3$s %4$s %5$s";
StringBuilder sbWhere = new StringBuilder();
if (name != null) {
sbWhere.append(" and ad.name = :name");
}
sbWhere.append(MySQLUtils.buildSeverityAndClause(severities));
String orderByPart = "";
if (sortBy != null && !sortBy.isEmpty()) {
orderByPart = " order by " + COMMA_JOINER.join(sortBy);
if (!orderByPart.contains("id")) {
orderByPart = orderByPart + ",id";
}
} else {
orderByPart = " order by id ";
}
String limitPart = "";
if (limit > 0) {
limitPart = " limit :limit";
}
String offsetPart = "";
if (offset != null) {
offsetPart = " offset " + offset + ' ';
}
String sql = String.format(query,
SubAlarmDefinitionQueries.buildJoinClauseFor(dimensions), sbWhere, orderByPart,
limitPart, offsetPart);
Query<?> q = h.createQuery(sql);
q.bind("tenantId", tenantId);
if (name != null) {
q.bind("name", name);
}
MySQLUtils.bindSeverityToQuery(q, severities);
if (limit > 0) {
q.bind("limit", limit + 1);
}
q.registerMapper(new AlarmDefinitionMapper());
q = q.mapTo(AlarmDefinition.class);
SubAlarmDefinitionQueries.bindDimensionsToQuery(q, dimensions);
List<AlarmDefinition> resultSet = (List<AlarmDefinition>) q.list();
return resultSet;
}
}
@Override
public AlarmDefinition findById(String tenantId, String alarmDefId) {
try (Handle h = db.open()) {
String query = "SELECT alarm_definition.id, alarm_definition.tenant_id, alarm_definition.name, alarm_definition.description, "
+ "alarm_definition.expression, alarm_definition.severity, alarm_definition.match_by, alarm_definition.actions_enabled, "
+" alarm_definition.created_at, alarm_definition.updated_at, alarm_definition.deleted_at, "
+ "GROUP_CONCAT(alarm_action.action_id) AS notificationIds,group_concat(alarm_action.alarm_state) AS states "
+ "FROM alarm_definition LEFT OUTER JOIN alarm_action ON alarm_definition.id=alarm_action.alarm_definition_id "
+ " WHERE alarm_definition.tenant_id=:tenantId AND alarm_definition.id=:alarmDefId AND alarm_definition.deleted_at "
+ " IS NULL GROUP BY alarm_definition.id";
Query<?> q = h.createQuery(query);
q.bind("tenantId", tenantId);
q.bind("alarmDefId", alarmDefId);
q.registerMapper(new AlarmDefinitionMapper());
q = q.mapTo(AlarmDefinition.class);
AlarmDefinition alarmDefinition = (AlarmDefinition) q.first();
if(alarmDefinition == null)
{
throw new EntityNotFoundException("No alarm definition exists for %s", alarmDefId);
}
return alarmDefinition;
}
}
@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");
// MySQL connector returns an Integer, Drizzle returns a Long for period and periods.
// Need to convert the results appropriately based on type.
Integer period = Conversions.variantToInteger(row.get("period"));
Integer periods = Conversions.variantToInteger(row.get("periods"));
Boolean isDeterministic = (Boolean) row.get("is_deterministic");
Map<String, String> dimensions =
DimensionQueries.dimensionsFor((String) row.get("dimensions"));
subExpressions.put(
id,
new AlarmSubExpression(
function,
new MetricDefinition(metricName, dimensions),
operator,
threshold,
period,
periods,
isDeterministic
)
);
}
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_DEF_SQL,
sa.getOperator().name(),
sa.getThreshold(),
sa.isDeterministic(),
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_definition_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_definition_id = ? and alarm_state = ?", id,
alarmState.name());
}
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 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(CREATE_SUB_EXPRESSION_SQL, subAlarmId, id, subExpr.getFunction().name(),
metricDef.name, subExpr.getOperator().name(), subExpr.getThreshold(),
subExpr.getPeriod(), subExpr.getPeriods(), subExpr.isDeterministic());
// 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());
}
}
}
private static class AlarmDefinitionMapper implements ResultSetMapper<AlarmDefinition> {
private static final Splitter
COMMA_SPLITTER =
Splitter.on(',').omitEmptyStrings().trimResults();
public AlarmDefinition map(int index, ResultSet r, StatementContext ctx) throws SQLException {
String notificationIds = r.getString("notificationIds");
String states = r.getString("states");
String matchBy = r.getString("match_by");
List<String> notifications = splitStringIntoList(notificationIds);
List<String> state = splitStringIntoList(states);
List<String> match = splitStringIntoList(matchBy);
List<String> okActionIds = new ArrayList<String>();
List<String> alarmActionIds = new ArrayList<String>();
List<String> undeterminedActionIds = new ArrayList<String>();
int stateAndActionIndex = 0;
for (String singleState : state) {
if (singleState.equals(AlarmState.UNDETERMINED.name())) {
undeterminedActionIds.add(notifications.get(stateAndActionIndex));
}
if (singleState.equals(AlarmState.OK.name())) {
okActionIds.add(notifications.get(stateAndActionIndex));
}
if (singleState.equals(AlarmState.ALARM.name())) {
alarmActionIds.add(notifications.get(stateAndActionIndex));
}
stateAndActionIndex++;
}
return new AlarmDefinition(r.getString("id"), r.getString("name"), r.getString("description"),
r.getString("severity"), r.getString("expression"), match,
r.getBoolean("actions_enabled"), alarmActionIds, okActionIds,
undeterminedActionIds);
}
private List<String> splitStringIntoList(String commaDelimitedString) {
if (commaDelimitedString == null) {
return new ArrayList<String>();
}
Iterable<String> split = COMMA_SPLITTER.split(commaDelimitedString);
return Lists.newArrayList(split);
}
}
}