diff --git a/doc/source/contributor/configuration.rst b/doc/source/contributor/configuration.rst index 118803085..2628dcd0b 100644 --- a/doc/source/contributor/configuration.rst +++ b/doc/source/contributor/configuration.rst @@ -35,3 +35,4 @@ Notifiers nova-notifier notifier-snmp-plugin + mistral-config diff --git a/doc/source/contributor/mistral-config.rst b/doc/source/contributor/mistral-config.rst new file mode 100644 index 000000000..0f1d616ce --- /dev/null +++ b/doc/source/contributor/mistral-config.rst @@ -0,0 +1,36 @@ +===================== +Mistral Configuration +===================== + +Vitrage can be configured to execute Mistral (the OpenStack Workflow service) +workflows based on certain topology or alarm conditions. + + +Enable Mistral Workflow Execution +--------------------------------- + +To enable Mistral workflow execution, add mistral to the list of notifiers in +/etc/vitrage/vitrage.conf file: + + .. code:: + + [DEFAULT] + notifiers = nova,mistral + + +Add execute_mistral action +-------------------------- + +To execute a Mistral workflow under a certain condition, add an +'execute_mistral' action to a template file: + + .. code:: yaml + + - scenario: + condition: host_down_alarm_on_host + actions: + action: + action_type: execute_mistral + properties: + workflow: evacuate_host # mandatory. The name of the workflow to be executed + hostname: host1 # optional. A list of properties to be passed to the workflow diff --git a/doc/source/contributor/vitrage-template-format.rst b/doc/source/contributor/vitrage-template-format.rst index e853dacea..3ffbbf554 100644 --- a/doc/source/contributor/vitrage-template-format.rst +++ b/doc/source/contributor/vitrage-template-format.rst @@ -401,6 +401,22 @@ This can be used along with nova notifier to call force_down for a host action_target: target: host # mandatory. entity (from the definitions section, only host) to be marked as down + +execute_mistral +^^^^^^^^^^^^^^^ +Execute a Mistral workflow. +If the Mistral notifier is used, the specified workflow will be executed with +its parameters. +:: + + action: + action_type: execute_mistral + properties: + workflow: demo_workflow # mandatory. The name of the workflow to be executed + farewell: Goodbye and Good Luck! # optional. A list of properties to be passed to the workflow + employee: John Smith # optional. A list of properties to be passed to the workflow + + Future support & Open Issues ============================ diff --git a/releasenotes/notes/support-mistral-13618b516699a1c2.yaml b/releasenotes/notes/support-mistral-13618b516699a1c2.yaml new file mode 100644 index 000000000..36851c221 --- /dev/null +++ b/releasenotes/notes/support-mistral-13618b516699a1c2.yaml @@ -0,0 +1,7 @@ +--- +features: + - Integration with Mistral (the OpenStack workflow service). + The Vitrage user can specify in a Vitrage template that if a certain + condition is met (like an alarm on the host, or a combination of a few + alarms) a Mistral workflow should be executed. The workflow can be used, + for example, to take corrective actions. diff --git a/vitrage/evaluator/actions/action_executor.py b/vitrage/evaluator/actions/action_executor.py index 4edf3abb5..f6490c4d0 100644 --- a/vitrage/evaluator/actions/action_executor.py +++ b/vitrage/evaluator/actions/action_executor.py @@ -14,6 +14,7 @@ import copy +from copy import deepcopy from oslo_log import log from oslo_utils import importutils @@ -113,11 +114,14 @@ class ActionExecutor(object): def _execute_external(self, params): # Send a notification to the external engine - external_engine = params[EXECUTION_ENGINE] + execution_engine = params[EXECUTION_ENGINE] + payload = deepcopy(params) + del payload[EXECUTION_ENGINE] + LOG.debug('Notifying external engine %s. Properties: %s', - external_engine, - str(params)) - self.notifier.notify(external_engine, params) + execution_engine, + str(payload)) + self.notifier.notify(execution_engine, payload) @staticmethod def _add_default_properties(event): diff --git a/vitrage/evaluator/actions/notifier.py b/vitrage/evaluator/actions/notifier.py index 304d47637..bfa6c13ec 100644 --- a/vitrage/evaluator/actions/notifier.py +++ b/vitrage/evaluator/actions/notifier.py @@ -50,22 +50,22 @@ class EvaluatorNotifier(object): def enabled(self): return len(self.oslo_notifiers) > 0 - def notify(self, external_engine, properties): + def notify(self, execution_engine, properties): """Send a message to the wanted notifier - :param external_engine: the external engine that should handle the + :param execution_engine: the external engine that should handle the notification and execute an action :param properties: Properties to be processed by the external engine """ - LOG.debug('external_engine: %s, properties: %s', - external_engine, + LOG.debug('execution_engine: %s, properties: %s', + execution_engine, str(properties)) try: - if external_engine in self.oslo_notifiers: - LOG.debug('Notifying %s', external_engine) - self.oslo_notifiers[external_engine].info( + if execution_engine in self.oslo_notifiers: + LOG.debug('Notifying %s', execution_engine) + self.oslo_notifiers[execution_engine].info( {}, NotifierEventTypes.EXECUTE_EXTERNAL_ACTION, properties) diff --git a/vitrage/notifier/__init__.py b/vitrage/notifier/__init__.py index 206c3d692..6c2c71df4 100644 --- a/vitrage/notifier/__init__.py +++ b/vitrage/notifier/__init__.py @@ -16,7 +16,8 @@ from oslo_config import cfg OPTS = [ cfg.ListOpt('notifiers', - help='Names of enabled notifiers (example aodh, nova, snmp)'), + help='Names of enabled notifiers ' + '(example: nova, snmp, mistral)'), cfg.ListOpt('notifiers_path', default=['vitrage.notifier.plugins'], help='list of base path for notifiers'), diff --git a/vitrage/notifier/plugins/mistral/__init__.py b/vitrage/notifier/plugins/mistral/__init__.py new file mode 100644 index 000000000..ade1625b6 --- /dev/null +++ b/vitrage/notifier/plugins/mistral/__init__.py @@ -0,0 +1,23 @@ +# 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_config import cfg + +OPTS = [ + cfg.StrOpt('notifier', + default='vitrage.notifier.plugins.mistral.' + 'mistral_notifier.MistralNotifier', + help='mistral notifier class path', + required=True), +] diff --git a/vitrage/notifier/plugins/mistral/mistral_notifier.py b/vitrage/notifier/plugins/mistral/mistral_notifier.py new file mode 100644 index 000000000..bd8089375 --- /dev/null +++ b/vitrage/notifier/plugins/mistral/mistral_notifier.py @@ -0,0 +1,80 @@ +# 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 vitrage.common.constants import NotifierEventTypes +from vitrage.evaluator.actions.recipes.execute_mistral import WORKFLOW +from vitrage.notifier.plugins.base import NotifierBase +from vitrage import os_clients + + +LOG = logging.getLogger(__name__) + + +class MistralNotifier(NotifierBase): + + def __init__(self, conf): + super(MistralNotifier, self).__init__(conf) + self.conf = conf + self._client = None + + @staticmethod + def get_notifier_name(): + return 'mistral' + + @staticmethod + def use_private_topic(): + return True + + @property + def client(self): + if not self._client: + self._client = os_clients.mistral_client(self.conf) + return self._client + + def info(self, ctxt, publisher_id, event_type, payload, metadata): + """Mistral Endpoint""" + LOG.info('Vitrage Event Info: publisher_id %s', publisher_id) + LOG.info('Vitrage Event Info: event_type %s', event_type) + LOG.info('Vitrage Event Info: metadata %s', metadata) + LOG.info('Vitrage Event Info: payload %s', payload) + + self.process_event(payload, event_type) + + def process_event(self, data, event_type): + if event_type == NotifierEventTypes.EXECUTE_EXTERNAL_ACTION: + LOG.debug('Going to execute Mistral workflow for: %s', data) + + if WORKFLOW not in data: + LOG.warning('Failed to execute a Mistral workflow without ' + 'a workflow name') + return + + try: + workflow = data[WORKFLOW] + del data[WORKFLOW] + + response = self.client.executions.create( + workflow_identifier=workflow, + workflow_input=data, + wf_params={}) + + if response: + LOG.debug('Mistral response: %s', response) + else: + LOG.error('Failed to execute Mistral action') + + except Exception as e: + LOG.warning('Failed to execute Mistral action. Exception: %s', + str(e)) diff --git a/vitrage/os_clients.py b/vitrage/os_clients.py index 130fa3797..e261e039e 100644 --- a/vitrage/os_clients.py +++ b/vitrage/os_clients.py @@ -28,6 +28,7 @@ OPTS = [ cfg.StrOpt('cinder_version', default='2', help='Cinder version'), cfg.StrOpt('glance_version', default='2', help='Glance version'), cfg.StrOpt('heat_version', default='1', help='Heat version'), + cfg.StrOpt('mistral_version', default='2', help='Mistral version'), ] _client_modules = { @@ -37,6 +38,7 @@ _client_modules = { 'glance': 'glanceclient.client', 'neutron': 'neutronclient.v2_0.client', 'heat': 'heatclient.v1.client', + 'mistral': 'mistralclient.api.v2.client', } @@ -148,3 +150,26 @@ def heat_client(conf): return client except Exception as e: LOG.exception('Create Heat client - Got Exception: %s', e) + + +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) + + endpoint = session.get_endpoint(service_type='workflowv2', + endpoint_type='internalURL') + args = { + 'mistral_url': endpoint, + 'session': session + } + client = driver_module('mistral').Client(**args) + LOG.info('Mistral client created') + return client + except Exception as e: + LOG.exception('Create Mistral client - Got Exception: %s', e)