diff --git a/devstack/gate_hook.sh b/devstack/gate_hook.sh index 0562d8b33..b804c39a6 100644 --- a/devstack/gate_hook.sh +++ b/devstack/gate_hook.sh @@ -27,6 +27,8 @@ export KEEP_LOCALRC=1 DEVSTACK_LOCAL_CONFIG+=$'\nenable_plugin heat git://git.openstack.org/openstack/heat' DEVSTACK_LOCAL_CONFIG+=$'\nenable_plugin ceilometer git://git.openstack.org/openstack/ceilometer' DEVSTACK_LOCAL_CONFIG+=$'\nenable_plugin aodh git://git.openstack.org/openstack/aodh' +DEVSTACK_LOCAL_CONFIG+=$'\nenable_plugin mistral git://git.openstack.org/openstack/mistral' + DEVSTACK_LOCAL_CONFIG+=$'\ndisable_service ceilometer-alarm-evaluator,ceilometer-alarm-notifier' DEVSTACK_LOCAL_CONFIG+=$'\ndisable_service n-net' DEVSTACK_LOCAL_CONFIG+=$'\ndisable_service s-account s-container s-object s-proxy' @@ -62,6 +64,10 @@ driver = messagingv2 topics = notifications, vitrage_notifications [[post-config|\$VITRAGE_CONF]] + +[DEFAULT] +notifiers = mistral + [static_physical] changes_interval = 5 diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 525bc4928..01d8a2fc2 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -196,6 +196,8 @@ function configure_vitrage { # copy datasources cp $VITRAGE_DIR/etc/vitrage/datasources_values/*.yaml $VITRAGE_CONF_DIR/datasources_values + # copy templates + cp -rf $VITRAGE_DIR/vitrage_tempest_tests/tests/resources/templates/api/* $VITRAGE_CONF_DIR/templates/ configure_auth_token_middleware $VITRAGE_CONF vitrage $VITRAGE_AUTH_CACHE_DIR diff --git a/devstack/post_test_hook.sh b/devstack/post_test_hook.sh index c6e6be1f2..0f2431c4c 100644 --- a/devstack/post_test_hook.sh +++ b/devstack/post_test_hook.sh @@ -13,21 +13,18 @@ # License for the specific language governing permissions and limitations # under the License. - DEVSTACK_PATH="$BASE/new" - if [ "$1" = "api" ]; then TESTS="topology" elif [ "$1" = "datasources" ]; then - TESTS="datasources|test_events" + TESTS="datasources|test_events|notifiers" else TESTS="topology" fi cd $DEVSTACK_PATH/ sudo cp -rf vitrage/vitrage_tempest_tests/tests/resources/static_physical/static_physical_configuration.yaml /etc/vitrage/ -sudo cp -rf vitrage/vitrage_tempest_tests/tests/resources/templates/api/* /etc/vitrage/templates/ sudo cp -rf vitrage/vitrage_tempest_tests/tests/resources/heat/heat_template.yaml /etc/vitrage/ sudo cp -rf vitrage/vitrage_tempest_tests/tests/resources/heat/policy.json-tempest /etc/heat/ diff --git a/vitrage/os_clients.py b/vitrage/os_clients.py index e261e039e..9d05c5350 100644 --- a/vitrage/os_clients.py +++ b/vitrage/os_clients.py @@ -155,13 +155,7 @@ def heat_client(conf): def mistral_client(conf): """Get an instance of Mistral client""" try: - auth = v2.Password( - auth_url=conf.service_credentials.auth_url + '/v2.0', - username=conf.service_credentials.username, - password=conf.service_credentials.password, - tenant_name=conf.service_credentials.project_name) - session = kssession.Session(auth=auth) - + session = keystone_client.get_session(conf) endpoint = session.get_endpoint(service_type='workflowv2', endpoint_type='internalURL') args = { diff --git a/vitrage_tempest_tests/tests/api/event/base.py b/vitrage_tempest_tests/tests/api/event/base.py new file mode 100644 index 000000000..2b8e861c0 --- /dev/null +++ b/vitrage_tempest_tests/tests/api/event/base.py @@ -0,0 +1,62 @@ +# Copyright 2017 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. + +from datetime import datetime +from oslo_log import log as logging + +from vitrage import keystone_client +from vitrage import service +from vitrage_tempest_tests.tests.api.base import BaseApiTest +from vitrageclient import client as v_client + + +LOG = logging.getLogger(__name__) +DOWN = 'down' +UP = 'up' + + +class BaseTestEvents(BaseApiTest): + """Test class for Vitrage event API""" + + # noinspection PyPep8Naming + @classmethod + def setUpClass(cls): + cls.conf = service.prepare_service([]) + cls.vitrage_client = \ + v_client.Client('1', session=keystone_client.get_session(cls.conf)) + + def _check_alarms(self): + api_alarms = self.vitrage_client.alarm.list(vitrage_id='all', + all_tenants=True) + if api_alarms: + return True, api_alarms + return False, api_alarms + + def _post_event(self, details): + event_time = datetime.now() + event_time_iso = event_time.isoformat() + event_type = 'compute.host.down' + self.vitrage_client.event.post(event_time_iso, event_type, details) + + @staticmethod + def _create_doctor_event_details(hostname, status): + return { + 'hostname': hostname, + 'source': 'sample_monitor', + 'cause': 'another alarm', + 'severity': 'critical', + 'status': status, + 'monitor_id': 'sample monitor', + 'monitor_event_id': '456', + } diff --git a/vitrage_tempest_tests/tests/api/event/test_events.py b/vitrage_tempest_tests/tests/api/event/test_events.py index 4da7c7971..3a6fc7dc3 100644 --- a/vitrage_tempest_tests/tests/api/event/test_events.py +++ b/vitrage_tempest_tests/tests/api/event/test_events.py @@ -16,55 +16,30 @@ import six from datetime import datetime from oslo_log import log as logging -from oslotest import base from vitrage.common.constants import EntityCategory from vitrage.common.constants import EventProperties as EventProps from vitrage.common.constants import VertexProperties as VProps -from vitrage import keystone_client -from vitrage import service +from vitrage_tempest_tests.tests.api.event.base import BaseTestEvents +from vitrage_tempest_tests.tests.api.event.base import DOWN from vitrage_tempest_tests.tests.utils import wait_for_answer -from vitrageclient import client as v_client LOG = logging.getLogger(__name__) -class TestEvents(base.BaseTestCase): +class TestEvents(BaseTestEvents): """Test class for Vitrage event API""" - # noinspection PyPep8Naming - @classmethod - def setUpClass(cls): - cls.conf = service.prepare_service([]) - cls.vitrage_client = \ - v_client.Client('1', session=keystone_client.get_session(cls.conf)) - def test_send_doctor_event_without_resource_id(self): """Sending an event in Doctor format should result in an alarm""" - details = { - 'hostname': 'host123', - 'source': 'sample_monitor', - 'cause': 'another alarm', - 'severity': 'critical', - 'status': 'down', - 'monitor_id': 'sample monitor', - 'monitor_event_id': '456', - } - self._test_send_doctor_event(details) + self._test_send_doctor_event( + self._create_doctor_event_details('host123', DOWN)) def test_send_doctor_event_without_resource_id_v2(self): """Sending an event in Doctor format should result in an alarm""" - details = { - 'hostname': 'host457', - 'source': 'sample_monitor', - 'cause': 'another alarm', - 'severity': 'critical', - 'status': 'down', - 'monitor_id': 'sample monitor', - 'monitor_event_id': '103', - } - self._test_send_doctor_event(details) + self._test_send_doctor_event( + self._create_doctor_event_details('host457', DOWN)) def _test_send_doctor_event(self, details): try: @@ -98,13 +73,6 @@ class TestEvents(base.BaseTestCase): finally: LOG.warning('done') - def _check_alarms(self): - api_alarms = self.vitrage_client.alarm.list(vitrage_id='all', - all_tenants=True) - if api_alarms: - return True, api_alarms - return False, api_alarms - def _check_alarm(self, alarm, event_time, event_type, details): self.assertEqual(EntityCategory.ALARM, alarm[VProps.VITRAGE_CATEGORY]) self.assertEqual(event_type, alarm[VProps.NAME]) diff --git a/vitrage_tempest_tests/tests/notifiers/__init__.py b/vitrage_tempest_tests/tests/notifiers/__init__.py new file mode 100644 index 000000000..bf9f61d74 --- /dev/null +++ b/vitrage_tempest_tests/tests/notifiers/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2017 - 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. + +__author__ = 'stack' diff --git a/vitrage_tempest_tests/tests/notifiers/test_mistral_notifier.py b/vitrage_tempest_tests/tests/notifiers/test_mistral_notifier.py new file mode 100644 index 000000000..053fd972a --- /dev/null +++ b/vitrage_tempest_tests/tests/notifiers/test_mistral_notifier.py @@ -0,0 +1,130 @@ +# Copyright 2017 - 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. + +from oslo_log import log as logging +from testtools.matchers import HasLength + +from vitrage import os_clients +from vitrage_tempest_tests.tests.api.event.base import BaseTestEvents +from vitrage_tempest_tests.tests.api.event.base import DOWN +from vitrage_tempest_tests.tests.api.event.base import UP +from vitrage_tempest_tests.tests import utils +from vitrage_tempest_tests.tests.utils import wait_for_answer + +LOG = logging.getLogger(__name__) + + +WF_NAME = 'wf_for_tempest_test_1234' + +WF_DEFINITION = """ +--- +version: '2.0' + +wf_for_tempest_test_1234: + type: direct + input: + - farewell + + tasks: + goodbye: + action: std.echo output="<% $.farewell %>, Tempest Test!" +""" + + +class TestMistralNotifier(BaseTestEvents): + + @classmethod + def setUpClass(cls): + super(TestMistralNotifier, cls).setUpClass() + cls.mistral_client = os_clients.mistral_client(cls.conf) + + @utils.tempest_logger + def test_execute_mistral(self): + hostname = self._get_host()['name'] + + workflows = self.mistral_client.workflows.list() + self.assertIsNotNone(workflows) + num_workflows = len(workflows) + + executions = self.mistral_client.executions.list() + self.assertIsNotNone(executions) + num_executions = len(executions) + + alarms = wait_for_answer(2, 0.5, self._check_alarms) + self.assertIsNotNone(alarms) + num_alarms = len(alarms) + + try: + # Create a Mistral workflow + self.mistral_client.workflows.create(WF_DEFINITION) + + # Validate the workflow creation + workflows = self.mistral_client.workflows.list() + self.assertIsNotNone(workflows) + self.assertThat(workflows, HasLength(num_workflows + 1)) + + # Send a Doctor event that should generate an alarm. According to + # execute_mistral.yaml template, the alarm should cause execution + # of the workflow + details = self._create_doctor_event_details(hostname, DOWN) + self._post_event(details) + + # Wait for the alarm to be raised + self.assertTrue( + self._wait_for_status(10, + self._check_num_vitrage_alarms, + num_alarms=num_alarms + 1)) + + # Wait for the Mistral workflow execution + self.assertTrue( + self._wait_for_status(20, + self._check_mistral_workflow_execution, + num_executions=num_executions + 1)) + + except Exception as e: + self._handle_exception(e) + raise + finally: + self._rollback_to_default(WF_NAME, num_workflows, + hostname, num_alarms) + pass + + def _rollback_to_default(self, workflow_name, num_workflows, + hostname, num_alarms): + # Delete the workflow + self.mistral_client.workflows.delete(workflow_name) + + workflows = self.mistral_client.workflows.list() + self.assertIsNotNone(workflows) + self.assertThat(workflows, HasLength(num_workflows)) + + # Clear the host down event and wait for the alarm to be deleted + details = self._create_doctor_event_details(hostname, UP) + self._post_event(details) + + self.assertTrue( + self._wait_for_status(10, + self._check_num_vitrage_alarms, + num_alarms=num_alarms)) + + def _check_num_vitrage_alarms(self, num_alarms): + if len(self.vitrage_client.alarm.list(vitrage_id='all', + all_tenants=True)) == num_alarms: + return True + return False + + def _check_mistral_workflow_execution(self, num_executions): + if len(self.mistral_client.executions.list()) == num_executions: + return True + return False diff --git a/vitrage_tempest_tests/tests/resources/templates/api/execute_mistral.yaml b/vitrage_tempest_tests/tests/resources/templates/api/execute_mistral.yaml new file mode 100644 index 000000000..b180d7565 --- /dev/null +++ b/vitrage_tempest_tests/tests/resources/templates/api/execute_mistral.yaml @@ -0,0 +1,28 @@ +metadata: + name: execute_mistral + description: execute mistral +definitions: + entities: + - entity: + category: ALARM + name: compute.host.down + template_id: host_down_alarm + - entity: + category: RESOURCE + type: nova.host + template_id: host + relationships: + - relationship: + source: host_down_alarm + relationship_type: on + target: host + template_id : host_down_alarm_on_host +scenarios: + - scenario: + condition: host_down_alarm_on_host + actions: + - action: + action_type: execute_mistral + properties: + workflow: wf_for_tempest_test_1234 + farewell: Hello and Goodbye