Files
neutron/neutron/notifiers/ironic.py
Riccardo Pittau c6ff9c922e Use openstacksdk for ironic notifiers
This patch removes the dependency from ironicclient for the ironic
event notifiers in favor of openstacksdk.
Also, increasing minimum required versions for mock and
openstacksdk.

Change-Id: Ib76e19d29f0ae3db6d181578b638da699181f60d
2019-09-19 06:56:24 +00:00

150 lines
6.2 KiB
Python

# 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 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 openstack import connection
from openstack import exceptions as os_exc
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."""
global IRONIC_SESSION
if not IRONIC_SESSION:
IRONIC_SESSION = self._get_session(IRONIC_CONF_SECTION)
return connection.Connection(
session=IRONIC_SESSION, oslo_conf=cfg.CONF).baremetal
def send_events(self, batched_events):
try:
response = self.irclient.post('/events',
json={'events': batched_events},
microversion='1.54')
os_exc.raise_from_response(response)
except Exception as e:
LOG.exception('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 != ''})