monasca-api/monasca_api/tests/test_alarms.py

579 lines
22 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2015 Cray Inc.
# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP
# Copyright 2016 FUJITSU LIMITED
#
# 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.
from collections import OrderedDict
import copy
import json
import falcon.testing
import fixtures
import testtools.matchers as matchers
from monasca_api.common.repositories.model import sub_alarm_definition
from monasca_api.tests import base
from monasca_api.v2.reference import alarm_definitions
from monasca_api.v2.reference import alarms
import oslo_config.fixture
import oslotest.base as oslotest
CONF = oslo_config.cfg.CONF
TENANT_ID = u"fedcba9876543210fedcba9876543210"
ALARM_HISTORY = OrderedDict((
# Only present in data returned from InfluxDB:
(u"time", u"2015-01-01T00:00:00.000Z"),
# Only present in data returned from API:
(u"timestamp", u"2015-01-01T00:00:00.000Z"),
(u"alarm_id", u"10000000-1000-1000-1000-10000000000"),
(u"metrics", [{
u"id": None,
u"name": u"test.metric",
u"dimensions": {u"dim1": u"dval1", u"dim2": u"dval2"}
}]),
(u"new_state", u"ALARM"),
(u"old_state", u"OK"),
(u"reason", u"Alarm reason"),
(u"reason_data", u"{}"),
(u"sub_alarms", [{
u"sub_alarm_expression": {
u"function": u"MAX",
# Only present in data returned from InfluxDB:
u"metric_definition": {
u"id": None,
u"name": u"test.metric",
u"dimensions": {u"dim1": u"dval1"},
},
# Only present in data returned from API:
u'metric_name': u'test.metric',
# Only present in data returned from API:
u'dimensions': {u'dim1': u'dval1'},
u"operator": u"GT",
u"threshold": 50.0,
u"period": 60,
u"periods": 1
},
u"sub_alarm_state": u"ALARM",
u"current_values": [50.1],
}]),
# Only present in data returned from InfluxDB:
(u"tenant_id", TENANT_ID),
# Only present in data returned from API:
(u"id", u"1420070400000"),
))
class MonascaApiConfigFixture(oslo_config.fixture.Config):
def setUp(self):
super(MonascaApiConfigFixture, self).setUp()
# [messaging]
self.conf.set_override(
'driver',
'monasca_api.common.messaging.kafka_publisher:KafkaPublisher',
group='messaging', enforce_type=True)
# [repositories]
self.conf.set_override(
'alarms_driver',
'monasca_api.common.repositories.sqla.alarms_repository:AlarmsRepository',
group='repositories', enforce_type=True)
self.conf.set_override(
'alarm_definitions_driver',
'monasca_api.common.repositories.alarm_definitions_repository:AlarmDefinitionsRepository',
group='repositories', enforce_type=True)
self.conf.set_override(
'metrics_driver',
'monasca_api.common.repositories.influxdb.metrics_repository:MetricsRepository',
group='repositories', enforce_type=True)
class InfluxClientAlarmHistoryResponseFixture(fixtures.MockPatch):
def _build_series(self, name, column_dict):
return {
"name": name,
"columns": column_dict.keys(),
"values": [column_dict.values(), ],
}
def _setUp(self):
super(InfluxClientAlarmHistoryResponseFixture, self)._setUp()
mock_data = copy.deepcopy(ALARM_HISTORY)
del mock_data[u"id"]
del mock_data[u"timestamp"]
del mock_data[u"sub_alarms"][0][u"sub_alarm_expression"][u"metric_name"]
del mock_data[u"sub_alarms"][0][u"sub_alarm_expression"][u"dimensions"]
mock_data[u"sub_alarms"] = json.dumps(mock_data[u"sub_alarms"])
mock_data[u"metrics"] = json.dumps(mock_data[u"metrics"])
self.mock.return_value.query.return_value.raw = {
"series": [self._build_series("alarm_state_history", mock_data)]
}
class RESTResponseEquals(object):
"""Match if the supplied data contains a single string containing a JSON
object which decodes to match expected_data, excluding the contents of
the 'links' key.
"""
def __init__(self, expected_data):
self.expected_data = expected_data
if u"links" in expected_data:
del expected_data[u"links"]
def __str__(self):
return 'RESTResponseEquals(%s)' % (self.expected,)
def match(self, actual):
if len(actual) != 1:
return matchers.Mismatch("Response contains <> 1 item: %r" % actual)
response_data = json.loads(actual[0])
if u"links" in response_data:
del response_data[u"links"]
return matchers.Equals(self.expected_data).match(response_data)
class AlarmTestBase(falcon.testing.TestBase, oslotest.BaseTestCase):
api_class = base.MockedAPI
def setUp(self):
super(AlarmTestBase, self).setUp()
self.useFixture(fixtures.MockPatch(
'monasca_api.common.messaging.kafka_publisher.KafkaPublisher'))
self.CONF = self.useFixture(MonascaApiConfigFixture(CONF)).conf
class TestAlarmsStateHistory(AlarmTestBase):
def setUp(self):
super(TestAlarmsStateHistory, self).setUp()
self.useFixture(InfluxClientAlarmHistoryResponseFixture(
'monasca_api.common.repositories.influxdb.metrics_repository.client.InfluxDBClient'))
self.useFixture(fixtures.MockPatch(
'monasca_api.common.repositories.sqla.alarms_repository.AlarmsRepository'))
self.alarms_resource = alarms.AlarmsStateHistory()
self.api.add_route(
'/v2.0/alarms/{alarm_id}/state-history/', self.alarms_resource)
def test_alarm_state_history(self):
expected_elements = {u"elements": [dict(ALARM_HISTORY)]}
del expected_elements[u"elements"][0][u"time"]
del expected_elements[u"elements"][0][u"sub_alarms"][0][u"sub_alarm_expression"][u"metric_definition"]
del expected_elements[u"elements"][0][u"tenant_id"]
response = self.simulate_request(
u'/v2.0/alarms/%s/state-history/' % ALARM_HISTORY[u"alarm_id"],
headers={
'X-Roles': 'admin',
'X-Tenant-Id': TENANT_ID,
})
self.assertEqual(self.srmock.status, falcon.HTTP_200)
self.assertThat(response, RESTResponseEquals(expected_elements))
class TestAlarmDefinition(AlarmTestBase):
def setUp(self):
super(TestAlarmDefinition, self).setUp()
self.alarm_def_repo_mock = self.useFixture(fixtures.MockPatch(
'monasca_api.common.repositories.alarm_definitions_repository.AlarmDefinitionsRepository'
)).mock
self.alarm_definition_resource = alarm_definitions.AlarmDefinitions()
self.api.add_route("/v2.0/alarm-definitions/",
self.alarm_definition_resource)
self.api.add_route("/v2.0/alarm-definitions/{alarm_definition_id}",
self.alarm_definition_resource)
def test_alarm_definition_create(self):
return_value = self.alarm_def_repo_mock.return_value
return_value.get_alarm_definitions.return_value = []
return_value.create_alarm_definition.return_value = u"00000001-0001-0001-0001-000000000001"
alarm_def = {
"name": "Test Definition",
"expression": "test.metric > 10"
}
expected_data = {
u'alarm_actions': [],
u'ok_actions': [],
u'description': u'',
u'match_by': [],
u'name': u'Test Definition',
u'actions_enabled': u'true',
u'undetermined_actions': [],
u'expression': u'test.metric > 10',
u'deterministic': False,
u'id': u'00000001-0001-0001-0001-000000000001',
u'severity': u'LOW',
}
response = self.simulate_request("/v2.0/alarm-definitions/",
headers={'X-Roles': 'admin', 'X-Tenant-Id': TENANT_ID},
method="POST",
body=json.dumps(alarm_def))
self.assertEqual(self.srmock.status, falcon.HTTP_201)
self.assertThat(response, RESTResponseEquals(expected_data))
def test_alarm_definition_create_with_valid_expressions(self):
return_value = self.alarm_def_repo_mock.return_value
return_value.get_alarm_definitions.return_value = []
return_value.create_alarm_definition.return_value = u"00000001-0001-0001-0001-000000000001"
valid_expressions = [
"max(-_.千幸福的笑脸{घोड़ा=馬, "
"dn2=dv2,千幸福的笑脸घ=千幸福的笑脸घ}) gte 100 "
"times 3 && "
"(min(ເຮືອນ{dn3=dv3,家=дом}) < 10 or sum(biz{dn5=dv5}) >99 and "
"count(fizzle) lt 0or count(baz) > 1)".decode('utf8'),
"max(foo{hostname=mini-mon,千=千}, 120) > 100 and (max(bar)>100 "
" or max(biz)>100)".decode('utf8'),
"max(foo)>=100",
"test_metric{this=that, that = this} < 1",
"max ( 3test_metric5 { this = that }) lt 5 times 3",
"3test_metric5 lt 3",
"ntp.offset > 1 or ntp.offset < -5",
]
alarm_def = {
u'name': u'Test Definition',
u'expression': u'test.metric > 10'
}
expected_data = {
u'alarm_actions': [],
u'ok_actions': [],
u'description': u'',
u'match_by': [],
u'name': u'Test Definition',
u'actions_enabled': u'true',
u'undetermined_actions': [],
u'expression': u'test.metric > 10',
u'deterministic': False,
u'id': u'00000001-0001-0001-0001-000000000001',
u'severity': u'LOW',
}
for expression in valid_expressions:
alarm_def[u'expression'] = expression
expected_data[u'expression'] = expression
response = self.simulate_request("/v2.0/alarm-definitions/",
headers={'X-Roles': 'admin', 'X-Tenant-Id': TENANT_ID},
method="POST",
body=json.dumps(alarm_def))
self.assertEqual(self.srmock.status, falcon.HTTP_201,
u'Expression {} should have passed'.format(expression))
self.assertThat(response, RESTResponseEquals(expected_data))
def test_alarm_definition_create_with_invalid_expressions(self):
bad_expressions = [
"test=metric > 10",
"test.metric{dim=this=that} > 10",
"test_metric(5) > 2"
"test_metric > 10 and or alt_metric > 10"
]
alarm_def = {
u'name': 'Test Definition',
u'expression': 'test.metric > 10'
}
for expression in bad_expressions:
alarm_def[u'expression'] = expression
self.simulate_request("/v2.0/alarm-definitions/",
headers={'X-Roles': 'admin', 'X-Tenant-Id': TENANT_ID},
method="POST",
body=json.dumps(alarm_def))
self.assertEqual(self.srmock.status, '422 Unprocessable Entity',
u'Expression {} should have failed'.format(expression))
def test_alarm_definition_update(self):
self.alarm_def_repo_mock.return_value.get_alarm_definitions.return_value = []
self.alarm_def_repo_mock.return_value.update_or_patch_alarm_definition.return_value = (
{u'alarm_actions': [],
u'ok_actions': [],
u'description': u'Non-ASCII character: \u2603'.encode('utf-8'),
u'match_by': u'hostname',
u'name': u'Test Alarm',
u'actions_enabled': True,
u'undetermined_actions': [],
u'is_deterministic': False,
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'id': u'00000001-0001-0001-0001-000000000001',
u'severity': u'LOW'},
{'old': {'11111': sub_alarm_definition.SubAlarmDefinition(
row={'id': '11111',
'alarm_definition_id': u'00000001-0001-0001-0001-000000000001',
'function': 'max',
'metric_name': 'test.metric',
'dimensions': 'hostname=host',
'operator': 'gte',
'threshold': 1,
'period': 60,
'is_deterministic': False,
'periods': 1})},
'changed': {},
'new': {},
'unchanged': {'11111': sub_alarm_definition.SubAlarmDefinition(
row={'id': '11111',
'alarm_definition_id': u'00000001-0001-0001-0001-000000000001',
'function': 'max',
'metric_name': 'test.metric',
'dimensions': 'hostname=host',
'operator': 'gte',
'threshold': 1,
'period': 60,
'is_deterministic': False,
'periods': 1})}
}
)
expected_def = {
u'id': u'00000001-0001-0001-0001-000000000001',
u'alarm_actions': [],
u'ok_actions': [],
u'description': u'Non-ASCII character: \u2603',
u'links': [{u'href': u'http://falconframework.org/v2.0/alarm-definitions/'
u'00000001-0001-0001-0001-000000000001/00000001-0001-0001-0001-000000000001',
u'rel': u'self'}],
u'match_by': [u'hostname'],
u'name': u'Test Alarm',
u'actions_enabled': True,
u'undetermined_actions': [],
u'deterministic': False,
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'severity': u'LOW',
}
alarm_def = {
u'alarm_actions': [],
u'ok_actions': [],
u'description': u'',
u'match_by': [u'hostname'],
u'name': u'Test Alarm',
u'actions_enabled': True,
u'undetermined_actions': [],
u'deterministic': False,
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'severity': u'LOW',
}
result = self.simulate_request("/v2.0/alarm-definitions/%s" % expected_def[u'id'],
headers={'X-Roles': 'admin', 'X-Tenant-Id': TENANT_ID},
method="PUT",
body=json.dumps(alarm_def))
self.assertEqual(self.srmock.status, falcon.HTTP_200)
result_def = json.loads(result[0])
self.assertEqual(result_def, expected_def)
def test_alarm_definition_update_missing_fields(self):
self.alarm_def_repo_mock.return_value.get_alarm_definitions.return_value = []
self.alarm_def_repo_mock.return_value.update_or_patch_alarm_definition.return_value = (
{u'alarm_actions': [],
u'ok_actions': [],
u'description': u'Non-ASCII character: \u2603'.encode('utf-8'),
u'match_by': u'hostname',
u'name': u'Test Alarm',
u'actions_enabled': True,
u'undetermined_actions': [],
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'id': u'00000001-0001-0001-0001-000000000001',
u'is_deterministic': False,
u'severity': u'LOW'},
{'old': {'11111': sub_alarm_definition.SubAlarmDefinition(
row={'id': '11111',
'alarm_definition_id': u'00000001-0001-0001-0001-000000000001',
'function': 'max',
'metric_name': 'test.metric',
'dimensions': 'hostname=host',
'operator': 'gte',
'threshold': 1,
'period': 60,
'periods': 1,
'is_deterministic': False})},
'changed': {},
'new': {},
'unchanged': {'11111': sub_alarm_definition.SubAlarmDefinition(
row={'id': '11111',
'alarm_definition_id': u'00000001-0001-0001-0001-000000000001',
'function': 'max',
'metric_name': 'test.metric',
'dimensions': 'hostname=host',
'operator': 'gte',
'threshold': 1,
'period': 60,
'periods': 1,
'is_deterministic': False})}
}
)
expected_def = {
u'id': u'00000001-0001-0001-0001-000000000001',
u'alarm_actions': [],
u'ok_actions': [],
u'description': u'Non-ASCII character: \u2603',
u'links': [{u'href': u'http://falconframework.org/v2.0/alarm-definitions/'
u'00000001-0001-0001-0001-000000000001/00000001-0001-0001-0001-000000000001',
u'rel': u'self'}],
u'match_by': [u'hostname'],
u'name': u'Test Alarm',
u'actions_enabled': True,
u'undetermined_actions': [],
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'severity': u'LOW',
u'deterministic': False
}
alarm_def = {
u'alarm_actions': [],
u'ok_actions': [],
u'description': u'',
u'match_by': [u'hostname'],
u'name': u'Test Alarm',
u'actions_enabled': True,
u'undetermined_actions': [],
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'severity': u'LOW'
}
result = self.simulate_request("/v2.0/alarm-definitions/%s" % expected_def[u'id'],
headers={'X-Roles': 'admin', 'X-Tenant-Id': TENANT_ID},
method="PUT",
body=json.dumps(alarm_def))
self.assertEqual(self.srmock.status, falcon.HTTP_200)
result_def = json.loads(result[0])
self.assertEqual(result_def, expected_def)
for key, value in alarm_def.iteritems():
del alarm_def[key]
self.simulate_request("/v2.0/alarm-definitions/%s" % expected_def[u'id'],
headers={'X-Roles': 'admin', 'X-Tenant-Id': TENANT_ID},
method="PUT",
body=json.dumps(alarm_def))
self.assertEqual(self.srmock.status, "422 Unprocessable Entity",
u"should have failed without key {}".format(key))
alarm_def[key] = value
def test_alarm_definition_get_specific_alarm(self):
self.alarm_def_repo_mock.return_value.get_alarm_definition.return_value = {
'alarm_actions': None,
'ok_actions': None,
'description': b'Non-ASCII character: \xe2\x98\x83',
'match_by': u'hostname',
'name': u'Test Alarm',
'actions_enabled': 1,
'undetermined_actions': None,
'deterministic': False,
'expression': u'max(test.metric{hostname=host}) gte 1',
'id': u'00000001-0001-0001-0001-000000000001',
'severity': u'LOW'
}
expected_data = {
u'alarm_actions': [],
u'ok_actions': [],
u'description': u'Non-ASCII character: \u2603',
u'match_by': [u'hostname'],
u'name': u'Test Alarm',
u'actions_enabled': True,
u'undetermined_actions': [],
u'deterministic': False,
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'id': u'00000001-0001-0001-0001-000000000001',
u'severity': u'LOW',
}
response = self.simulate_request(
'/v2.0/alarm-definitions/%s' % (expected_data[u'id']),
headers={
'X-Roles': 'admin',
'X-Tenant-Id': TENANT_ID,
})
self.assertEqual(self.srmock.status, falcon.HTTP_200)
self.assertThat(response, RESTResponseEquals(expected_data))
def test_alarm_definition_get_specific_alarm_description_none(self):
self.alarm_def_repo_mock.return_value.get_alarm_definition.return_value = {
'alarm_actions': None,
'ok_actions': None,
'description': None,
'match_by': u'hostname',
'name': u'Test Alarm',
'actions_enabled': 1,
'undetermined_actions': None,
'expression': u'max(test.metric{hostname=host}) gte 1',
'id': u'00000001-0001-0001-0001-000000000001',
'severity': u'LOW'
}
expected_data = {
u'alarm_actions': [],
u'ok_actions': [],
u'description': None,
u'match_by': [u'hostname'],
u'name': u'Test Alarm',
u'actions_enabled': True,
u'undetermined_actions': [],
u'deterministic': False,
u'expression': u'max(test.metric{hostname=host}) gte 1',
u'id': u'00000001-0001-0001-0001-000000000001',
u'severity': u'LOW',
}
response = self.simulate_request(
'/v2.0/alarm-definitions/%s' % (expected_data[u'id']),
headers={
'X-Roles': 'admin',
'X-Tenant-Id': TENANT_ID,
})
self.assertEqual(self.srmock.status, falcon.HTTP_200)
self.assertThat(response, RESTResponseEquals(expected_data))