diff --git a/thresh/src/main/java/monasca/thresh/TopologyModule.java b/thresh/src/main/java/monasca/thresh/TopologyModule.java index 761241d..35b8c82 100644 --- a/thresh/src/main/java/monasca/thresh/TopologyModule.java +++ b/thresh/src/main/java/monasca/thresh/TopologyModule.java @@ -1,5 +1,5 @@ /* - * (C) Copyright 2015-2016 Hewlett Packard Enterprise Development Company LP. + * (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -159,7 +159,7 @@ public class TopologyModule extends AbstractModule { // Filtering / Event / Alarm Creation -> Aggregation builder .setBolt("aggregation-bolt", - new MetricAggregationBolt(config), config.aggregationBoltThreads) + new MetricAggregationBolt(config, config.database), config.aggregationBoltThreads) .fieldsGrouping("filtering-bolt", new Fields(MetricFilteringBolt.FIELDS[0])) .allGrouping("filtering-bolt", MetricAggregationBolt.METRIC_AGGREGATION_CONTROL_STREAM) .fieldsGrouping("filtering-bolt", AlarmCreationBolt.ALARM_CREATION_STREAM, diff --git a/thresh/src/main/java/monasca/thresh/domain/model/SubAlarm.java b/thresh/src/main/java/monasca/thresh/domain/model/SubAlarm.java index 9572b13..d615b91 100644 --- a/thresh/src/main/java/monasca/thresh/domain/model/SubAlarm.java +++ b/thresh/src/main/java/monasca/thresh/domain/model/SubAlarm.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. + * (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP * Copyright 2016 FUJITSU LIMITED * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -197,8 +197,11 @@ public class SubAlarm extends AbstractEntity implements Serializable { return true; } - public boolean canEvaluateImmediately() { + public boolean canEvaluateAlarmImmediately() { switch (this.getExpression().getFunction()) { + // LAST must be evaluated immediately + case LAST: + return true; // MIN never gets larger so if the operator is < or <=, // then they can be immediately evaluated case MIN: @@ -227,6 +230,50 @@ public class SubAlarm extends AbstractEntity implements Serializable { } } + public boolean canEvaluateOkImmediately() { + switch (this.getExpression().getFunction()) { + // LAST must be evaluated immediately + case LAST: + return true; + // MIN never gets larger so if the operator is > or >=, + // then they can be immediately evaluated + case MIN: + switch(this.getExpression().getOperator()) { + case GT: + case GTE: + return true; + default: + return false; + } + // These two never get smaller so if the operator is < or <=, + // then they can be immediately evaluated + case MAX: + case COUNT: + switch(this.getExpression().getOperator()) { + case LT: + case LTE: + return true; + default: + return false; + } + // SUM can increase on a positive measurement or decrease on a negative + // AVG can't be computed until all the metrics have come in + default: + return false; + } + } + + public boolean onlyImmediateEvaluation() { + switch (this.getExpression().getFunction()) { + // LAST must be evaluated immediately + case LAST: + return true; + // All others at this time can't be evaluated immediately + default: + return false; + } + } + /** * Computes initial state for an {@link SubAlarm} based on * underlying {@link SubExpression}. diff --git a/thresh/src/main/java/monasca/thresh/domain/model/SubAlarmStats.java b/thresh/src/main/java/monasca/thresh/domain/model/SubAlarmStats.java index 12e917b..37e7b90 100644 --- a/thresh/src/main/java/monasca/thresh/domain/model/SubAlarmStats.java +++ b/thresh/src/main/java/monasca/thresh/domain/model/SubAlarmStats.java @@ -1,5 +1,5 @@ /* - * (C) Copyright 2014-2016 Hewlett Packard Enterprise Development Company LP. + * (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP * Copyright 2016 FUJITSU LIMITED * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -101,6 +101,11 @@ public class SubAlarmStats { return stats; } + public boolean addValue(double value, long timestamp) { + return this.getStats().addValue(value, timestamp, + this.getSubAlarm().onlyImmediateEvaluation()); + } + /** * Returns the SubAlarm. */ @@ -124,19 +129,28 @@ public class SubAlarmStats { final boolean shouldEvaluate = this.stats.shouldEvaluate(now, alarmDelay); final AlarmState newState; + final boolean immediateAlarmTransition; if (immediateAlarmEvaluate()) { newState = AlarmState.ALARM; - } else { + immediateAlarmTransition = true; + } + else if (immediateOkEvaluate()) { + newState = AlarmState.OK; + immediateAlarmTransition = true; + } + else { if (!shouldEvaluate) { return false; } + if (this.subAlarm.onlyImmediateEvaluation()) { + return false; + } newState = this.determineAlarmStateUsingView(); + immediateAlarmTransition = false; } - + final boolean shouldSendStateChange = this.shouldSendStateChange(newState); - final boolean immediateAlarmTransition = - newState == AlarmState.ALARM && this.subAlarm.canEvaluateImmediately(); - + if (shouldSendStateChange && (shouldEvaluate || immediateAlarmTransition)) { logger.debug("SubAlarm[deterministic={}] {} transitions from {} to {}", this.getSubAlarm().isDeterministic(), @@ -209,7 +223,7 @@ public class SubAlarmStats { } private boolean immediateAlarmEvaluate() { - if (!this.subAlarm.canEvaluateImmediately()) { + if (!this.subAlarm.canEvaluateAlarmImmediately()) { return false; } // Check the future slots as well @@ -240,6 +254,31 @@ public class SubAlarmStats { return false; } + private boolean immediateOkEvaluate() { + if (!this.subAlarm.canEvaluateOkImmediately()) { + return false; + } + // Check the future slots as well + final double[] allValues = stats.getWindowValues(); + subAlarm.clearCurrentValues(); + for (final double value : allValues) { + if (Double.isNaN(value)) { + subAlarm.clearCurrentValues(); + } else { + // Check if value is ALARM + if (subAlarm.getExpression().getOperator() + .evaluate(value, subAlarm.getExpression().getThreshold())) { + subAlarm.clearCurrentValues(); + } + else { + subAlarm.addCurrentValue(value); + return true; + } + } + } + return false; + } + private boolean shouldSendStateChange(AlarmState newState) { return newState != null && (!subAlarm.getState().equals(newState) || subAlarm.isNoState()); } diff --git a/thresh/src/main/java/monasca/thresh/domain/service/AlarmDAO.java b/thresh/src/main/java/monasca/thresh/domain/service/AlarmDAO.java index 33d2968..fe698e9 100644 --- a/thresh/src/main/java/monasca/thresh/domain/service/AlarmDAO.java +++ b/thresh/src/main/java/monasca/thresh/domain/service/AlarmDAO.java @@ -51,4 +51,7 @@ public interface AlarmDAO { /** Deletes all alarms for the given AlarmDefinition */ void deleteByDefinitionId(String alarmDefinitionId); + + /** Update the state of the given SubAlarm */ + void updateSubAlarmState(String subAlarmId, AlarmState subAlarmState); } diff --git a/thresh/src/main/java/monasca/thresh/infrastructure/persistence/AlarmDAOImpl.java b/thresh/src/main/java/monasca/thresh/infrastructure/persistence/AlarmDAOImpl.java index 85058e0..d8df24f 100644 --- a/thresh/src/main/java/monasca/thresh/infrastructure/persistence/AlarmDAOImpl.java +++ b/thresh/src/main/java/monasca/thresh/infrastructure/persistence/AlarmDAOImpl.java @@ -79,7 +79,8 @@ public class AlarmDAOImpl implements AlarmDAO { try (final Handle h = db.open()) { final String ALARMS_SQL = - "select a.id, a.alarm_definition_id, a.state, sa.id as sub_alarm_id, sa.expression, sa.sub_expression_id, ad.tenant_id from alarm a " + "select a.id, a.alarm_definition_id, a.state, sa.id as sub_alarm_id, sa.expression, " + + "sa.state as sub_alarm_state, sa.sub_expression_id, ad.tenant_id from alarm a " + "inner join sub_alarm sa on sa.alarm_id = a.id " + "inner join alarm_definition ad on a.alarm_definition_id = ad.id " + "where ad.deleted_at is null and %s " @@ -113,8 +114,9 @@ public class AlarmDAOImpl implements AlarmDAO { final SubExpression subExpression = new SubExpression(getString(row, "sub_expression_id"), AlarmSubExpression.of(getString( row, "expression"))); + final AlarmState subAlarmState = AlarmState.valueOf(getString(row, "sub_alarm_state")); final SubAlarm subAlarm = - new SubAlarm(getString(row, "sub_alarm_id"), alarmId, subExpression); + new SubAlarm(getString(row, "sub_alarm_id"), alarmId, subExpression, subAlarmState); subAlarms.add(subAlarm); prevAlarmId = alarmId; } @@ -256,9 +258,9 @@ public class AlarmDAOImpl implements AlarmDAO { for (final SubAlarm subAlarm : alarm.getSubAlarms()) { h.insert( - "insert into sub_alarm (id, alarm_id, sub_expression_id, expression, created_at, updated_at) values (?, ?, ?, ?, ?, ?)", + "insert into sub_alarm (id, alarm_id, sub_expression_id, expression, state, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?)", subAlarm.getId(), subAlarm.getAlarmId(), subAlarm.getAlarmSubExpressionId(), subAlarm - .getExpression().getExpression(), timestamp, timestamp); + .getExpression().getExpression(), subAlarm.getState().toString(), timestamp, timestamp); } for (final MetricDefinitionAndTenantId md : alarm.getAlarmedMetrics()) { createAlarmedMetric(h, md, alarm.getId()); @@ -306,6 +308,18 @@ public class AlarmDAOImpl implements AlarmDAO { } } + @Override + public void updateSubAlarmState(String id, AlarmState subAlarmState) { + try (Handle h = db.open()) { + final String timestamp = formatDateFromMillis(System.currentTimeMillis()); + h.createStatement( + "update sub_alarm set state=:state, updated_at=:updated_at where id=:id") + .bind("state", subAlarmState.toString()) + .bind("updated_at", timestamp) + .bind("id", id).execute(); + } + } + @Override public void deleteByDefinitionId(String alarmDefinitionId){ try (Handle h = db.open()) { diff --git a/thresh/src/main/java/monasca/thresh/infrastructure/persistence/hibernate/AlarmSqlImpl.java b/thresh/src/main/java/monasca/thresh/infrastructure/persistence/hibernate/AlarmSqlImpl.java index 3866ba7..7721835 100644 --- a/thresh/src/main/java/monasca/thresh/infrastructure/persistence/hibernate/AlarmSqlImpl.java +++ b/thresh/src/main/java/monasca/thresh/infrastructure/persistence/hibernate/AlarmSqlImpl.java @@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; + import org.apache.commons.codec.digest.DigestUtils; import org.hibernate.Criteria; import org.hibernate.Query; @@ -210,6 +211,7 @@ public class AlarmSqlImpl .setAlarm(alarm) .setSubExpression(session.get(SubAlarmDefinitionDb.class, subAlarm.getAlarmSubExpressionId())) .setExpression(subAlarm.getExpression().getExpression()) + .setState(subAlarm.getState()) .setUpdatedAt(now) .setCreatedAt(now) .setId(subAlarm.getId()) @@ -260,6 +262,34 @@ public class AlarmSqlImpl } + @Override + public void updateSubAlarmState(String subAlarmId, AlarmState subAlarmState) { + Transaction tx = null; + Session session = null; + try { + + session = sessionFactory.openSession(); + tx = session.beginTransaction(); + + final DateTime now = DateTime.now(); + + final SubAlarmDb subAlarm = (SubAlarmDb) session.get(SubAlarmDb.class, subAlarmId); + subAlarm.setState(subAlarmState); + subAlarm.setUpdatedAt(now); + + session.update(subAlarm); + + tx.commit(); + tx = null; + + } finally { + this.rollbackIfNotNull(tx); + if (session != null) { + session.close(); + } + } + } + @Override public void deleteByDefinitionId(final String alarmDefinitionId) { Transaction tx = null; diff --git a/thresh/src/main/java/monasca/thresh/infrastructure/thresholding/AlarmThresholdingBolt.java b/thresh/src/main/java/monasca/thresh/infrastructure/thresholding/AlarmThresholdingBolt.java index 706895e..47c79a7 100644 --- a/thresh/src/main/java/monasca/thresh/infrastructure/thresholding/AlarmThresholdingBolt.java +++ b/thresh/src/main/java/monasca/thresh/infrastructure/thresholding/AlarmThresholdingBolt.java @@ -207,7 +207,7 @@ public class AlarmThresholdingBolt extends BaseRichBolt { private boolean allSubAlarmsHaveState(final Alarm alarm) { for (SubAlarm subAlarm : alarm.getSubAlarms()) { - if (subAlarm.isNoState()) { + if (subAlarm.isNoState() && !subAlarm.onlyImmediateEvaluation()) { return false; } } diff --git a/thresh/src/main/java/monasca/thresh/infrastructure/thresholding/MetricAggregationBolt.java b/thresh/src/main/java/monasca/thresh/infrastructure/thresholding/MetricAggregationBolt.java index 060781e..83f68e1 100644 --- a/thresh/src/main/java/monasca/thresh/infrastructure/thresholding/MetricAggregationBolt.java +++ b/thresh/src/main/java/monasca/thresh/infrastructure/thresholding/MetricAggregationBolt.java @@ -1,5 +1,5 @@ /* - * (C) Copyright 2014-2016 Hewlett Packard Enterprise Development Company LP. + * (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP * Copyright 2016 FUJITSU LIMITED * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,13 +19,16 @@ package monasca.thresh.infrastructure.thresholding; import monasca.common.model.metric.Metric; +import monasca.common.util.Injector; import monasca.thresh.ThresholdingConfiguration; import monasca.thresh.domain.model.MetricDefinitionAndTenantId; import monasca.thresh.domain.model.SubAlarm; import monasca.thresh.domain.model.SubAlarmStats; import monasca.thresh.domain.model.SubExpression; import monasca.thresh.domain.model.TenantIdAndMetricName; +import monasca.thresh.domain.service.AlarmDAO; import monasca.thresh.domain.service.SubAlarmStatsRepository; +import monasca.thresh.infrastructure.persistence.PersistenceModule; import monasca.thresh.utils.Logging; import monasca.thresh.utils.Streams; import monasca.thresh.utils.Tuples; @@ -72,9 +75,11 @@ public class MetricAggregationBolt extends BaseRichBolt { public static final String METRIC_AGGREGATION_CONTROL_STREAM = "MetricAggregationControl"; public static final String[] METRIC_AGGREGATION_CONTROL_FIELDS = new String[] {"directive"}; public static final String METRICS_BEHIND = "MetricsBehind"; - private static final int MAX_SAVED_METRIC_AGE_SECONDS = 10; private final ThresholdingConfiguration config; + private DataSourceFactory dbConfig; + private transient AlarmDAO alarmDAO; + final Map metricDefToSubAlarmStatsRepos = new HashMap<>(); private final Set subAlarmStatsSet = new HashSet<>(); @@ -85,8 +90,14 @@ public class MetricAggregationBolt extends BaseRichBolt { private boolean upToDate = true; private Map savedMetrics = new HashMap<>(); - public MetricAggregationBolt(ThresholdingConfiguration config) { + public MetricAggregationBolt(ThresholdingConfiguration config, DataSourceFactory dbConfig) { this.config = config; + this.dbConfig = dbConfig; + } + + public MetricAggregationBolt(ThresholdingConfiguration config, AlarmDAO alarmDAO) { + this.config = config; + this.alarmDAO = alarmDAO; } @Override @@ -118,7 +129,8 @@ public class MetricAggregationBolt extends BaseRichBolt { final String subAlarmId = tuple.getString(4); if (EventProcessingBolt.DELETED.equals(eventType)) { handleAlarmDeleted(metricDefinitionAndTenantId, subAlarmId); - } else if (EventProcessingBolt.RESEND.equals(eventType)) { + } else if (EventProcessingBolt.RESEND.equals(eventType) || + EventProcessingBolt.UPDATED.equals(eventType)) { handleAlarmResend(metricDefinitionAndTenantId, subAlarmId); } } else if (EventProcessingBolt.METRIC_SUB_ALARM_EVENT_STREAM_ID.equals(tuple @@ -167,6 +179,11 @@ public class MetricAggregationBolt extends BaseRichBolt { logger = LoggerFactory.getLogger(Logging.categoryFor(getClass(), context)); logger.info("Preparing"); this.collector = collector; + + if (this.alarmDAO == null) { + Injector.registerIfNotBound(AlarmDAO.class, new PersistenceModule(this.dbConfig)); + this.alarmDAO = Injector.getInstance(AlarmDAO.class); + } } /** @@ -184,7 +201,7 @@ public class MetricAggregationBolt extends BaseRichBolt { for (SubAlarmStats stats : subAlarmStatsRepo.get()) { final long timestamp_secs = metricTimestampInSeconds(metric); - if (stats.getStats().addValue(metric.value, timestamp_secs)) { + if (stats.addValue(metric.value, timestamp_secs)) { logger.trace("Aggregated value {} at {} for {}. Updated {}", metric.value, metric.timestamp, metricDefinitionAndTenantId, stats.getStats()); if (stats.evaluateAndSlideWindow(timestamp_secs, config.alarmDelay)) { @@ -229,42 +246,14 @@ public class MetricAggregationBolt extends BaseRichBolt { logger.info("Did not evaluate SubAlarms because Metrics are not up to date"); upToDate = true; } - cleanSavedMetrics(); - } - - /** - * Clean saved metrics since the SubAlarm should show up within seconds of - * the metric being received - */ - private void cleanSavedMetrics() { - if (savedMetrics.isEmpty()) { - return; - } - final List toRemove = new ArrayList<>(); - for (Map.Entry entry: savedMetrics.entrySet()) { - if (savedMetricTooOld(entry.getValue())) { - toRemove.add(entry.getKey()); - } - } - logger.debug("Removing {} too old saved metrics", toRemove.size()); - for (MetricDefinitionAndTenantId mdtid : toRemove) { - savedMetrics.remove(mdtid); - } - } - - /** - * Check if a save Metric is too old - * @param Metric to check - * @return true if saved Metric is too old, false otherwise - */ - private boolean savedMetricTooOld(final Metric metric) { - final long now = currentTimeSeconds(); - final long age = metricTimestampInSeconds(metric) - now; - return age > MAX_SAVED_METRIC_AGE_SECONDS; } private void sendSubAlarmStateChange(SubAlarmStats subAlarmStats) { logger.debug("Alarm state changed for {}", subAlarmStats); + if (subAlarmStats.getSubAlarm().onlyImmediateEvaluation()) { + alarmDAO.updateSubAlarmState(subAlarmStats.getSubAlarm().getId(), + subAlarmStats.getSubAlarm().getState()); + } collector.emit(new Values(subAlarmStats.getSubAlarm().getAlarmId(), duplicate(subAlarmStats .getSubAlarm()))); } @@ -332,11 +321,11 @@ public class MetricAggregationBolt extends BaseRichBolt { logger.info("Received AlarmCreatedEvent for {}", subAlarm); final SubAlarmStats newStats = addSubAlarm(metricDefinitionAndTenantId, subAlarm); // See if we have a saved metric for this SubAlarm. Add to the SubAlarm if we do. - // Because the Metric comes directly from the MetricFilterinBolt but the + // Because the Metric comes directly from the MetricFilteringBolt but the // SubAlarm comes from the AlarmCreationBolt, it is very likely that the // Metric arrives first final Metric metric = savedMetrics.get(metricDefinitionAndTenantId); - if (metric != null && !savedMetricTooOld(metric)) { + if (metric != null) { aggregateValues(metricDefinitionAndTenantId, metric); logger.trace("Aggregated saved value {} at {} for {}. Updated {}", metric.value, metric.timestamp, metricDefinitionAndTenantId, newStats.getStats()); diff --git a/thresh/src/test/java/monasca/thresh/ThresholdingEngineAlarmTest.java b/thresh/src/test/java/monasca/thresh/ThresholdingEngineAlarmTest.java index 5241492..4ef0711 100644 --- a/thresh/src/test/java/monasca/thresh/ThresholdingEngineAlarmTest.java +++ b/thresh/src/test/java/monasca/thresh/ThresholdingEngineAlarmTest.java @@ -434,6 +434,17 @@ public class ThresholdingEngineAlarmTest extends TopologyTestCase { return updated; } + @Override + public void updateSubAlarmState(String subAlarmId, AlarmState subAlarmState) { + for (final Alarm alarm : alarms) { + for (final SubAlarm subAlarm : alarm.getSubAlarms()) { + if (subAlarm.getId().equals(subAlarmId)) { + subAlarm.setState(subAlarmState); + } + } + } + } + public boolean deleteAlarm(final Alarm toDelete) { for (final Alarm alarm : alarms) { if (alarm.getId().equals(toDelete.getId())) { diff --git a/thresh/src/test/java/monasca/thresh/domain/model/SubAlarmStatsTest.java b/thresh/src/test/java/monasca/thresh/domain/model/SubAlarmStatsTest.java index 92659b3..25a076d 100644 --- a/thresh/src/test/java/monasca/thresh/domain/model/SubAlarmStatsTest.java +++ b/thresh/src/test/java/monasca/thresh/domain/model/SubAlarmStatsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. + * (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP * Copyright 2016 FUJITSU LIMITED * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,48 +32,139 @@ import org.testng.annotations.Test; @Test public class SubAlarmStatsTest { - private SubExpression expression; - private SubAlarm subAlarm; - private SubAlarmStats subAlarmStats; + private SubExpression avgExpression; + private SubAlarm avgSubAlarm; + private SubAlarmStats avgSubAlarmStats; + private SubExpression lastExpression; + private SubAlarm lastSubAlarm; + private SubAlarmStats lastSubAlarmStats; + private long lastViewStartTime; @BeforeMethod protected void beforeMethod() { - expression = + avgExpression = new SubExpression(UUID.randomUUID().toString(), AlarmSubExpression.of("avg(hpcs.compute.cpu{id=5}, 60) > 3 times 3")); - subAlarm = new SubAlarm("123", "1", expression); - subAlarm.setNoState(true); - subAlarmStats = new SubAlarmStats(subAlarm, expression.getAlarmSubExpression().getPeriod()); + avgSubAlarm = new SubAlarm("123", "1", avgExpression); + avgSubAlarm.setNoState(true); + avgSubAlarmStats = new SubAlarmStats(avgSubAlarm, avgExpression.getAlarmSubExpression().getPeriod()); + + lastExpression = + new SubExpression(UUID.randomUUID().toString(), + AlarmSubExpression.of("last(hpcs.compute.cpu{id=5}) > 0")); + lastSubAlarm = new SubAlarm("456", "1", lastExpression, AlarmState.UNDETERMINED); + lastSubAlarm.setNoState(true); + lastViewStartTime = 10000; + lastSubAlarmStats = new SubAlarmStats(lastSubAlarm, + lastViewStartTime + lastExpression.getAlarmSubExpression().getPeriod()); + } + + public void shouldAcceptLastMetricIfOld() { + assertTrue(lastSubAlarmStats.addValue(99, 10)); + assertTrue(lastSubAlarmStats.evaluate(lastViewStartTime + 10, 0)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + } + + public void shouldImmediateTransitionToOk() { + assertTrue(lastSubAlarmStats.addValue(0, lastViewStartTime + 10)); + assertTrue(lastSubAlarmStats.evaluate(lastViewStartTime + 10, 0)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); + } + + public void shouldNotTransitionToAlarmTwice() { + assertTrue(lastSubAlarmStats.addValue(99, lastViewStartTime + 10)); + assertTrue(lastSubAlarmStats.evaluate(lastViewStartTime + 10, 0)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + + assertTrue(lastSubAlarmStats.addValue(98, lastViewStartTime + 20)); + assertFalse(lastSubAlarmStats.evaluate(lastViewStartTime + 20, 0)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + } + + public void shouldNotTransitionToOkTwice() { + assertTrue(lastSubAlarmStats.addValue(0, lastViewStartTime + 10)); + assertTrue(lastSubAlarmStats.evaluate(lastViewStartTime + 10, 0)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); + + assertTrue(lastSubAlarmStats.addValue(0, lastViewStartTime + 20)); + assertFalse(lastSubAlarmStats.evaluate(lastViewStartTime + 20, 0)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); + } + + public void shouldNotTransitionOnOldMeasurement() { + assertTrue(lastSubAlarmStats.addValue(99, lastViewStartTime + 10)); + assertTrue(lastSubAlarmStats.evaluate(lastViewStartTime + 10, 0)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + + assertTrue(lastSubAlarmStats.addValue(0, lastViewStartTime + 5)); + assertFalse(lastSubAlarmStats.evaluate(lastViewStartTime + 5, 0)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + } + + public void shouldImmediatelyTransition() { + assertTrue(lastSubAlarmStats.addValue(99, lastViewStartTime + 10)); + assertTrue(lastSubAlarmStats.evaluate(lastViewStartTime + 10, 0)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + + assertTrue(lastSubAlarmStats.addValue(0, lastViewStartTime + 15)); + assertTrue(lastSubAlarmStats.evaluate(lastViewStartTime + 15, 0)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); + + assertTrue(lastSubAlarmStats.addValue(99, lastViewStartTime + 20)); + assertTrue(lastSubAlarmStats.evaluate(lastViewStartTime + 20, 0)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + } + + public void shouldNotTransitionFromAlarmWithNoMetrics() { + assertTrue(lastSubAlarmStats.addValue(99, lastViewStartTime + 10)); + assertTrue(lastSubAlarmStats.evaluate(lastViewStartTime + 10, 0)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + for (int period = 1; period < 10; period++) { + long time = lastViewStartTime + period * lastSubAlarm.getExpression().getPeriod(); + assertFalse(lastSubAlarmStats.evaluateAndSlideWindow(time, 30)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + } + } + + public void shouldNotTransitionFromOkWithNoMetrics() { + assertTrue(lastSubAlarmStats.addValue(0, lastViewStartTime + 10)); + assertTrue(lastSubAlarmStats.evaluate(lastViewStartTime + 10, 0)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); + for (int period = 1; period < 10; period++) { + long time = lastViewStartTime + period * lastSubAlarm.getExpression().getPeriod(); + assertFalse(lastSubAlarmStats.evaluateAndSlideWindow(time, 30)); + assertEquals(lastSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); + } } public void shouldBeOkIfAnySlotsInViewAreBelowThreshold() { sendMetric(5, 1, false); - assertFalse(subAlarmStats.evaluateAndSlideWindow(62, 1)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(62, 1)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); sendMetric(1, 62, false); - assertTrue(subAlarmStats.evaluateAndSlideWindow(122, 1)); + assertTrue(avgSubAlarmStats.evaluateAndSlideWindow(122, 1)); // This went to OK because at least one period is under the threshold - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.OK); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); sendMetric(5, 123, false); - assertFalse(subAlarmStats.evaluateAndSlideWindow(182, 1)); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(182, 1)); // Still one under the threshold - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.OK); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); } public void shouldBeAlarmedIfAllSlotsInViewExceedThreshold() { sendMetric(5, 1, false); - assertFalse(subAlarmStats.evaluateAndSlideWindow(62, 1)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(62, 1)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); sendMetric(5, 62, false); - assertFalse(subAlarmStats.evaluateAndSlideWindow(122, 1)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(122, 1)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); sendMetric(5, 123, false); - assertTrue(subAlarmStats.evaluateAndSlideWindow(182, 1)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + assertTrue(avgSubAlarmStats.evaluateAndSlideWindow(182, 1)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); } /** @@ -82,53 +173,53 @@ public class SubAlarmStatsTest { public void shouldEvaluateAndSlideWindow() { long initialTime = 11; - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); // Add value and trigger OK sendMetric(1, initialTime - 1, false); - assertTrue(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.OK); + assertTrue(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); // Slide in some values that exceed the threshold sendMetric(5, initialTime - 1, false); - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.OK); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); sendMetric(5, initialTime - 1, false); - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.OK); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); sendMetric(5, initialTime - 1, false); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.OK); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); // Trigger ALARM - assertTrue(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + assertTrue(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); // Add value and trigger OK sendMetric(1, initialTime - 1, false); - assertTrue(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.OK); + assertTrue(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); // Must slide 8 times total from the last added value to trigger UNDETERMINED. This is // equivalent to the behavior in CloudWatch for an alarm with 3 evaluation periods. 2 more // slides to move the value outside of the window and 6 more to exceed the observation // threshold. for (int i = 0; i < 7; i++) { - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); } - assertTrue(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertTrue(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 10)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); sendMetric(5, initialTime - 1, false); } private void sendMetric(double value, long timestamp, boolean expected) { - subAlarmStats.getStats().addValue(value, timestamp); - assertEquals(subAlarmStats.evaluateAndSlideWindow(timestamp, timestamp), expected); + assertTrue(avgSubAlarmStats.addValue(value, timestamp)); + assertEquals(avgSubAlarmStats.evaluateAndSlideWindow(timestamp, timestamp), expected); } /** @@ -138,120 +229,120 @@ public class SubAlarmStatsTest { long initialTime = 11; // Need a different expression for this test - expression = + avgExpression = new SubExpression(UUID.randomUUID().toString(), AlarmSubExpression.of("max(hpcs.compute.cpu{id=5}, 60) > 3 times 3")); - subAlarm = new SubAlarm("123", "1", expression); - assertEquals(subAlarm.getState(), AlarmState.UNDETERMINED); - subAlarm.setNoState(true); - subAlarmStats = new SubAlarmStats(subAlarm, expression.getAlarmSubExpression().getPeriod()); + avgSubAlarm = new SubAlarm("123", "1", avgExpression); + assertEquals(avgSubAlarm.getState(), AlarmState.UNDETERMINED); + avgSubAlarm.setNoState(true); + avgSubAlarmStats = new SubAlarmStats(avgSubAlarm, avgExpression.getAlarmSubExpression().getPeriod()); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); // Add value and trigger OK sendMetric(1, initialTime - 1, false); - assertTrue(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.OK); + assertTrue(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); // Slide in some values that exceed the threshold sendMetric(5, initialTime - 1, false); - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.OK); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); sendMetric(5, initialTime - 1, false); - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); // Trigger ALARM sendMetric(5, initialTime - 1, true); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); // Ensure it is still ALARM on next evaluation - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); // Add value and trigger OK sendMetric(1, initialTime - 1, false); - assertTrue(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.OK); + assertTrue(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.OK); // Must slide 8 times total from the last added value to trigger UNDETERMINED. This is // equivalent to the behavior in CloudWatch for an alarm with 3 evaluation periods. 2 more // slides to move the value outside of the window and 6 more to exceed the observation // threshold. for (int i = 0; i < 7; i++) { - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); } - assertTrue(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertTrue(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); // Now test that future buckets are evaluated // Set the current bucket to ALARM sendMetric(5, initialTime - 1, false); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); // Set the future bucket of current + 2 to ALARM sendMetric(5, initialTime + 120, false); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); // Set the future bucket of current + 1 to ALARM. That will trigger the // SubAlarm to go to ALARM sendMetric(5, initialTime + 60, true); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); } public void shouldAlarmIfAllSlotsAlarmed() { long initialTime = 11; - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.UNDETERMINED); - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); sendMetric(5, initialTime - 1, false); - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); sendMetric(5, initialTime - 1, false); - assertFalse(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); + assertFalse(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); sendMetric(5, initialTime - 1, false); - assertTrue(subAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); - assertEquals(subAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); + assertTrue(avgSubAlarmStats.evaluateAndSlideWindow(initialTime += 60, 1)); + assertEquals(avgSubAlarmStats.getSubAlarm().getState(), AlarmState.ALARM); } public void testEmptyWindowObservationThreshold() { - expression = + avgExpression = new SubExpression(UUID.randomUUID().toString(), AlarmSubExpression.of("avg(hpcs.compute.cpu{id=5}) > 3 times 3")); - subAlarm = new SubAlarm("123", "1", expression); - assertEquals(subAlarm.getState(), AlarmState.UNDETERMINED); - SubAlarmStats saStats = new SubAlarmStats(subAlarm, (System.currentTimeMillis() / 1000) + 60); + avgSubAlarm = new SubAlarm("123", "1", avgExpression); + assertEquals(avgSubAlarm.getState(), AlarmState.UNDETERMINED); + SubAlarmStats saStats = new SubAlarmStats(avgSubAlarm, (System.currentTimeMillis() / 1000) + 60); assertEquals(saStats.emptyWindowObservationThreshold, 6); } public void checkUpdateSubAlarm() { // Can keep data with threshold change - verifyUpdateSubAlarm(expression.getAlarmSubExpression().getExpression().replace("> 3", "> 6"), 100.0); + verifyUpdateSubAlarm(avgExpression.getAlarmSubExpression().getExpression().replace("> 3", "> 6"), 100.0); // Can keep data with operator change - verifyUpdateSubAlarm(expression.getAlarmSubExpression().getExpression().replace("< 3", "< 6"), 100.0); + verifyUpdateSubAlarm(avgExpression.getAlarmSubExpression().getExpression().replace("< 3", "< 6"), 100.0); // Have to flush data with function change - verifyUpdateSubAlarm(expression.getAlarmSubExpression().getExpression().replace("avg", "max"), Double.NaN); + verifyUpdateSubAlarm(avgExpression.getAlarmSubExpression().getExpression().replace("avg", "max"), Double.NaN); // Have to flush data with periods change - verifyUpdateSubAlarm(expression.getAlarmSubExpression().getExpression().replace("times 3", "times 2"), Double.NaN); + verifyUpdateSubAlarm(avgExpression.getAlarmSubExpression().getExpression().replace("times 3", "times 2"), Double.NaN); // Have to flush data with period change - verifyUpdateSubAlarm(expression.getAlarmSubExpression().getExpression().replace(", 60", ", 120"), Double.NaN); + verifyUpdateSubAlarm(avgExpression.getAlarmSubExpression().getExpression().replace(", 60", ", 120"), Double.NaN); } private void verifyUpdateSubAlarm(String newExpressionString, double expectedValue) { final AlarmSubExpression newExpression = AlarmSubExpression.of(newExpressionString); - assertNotEquals(newExpression, expression.getAlarmSubExpression().getExpression()); - int timestamp = expression.getAlarmSubExpression().getPeriod() / 2; + assertNotEquals(newExpression, avgExpression.getAlarmSubExpression().getExpression()); + int timestamp = avgExpression.getAlarmSubExpression().getPeriod() / 2; sendMetric(100.00, timestamp, false); - assertEquals(subAlarmStats.getStats().getValue(timestamp), 100.0); - subAlarmStats.updateSubAlarm(newExpression, expression.getAlarmSubExpression().getPeriod()); - assertEquals(subAlarmStats.getStats().getValue(timestamp), expectedValue); - assertTrue(subAlarm.isNoState()); + assertEquals(avgSubAlarmStats.getStats().getValue(timestamp), 100.0); + avgSubAlarmStats.updateSubAlarm(newExpression, avgExpression.getAlarmSubExpression().getPeriod()); + assertEquals(avgSubAlarmStats.getStats().getValue(timestamp), expectedValue); + assertTrue(avgSubAlarm.isNoState()); } @@ -266,7 +357,7 @@ public class SubAlarmStatsTest { final SubAlarmStats stats = new SubAlarmStats(subAlarm, t1 + subExpr.getAlarmSubExpression().getPeriod()); for (int i = 0; i < 360; i++) { t1++; - stats.getStats().addValue(1.0, t1); + stats.addValue(1.0, t1); if ((t1 % 60) == 2) { stats.evaluateAndSlideWindow(t1, 1); if (i <= subExpr.getAlarmSubExpression().getPeriod()) { @@ -296,7 +387,7 @@ public class SubAlarmStatsTest { int t1 = 0; for (int i = 0; i < 1080; i++) { t1++; - stats.getStats().addValue(1.0, t1); + stats.getStats().addValue(1.0, t1, false); if ((t1 % 60) == 2) { stats.evaluateAndSlideWindow(t1, 1); if (i <= subExpr.getAlarmSubExpression().getPeriod()) { diff --git a/thresh/src/test/java/monasca/thresh/domain/model/SubAlarmTest.java b/thresh/src/test/java/monasca/thresh/domain/model/SubAlarmTest.java index 067e417..e4704ff 100644 --- a/thresh/src/test/java/monasca/thresh/domain/model/SubAlarmTest.java +++ b/thresh/src/test/java/monasca/thresh/domain/model/SubAlarmTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Hewlett-Packard Development Company, L.P. + * (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP * Copyright 2016 FUJITSU LIMITED * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -78,7 +78,7 @@ public class SubAlarmTest { private void checkExpression(String expressionString, boolean expected) { final SubAlarm subAlarm = this.getSubAlarm(expressionString); - assertEquals(subAlarm.canEvaluateImmediately(), expected); + assertEquals(subAlarm.canEvaluateAlarmImmediately(), expected); assertEquals(subAlarm.getState(), AlarmState.UNDETERMINED); assertFalse(subAlarm.isDeterministic()); } diff --git a/thresh/src/test/java/monasca/thresh/infrastructure/persistence/AlarmDAOImplTest.java b/thresh/src/test/java/monasca/thresh/infrastructure/persistence/AlarmDAOImplTest.java index 05be4af..13dfd78 100644 --- a/thresh/src/test/java/monasca/thresh/infrastructure/persistence/AlarmDAOImplTest.java +++ b/thresh/src/test/java/monasca/thresh/infrastructure/persistence/AlarmDAOImplTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright 2014,2016 Hewlett Packard Enterprise Development LP + * (C) Copyright 2014,2016 Hewlett Packard Enterprise Development LP * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,7 +72,7 @@ public class AlarmDAOImplTest { @BeforeClass protected void setupClass() throws Exception { // See class comment - db = new DBI("jdbc:mysql://192.168.10.4/mon", "monapi", "password"); + db = new DBI("jdbc:mysql://192.168.10.6/mon", "monapi", "password"); handle = db.open(); dao = new AlarmDAOImpl(db); } diff --git a/thresh/src/test/java/monasca/thresh/infrastructure/thresholding/AlarmThresholdingBoltTest.java b/thresh/src/test/java/monasca/thresh/infrastructure/thresholding/AlarmThresholdingBoltTest.java index 0921453..06ee0ae 100644 --- a/thresh/src/test/java/monasca/thresh/infrastructure/thresholding/AlarmThresholdingBoltTest.java +++ b/thresh/src/test/java/monasca/thresh/infrastructure/thresholding/AlarmThresholdingBoltTest.java @@ -65,14 +65,15 @@ public class AlarmThresholdingBoltTest { private AlarmDefinition alarmDefinition; private Alarm alarm; private List subAlarms; + private SubAlarm lastSubAlarm = null; private AlarmEventForwarder alarmEventForwarder; private AlarmDAO alarmDAO; private AlarmDefinitionDAO alarmDefinitionDAO; private AlarmThresholdingBolt bolt; private OutputCollector collector; - private final String[] subExpressions = {"avg(cpu{instance_id=123,device=42}, 1) > 5", - "max(load{instance_id=123,device=42}, 1) > 8", - "sum(diskio{instance_id=123,device=42}, 1) > 5000"}; + private final String[] subExpressions = {"avg(cpu{instance_id=123,device=42}) > 5", + "max(load{instance_id=123,device=42}) > 8", + "last(diskio{instance_id=123,device=42}) > 5000"}; @BeforeMethod protected void beforeMethod() { @@ -99,6 +100,14 @@ public class AlarmThresholdingBoltTest { final Map config = new HashMap<>(); final TopologyContext context = mock(TopologyContext.class); bolt.prepare(config, context, collector); + + for (SubAlarm subAlarm : subAlarms) { + if (subAlarm.getExpression().getFunction().equals(AggregateFunction.LAST)) { + lastSubAlarm = subAlarm; + } + } + assertNotNull(lastSubAlarm, "Did not find a SubAlarm with Function of last"); + lastSubAlarm.setState(AlarmState.OK); } /** @@ -158,6 +167,50 @@ public class AlarmThresholdingBoltTest { verify(alarmDAO, times(1)).updateState(eq(alarmId), eq(AlarmState.OK), anyLong()); } + /** + * Create a Alarm with all sub expressions. Send a SubAlarm with state set to ALARM for + * the first SubAlarm which is not the one with operator Last. That one is initialized to + * OK. Send OK for the other SubAlarm which is not the one with operator Last. Ensure + * that the Alarm was triggered to ALARM and sent. + * + * Since SubAlarms with the function last() are only sent when they change while all the other + * types are sent when they first achieve a state after startup, this test ensures + * that Alarms with more than one SubAlarm that has at least one function last() work correctly + * on startup + */ + public void triggerAlarmWithLast() { + + final String alarmId = alarm.getId(); + when(alarmDAO.findById(alarmId)).thenReturn(alarm); + when(alarmDefinitionDAO.findById(alarmDefinition.getId())).thenReturn(alarmDefinition); + SubAlarm firstAlarmSubAlarm = null; + AlarmState sendState = AlarmState.ALARM; + for (SubAlarm subAlarm : subAlarms) { + if (lastSubAlarm != subAlarm) { + if (firstAlarmSubAlarm == null) { + firstAlarmSubAlarm = subAlarm; + } + emitSubAlarmStateChange(alarmId, subAlarm, sendState); + sendState = AlarmState.OK; + } + } + final String alarmJson = + "{\"alarm-transitioned\":{\"tenantId\":\"" + + tenantId + + "\"," + + "\"alarmId\":\"" + alarmId + "\"," + + "\"alarmDefinitionId\":\"" + alarmDefinition.getId() + "\",\"metrics\":[]," + + "\"alarmName\":\"Test CPU Alarm\"," + + "\"alarmDescription\":\"Description of Alarm\",\"oldState\":\"OK\",\"newState\":\"ALARM\"," + + "\"actionsEnabled\":true," + + "\"stateChangeReason\":\"Thresholds were exceeded for the sub-alarms: " + + firstAlarmSubAlarm.getExpression().getExpression() + " with the values: []\"," + "\"severity\":\"LOW\"," + + "\"link\":null," + "\"lifecycleState\":null," + + "\"subAlarms\":[" + buildSubAlarmJson(alarm.getSubAlarms()) + "]," + + "\"timestamp\":1395587091003}}"; + + verify(alarmEventForwarder, times(1)).send(alarmJson); + verify(alarmDAO, times(1)).updateState(eq(alarmId), eq(AlarmState.ALARM), anyLong()); } public void simpleAlarmUpdate() { // Now send an AlarmUpdatedEvent final AlarmState newState = AlarmState.OK; diff --git a/thresh/src/test/java/monasca/thresh/infrastructure/thresholding/MetricAggregationBoltTest.java b/thresh/src/test/java/monasca/thresh/infrastructure/thresholding/MetricAggregationBoltTest.java index e32c583..17f2619 100644 --- a/thresh/src/test/java/monasca/thresh/infrastructure/thresholding/MetricAggregationBoltTest.java +++ b/thresh/src/test/java/monasca/thresh/infrastructure/thresholding/MetricAggregationBoltTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright 2014,2016 Hewlett Packard Enterprise Development Company LP. + * (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP * Copyright 2016 FUJITSU LIMITED * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,8 @@ package monasca.thresh.infrastructure.thresholding; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -31,7 +33,6 @@ import static org.testng.Assert.assertTrue; import monasca.common.model.alarm.AlarmOperator; import monasca.common.model.alarm.AlarmState; - import monasca.common.model.alarm.AlarmSubExpression; import monasca.common.model.metric.Metric; import monasca.common.model.metric.MetricDefinition; @@ -41,6 +42,7 @@ import monasca.thresh.domain.model.SubAlarm; import monasca.thresh.domain.model.SubAlarmStats; import monasca.thresh.domain.model.SubExpression; import monasca.thresh.domain.model.TenantIdAndMetricName; +import monasca.thresh.domain.service.AlarmDAO; import monasca.thresh.domain.service.SubAlarmStatsRepository; import monasca.thresh.utils.Streams; @@ -71,14 +73,18 @@ public class MetricAggregationBoltTest { private SubAlarm subAlarm2; private SubAlarm subAlarm3; private SubAlarm subAlarm4; + private SubAlarm subAlarm5; private SubExpression subExpr1; private SubExpression subExpr2; private SubExpression subExpr3; private SubExpression subExpr4; + private SubExpression subExpr5; private MetricDefinition metricDef1; private MetricDefinition metricDef2; private MetricDefinition metricDef3; private MetricDefinition metricDef4; + private MetricDefinition metricDef5; + private AlarmDAO alarmDao; @BeforeClass protected void beforeClass() { @@ -90,10 +96,12 @@ public class MetricAggregationBoltTest { subExpr4 = new SubExpression("777", AlarmSubExpression.of( "count(log.error{id=5},deterministic,60) >= 5") ); + subExpr5 = new SubExpression("888", AlarmSubExpression.of("last(hpcs.compute.mem{id=5}) > 0")); metricDef1 = subExpr1.getAlarmSubExpression().getMetricDefinition(); metricDef2 = subExpr2.getAlarmSubExpression().getMetricDefinition(); metricDef3 = subExpr3.getAlarmSubExpression().getMetricDefinition(); metricDef4 = subExpr4.getAlarmSubExpression().getMetricDefinition(); + metricDef5 = subExpr5.getAlarmSubExpression().getMetricDefinition(); } @BeforeMethod @@ -103,15 +111,18 @@ public class MetricAggregationBoltTest { subAlarm2 = new SubAlarm("456", "1", subExpr2); subAlarm3 = new SubAlarm("789", "2", subExpr3); subAlarm4 = new SubAlarm("666", "3", subExpr4); + subAlarm5 = new SubAlarm("891", "3", subExpr5); subAlarms = new ArrayList<>(); subAlarms.add(subAlarm1); subAlarms.add(subAlarm2); subAlarms.add(subAlarm3); subAlarms.add(subAlarm4); + subAlarms.add(subAlarm5); final ThresholdingConfiguration config = new ThresholdingConfiguration(); config.alarmDelay = 10; - bolt = new MockMetricAggregationBolt(config); + alarmDao = mock(AlarmDAO.class); + bolt = new MockMetricAggregationBolt(config, alarmDao); context = mock(TopologyContext.class); collector = mock(OutputCollector.class); bolt.prepare(null, context, collector); @@ -461,6 +472,176 @@ public class MetricAggregationBoltTest { verify(collector, times(1)).emit(new Values(subAlarm4.getAlarmId(), subAlarm4)); } + public void shouldTransitionLastImmediatelyForNewAlarm() { + long t1 = 50000; + bolt.setCurrentTime(t1); + sendSubAlarmCreated(metricDef5, subAlarm5); + verify(alarmDao, never()).updateSubAlarmState(eq(subAlarm5.getId()), (AlarmState) any()); + t1 += 1000; + bolt.execute(createMetricTuple(metricDef5, new Metric(metricDef5, t1, 1.0, null))); + assertEquals(subAlarm5.getState(), AlarmState.ALARM); + verify(collector, times(1)).emit(new Values(subAlarm5.getAlarmId(), subAlarm5)); + verify(alarmDao, times(1)).updateSubAlarmState(subAlarm5.getId(), AlarmState.ALARM); + + t1 += 1000; + + // Have to reset the mock so it can tell the difference when subAlarm5 is emitted again. + reset(collector); + reset(alarmDao); + + // Make sure it doesn't transition out of ALARM even with no measurements arriving + bolt.setCurrentTime(t1 += 60000); + sendTickTuple(); + assertEquals(subAlarm5.getState(), AlarmState.ALARM); + verify(collector, never()).emit(new Values(subAlarm5.getAlarmId(), subAlarm5)); + verify(alarmDao, never()).updateSubAlarmState(eq(subAlarm5.getId()), (AlarmState) any()); + + bolt.setCurrentTime(t1 += 60000); + sendTickTuple(); + assertEquals(subAlarm5.getState(), AlarmState.ALARM); + verify(collector, never()).emit(new Values(subAlarm5.getAlarmId(), subAlarm5)); + verify(alarmDao, never()).updateSubAlarmState(eq(subAlarm5.getId()), (AlarmState) any()); + + bolt.setCurrentTime(t1 += 60000); + sendTickTuple(); + assertEquals(subAlarm5.getState(), AlarmState.ALARM); + verify(collector, never()).emit(new Values(subAlarm5.getAlarmId(), subAlarm5)); + verify(alarmDao, never()).updateSubAlarmState(eq(subAlarm5.getId()), (AlarmState) any()); + + bolt.setCurrentTime(t1 += 60000); + sendTickTuple(); + assertEquals(subAlarm5.getState(), AlarmState.ALARM); + verify(collector, never()).emit(new Values(subAlarm5.getAlarmId(), subAlarm5)); + + bolt.execute(createMetricTuple(metricDef5, new Metric(metricDef5, t1, 0.0, null))); + assertEquals(subAlarm5.getState(), AlarmState.OK); + verify(collector, times(1)).emit(new Values(subAlarm5.getAlarmId(), subAlarm5)); + verify(alarmDao, times(1)).updateSubAlarmState(subAlarm5.getId(), AlarmState.OK); + + // Have to reset the mock so it can tell the difference when subAlarm5 is emitted again. + reset(collector); + reset(alarmDao); + + // Make sure it doesn't transition out of ALARM even with no measurements arriving + bolt.setCurrentTime(t1 += 60000); + sendTickTuple(); + assertEquals(subAlarm5.getState(), AlarmState.OK); + verify(collector, never()).emit(new Values(subAlarm5.getAlarmId(), subAlarm5)); + verify(alarmDao, never()).updateSubAlarmState(eq(subAlarm5.getId()), (AlarmState) any()); + + bolt.setCurrentTime(t1 += 60000); + sendTickTuple(); + assertEquals(subAlarm5.getState(), AlarmState.OK); + verify(collector, never()).emit(new Values(subAlarm5.getAlarmId(), subAlarm5)); + verify(alarmDao, never()).updateSubAlarmState(eq(subAlarm5.getId()), (AlarmState) any()); + + bolt.setCurrentTime(t1 += 60000); + sendTickTuple(); + assertEquals(subAlarm5.getState(), AlarmState.OK); + verify(collector, never()).emit(new Values(subAlarm5.getAlarmId(), subAlarm5)); + verify(alarmDao, never()).updateSubAlarmState(eq(subAlarm5.getId()), (AlarmState) any()); + + bolt.setCurrentTime(t1 += 60000); + sendTickTuple(); + assertEquals(subAlarm5.getState(), AlarmState.OK); + verify(collector, never()).emit(new Values(subAlarm5.getAlarmId(), subAlarm5)); + verify(alarmDao, never()).updateSubAlarmState(eq(subAlarm5.getId()), (AlarmState) any()); + } + + public void shouldTransitionLastImmediatelyToAlarmForExistingAlarm() { + testImmediateChangeWithInitialState(AlarmState.OK, 1.0, AlarmState.ALARM); + } + + public void shouldTransitionLastImmediatelyToOKForExistingAlarm() { + testImmediateChangeWithInitialState(AlarmState.ALARM, 0.0, AlarmState.OK); + } + + private void testImmediateChangeWithInitialState(AlarmState initialState, double value, + final AlarmState expectedState) { + long t1 = 50000; + bolt.setCurrentTime(t1); + subAlarm5.setState(initialState); + sendSubAlarmCreated(metricDef5, subAlarm5); + verify(alarmDao, never()).updateSubAlarmState(eq(subAlarm5.getId()), (AlarmState) any()); + t1 += 1000; + bolt.execute(createMetricTuple(metricDef5, new Metric(metricDef5, t1, value, null))); + assertEquals(subAlarm5.getState(), expectedState); + verify(collector, times(1)).emit(new Values(subAlarm5.getAlarmId(), subAlarm5)); + verify(alarmDao, times(1)).updateSubAlarmState(subAlarm5.getId(), expectedState); + } + + public void testAlarmStateUpdatedWithLast() { + + final AlarmState initialState = AlarmState.OK; + final AlarmState expectedState = AlarmState.OK; + long t1 = 50000; + bolt.setCurrentTime(t1); + subAlarm5.setState(initialState); + sendSubAlarmCreated(metricDef5, subAlarm5); + verify(alarmDao, never()).updateSubAlarmState(eq(subAlarm5.getId()), (AlarmState) any()); + t1 += 1000; + final SubAlarm updated = new SubAlarm(subAlarm5.getId(), subAlarm5.getAlarmId(), new SubExpression("", subAlarm5.getExpression()), AlarmState.OK); + // Simulate an Alarm Update message from the API that would toggle the Alarm to ALARM + sendSubAlarmMsg(EventProcessingBolt.UPDATED, metricDef5, updated); + + // Send another OK measurement. SubAlarm needs to be sent again + bolt.execute(createMetricTuple(metricDef5, new Metric(metricDef5, t1, 0, null))); + assertEquals(subAlarm5.getState(), expectedState); + verify(collector, times(1)).emit(new Values(subAlarm5.getAlarmId(), subAlarm5)); + verify(alarmDao, times(1)).updateSubAlarmState(subAlarm5.getId(), expectedState); + + // Simulate another Alarm Update message from the API that would toggle the Alarm to ALARM + t1 += 1000; + sendSubAlarmMsg("updated", metricDef5, updated); + + // Have to reset the mocks so they can tell the difference when subAlarm5 is emitted again. + reset(collector); + reset(alarmDao); + + // Send another OK measurement. SubAlarm needs to be sent again + bolt.execute(createMetricTuple(metricDef5, new Metric(metricDef5, t1, 0, null))); + assertEquals(subAlarm5.getState(), expectedState); + verify(collector, times(1)).emit(new Values(subAlarm5.getAlarmId(), subAlarm5)); + verify(alarmDao, times(1)).updateSubAlarmState(subAlarm5.getId(), expectedState); + } + + public void testImmediateChangeWithOldMetric() { + final AlarmState initialState = AlarmState.OK; + final double value = 1.0; + final AlarmState expectedState = AlarmState.ALARM; + final SubAlarm subAlarm = subAlarm5; + final MetricDefinition metricDef = metricDef5; + + testOldMetric(subAlarm, metricDef, initialState, value, expectedState); + } + + public void testNonImmediateChangeWithOldMetric() { + final AlarmState initialState = AlarmState.OK; + final double value = 1000.0; + final AlarmState expectedState = AlarmState.OK; + final SubAlarm subAlarm = subAlarm3; + final MetricDefinition metricDef = metricDef3; + + testOldMetric(subAlarm, metricDef, initialState, value, expectedState); + } + + private void testOldMetric(final SubAlarm subAlarm, final MetricDefinition metricDef, + final AlarmState initialState, final double value, final AlarmState expectedState) { + long t1 = 500000; + bolt.setCurrentTime(t1); + subAlarm.setState(initialState); + sendSubAlarmCreated(metricDef, subAlarm); + verify(alarmDao, never()).updateSubAlarmState(eq(subAlarm.getId()), (AlarmState) any()); + // Even though this measurement is way outside the window, make sure it gets processed + // anyways + bolt.execute(createMetricTuple(metricDef, new Metric(metricDef, 1000, value, null))); + assertEquals(subAlarm.getState(), expectedState); + if (initialState != expectedState) { + verify(collector, times(1)).emit(new Values(subAlarm.getAlarmId(), subAlarm)); + verify(alarmDao, times(1)).updateSubAlarmState(subAlarm.getId(), expectedState); + } + } + public void shouldNeverLeaveOkIfThresholdNotExceededForDeterministic() { long t1 = 50000; bolt.setCurrentTime(t1); @@ -620,7 +801,7 @@ public class MetricAggregationBoltTest { final SubAlarmStats oldStats = bolt.metricDefToSubAlarmStatsRepos.get(metricDefinitionAndTenantId).get(ALARM_ID_1); assertEquals(oldStats.getSubAlarm().getExpression().getThreshold(), 90.0); - assertTrue(oldStats.getStats().addValue(80.0, System.currentTimeMillis() / 1000)); + assertTrue(oldStats.addValue(80.0, System.currentTimeMillis() / 1000)); assertFalse(Double.isNaN(oldStats.getStats().getWindowValues()[0])); assertNotNull(bolt.metricDefToSubAlarmStatsRepos.get(metricDefinitionAndTenantId).get(ALARM_ID_1)); @@ -673,8 +854,8 @@ public class MetricAggregationBoltTest { private long currentTime; - public MockMetricAggregationBolt(ThresholdingConfiguration config) { - super(config); + public MockMetricAggregationBolt(ThresholdingConfiguration config, AlarmDAO alarmDao) { + super(config, alarmDao); } @Override