Tempest test for Vitrage integration with Mistral

Change-Id: I0021442ce0b426282fc22e4af39dcb001a45e7e0
Depends-On: Ie892482f1dede7487fbd690aff85febb17d17462
Implements: blueprint integration-with-mistral
This commit is contained in:
Ifat Afek 2017-08-08 19:47:19 +00:00
parent cf8782b453
commit 43f7ea634c
9 changed files with 252 additions and 50 deletions

View File

@ -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 heat git://git.openstack.org/openstack/heat'
DEVSTACK_LOCAL_CONFIG+=$'\nenable_plugin ceilometer git://git.openstack.org/openstack/ceilometer' 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 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 ceilometer-alarm-evaluator,ceilometer-alarm-notifier'
DEVSTACK_LOCAL_CONFIG+=$'\ndisable_service n-net' DEVSTACK_LOCAL_CONFIG+=$'\ndisable_service n-net'
DEVSTACK_LOCAL_CONFIG+=$'\ndisable_service s-account s-container s-object s-proxy' DEVSTACK_LOCAL_CONFIG+=$'\ndisable_service s-account s-container s-object s-proxy'
@ -62,6 +64,10 @@ driver = messagingv2
topics = notifications, vitrage_notifications topics = notifications, vitrage_notifications
[[post-config|\$VITRAGE_CONF]] [[post-config|\$VITRAGE_CONF]]
[DEFAULT]
notifiers = mistral
[static_physical] [static_physical]
changes_interval = 5 changes_interval = 5

View File

@ -196,6 +196,8 @@ function configure_vitrage {
# copy datasources # copy datasources
cp $VITRAGE_DIR/etc/vitrage/datasources_values/*.yaml $VITRAGE_CONF_DIR/datasources_values 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 configure_auth_token_middleware $VITRAGE_CONF vitrage $VITRAGE_AUTH_CACHE_DIR

View File

@ -13,21 +13,18 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
DEVSTACK_PATH="$BASE/new" DEVSTACK_PATH="$BASE/new"
if [ "$1" = "api" ]; then if [ "$1" = "api" ]; then
TESTS="topology" TESTS="topology"
elif [ "$1" = "datasources" ]; then elif [ "$1" = "datasources" ]; then
TESTS="datasources|test_events" TESTS="datasources|test_events|notifiers"
else else
TESTS="topology" TESTS="topology"
fi fi
cd $DEVSTACK_PATH/ 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/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/heat_template.yaml /etc/vitrage/
sudo cp -rf vitrage/vitrage_tempest_tests/tests/resources/heat/policy.json-tempest /etc/heat/ sudo cp -rf vitrage/vitrage_tempest_tests/tests/resources/heat/policy.json-tempest /etc/heat/

View File

@ -155,13 +155,7 @@ def heat_client(conf):
def mistral_client(conf): def mistral_client(conf):
"""Get an instance of Mistral client""" """Get an instance of Mistral client"""
try: try:
auth = v2.Password( session = keystone_client.get_session(conf)
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)
endpoint = session.get_endpoint(service_type='workflowv2', endpoint = session.get_endpoint(service_type='workflowv2',
endpoint_type='internalURL') endpoint_type='internalURL')
args = { args = {

View File

@ -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',
}

View File

@ -16,55 +16,30 @@ import six
from datetime import datetime from datetime import datetime
from oslo_log import log as logging from oslo_log import log as logging
from oslotest import base
from vitrage.common.constants import EntityCategory from vitrage.common.constants import EntityCategory
from vitrage.common.constants import EventProperties as EventProps from vitrage.common.constants import EventProperties as EventProps
from vitrage.common.constants import VertexProperties as VProps from vitrage.common.constants import VertexProperties as VProps
from vitrage import keystone_client from vitrage_tempest_tests.tests.api.event.base import BaseTestEvents
from vitrage import service from vitrage_tempest_tests.tests.api.event.base import DOWN
from vitrage_tempest_tests.tests.utils import wait_for_answer from vitrage_tempest_tests.tests.utils import wait_for_answer
from vitrageclient import client as v_client
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class TestEvents(base.BaseTestCase): class TestEvents(BaseTestEvents):
"""Test class for Vitrage event API""" """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): def test_send_doctor_event_without_resource_id(self):
"""Sending an event in Doctor format should result in an alarm""" """Sending an event in Doctor format should result in an alarm"""
details = { self._test_send_doctor_event(
'hostname': 'host123', self._create_doctor_event_details('host123', DOWN))
'source': 'sample_monitor',
'cause': 'another alarm',
'severity': 'critical',
'status': 'down',
'monitor_id': 'sample monitor',
'monitor_event_id': '456',
}
self._test_send_doctor_event(details)
def test_send_doctor_event_without_resource_id_v2(self): def test_send_doctor_event_without_resource_id_v2(self):
"""Sending an event in Doctor format should result in an alarm""" """Sending an event in Doctor format should result in an alarm"""
details = { self._test_send_doctor_event(
'hostname': 'host457', self._create_doctor_event_details('host457', DOWN))
'source': 'sample_monitor',
'cause': 'another alarm',
'severity': 'critical',
'status': 'down',
'monitor_id': 'sample monitor',
'monitor_event_id': '103',
}
self._test_send_doctor_event(details)
def _test_send_doctor_event(self, details): def _test_send_doctor_event(self, details):
try: try:
@ -98,13 +73,6 @@ class TestEvents(base.BaseTestCase):
finally: finally:
LOG.warning('done') 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): def _check_alarm(self, alarm, event_time, event_type, details):
self.assertEqual(EntityCategory.ALARM, alarm[VProps.VITRAGE_CATEGORY]) self.assertEqual(EntityCategory.ALARM, alarm[VProps.VITRAGE_CATEGORY])
self.assertEqual(event_type, alarm[VProps.NAME]) self.assertEqual(event_type, alarm[VProps.NAME])

View File

@ -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'

View File

@ -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

View File

@ -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