Tempest test for Vitrage integration with Mistral

Change-Id: Ieed633ea1e9c29da8fb3f94f17087fb387341078
Implements: blueprint integration-with-mistral
This commit is contained in:
Ifat Afek 2017-08-08 19:47:19 +00:00
parent 3a90745853
commit e8c8d540f8
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 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'
@ -60,6 +62,10 @@ driver = messagingv2
topics = notifications, vitrage_notifications
[[post-config|\$VITRAGE_CONF]]
[DEFAULT]
notifiers = mistral
[static_physical]
changes_interval = 5

View File

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

View File

@ -21,7 +21,7 @@ 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
@ -29,7 +29,6 @@ fi
#(cd $DEVSTACK_PATH/tempest/; sudo pip install -r requirements.txt -r test-requirements.txt)
(cd $DEVSTACK_PATH/; sudo sh -c 'cp -rf vitrage/vitrage_tempest_tests/tests/resources/static_physical/static_physical_configuration.yaml /etc/vitrage/')
(cd $DEVSTACK_PATH/; sudo sh -c 'cp -rf vitrage/vitrage_tempest_tests/tests/resources/templates/api/* /etc/vitrage/templates/')
(cd $DEVSTACK_PATH/; sudo sh -c 'cp -rf vitrage/vitrage_tempest_tests/tests/resources/heat/heat_template.yaml /etc/vitrage/')
(cd $DEVSTACK_PATH/; sudo sh -c 'cp -rf vitrage/vitrage_tempest_tests/tests/resources/heat/policy.json-tempest /etc/heat/')

View File

@ -12,8 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import keystoneauth1.identity.v2 as v2
import keystoneauth1.session as kssession
from oslo_config import cfg
from oslo_log import log
from oslo_utils import importutils as utils
@ -150,13 +148,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 = {

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 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])

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