diff --git a/docs/monasca-api-spec.md b/docs/monasca-api-spec.md index fa04b4f52..ed3d30a24 100644 --- a/docs/monasca-api-spec.md +++ b/docs/monasca-api-spec.md @@ -656,11 +656,11 @@ The list of available statistical functions include the following. ``` - ::= 'min' | 'max' | 'sum' | 'count' | 'avg' + ::= 'min' | 'max' | 'sum' | 'count' | 'avg' | 'last' ``` -where 'avg' is the arithmetic average. Note, threshold values are always in the same units as the metric that they are being compared to. +where 'avg' is the arithmetic average and last is the single most recent value of the metric. When using the last function, the values for 'period' and 'periods' will be ignored. Note, threshold values are always in the same units as the metric that they are being compared to. #### Simple Example @@ -686,6 +686,7 @@ Functions work on all metric measurements during the period time frame. * sum (returns the sum of all the values) * count (returns the number of metric observations) * avg (returns the average of all the values) +* last (returns the single most recent value, ignores values for 'period' and 'periods') The metric is a complex identifier that says the name and optional dimensions. diff --git a/monasca_api/expression_parser/alarm_expr_parser.py b/monasca_api/expression_parser/alarm_expr_parser.py index 7536bba48..827567eda 100644 --- a/monasca_api/expression_parser/alarm_expr_parser.py +++ b/monasca_api/expression_parser/alarm_expr_parser.py @@ -1,6 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # Copyright 2014 Hewlett-Packard +# (C) Copyright 2016 Hewlett Packard Enterprise 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 @@ -212,7 +213,8 @@ min = pyparsing.CaselessLiteral("min") avg = pyparsing.CaselessLiteral("avg") count = pyparsing.CaselessLiteral("count") sum = pyparsing.CaselessLiteral("sum") -func = (max | min | avg | count | sum)("func") +last = pyparsing.CaselessLiteral("last") +func = (max | min | avg | count | sum | last)("func") less_than_op = ( (pyparsing.CaselessLiteral("<") | pyparsing.CaselessLiteral("lt"))) @@ -311,7 +313,9 @@ def main(): "count(log.error{test=1}, deterministic) > 1.0", - "count(log.error{test=1}, deterministic, 120) > 1.0" + "count(log.error{test=1}, deterministic, 120) > 1.0", + + "last(test_metric{hold=here}) < 13" ] for expr in expr_list: diff --git a/monasca_tempest_tests/tests/api/test_alarm_transitions.py b/monasca_tempest_tests/tests/api/test_alarm_transitions.py new file mode 100644 index 000000000..3616bde24 --- /dev/null +++ b/monasca_tempest_tests/tests/api/test_alarm_transitions.py @@ -0,0 +1,171 @@ +# (C) Copyright 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. 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. + +import time + +import base +from monasca_tempest_tests.tests.api import helpers +from tempest.common.utils import data_utils +from tempest import test + +WAIT_SECS = 10 + + +class TestAlarmTransitions(base.BaseMonascaTest): + + @classmethod + def resource_setup(cls): + super(TestAlarmTransitions, cls).resource_setup() + + @classmethod + def resource_cleanup(cls): + super(TestAlarmTransitions, cls).resource_cleanup() + + def _wait_for_alarm_creation(self, definition_id): + for x in xrange(WAIT_SECS): + time.sleep(1) + resp, resp_body = self.monasca_client.list_alarms( + query_params="?alarm_definition_id=" + definition_id) + + self.assertEqual(200, resp.status) + if len(resp_body['elements']) != 0: + break + self.assertEqual(1, len(resp_body['elements'])) + alarm_id = resp_body['elements'][0]['id'] + initial_state = resp_body['elements'][0]['state'] + return alarm_id, initial_state + + def _wait_for_alarm_transition(self, alarm_id, expected_state): + for x in xrange(WAIT_SECS): + time.sleep(1) + resp, resp_body = self.monasca_client.get_alarm(alarm_id) + self.assertEqual(200, resp.status) + if resp_body['state'] == expected_state: + break + self.assertEqual(expected_state, resp_body['state']) + + def _send_measurement(self, metric_def, value): + metric = helpers.create_metric(name=metric_def['name'], + dimensions=metric_def['dimensions'], + value=value) + resp, resp_body = self.monasca_client.create_metrics([metric]) + self.assertEqual(204, resp.status) + + @test.attr(type="gate") + def test_alarm_max_function(self): + metric_def = {'name': data_utils.rand_name("max_test"), + 'dimensions': { + 'dim_to_match': data_utils.rand_name("max_match") + }} + expression = "max(" + metric_def['name'] + ") > 14" + definition = helpers.create_alarm_definition(name="Test Max Function", + description="", + expression=expression, + match_by=["dim_to_match"]) + resp, resp_body = self.monasca_client.create_alarm_definitions(definition) + self.assertEqual(201, resp.status) + definition_id = resp_body['id'] + time.sleep(1) + + self._send_measurement(metric_def, 1) + + alarm_id, initial_state = self._wait_for_alarm_creation(definition_id) + self.assertEqual("UNDETERMINED", initial_state) + + self._send_measurement(metric_def, 20) + + self._wait_for_alarm_transition(alarm_id, "ALARM") + + @test.attr(type="gate") + def test_alarm_max_with_deterministic(self): + metric_def = {'name': data_utils.rand_name("max_deterministic_test"), + 'dimensions': { + 'dim_to_match': data_utils.rand_name("max_match") + }} + expression = "max(" + metric_def['name'] + ",deterministic) > 14" + definition = helpers.create_alarm_definition(name="Test Max Deterministic Function", + description="", + expression=expression, + match_by=["dim_to_match"]) + resp, resp_body = self.monasca_client.create_alarm_definitions(definition) + self.assertEqual(201, resp.status) + definition_id = resp_body['id'] + time.sleep(1) + + self._send_measurement(metric_def, 1) + + alarm_id, initial_state = self._wait_for_alarm_creation(definition_id) + self.assertEqual("OK", initial_state) + + self._send_measurement(metric_def, 20) + + self._wait_for_alarm_transition(alarm_id, "ALARM") + + @test.attr(type="gate") + def test_alarm_last_function(self): + metric_def = {'name': data_utils.rand_name("last_test"), + 'dimensions': { + 'dim_to_match': data_utils.rand_name("last_match") + }} + expression = "last(" + metric_def['name'] + ") > 14" + definition = helpers.create_alarm_definition(name="Test Last Function", + description="", + expression=expression, + match_by=["dim_to_match"]) + resp, resp_body = self.monasca_client.create_alarm_definitions(definition) + self.assertEqual(201, resp.status) + definition_id = resp_body['id'] + time.sleep(1) + + self._send_measurement(metric_def, 1) + + alarm_id, initial_state = self._wait_for_alarm_creation(definition_id) + self.assertEqual("OK", initial_state) + + self._send_measurement(metric_def, 20) + + self._wait_for_alarm_transition(alarm_id, "ALARM") + + self._send_measurement(metric_def, 3) + + self._wait_for_alarm_transition(alarm_id, "OK") + + @test.attr(type="gate") + def test_alarm_last_with_deterministic(self): + metric_def = {'name': data_utils.rand_name("last_deterministic_test"), + 'dimensions': { + 'dim_to_match': data_utils.rand_name("last_match") + }} + expression = "last(" + metric_def['name'] + ",deterministic) > 14" + definition = helpers.create_alarm_definition(name="Test Last Deterministic Function", + description="", + expression=expression, + match_by=["dim_to_match"]) + resp, resp_body = self.monasca_client.create_alarm_definitions(definition) + self.assertEqual(201, resp.status) + definition_id = resp_body['id'] + time.sleep(1) + + self._send_measurement(metric_def, 1) + + alarm_id, initial_state = self._wait_for_alarm_creation(definition_id) + self.assertEqual("OK", initial_state) + + self._send_measurement(metric_def, 20) + + self._wait_for_alarm_transition(alarm_id, "ALARM") + + self._send_measurement(metric_def, 3) + + self._wait_for_alarm_transition(alarm_id, "OK")