diff options
author | Harald Jensås <hjensas@redhat.com> | 2019-05-09 10:38:15 +0200 |
---|---|---|
committer | Harald Jensås <hjensas@redhat.com> | 2019-05-27 13:38:42 +0200 |
commit | afff649a3900dd75ffe699f4a599bcbdc449598c (patch) | |
tree | c1234dd01ba7e2ec2cb745ae06e9203281f940e0 /neutron/notifiers | |
parent | 3f837836f679e13a93607c1ad7f9714fa1405b9b (diff) | |
download | neutron-afff649a3900dd75ffe699f4a599bcbdc449598c.tar.gz |
Notify ironic on port status changes
This patch adds an ironic notifier that sends notifications
to ironic endpoint /v1/events. The events are triggered by
port updates and deletions. Only ports with vnic_type
baremetal are honored.
Story: 1304673
Task: 22263
Closes-Bug: #1828367
Implements: blueprint event-notifier-ironic
Authored-By: Vasyl Saienko <vsaienko@mirantis.com>
Co-Authored-By: Harald Jensås <hjensas@redhat.com>
Co-Authored-By: Julia Kreger <juliaashleykreger@gmail.com>
Change-Id: I0bb3187a88a7f20adb8c60e24945db159afb83f1
Diffstat (limited to 'neutron/notifiers')
-rw-r--r-- | neutron/notifiers/ironic.py | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/neutron/notifiers/ironic.py b/neutron/notifiers/ironic.py new file mode 100644 index 0000000000..978c0c8f4b --- /dev/null +++ b/neutron/notifiers/ironic.py @@ -0,0 +1,159 @@ +# Copyright (c) 2019 OpenStack Foundation. +# All Rights Reserved. +# +# 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 ironicclient import client +from ironicclient import exc as ironic_exc +from keystoneauth1 import loading as ks_loading +from neutron_lib.api.definitions import port as port_def +from neutron_lib.api.definitions import portbindings as portbindings_def +from neutron_lib.callbacks import events +from neutron_lib.callbacks import registry +from neutron_lib.callbacks import resources +from neutron_lib import constants as n_const +from oslo_config import cfg +from oslo_log import log as logging + +from neutron.notifiers import batch_notifier + +LOG = logging.getLogger(__name__) + +BAREMETAL_EVENT_TYPE = 'network' +IRONIC_API_VERSION = 'latest' +IRONIC_SESSION = None +IRONIC_CONF_SECTION = 'ironic' +IRONIC_CLIENT_VERSION = 1 + + +@registry.has_registry_receivers +class Notifier(object): + + _instance = None + + @classmethod + def get_instance(cls): + if cls._instance is None: + cls._instance = cls() + return cls._instance + + def __init__(self): + self.batch_notifier = batch_notifier.BatchNotifier( + cfg.CONF.send_events_interval, self.send_events) + self.irclient = self._get_ironic_client() + + def _get_session(self, group): + auth = ks_loading.load_auth_from_conf_options(cfg.CONF, group) + session = ks_loading.load_session_from_conf_options( + cfg.CONF, group, auth=auth) + return session + + def _get_ironic_client(self): + """Get Ironic client instance.""" + + # NOTE: To support standalone ironic without keystone + if cfg.CONF.ironic.auth_strategy == 'noauth': + args = {'token': 'noauth', + 'endpoint': cfg.CONF.ironic.ironic_url} + else: + global IRONIC_SESSION + if not IRONIC_SESSION: + IRONIC_SESSION = self._get_session(IRONIC_CONF_SECTION) + args = {'session': IRONIC_SESSION, + 'region_name': cfg.CONF.ironic.region_name, + 'endpoint_type': cfg.CONF.ironic.endpoint_type} + args['os_ironic_api_version'] = IRONIC_API_VERSION + args['max_retries'] = cfg.CONF.ironic.max_retries + args['retry_interval'] = cfg.CONF.ironic.retry_interval + return client.Client(IRONIC_CLIENT_VERSION, **args) + + def send_events(self, batched_events): + # NOTE(TheJulia): Friendly exception handling so operators + # can decouple updates. + try: + self.irclient.events.create(events=batched_events) + except ironic_exc.NotFound: + LOG.error('The ironic API appears to not support posting events. ' + 'The API likely needs to be upgraded.') + except Exception as e: + LOG.error('Unknown error encountered posting the event to ' + 'ironic. {error}'.format(error=e)) + + @registry.receives(resources.PORT, [events.AFTER_UPDATE]) + def process_port_update_event(self, resource, event, trigger, + original_port=None, port=None, + **kwargs): + # We only want to notify about baremetal ports. + if not (port[portbindings_def.VNIC_TYPE] == + portbindings_def.VNIC_BAREMETAL): + # TODO(TheJulia): Add the smartnic flag at some point... + return + + original_port_status = original_port['status'] + current_port_status = port['status'] + port_event = None + if (original_port_status == n_const.PORT_STATUS_ACTIVE and + current_port_status in [n_const.PORT_STATUS_DOWN, + n_const.PORT_STATUS_ERROR]): + port_event = 'unbind_port' + elif (original_port_status == n_const.PORT_STATUS_DOWN and + current_port_status in [n_const.PORT_STATUS_ACTIVE, + n_const.PORT_STATUS_ERROR]): + port_event = 'bind_port' + LOG.debug('Queuing event for {event_type} for port {port} ' + 'for status {status}.'.format(event_type=port_event, + port=port['id'], + status=current_port_status)) + if port_event: + notify_event = { + 'event': '.'.join([BAREMETAL_EVENT_TYPE, port_event]), + 'port_id': port['id'], + 'mac_address': port[port_def.PORT_MAC_ADDRESS], + 'status': current_port_status, + 'device_id': port['device_id'], + 'binding:host_id': port[portbindings_def.HOST_ID], + 'binding:vnic_type': port[portbindings_def.VNIC_TYPE] + } + # Filter keys with empty string as value. In case a type UUID field + # or similar is not set the API won't accept empty string. + self.batch_notifier.queue_event( + {k: v for k, v in notify_event.items() if v != ''}) + + @registry.receives(resources.PORT, [events.AFTER_DELETE]) + def process_port_delete_event(self, resource, event, trigger, + original_port=None, port=None, + **kwargs): + # We only want to notify about baremetal ports. + if not (port[portbindings_def.VNIC_TYPE] == + portbindings_def.VNIC_BAREMETAL): + # TODO(TheJulia): Add the smartnic flag at some point... + return + + port_event = 'delete_port' + LOG.debug('Queuing event for {event_type} for port {port} ' + 'for status {status}.'.format(event_type=port_event, + port=port['id'], + status='DELETED')) + notify_event = { + 'event': '.'.join([BAREMETAL_EVENT_TYPE, port_event]), + 'port_id': port['id'], + 'mac_address': port[port_def.PORT_MAC_ADDRESS], + 'status': 'DELETED', + 'device_id': port['device_id'], + 'binding:host_id': port[portbindings_def.HOST_ID], + 'binding:vnic_type': port[portbindings_def.VNIC_TYPE] + } + # Filter keys with empty string as value. In case a type UUID field + # or similar is not set the API won't accept empty string. + self.batch_notifier.queue_event( + {k: v for k, v in notify_event.items() if v != ''}) |