diff --git a/vitrage_tempest_tests/tests/api/alarms/base.py b/vitrage_tempest_tests/tests/api/alarms/base.py new file mode 100644 index 000000000..8c94fc4e3 --- /dev/null +++ b/vitrage_tempest_tests/tests/api/alarms/base.py @@ -0,0 +1,106 @@ +# Copyright 2016 Nokia +# +# 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 random +import time + +from oslo_log import log as logging +from vitrage import clients +from vitrage.common.constants import EntityCategory +from vitrage.common.constants import VertexProperties + +from vitrage_tempest_tests.tests.api.base import BaseApiTest + +import vitrage_tempest_tests.tests.utils as utils + +LOG = logging.getLogger(__name__) + +TEMPLATES_RESOURCES_PATH = 'resources/templates/' +TEMPLATES_SOURCES_PATH = '/etc/vitrage/templates/' + + +class BaseAlarmsTest(BaseApiTest): + """Topology test class for Vitrage API tests.""" + + @classmethod + def setUpClass(cls): + super(BaseAlarmsTest, cls).setUpClass() + cls.ceilometer_client = clients.ceilometer_client(cls.conf) + + @staticmethod + def _filter_alarms_by_parameter(alarms_list, + keys, values): + filtered_alarms_list = [] + for item in alarms_list: + verification = 0 + category = utils.uni2str(item[VertexProperties.CATEGORY]) + for index in range(len(keys)): + key = utils.uni2str(item[keys[index]]) + if category == EntityCategory.ALARM \ + and key == values[index]: + verification += 1 + else: + break + if verification == len(keys): + filtered_alarms_list.append(item) + return filtered_alarms_list + + def _create_ceilometer_alarm(self, resource_id=None, + name=None, unic=True): + if not name: + name = '%s-%s' % ('test_', random.randrange(0, 100000, 1)) + elif unic: + name = '%s-%s' % (name, random.randrange(0, 100000, 1)) + + aodh_request = self._aodh_request(resource_id=resource_id, name=name) + self.ceilometer_client.alarms.create(**aodh_request) + self._wait_for_status(20, + self._check_num_alarms, + num_alarms=1) + time.sleep(25) + + def _delete_ceilometer_alarms(self): + alarms = self.ceilometer_client.alarms.list() + for alarm in alarms: + self.ceilometer_client.alarms.delete(alarm.alarm_id) + self._wait_for_status(20, + self._check_num_alarms, + num_alarms=0) + time.sleep(25) + + @staticmethod + def _aodh_request(resource_id=None, name=None): + query = [] + if resource_id: + query = [ + dict( + field=u'resource_id', + type='', + op=u'eq', + value=resource_id) + ] + + return dict( + name=name, + description=u'test alarm', + event_rule=dict(query=query), + severity='low', + state='alarm', + type=u'event') + + def _check_num_alarms(self, num_alarms=0, state=''): + if len(self.ceilometer_client.alarms.list()) != num_alarms: + return False + + return all(alarm.state.upper() == state.upper() + for alarm in self.ceilometer_client.alarms.list()) diff --git a/vitrage_tempest_tests/tests/api/alarms/test_alarms.py b/vitrage_tempest_tests/tests/api/alarms/test_alarms.py index 10c22a277..88c4f4934 100644 --- a/vitrage_tempest_tests/tests/api/alarms/test_alarms.py +++ b/vitrage_tempest_tests/tests/api/alarms/test_alarms.py @@ -14,80 +14,42 @@ import json from oslo_log import log as logging +from vitrage.datasources.aodh import AODH_DATASOURCE -from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE -from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE +from vitrage_tempest_tests.tests.api.alarms.base import BaseAlarmsTest -from vitrage_tempest_tests.tests.api.base import BaseApiTest import vitrage_tempest_tests.tests.utils as utils LOG = logging.getLogger(__name__) -class BaseAlarmsTest(BaseApiTest): +class TestAlarms(BaseAlarmsTest): """Alarms test class for Vitrage API tests.""" @classmethod def setUpClass(cls): - super(BaseAlarmsTest, cls).setUpClass() + super(TestAlarms, cls).setUpClass() - '''' Nova instances have alarms due to deduced alarm template: ''''' - '''' nova_alarm_for_every_host.yaml ''''' - '''' nova_alarm_for_every_instance.yaml ''''' - '''' Nagios alarm template is nagios_alarm.yaml ''''' - - @staticmethod - def copy_alarms_templates_files(): - utils.run_from_terminal( - "cp " + - "vitrage_tempest_tests/tests/resources/templates/" - + "*_alarm* /etc/vitrage/templates/.") - - @staticmethod - def delete_alarms_templates_files(): - utils.run_from_terminal( - "rm /etc/vitrage/templates/*_alarm*") - - def test_compare_alarms(self): + def test_compare_cli_vs_api_alarms(self): """Wrapper that returns a test graph.""" - self._create_instances(num_instances=3) - api_alarms = self.vitrage_client.alarms.list(vitrage_id=None) - cli_alarms = utils.run_vitrage_command('vitrage alarms list', - self.conf) - self.assertEqual(True, - self._compare_alarms_lists(api_alarms, cli_alarms)) - self._delete_instances() + try: + resources = self._create_instances(num_instances=1) + self._create_ceilometer_alarm(resource_id=resources[0].id, + name='tempest_aodh_test') - def test_nova_alarms(self): - """Wrapper that returns test nova alarms.""" - self._create_instances(num_instances=4) - resources = self.nova_client.servers.list() + api_alarms = self.vitrage_client.alarms.list(vitrage_id=None) + cli_alarms = utils.run_vitrage_command( + 'vitrage alarms list', self.conf) + self.assertTrue(self._compare_alarms_lists( + api_alarms, cli_alarms, AODH_DATASOURCE, + utils.uni2str(resources[0].id))) - alarms = self.vitrage_client.alarms.list(vitrage_id=None) - nova_alarms = self._filter_alarms_by_resource_type( - alarms, NOVA_INSTANCE_DATASOURCE) - self.assertEqual(True, self._validate_alarms_correctness(nova_alarms, - resources)) - self._delete_instances() + finally: + self._delete_ceilometer_alarms() + self._delete_instances() - # def test_nagios_alarms(self): - # """Wrapper that returns test nagios alarms.""" - # alarms = self.vitrage_client.alarms.list() - # nagios_alarms = self._filter_alarms_by_resource_type(alarms, - # 'nagios') - # self.assertEqual(True, self._validate_alarms_correctness( - # nagios_alarms, 'nagios')) - - # def test_aodh_alarms(self): - # """Wrapper that returns test aodh alarms.""" - # # self.create_alarms_per_component('aodh') - # alarms = self.vitrage_client.alarms.list() - # aodh_alarms = self._filter_alarms_by_resource_type(alarms, - # 'aodh') - # self.assertEqual(True, self._validate_alarms_correctness( - # aodh_alarms, 'aodh')) - - def _compare_alarms_lists(self, api_alarms, cli_alarms): + def _compare_alarms_lists(self, api_alarms, cli_alarms, + resource_type, resource_id): """Validate alarm existence """ if not api_alarms: LOG.error("The alarms list taken from api is empty") @@ -100,48 +62,15 @@ class BaseAlarmsTest(BaseApiTest): LOG.debug("The alarms list taken by api is : %s", json.dumps(api_alarms)) - cli_items = cli_alarms.count('vitrage') - nova_instance_alarms = \ - self._filter_alarms_by_resource_type(api_alarms, - NOVA_INSTANCE_DATASOURCE) - nova_instances = cli_alarms.count(NOVA_INSTANCE_DATASOURCE) - nova_host_alarms = \ - self._filter_alarms_by_resource_type(api_alarms, - NOVA_HOST_DATASOURCE) - nova_hosts = cli_alarms.count(NOVA_HOST_DATASOURCE) - return (cli_items == len(api_alarms) and - nova_instances == len(nova_instance_alarms) and - nova_hosts == len(nova_host_alarms)) + cli_items = cli_alarms.splitlines() - @staticmethod - def _validate_alarms_correctness(alarms, resources): - """Validate alarm existence """ - if not alarms: - LOG.error("The alarms list is empty") - return False - if not resources: - LOG.error("The resources list is empty") - return False + api_by_type = self._filter_alarms_by_parameter( + api_alarms, ['type'], [resource_type]) + cli_by_type = cli_alarms.count(' ' + resource_type + ' ') - count = 0 - for resource in resources: - LOG.info("______________________") - LOG.info("The resource id is %s", resource.id) - for item in alarms: - LOG.info("The alarms resource id is %s", item["resource_id"]) - if item["resource_id"] == resource.id: - count += 1 - - LOG.info("The resources list size is %s", len(resources)) - LOG.info("The common items list size is %s", count) - return count == len(resources) - - @staticmethod - def _filter_alarms_by_resource_type(alarms_list, alarm_type): - filtered_alarms_list = [] - for item in alarms_list: - if item["category"] == "ALARM" \ - and item["resource_type"] == alarm_type: - filtered_alarms_list.append(item) - - return filtered_alarms_list + api_by_id = self._filter_alarms_by_parameter( + api_alarms, ['resource_id'], [resource_id]) + cli_by_id = cli_alarms.count(resource_id) + return (len(cli_items) - 4 == len(api_alarms) and + cli_by_type == len(api_by_type) and + cli_by_id == len(api_by_id)) diff --git a/vitrage_tempest_tests/tests/api/base.py b/vitrage_tempest_tests/tests/api/base.py index 3a4d12404..7b60668a7 100644 --- a/vitrage_tempest_tests/tests/api/base.py +++ b/vitrage_tempest_tests/tests/api/base.py @@ -11,7 +11,6 @@ # 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 from oslo_log import log as logging @@ -32,9 +31,11 @@ from vitrage.graph import NXGraph from vitrage.graph import Vertex from vitrage import keystone_client from vitrage_tempest_tests.tests import OPTS -import vitrage_tempest_tests.tests.utils as utils from vitrageclient import client as v_client +import vitrage_tempest_tests.tests.utils as utils + + LOG = logging.getLogger(__name__) @@ -50,6 +51,7 @@ class BaseApiTest(base.BaseTestCase): super(BaseApiTest, cls).setUpClass() cls.conf = utils.get_conf() cls.conf.register_opts(list(OPTS), group='keystone_authtoken') + cls.vitrage_client = \ v_client.Client('1', session=keystone_client.get_session(cls.conf)) cls.nova_client = clients.nova_client(cls.conf) @@ -72,6 +74,13 @@ class BaseApiTest(base.BaseTestCase): return volume + def _get_host(self): + topology = self.vitrage_client.topology.get() + for item in topology['nodes']: + if item[VProps.TYPE] == NOVA_HOST_DATASOURCE: + return item + return None + def _create_instances(self, num_instances): flavors_list = self.nova_client.flavors.list() images_list = self.nova_client.images.list() diff --git a/vitrage_tempest_tests/tests/api/datasources/test_aodh.py b/vitrage_tempest_tests/tests/api/datasources/test_aodh.py index 1bc101b29..a858e54de 100644 --- a/vitrage_tempest_tests/tests/api/datasources/test_aodh.py +++ b/vitrage_tempest_tests/tests/api/datasources/test_aodh.py @@ -11,19 +11,15 @@ # 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 random -import time - from oslo_log import log as logging from vitrage import clients -from vitrage_tempest_tests.tests.api.base import BaseApiTest +from vitrage_tempest_tests.tests.api.alarms.base import BaseAlarmsTest LOG = logging.getLogger(__name__) -class TestAodhAlarm(BaseApiTest): +class TestAodhAlarm(BaseAlarmsTest): @classmethod def setUpClass(cls): @@ -63,51 +59,6 @@ class TestAodhAlarm(BaseApiTest): finally: self._delete_ceilometer_alarms() - def _create_ceilometer_alarm(self, resource_id=None): - aodh_request = self._aodh_request(resource_id=resource_id) - self.ceilometer_client.alarms.create(**aodh_request) - self._wait_for_status(30, - self._check_num_alarms, - num_alarms=1, - state='alarm') - time.sleep(25) - - def _delete_ceilometer_alarms(self): - alarms = self.ceilometer_client.alarms.list() - for alarm in alarms: - self.ceilometer_client.alarms.delete(alarm.alarm_id) - self._wait_for_status(30, - self._check_num_alarms, - num_alarms=0) - time.sleep(25) - - def _check_num_alarms(self, num_alarms=0, state=''): - if len(self.ceilometer_client.alarms.list()) != num_alarms: - return False - - return all(alarm.__dict__['state'].upper() == state.upper() - for alarm in self.ceilometer_client.alarms.list()) - - def _aodh_request(self, resource_id=None): - query = [] - if resource_id: - query = [ - dict( - field=u'resource_id', - type='', - op=u'eq', - value=resource_id) - ] - - random_name = '%s-%s' % ('test', random.randrange(0, 100000, 1)) - return dict( - name=random_name, - description=u'test alarm', - event_rule=dict(query=query), - severity='low', - state='alarm', # ok/alarm/insufficient data - type=u'event') - def _find_instance_resource_id(self): servers = self.nova_client.servers.list() return servers[0].id diff --git a/vitrage_tempest_tests/tests/api/rca/test_rca.py b/vitrage_tempest_tests/tests/api/rca/test_rca.py index dd32b852f..fefb94f8c 100644 --- a/vitrage_tempest_tests/tests/api/rca/test_rca.py +++ b/vitrage_tempest_tests/tests/api/rca/test_rca.py @@ -11,5 +11,149 @@ # 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 json -__author__ = 'stack' +from oslo_log import log as logging +from vitrage.common.constants import VertexProperties +from vitrage.datasources import AODH_DATASOURCE +from vitrage.datasources import NOVA_HOST_DATASOURCE +from vitrage.datasources import NOVA_INSTANCE_DATASOURCE + +from vitrage_tempest_tests.tests.api.alarms.base import BaseAlarmsTest +import vitrage_tempest_tests.tests.utils as utils + +LOG = logging.getLogger(__name__) +RCA_ALARM_NAME = 'rca_test_host_alarm' +VITRAGE_ALARM_NAME = 'instance_deduce' +VITRAGE_DATASOURCE = 'vitrage' + + +class TestRca(BaseAlarmsTest): + """RCA test class for Vitrage API tests.""" + + @classmethod + def setUpClass(cls): + super(TestRca, cls).setUpClass() + + def test_compare_cil_and_api(self): + try: + vitrage_id = self._get_alarm_id( + resource_type=NOVA_INSTANCE_DATASOURCE, + alarm_name='instance_rca_alarm', unic=True) + + api_rca = self.vitrage_client.rca.get(alarm_id=vitrage_id) + cli_rca = utils.run_vitrage_command( + 'vitrage rca show ' + vitrage_id, self.conf) + + self.assertTrue(self._compare_rca(api_rca, cli_rca)) + + finally: + self._delete_ceilometer_alarms() + self._delete_instances() + + def test_validate_rca(self): + try: + vitrage_id = self._get_alarm_id(resource_type=NOVA_HOST_DATASOURCE, + alarm_name=RCA_ALARM_NAME, + unic=False) + resources = self._create_instances(2) + api_rca = self.vitrage_client.rca.get(alarm_id=vitrage_id) + api_alarms = self.vitrage_client.alarms.list(vitrage_id=None) + + self.assertTrue(self._validate_rca(rca=api_rca['nodes'])) + self.assertTrue(self._validate_deduce_alarms(alarms=api_alarms, + resources=resources)) + finally: + self._delete_ceilometer_alarms() + self._delete_instances() + + def _get_alarm_id(self, resource_type, alarm_name, unic): + if resource_type is NOVA_INSTANCE_DATASOURCE: + resource = self._create_instances(num_instances=1) + resource_id = utils.uni2str(resource[0].id) + else: + resource = self._get_host() + resource_id = utils.uni2str(resource[VertexProperties.ID]) + + self._create_ceilometer_alarm(resource_id=resource_id, + name=alarm_name, unic=unic) + + list_alarms = self.vitrage_client.alarms.list(vitrage_id=None) + expected_alarm = self._filter_alarms_by_parameter( + list_alarms, ['resource_id', 'type'], + [resource_id, AODH_DATASOURCE]) + return utils.uni2str( + expected_alarm[0][VertexProperties.VITRAGE_ID]) + + def _compare_rca(self, api_rca, cli_rca): + """Validate alarm existence """ + if not api_rca: + LOG.error("The rca taken from api is empty") + return False + if cli_rca is None: + LOG.error("The rca taken from cli is empty") + return False + + LOG.debug("The rca taken from cli is : %s", cli_rca) + LOG.debug("The rca taken by api is : %s", + json.dumps(api_rca)) + + parsed_rca = json.loads(cli_rca) + sorted_cli_graph = self._clean_timestamps(sorted(parsed_rca.items())) + sorted_api_graph = self._clean_timestamps(sorted(api_rca.items())) + return sorted_cli_graph == sorted_api_graph + + def _validate_rca(self, rca): + """Validate alarm existence """ + if not rca: + LOG.error("The alarms list is empty") + return False + + LOG.debug("The rca alarms list is : %s", + json.dumps(rca)) + + resource_alarm = self._filter_alarms_by_parameter( + rca, ['type', 'name'], + [AODH_DATASOURCE, RCA_ALARM_NAME]) + + deduce_alarms = self._filter_alarms_by_parameter( + rca, ['type', 'name'], + [VITRAGE_DATASOURCE, VITRAGE_ALARM_NAME]) + + return (len(resource_alarm) == 1 and + len(deduce_alarms) == 2) + + def _validate_deduce_alarms(self, alarms, resources): + """Validate alarm existence """ + if not alarms: + LOG.error("The alarms list is empty") + return False + + LOG.debug("The alarms list is : %s", + json.dumps(alarms)) + + deduce_alarms_1 = self._filter_alarms_by_parameter( + alarms, + ['type', 'name', 'resource_type', 'resource_id'], + [VITRAGE_DATASOURCE, VITRAGE_ALARM_NAME, + NOVA_INSTANCE_DATASOURCE, + utils.uni2str(resources[0].id)]) + + deduce_alarms_2 = self._filter_alarms_by_parameter( + alarms, + ['type', 'name', 'resource_type', 'resource_id'], + [VITRAGE_DATASOURCE, VITRAGE_ALARM_NAME, + NOVA_INSTANCE_DATASOURCE, + utils.uni2str(resources[1].id)]) + + return (len(deduce_alarms_1) == 1 and + len(deduce_alarms_2) == 1) + + @staticmethod + def _clean_timestamps(alist): + try: + del alist[5][1][0][VertexProperties.SAMPLE_TIMESTAMP] + del alist[5][1][0][VertexProperties.UPDATE_TIMESTAMP] + except Exception: + pass + return alist diff --git a/vitrage_tempest_tests/tests/resources/templates/api/host_aodh_alarm_for_rca.yaml b/vitrage_tempest_tests/tests/resources/templates/api/host_aodh_alarm_for_rca.yaml new file mode 100644 index 000000000..4beb20c2f --- /dev/null +++ b/vitrage_tempest_tests/tests/resources/templates/api/host_aodh_alarm_for_rca.yaml @@ -0,0 +1,72 @@ +metadata: + id: host_aodh_alarm +definitions: + entities: + - entity: + category: ALARM + type: aodh + name: 'rca_test_host_alarm' + template_id: host_alarm + - entity: + category: RESOURCE + type: nova.host + template_id: host + - entity: + category: RESOURCE + type: nova.instance + template_id: instance + - entity: + category: ALARM + type: vitrage + name: instance_deduce + template_id: instance_alarm + relationships: + - relationship: + source: host_alarm + target: host + relationship_type: on + template_id : alarm_on_host + - relationship: + source: instance_alarm + target: instance + relationship_type: on + template_id : alarm_on_instance + - relationship: + source: host + target: instance + relationship_type: contains + template_id: host_contains_instance +scenarios: + - scenario: + condition: alarm_on_host + actions: + - action: + action_type: set_state + action_target: + target: host + properties: + state: ERROR + - scenario: + condition: alarm_on_host and host_contains_instance + actions: + - action: + action_type: raise_alarm + action_target: + target: instance + properties: + alarm_name: instance_deduce + severity: WARNING + - action: + action_type: set_state + action_target: + target: instance + properties: + state: SUBOPTIMAL + - scenario: + condition: alarm_on_host and host_contains_instance and alarm_on_instance + actions: + - action: + action_type: add_causal_relationship + action_target: + source: host_alarm + target: instance_alarm diff --git a/vitrage_tempest_tests/tests/resources/templates/nagios_alarm.yaml b/vitrage_tempest_tests/tests/resources/templates/api/nagios_alarm_for_alarms.yaml similarity index 100% rename from vitrage_tempest_tests/tests/resources/templates/nagios_alarm.yaml rename to vitrage_tempest_tests/tests/resources/templates/api/nagios_alarm_for_alarms.yaml diff --git a/vitrage_tempest_tests/tests/resources/templates/nova_alarm_for_every_host.yaml b/vitrage_tempest_tests/tests/resources/templates/nova_alarm_for_every_host.yaml deleted file mode 100644 index 1cfe8c268..000000000 --- a/vitrage_tempest_tests/tests/resources/templates/nova_alarm_for_every_host.yaml +++ /dev/null @@ -1,20 +0,0 @@ -metadata: - id: free alarms for hosts -definitions: - entities: - - entity: - category: RESOURCE - type: nova.host - template_id: host -scenarios: - - scenario: - condition: host - actions: - - action: - action_type: raise_alarm - action_target: - target: host - properties: - alarm_name: host_alarm - severity: so_so - diff --git a/vitrage_tempest_tests/tests/resources/templates/nova_alarm_for_every_instance.yaml b/vitrage_tempest_tests/tests/resources/templates/nova_alarm_for_every_instance.yaml deleted file mode 100644 index b5dc8997b..000000000 --- a/vitrage_tempest_tests/tests/resources/templates/nova_alarm_for_every_instance.yaml +++ /dev/null @@ -1,20 +0,0 @@ -metadata: - id: free alarms for instances -definitions: - entities: - - entity: - category: RESOURCE - type: nova.instance - template_id: vm -scenarios: - - scenario: - condition: vm - actions: - - action: - action_type: raise_alarm - action_target: - target: vm - properties: - alarm_name: vm_alarm - severity: so_so - diff --git a/vitrage_tempest_tests/tests/utils.py b/vitrage_tempest_tests/tests/utils.py index 4624c020b..4e97a1d53 100644 --- a/vitrage_tempest_tests/tests/utils.py +++ b/vitrage_tempest_tests/tests/utils.py @@ -67,14 +67,18 @@ def run_vitrage_command(command, conf): project_name_param, auth_url_param) LOG.info('Full command: %s', full_command) - p = subprocess.Popen(full_command, shell=True, executable="/bin/bash", stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() - return stdout + if stderr != '': + LOG.error("The command output error is : " + stderr) + if stdout != '': + LOG.debug("The command output is : \n" + stdout) + return stdout + return None def get_property_value(environment_name, conf_name, default_value, conf): @@ -114,16 +118,6 @@ def get_client(): return oslo_messaging.RPCClient(transport, target) -def get_regex_from_array(pattern, lines_arr): - p = re.compile(pattern) - for line in lines_arr: - m = p.search(line) - if m: - LOG.debug("The field value is " + m.group(1)) - return m.group(1) - return None - - def get_regex_result(pattern, text): p = re.compile(pattern) m = p.search(text) @@ -131,3 +125,7 @@ def get_regex_result(pattern, text): LOG.debug("The regex value is " + m.group(1)) return m.group(1) return None + + +def uni2str(text): + return text.encode('ascii', 'ignore')