diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 463842c94..53fe7c7a0 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -182,6 +182,7 @@ function start_vitrage { fi run_process vitrage-graph "$VITRAGE_BIN_DIR/vitrage-graph --config-file $VITRAGE_CONF" + run_process vitrage-notifier "$VITRAGE_BIN_DIR/vitrage-notifier --config-file $VITRAGE_CONF" } # stop_vitrage() - Stop running processes @@ -191,7 +192,7 @@ function stop_vitrage { restart_apache_server fi # Kill the vitrage screen windows - for serv in vitrage-api vitrage-graph; do + for serv in vitrage-api vitrage-graph vitrage-notifier; do stop_process $serv done } diff --git a/devstack/settings b/devstack/settings index e1bf04267..e4798f2a0 100644 --- a/devstack/settings +++ b/devstack/settings @@ -3,6 +3,8 @@ enable_service vitrage-api # Graph enable_service vitrage-graph +# Notifier +enable_service vitrage-notifier # Default directories diff --git a/doc/source/notifier-aodh-plugin.rst b/doc/source/notifier-aodh-plugin.rst new file mode 100644 index 000000000..70d25f839 --- /dev/null +++ b/doc/source/notifier-aodh-plugin.rst @@ -0,0 +1,67 @@ +=============================== +Vitrage Notifier plugins - AODH +=============================== + +Overview +======== +The Evaluator performs root cause analysis on the Vitrage Graph and may determine that an alarm should be created, deleted or otherwise updated. +Other components are notified of such changes by the Vitrage Notifier service. Among others, Vitrage Notifier is responsible for handling Aodh Alarms. + +This document describes the implementation of Vitrage Notifier infrastructure and specifically notifying Aodh on Vitrage alarms. + +Design +====== + +:: + + +------------------+ +--------+ + | Vitrage | | Message| +------------------+ + | Evaluator ---------->| Bus --------> Vitrage Notifier | + +------------------+ | | +------------------+ + | | | | | + | | +---|------+ | | + | | | Aodh |-|-+ | + | | | notifier | |-|--+ + | | +--| plugin | | | + | | | +----------+ | | + +--------+ | +-----------+ | + | +------------+ + +------------------+ | + | Aodh <-----------------------+ + +------------------+ + +... + +Evaluator bus notifications +--------------------------- +Vitrage Evaluator will use the **vitrage.evaluator** message bus topic, and will post messages as follows: + + - message of type **vitrage.deduce_alarm.activate** : + + * name - is the alarm name in vitrage + * severity - is the alarm severity + * affected_resource_id - is the openstack id of the resource on which the alarm was raised + + - **vitrage.deduce_alarm.deactivate** + + * id - is the alarm id + +Notifier +======== + - Is a new running service + - Receives notifications from the message bus + - Holds instances of all the plugins + - Upon a received notification, calls 'notify(msg)' for all the plugins + - Each plugin is responsible of how and whether to process the notification + +Aodh Plugin +=========== +Vitrage alarms should be reflected as possible in Aodh. the aodh plugin has ceilometer client by which it can send rest calls to aodh + +Handle vitrage.deduce_alarm.activate: +------------------------------------- +Create an event alarm with the specified severity, where the alarm name is vitrage_alarm_name+resource_id so to be unique + +Handle vitrage.deduce_alarm.deactivate: +--------------------------------------- +delete an event alarm with the specified id diff --git a/setup.cfg b/setup.cfg index 94b59be70..4ed6aea44 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,11 +26,15 @@ setup-hooks = console_scripts = vitrage-api = vitrage.cmd.api:main vitrage-graph = vitrage.cmd.graph:main + vitrage-notifier = vitrage.cmd.notifier:main oslo.config.opts = vitrage = vitrage.opts:list_opts plugins = vitrage.opts:plugins_opts +keystoneauth1.plugin = + password-vitrage-legacy = vitrage.keystone_client:LegacyVitrageKeystoneLoader + vitrage.transformers = nova.instance = vitrage.entity_graph.transformer.nova_transformer.instance_transformer.InstanceTransformer nova.host = vitrage.entity_graph.transformer.nova_transformer.host_transformer.HostTransformer diff --git a/vitrage/clients.py b/vitrage/clients.py new file mode 100644 index 000000000..09ffc5491 --- /dev/null +++ b/vitrage/clients.py @@ -0,0 +1,35 @@ +# 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. + +from ceilometerclient import client as cm_client +from oslo_log import log + +from vitrage import keystone_client +LOG = log.getLogger(__name__) + + +def ceilometer_client(conf): + """Get an instance of ceilometer client""" + auth_config = conf.service_credentials + try: + client = cm_client.get_client( + version=2, + session=keystone_client.get_session(conf), + region_name=auth_config.region_name, + interface=auth_config.interface, + ) + LOG.info('Ceilometer client created') + return client + except Exception as e: + LOG.exception('Create Ceilometer client - Got Exception: %s', e) diff --git a/vitrage/cmd/notifier.py b/vitrage/cmd/notifier.py new file mode 100644 index 000000000..dd8e05b9f --- /dev/null +++ b/vitrage/cmd/notifier.py @@ -0,0 +1,30 @@ +# 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. + +from oslo_service import service as os_service +import sys + +from vitrage.notifier.service import VitrageNotifierService +from vitrage import service + + +def main(): + conf = service.prepare_service() + launcher = os_service.ServiceLauncher(conf) + launcher.launch_service(VitrageNotifierService(conf)) + launcher.wait() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/vitrage/common/constants.py b/vitrage/common/constants.py index c81890285..e6ebe70b1 100644 --- a/vitrage/common/constants.py +++ b/vitrage/common/constants.py @@ -81,3 +81,8 @@ class EventAction(object): DELETE_RELATIONSHIP = 'delete_relationship' UPDATE_RELATIONSHIP = 'update_relationship' END_MESSAGE = 'end_message' + + +class NotifierEventTypes(object): + ACTIVATE_ALARM_EVENT = 'vitrage.deduced_alarm.activate' + DEACTIVATE_ALARM_EVENT = 'vitrage.deduced_alarm.deactivate' diff --git a/vitrage/entity_graph/processor/entity_graph.py b/vitrage/entity_graph/processor/entity_graph.py index c54e3050d..963d89de9 100644 --- a/vitrage/entity_graph/processor/entity_graph.py +++ b/vitrage/entity_graph/processor/entity_graph.py @@ -62,14 +62,12 @@ class EntityGraph(NXGraph): def mark_vertex_as_deleted(self, vertex): """Marks the vertex as is deleted, and updates deletion timestamp""" - # TODO(Alexey): change the update_vertex so it will raise a trigger vertex[VProps.IS_DELETED] = True vertex[VProps.UPDATE_TIMESTAMP] = str(utcnow()) self.update_vertex(vertex) def mark_edge_as_deleted(self, edge): """Marks the edge as is deleted, and updates delete timestamp""" - # TODO(Alexey): change the update_edge so it will raise a trigger edge[EProps.IS_DELETED] = True edge[EProps.UPDATE_TIMESTAMP] = str(utcnow()) self.update_edge(edge) diff --git a/vitrage/entity_graph/service.py b/vitrage/entity_graph/service.py index 8273cb9df..3d0f2399e 100644 --- a/vitrage/entity_graph/service.py +++ b/vitrage/entity_graph/service.py @@ -26,37 +26,31 @@ LOG = log.getLogger(__name__) class VitrageGraphService(os_service.Service): - def __init__(self, cfg, event_queue, entity_graph, initialization_status): + def __init__(self, conf, event_queue, entity_graph, initialization_status): super(VitrageGraphService, self).__init__() self.queue = event_queue - self.cfg = cfg - self.processor = proc.Processor(self.cfg, - initialization_status, - e_graph=entity_graph) - - self.scenario_repo = ScenarioRepository(cfg) - self.evaluator = ScenarioEvaluator(entity_graph, - self.scenario_repo, - event_queue) + self.conf = conf + self.scenario_repo = ScenarioRepository(conf) + self.processor = proc.Processor( + conf, + initialization_status, + e_graph=entity_graph) + self.evaluator = ScenarioEvaluator( + conf, + entity_graph, + self.scenario_repo, + event_queue) def start(self): - LOG.info("Start VitrageGraphService") - + LOG.info("Vitrage Graph Service - Starting...") super(VitrageGraphService, self).start() - self.tg.add_timer(1.0, self._process_event_non_blocking) - - LOG.info("Finish start VitrageGraphService") + LOG.info("Vitrage Graph Service - Started!") def stop(self, graceful=False): - LOG.info("Stop VitrageGraphService") - - # TODO(Alexey): check if we need this command here - self.tg.stop_timers() - - super(VitrageGraphService, self).stop() - - LOG.info("Finish stop VitrageGraphService") + LOG.info("Vitrage Graph Service - Stopping...") + super(VitrageGraphService, self).stop(graceful) + LOG.info("Vitrage Graph Service - Stopped!") def _process_events(self): while True: diff --git a/vitrage/evaluator/__init__.py b/vitrage/evaluator/__init__.py index 152020da4..2a2d14af7 100644 --- a/vitrage/evaluator/__init__.py +++ b/vitrage/evaluator/__init__.py @@ -21,4 +21,9 @@ OPTS = [ default='/etc/vitrage/templates', help='A path for the templates used by the evaluator' ), + + cfg.StrOpt('notifier_topic', + default='vitrage.evaluator', + help='The topic that vitrage-evaluator uses for alarm ' + 'notifications messages.'), ] diff --git a/vitrage/evaluator/actions/action_executor.py b/vitrage/evaluator/actions/action_executor.py index aa21f1e88..ab9c3b2f9 100644 --- a/vitrage/evaluator/actions/action_executor.py +++ b/vitrage/evaluator/actions/action_executor.py @@ -41,9 +41,10 @@ LOG = logging.getLogger(__name__) class ActionExecutor(object): - def __init__(self, event_queue): + def __init__(self, event_queue, notifier=None): self.event_queue = event_queue self.action_recipes = ActionExecutor._register_action_recipes() + self.notifier = notifier self.action_step_defs = { ADD_VERTEX: self.add_vertex, @@ -105,7 +106,10 @@ class ActionExecutor(object): self.event_queue.put(event) def notify(self, params): - pass + if self.notifier: + event_type = params['event_type'] + del params['event_type'] + self.notifier.notify(event_type, params) @staticmethod def _add_default_properties(event): diff --git a/vitrage/evaluator/actions/recipes/raise_alarm.py b/vitrage/evaluator/actions/recipes/raise_alarm.py index 31f37dbe2..4105ef714 100644 --- a/vitrage/evaluator/actions/recipes/raise_alarm.py +++ b/vitrage/evaluator/actions/recipes/raise_alarm.py @@ -12,12 +12,14 @@ # License for the specific language governing permissions and limitations # under the License. +from vitrage.common.constants import NotifierEventTypes from vitrage.common.constants import VertexProperties as VProps from vitrage.evaluator.actions.recipes.action_steps import ADD_VERTEX from vitrage.evaluator.actions.recipes.action_steps import NOTIFY from vitrage.evaluator.actions.recipes.action_steps import REMOVE_VERTEX from vitrage.evaluator.actions.recipes import base from vitrage.evaluator.actions.recipes.base import ActionStepWrapper +from vitrage.evaluator.template_fields import TemplateFields as TFields from vitrage.synchronizer.plugins.base.alarm.properties \ import AlarmProperties as AlarmProps @@ -31,7 +33,9 @@ class RaiseAlarm(base.Recipe): params[VProps.STATE] = AlarmProps.ALARM_ACTIVE_STATE add_vertex_step = ActionStepWrapper(ADD_VERTEX, params) - notify_step = RaiseAlarm._get_notify_step() + notify_step = RaiseAlarm._get_notify_step( + action_spec, + NotifierEventTypes.ACTIVATE_ALARM_EVENT) return [add_vertex_step, notify_step] @@ -42,15 +46,22 @@ class RaiseAlarm(base.Recipe): params[VProps.STATE] = AlarmProps.ALARM_INACTIVE_STATE remove_vertex_step = ActionStepWrapper(REMOVE_VERTEX, params) - notify_step = RaiseAlarm._get_notify_step() + notify_step = RaiseAlarm._get_notify_step( + action_spec, + NotifierEventTypes.DEACTIVATE_ALARM_EVENT) return [remove_vertex_step, notify_step] @staticmethod - def _get_notify_step(): + def _get_notify_step(action_spec, event_type): - # TODO(lhartal): add params - return ActionStepWrapper(NOTIFY, {}) + notify_params = { + 'affected_resource_id': action_spec.targets[TFields.TARGET], + 'name': action_spec.properties[TFields.ALARM_NAME], + 'event_type': event_type, + } + notify_step = ActionStepWrapper(NOTIFY, notify_params) + return notify_step @staticmethod def _get_vertex_params(action_spec): diff --git a/vitrage/evaluator/actions/recipes/set_state.py b/vitrage/evaluator/actions/recipes/set_state.py index 6f9d301da..071d72ffc 100644 --- a/vitrage/evaluator/actions/recipes/set_state.py +++ b/vitrage/evaluator/actions/recipes/set_state.py @@ -12,7 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. from vitrage.common.constants import VertexProperties as VProps -from vitrage.evaluator.actions.recipes.action_steps import NOTIFY from vitrage.evaluator.actions.recipes.action_steps import UPDATE_VERTEX from vitrage.evaluator.actions.recipes import base from vitrage.evaluator.actions.recipes.base import ActionStepWrapper @@ -28,9 +27,7 @@ class SetState(base.Recipe): action_spec.targets[TFields.TARGET], action_spec.properties[TFields.STATE]) - notify_step = SetState._get_notify_step() - - return [update_vertex_step, notify_step] + return [update_vertex_step] @staticmethod def get_undo_recipe(action_spec): @@ -39,9 +36,7 @@ class SetState(base.Recipe): action_spec.targets[TFields.TARGET], None) - notify_step = SetState._get_notify_step() - - return [update_vertex_step, notify_step] + return [update_vertex_step] @staticmethod def _get_update_vertex_step(target_id, vitrage_state): @@ -54,9 +49,3 @@ class SetState(base.Recipe): update_vertex_params) return update_vertex_step - - @staticmethod - def _get_notify_step(): - - # TODO(lhartal): add params - return ActionStepWrapper(NOTIFY, {}) diff --git a/vitrage/evaluator/scenario_evaluator.py b/vitrage/evaluator/scenario_evaluator.py index b21ff9c7a..c0e0bc6fc 100644 --- a/vitrage/evaluator/scenario_evaluator.py +++ b/vitrage/evaluator/scenario_evaluator.py @@ -25,6 +25,7 @@ from vitrage.graph.algo_driver.algorithm import Mapping from vitrage.graph import create_algorithm from vitrage.graph import create_graph from vitrage.graph.driver import Vertex +from vitrage.messaging import VitrageNotifier LOG = log.getLogger(__name__) @@ -32,11 +33,14 @@ LOG = log.getLogger(__name__) class ScenarioEvaluator(object): - def __init__(self, entity_graph, scenario_repo, event_queue): + def __init__(self, conf, entity_graph, scenario_repo, event_queue): self._entity_graph = entity_graph self._graph_algs = create_algorithm(entity_graph) self._scenario_repo = scenario_repo - self._action_executor = ActionExecutor(event_queue) + self._notifier = VitrageNotifier(conf, + 'vitrage.evaluator', + conf.evaluator.notifier_topic) + self._action_executor = ActionExecutor(event_queue, self._notifier) self._entity_graph.subscribe(self.process_event) self.enabled = True diff --git a/vitrage/keystone_client.py b/vitrage/keystone_client.py new file mode 100644 index 000000000..fe6a94341 --- /dev/null +++ b/vitrage/keystone_client.py @@ -0,0 +1,170 @@ +# +# Copyright 2015 eNovance +# +# 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 os + + +from keystoneauth1 import exceptions as ka_exception +from keystoneauth1 import identity as ka_identity +from keystoneauth1 import loading as ka_loading +from keystoneclient.v3 import client as ks_client_v3 +from oslo_config import cfg +from oslo_log import log + +LOG = log.getLogger(__name__) + +CFG_GROUP = "service_credentials" + + +def get_session(conf, requests_session=None): + """Get a vitrage service credentials auth session.""" + auth_plugin = ka_loading.load_auth_from_conf_options(conf, CFG_GROUP) + session = ka_loading.load_session_from_conf_options( + conf, CFG_GROUP, auth=auth_plugin, session=requests_session + ) + return session + + +def get_client(conf, trust_id=None, requests_session=None): + """Return a client for keystone v3 endpoint, optionally using a trust.""" + session = get_session(conf, requests_session=requests_session) + return ks_client_v3.Client(session=session, trust_id=trust_id) + + +def get_service_catalog(client): + return client.session.auth.get_access(client.session).service_catalog + + +def get_auth_token(client): + return client.session.auth.get_access(client.session).auth_token + + +def get_client_on_behalf_user(conf, auth_plugin, trust_id=None, + requests_session=None): + """Return a client for keystone v3 endpoint, optionally using a trust.""" + session = ka_loading.load_session_from_conf_options( + conf, CFG_GROUP, auth=auth_plugin, session=requests_session + ) + return ks_client_v3.Client(session=session, trust_id=trust_id) + + +def create_trust_id(conf, trustor_user_id, trustor_project_id, roles, + auth_plugin): + """Create a new trust using the vitrage service user.""" + admin_client = get_client(conf) + trustee_user_id = admin_client.auth_ref.user_id + + client = get_client_on_behalf_user(conf, auth_plugin=auth_plugin) + trust = client.trusts.create(trustor_user=trustor_user_id, + trustee_user=trustee_user_id, + project=trustor_project_id, + impersonation=True, + role_names=roles) + return trust.id + + +def delete_trust_id(conf, trust_id, auth_plugin): + """Delete a trust previously setup for the vitrage user.""" + client = get_client_on_behalf_user(conf, auth_plugin=auth_plugin) + try: + client.trusts.delete(trust_id) + except ka_exception.NotFound: + pass + + +OPTS = [ + cfg.StrOpt('region-name', + default=os.environ.get('OS_REGION_NAME'), + deprecated_name="os-region-name", + help='Region name to use for OpenStack service endpoints.'), + cfg.StrOpt('interface', + default=os.environ.get( + 'OS_INTERFACE', os.environ.get('OS_ENDPOINT_TYPE', + 'public')), + deprecated_name="os-endpoint-type", + choices=('public', 'internal', 'admin', 'auth', 'publicURL', + 'internalURL', 'adminURL'), + help='Type of endpoint in Identity service catalog to use for ' + 'communication with OpenStack services.'), +] + + +def register_keystoneauth_opts(conf): + ka_loading.register_auth_conf_options(conf, CFG_GROUP) + ka_loading.register_session_conf_options( + conf, CFG_GROUP, + deprecated_opts={'cacert': [ + cfg.DeprecatedOpt('os-cacert', group=CFG_GROUP), + cfg.DeprecatedOpt('os-cacert', group="DEFAULT")] + }) + conf.set_default("auth_type", default="password-vitrage-legacy", + group=CFG_GROUP) + + +def setup_keystoneauth(conf): + if conf[CFG_GROUP].auth_type == "password-vitrage-legacy": + LOG.warn("Value 'password-vitrage-legacy' for '[%s]/auth_type' " + "is deprecated. And will be removed in Vitrage 2.0. " + "Use 'password' instead.", + CFG_GROUP) + ka_loading.load_auth_from_conf_options(conf, CFG_GROUP) + + +class LegacyVitrageKeystoneLoader(ka_loading.BaseLoader): + @property + def plugin_class(self): + return ka_identity.V2Password + + def get_options(self): + options = super(LegacyVitrageKeystoneLoader, self).get_options() + options.extend([ + ka_loading.Opt( + 'os-username', + default=os.environ.get('OS_USERNAME', 'vitrage'), + help='User name to use for OpenStack service access.'), + ka_loading.Opt( + 'os-password', + secret=True, + default=os.environ.get('OS_PASSWORD', 'admin'), + help='Password to use for OpenStack service access.'), + ka_loading.Opt( + 'os-tenant-id', + default=os.environ.get('OS_TENANT_ID', ''), + help='Tenant ID to use for OpenStack service access.'), + ka_loading.Opt( + 'os-tenant-name', + default=os.environ.get('OS_TENANT_NAME', 'admin'), + help='Tenant name to use for OpenStack service access.'), + ka_loading.Opt( + 'os-auth-url', + default=os.environ.get('OS_AUTH_URL', + 'http://localhost:5000/v2.0'), + help='Auth URL to use for OpenStack service access.'), + ]) + return options + + def load_from_options(self, **kwargs): + options_map = { + 'os_auth_url': 'auth_url', + 'os_username': 'username', + 'os_password': 'password', + 'os_tenant_name': 'tenant_name', + 'os_tenant_id': 'tenant_id', + } + identity_kwargs = dict((options_map[o.dest], + kwargs.get(o.dest) or o.default) + for o in self.get_options() + if o.dest in options_map) + return self.plugin_class(**identity_kwargs) diff --git a/vitrage/messaging.py b/vitrage/messaging.py new file mode 100644 index 000000000..14a0c6a26 --- /dev/null +++ b/vitrage/messaging.py @@ -0,0 +1,77 @@ +# 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. +from oslo_log import log +import oslo_messaging + +# from oslo_messaging import serializer as oslo_serializer + +LOG = log.getLogger(__name__) + +DEFAULT_URL = "__default__" +TRANSPORTS = {} +# _SERIALIZER = oslo_serializer.JsonPayloadSerializer() + + +def setup(): + # Set the default exchange under which topics are scoped + oslo_messaging.set_transport_defaults('vitrage') + + +def get_transport(conf, url=None, optional=False, cache=True): + """Initialise the oslo_messaging layer.""" + global TRANSPORTS, DEFAULT_URL + cache_key = url or DEFAULT_URL + transport = TRANSPORTS.get(cache_key) + if not transport or not cache: + try: + transport = oslo_messaging.get_transport(conf, url) + except oslo_messaging.InvalidTransportURL as e: + if not optional or e.url: + # NOTE(sileht): oslo_messaging is configured but unloadable + # so reraise the exception + raise + return None + else: + if cache: + TRANSPORTS[cache_key] = transport + return transport + + +def get_notification_listener(transport, targets, endpoints, + allow_requeue=False): + """Return a configured oslo_messaging notification listener.""" + return oslo_messaging.get_notification_listener( + transport, targets, endpoints, executor='threading', + allow_requeue=allow_requeue) + + +class VitrageNotifier(object): + """Allows writing to message bus""" + def __init__(self, conf, publisher_id, topic): + transport = get_transport(conf) + self.notifier = oslo_messaging.Notifier( + transport, + driver='messagingv2', + publisher_id=publisher_id, + topic=topic) + + def notify(self, event_type, data): + LOG.info('notify : ' + event_type + ' ' + str(data)) + if self.notifier: + try: + self.notifier.info({}, event_type, data) + except Exception as e: + LOG.exception('Notifier cannot notify - %e', e) + else: + LOG.error('Notifier cannot notify') diff --git a/vitrage/notifier/__init__.py b/vitrage/notifier/__init__.py new file mode 100644 index 000000000..70097d786 --- /dev/null +++ b/vitrage/notifier/__init__.py @@ -0,0 +1,15 @@ +# 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. + +pass diff --git a/vitrage/notifier/plugins/__init__.py b/vitrage/notifier/plugins/__init__.py new file mode 100644 index 000000000..70097d786 --- /dev/null +++ b/vitrage/notifier/plugins/__init__.py @@ -0,0 +1,15 @@ +# 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. + +pass diff --git a/vitrage/notifier/plugins/aodh/__init__.py b/vitrage/notifier/plugins/aodh/__init__.py new file mode 100644 index 000000000..70097d786 --- /dev/null +++ b/vitrage/notifier/plugins/aodh/__init__.py @@ -0,0 +1,15 @@ +# 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. + +pass diff --git a/vitrage/notifier/plugins/aodh/aodh_notifier.py b/vitrage/notifier/plugins/aodh/aodh_notifier.py new file mode 100644 index 000000000..04f60c340 --- /dev/null +++ b/vitrage/notifier/plugins/aodh/aodh_notifier.py @@ -0,0 +1,95 @@ +# 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 string + +from oslo_log import log as logging + +from vitrage import clients +from vitrage.common.constants import NotifierEventTypes +from vitrage.notifier.plugins.base import NotifierBase + + +LOG = logging.getLogger(__name__) + + +def aodh_alarm_name_generator(name, unique=None, size=6, + chars=string.ascii_uppercase + string.digits): + if unique: + return name.join(['_', unique]) + else: + unique = ''.join(random.choice(chars) for _ in range(size)) + return name.join(['_', unique]) + + +class AodhNotifier(NotifierBase): + + def __init__(self, conf): + super(AodhNotifier, self).__init__(conf) + self.client = clients.ceilometer_client(conf) + + def process_event(self, data, event_type): + if event_type == NotifierEventTypes.DEACTIVATE_ALARM_EVENT: + self._deactivate_aodh_alarm(data) + elif event_type == NotifierEventTypes.ACTIVATE_ALARM_EVENT: + self._activate_aodh_alarm(data) + + def _activate_aodh_alarm(self, data): + LOG.info('### Activate aodh alarm') + # alarm_name = aodh_alarm_name_generator( + # data.get(VProps.NAME), + # data.get('affected_resource_id')) + # query = [dict( + # field='resource_id', + # type='string', + # op='eq', + # value=data.get('affected_resource_id'))] + # severity = data.get(VProps.SEVERITY) + # try: + # alarm = self.client.alarms.create( + # name=alarm_name, + # description='Vitrage deduced alarm', + # query=query, + # severity=severity, + # state='alarm', + # type='event', + # event_rule={"event_type": '*'}) + # LOG.info('Aodh Alarm created: ' + str(alarm)) + # except Exception as e: + # LOG.exception('Failed to create Aodh Alarm, Got Exception: %s',e) + # name + # description + # type' : event or threshold + # threshold_rule + # event_rule + # state': ok, alarm, insufficient data + # severity': moderate, critical, low + # enabled + # alarm_actions + # ok_actions + # insufficient_data_actions + # repeat_actions + # project_id + # user_id + # time_constraints + + def _deactivate_aodh_alarm(self, data): + LOG.info('### Deactivate aodh alarm') + # try: + # alarm = self.client.alarms.update( + # alarm_id=data.get(VProps.ID), + # state='ok') + # LOG.info('Aodh Alarm deactivated ' + str(alarm)) + # except Exception as e: + # LOG.exception('Failed to update Aodh Alarm, Got Exception: %s',e) diff --git a/vitrage/notifier/plugins/base.py b/vitrage/notifier/plugins/base.py new file mode 100644 index 000000000..7ca51311b --- /dev/null +++ b/vitrage/notifier/plugins/base.py @@ -0,0 +1,27 @@ +# 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 abc +import six + + +@six.add_metaclass(abc.ABCMeta) +class NotifierBase(object): + + def __init__(self, conf): + self.conf = conf + + @abc.abstractmethod + def process_event(self, data, event_type): + pass diff --git a/vitrage/notifier/service.py b/vitrage/notifier/service.py new file mode 100644 index 000000000..4c134c272 --- /dev/null +++ b/vitrage/notifier/service.py @@ -0,0 +1,64 @@ +# 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. + +from oslo_log import log +import oslo_messaging +from oslo_service import service as os_service + +from vitrage import messaging +from vitrage.notifier.plugins.aodh.aodh_notifier import AodhNotifier + + +LOG = log.getLogger(__name__) + + +class VitrageNotifierService(os_service.Service): + + def __init__(self, conf): + super(VitrageNotifierService, self).__init__() + self.conf = conf + self.notifiers = [AodhNotifier(conf)] + transport = messaging.get_transport(conf) + target = oslo_messaging.Target(topic=conf.evaluator.notifier_topic) + self.listener = messaging.get_notification_listener( + transport, [target], + [VitrageEventEndpoint(self.notifiers)]) + + def start(self): + LOG.info("Vitrage Notifier Service - Starting...") + super(VitrageNotifierService, self).start() + self.listener.start() + LOG.info("Vitrage Notifier Service - Started!") + + def stop(self, graceful=False): + LOG.info("Vitrage Notifier Service - Stopping...") + self.listener.stop() + self.listener.wait() + super(VitrageNotifierService, self).stop(graceful) + LOG.info("Vitrage Notifier Service - Stopped!") + + +class VitrageEventEndpoint(object): + + def __init__(self, notifiers): + self.notifiers = notifiers + + def info(self, ctxt, publisher_id, event_type, payload, metadata): + """Endpoint for alarm notifications""" + 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) + for plugin in self.notifiers: + plugin.process_event(payload, event_type) diff --git a/vitrage/opts.py b/vitrage/opts.py index 36d8d8b99..00fa2ec33 100644 --- a/vitrage/opts.py +++ b/vitrage/opts.py @@ -21,6 +21,7 @@ from oslo_utils import importutils import vitrage.api import vitrage.entity_graph.consistency import vitrage.evaluator +import vitrage.keystone_client import vitrage.rpc import vitrage.synchronizer import vitrage.synchronizer.plugins @@ -36,6 +37,7 @@ def list_opts(): ('synchronizer_plugins', vitrage.synchronizer.plugins.OPTS), ('consistency', vitrage.entity_graph.consistency.OPTS), ('entity_graph', vitrage.entity_graph.OPTS), + ('service_credentials', vitrage.keystone_client.OPTS), ('DEFAULT', vitrage.rpc.OPTS) ] diff --git a/vitrage/service.py b/vitrage/service.py index 7c1a56f1a..237e70d21 100644 --- a/vitrage/service.py +++ b/vitrage/service.py @@ -18,6 +18,8 @@ from oslo_log import log from oslo_policy import opts as policy_opts from oslo_utils import importutils +from vitrage import keystone_client +from vitrage import messaging from vitrage import opts PLUGINS_PATH = 'vitrage.synchronizer.plugins.' @@ -40,9 +42,13 @@ def prepare_service(args=None, default_opts=None, conf=None): for plugin_name in conf.synchronizer_plugins.plugin_type: load_plugin(conf, plugin_name) + keystone_client.register_keystoneauth_opts(conf) conf(args, project='vitrage', validate_default_values=True) + + keystone_client.setup_keystoneauth(conf) log.setup(conf, 'vitrage') conf.log_opt_values(LOG, logging.DEBUG) + messaging.setup() return conf diff --git a/vitrage/tests/functional/evaluator/test_scenario_evaluator.py b/vitrage/tests/functional/evaluator/test_scenario_evaluator.py index 9b9125db7..33314ca25 100644 --- a/vitrage/tests/functional/evaluator/test_scenario_evaluator.py +++ b/vitrage/tests/functional/evaluator/test_scenario_evaluator.py @@ -30,7 +30,6 @@ class TestScenarioEvaluator(TestEntityGraphFunctionalBase): @classmethod def setUpClass(cls): - cls.conf = cfg.ConfigOpts() cls.conf.register_opts(cls.PROCESSOR_OPTS, group='entity_graph') cls.conf.register_opts(cls.EVALUATOR_OPTS, group='evaluator') @@ -44,7 +43,8 @@ class TestScenarioEvaluator(TestEntityGraphFunctionalBase): # Test Setup processor = self._create_processor_with_graph(self.conf) event_queue = multiprocessing.Queue() - ScenarioEvaluator(processor.entity_graph, + ScenarioEvaluator(self.conf, + processor.entity_graph, self.scenario_repository, event_queue) diff --git a/vitrage/tests/unit/entity_graph/base.py b/vitrage/tests/unit/entity_graph/base.py index 70216d7b4..5ca994fe0 100644 --- a/vitrage/tests/unit/entity_graph/base.py +++ b/vitrage/tests/unit/entity_graph/base.py @@ -37,7 +37,12 @@ class TestEntityGraphUnitBase(base.BaseTest): EVALUATOR_OPTS = [ cfg.StrOpt('templates_dir', default=utils.get_resources_dir() + '/evaluator_templates', - )] + ), + cfg.StrOpt('notifier_topic', + default='vitrage.evaluator', + help='The topic that vitrage-evaluator uses for alarm ' + 'notifications messages.'), + ] PLUGINS_OPTS = [ cfg.ListOpt('plugin_type', diff --git a/vitrage/tests/unit/evaluator/recipes/test_set_state_recipe.py b/vitrage/tests/unit/evaluator/recipes/test_set_state_recipe.py index c07fa4291..9f3093375 100644 --- a/vitrage/tests/unit/evaluator/recipes/test_set_state_recipe.py +++ b/vitrage/tests/unit/evaluator/recipes/test_set_state_recipe.py @@ -41,7 +41,7 @@ class SetStateRecipeTest(base.BaseTest): action_steps = SetState.get_do_recipe(action_spec) # Test Assertions - self.assertEqual(2, len(action_steps)) + self.assertEqual(1, len(action_steps)) self.assertEqual(UPDATE_VERTEX, action_steps[0].type) update_vertex_step_params = action_steps[0].params