Merge "Use openstacksdk for ironic notifiers"

This commit is contained in:
Zuul
2019-09-21 00:56:53 +00:00
committed by Gerrit Code Review
7 changed files with 39 additions and 105 deletions

View File

@@ -49,7 +49,7 @@ logutils==0.3.5
Mako==0.4.0 Mako==0.4.0
MarkupSafe==1.0 MarkupSafe==1.0
mccabe==0.2.1 mccabe==0.2.1
mock==2.0.0 mock==3.0.0
monotonic==0.6;python_version<'3.3' monotonic==0.6;python_version<'3.3'
mox3==0.20.0 mox3==0.20.0
msgpack-python==0.4.0 msgpack-python==0.4.0
@@ -58,7 +58,7 @@ netaddr==0.7.18
netifaces==0.10.4 netifaces==0.10.4
neutron-lib==1.29.1 neutron-lib==1.29.1
openstackdocstheme==1.30.0 openstackdocstheme==1.30.0
openstacksdk==0.11.2 openstacksdk==0.31.2
os-client-config==1.28.0 os-client-config==1.28.0
os-ken==0.3.0 os-ken==0.3.0
os-service-types==1.2.0 os-service-types==1.2.0
@@ -111,7 +111,6 @@ pyroute2==0.5.3
python-dateutil==2.5.3 python-dateutil==2.5.3
python-designateclient==2.7.0 python-designateclient==2.7.0
python-editor==1.0.3 python-editor==1.0.3
python-ironicclient==2.7.0
python-keystoneclient==3.8.0 python-keystoneclient==3.8.0
python-mimeparse==1.6.0 python-mimeparse==1.6.0
python-neutronclient==6.7.0 python-neutronclient==6.7.0

View File

@@ -77,6 +77,8 @@ ks_loading.register_auth_conf_options(cfg.CONF,
common_config.IRONIC_CONF_SECTION) common_config.IRONIC_CONF_SECTION)
ks_loading.register_session_conf_options(cfg.CONF, ks_loading.register_session_conf_options(cfg.CONF,
common_config.IRONIC_CONF_SECTION) common_config.IRONIC_CONF_SECTION)
ks_loading.register_adapter_conf_options(cfg.CONF,
common_config.IRONIC_CONF_SECTION)
common_config.register_ironic_opts() common_config.register_ironic_opts()

View File

@@ -198,33 +198,6 @@ ironic_opts = [
cfg.BoolOpt('enable_notifications', default=False, cfg.BoolOpt('enable_notifications', default=False,
help=_("Send notification events to ironic. (For example on " help=_("Send notification events to ironic. (For example on "
"relevant port status changes.)")), "relevant port status changes.)")),
cfg.StrOpt('region_name',
help=_('Name of region used to get Ironic endpoints. Useful if '
'keystone manages more than one region.')),
cfg.StrOpt('endpoint_type',
default='public',
choices=['public', 'admin', 'internal'],
help=_('Type of the ironic endpoint to use. This endpoint '
'will be looked up in the keystone catalog and should '
'be one of public, internal or admin.')),
cfg.StrOpt('auth_strategy',
default='keystone',
choices=('keystone', 'noauth'),
help=_('Method to use for authentication: noauth or '
'keystone.')),
cfg.StrOpt('ironic_url',
default='http://localhost:6385/',
help=_('Ironic API URL, used to set Ironic API URL when '
'auth_strategy option is noauth to work with standalone '
'Ironic without keystone.')),
cfg.IntOpt('retry_interval',
default=2,
help=_('Interval between retries in case of conflict error '
'(HTTP 409).')),
cfg.IntOpt('max_retries',
default=30,
help=_('Maximum number of retries in case of conflict error '
'(HTTP 409).')),
] ]

View File

@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from ironicclient import client
from ironicclient import exc as ironic_exc
from keystoneauth1 import loading as ks_loading from keystoneauth1 import loading as ks_loading
from neutron_lib.api.definitions import port as port_def from neutron_lib.api.definitions import port as port_def
from neutron_lib.api.definitions import portbindings as portbindings_def from neutron_lib.api.definitions import portbindings as portbindings_def
@@ -22,6 +20,8 @@ from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources from neutron_lib.callbacks import resources
from neutron_lib import constants as n_const 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_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
@@ -61,33 +61,23 @@ class Notifier(object):
def _get_ironic_client(self): def _get_ironic_client(self):
"""Get Ironic client instance.""" """Get Ironic client instance."""
# NOTE: To support standalone ironic without keystone global IRONIC_SESSION
if cfg.CONF.ironic.auth_strategy == 'noauth':
args = {'token': 'noauth', if not IRONIC_SESSION:
'endpoint': cfg.CONF.ironic.ironic_url} IRONIC_SESSION = self._get_session(IRONIC_CONF_SECTION)
else:
global IRONIC_SESSION return connection.Connection(
if not IRONIC_SESSION: session=IRONIC_SESSION, oslo_conf=cfg.CONF).baremetal
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): def send_events(self, batched_events):
# NOTE(TheJulia): Friendly exception handling so operators
# can decouple updates.
try: try:
self.irclient.events.create(events=batched_events) response = self.irclient.post('/events',
except ironic_exc.NotFound: json={'events': batched_events},
LOG.error('The ironic API appears to not support posting events. ' microversion='1.54')
'The API likely needs to be upgraded.') os_exc.raise_from_response(response)
except Exception as e: except Exception as e:
LOG.error('Unknown error encountered posting the event to ' LOG.exception('Error encountered posting the event to '
'ironic. {error}'.format(error=e)) 'ironic. {error}'.format(error=e))
@registry.receives(resources.PORT, [events.AFTER_UPDATE]) @registry.receives(resources.PORT, [events.AFTER_UPDATE])
def process_port_update_event(self, resource, event, trigger, def process_port_update_event(self, resource, event, trigger,

View File

@@ -16,10 +16,9 @@
import eventlet import eventlet
import mock import mock
from ironicclient import client
from ironicclient import exc as ironic_exc
from neutron_lib.api.definitions import portbindings as portbindings_def from neutron_lib.api.definitions import portbindings as portbindings_def
from neutron_lib import constants as n_const from neutron_lib import constants as n_const
from openstack import connection
from neutron.notifiers import batch_notifier from neutron.notifiers import batch_notifier
from neutron.notifiers import ironic from neutron.notifiers import ironic
@@ -40,13 +39,13 @@ def get_fake_port():
class TestIronicNotifier(base.BaseTestCase): class TestIronicNotifier(base.BaseTestCase):
def setUp(self): def setUp(self):
super(TestIronicNotifier, self).setUp() super(TestIronicNotifier, self).setUp()
with mock.patch.object(connection.Connection, 'baremetal',
autospec=False):
self.ironic_notifier = ironic.Notifier()
@mock.patch.object(batch_notifier.BatchNotifier, 'queue_event', @mock.patch.object(batch_notifier.BatchNotifier, 'queue_event',
autospec=True) autospec=True)
@mock.patch.object(client, 'Client', autospec=False) def test_process_port_update_event_bind_port(self, mock_queue_event):
def test_process_port_update_event_bind_port(self, mock_client,
mock_queue_event):
self.ironic_notifier = ironic.Notifier()
port = get_fake_port() port = get_fake_port()
port.update({'status': n_const.PORT_STATUS_ACTIVE}) port.update({'status': n_const.PORT_STATUS_ACTIVE})
original_port = get_fake_port() original_port = get_fake_port()
@@ -66,10 +65,7 @@ class TestIronicNotifier(base.BaseTestCase):
@mock.patch.object(batch_notifier.BatchNotifier, 'queue_event', @mock.patch.object(batch_notifier.BatchNotifier, 'queue_event',
autospec=True) autospec=True)
@mock.patch.object(client, 'Client', autospec=False) def test_process_port_update_event_bind_port_err(self, mock_queue_event):
def test_process_port_update_event_bind_port_err(self, mock_client,
mock_queue_event):
self.ironic_notifier = ironic.Notifier()
port = get_fake_port() port = get_fake_port()
port.update({'status': n_const.PORT_STATUS_ERROR}) port.update({'status': n_const.PORT_STATUS_ERROR})
original_port = get_fake_port() original_port = get_fake_port()
@@ -89,10 +85,7 @@ class TestIronicNotifier(base.BaseTestCase):
@mock.patch.object(batch_notifier.BatchNotifier, 'queue_event', @mock.patch.object(batch_notifier.BatchNotifier, 'queue_event',
autospec=True) autospec=True)
@mock.patch.object(client, 'Client', autospec=False) def test_process_port_update_event_unbind_port(self, mock_queue_event):
def test_process_port_update_event_unbind_port(self, mock_client,
mock_queue_event):
self.ironic_notifier = ironic.Notifier()
port = get_fake_port() port = get_fake_port()
port.update({'status': n_const.PORT_STATUS_DOWN}) port.update({'status': n_const.PORT_STATUS_DOWN})
original_port = get_fake_port() original_port = get_fake_port()
@@ -112,10 +105,7 @@ class TestIronicNotifier(base.BaseTestCase):
@mock.patch.object(batch_notifier.BatchNotifier, 'queue_event', @mock.patch.object(batch_notifier.BatchNotifier, 'queue_event',
autospec=True) autospec=True)
@mock.patch.object(client, 'Client', autospec=False) def test_process_port_update_event_unbind_port_err(self, mock_queue_event):
def test_process_port_update_event_unbind_port_err(self, mock_client,
mock_queue_event):
self.ironic_notifier = ironic.Notifier()
port = get_fake_port() port = get_fake_port()
port.update({'status': n_const.PORT_STATUS_ERROR}) port.update({'status': n_const.PORT_STATUS_ERROR})
original_port = get_fake_port() original_port = get_fake_port()
@@ -135,9 +125,7 @@ class TestIronicNotifier(base.BaseTestCase):
@mock.patch.object(batch_notifier.BatchNotifier, 'queue_event', @mock.patch.object(batch_notifier.BatchNotifier, 'queue_event',
autospec=True) autospec=True)
@mock.patch.object(client, 'Client', autospec=False) def test_process_port_delete_event(self, mock_queue_event):
def test_process_port_delete_event(self, mock_client, mock_queue_event):
self.ironic_notifier = ironic.Notifier()
port = get_fake_port() port = get_fake_port()
self.ironic_notifier.process_port_delete_event( self.ironic_notifier.process_port_delete_event(
'fake_resource', 'fake_event', 'fake_trigger', original_port=None, 'fake_resource', 'fake_event', 'fake_trigger', original_port=None,
@@ -154,10 +142,7 @@ class TestIronicNotifier(base.BaseTestCase):
@mock.patch.object(batch_notifier.BatchNotifier, 'queue_event', @mock.patch.object(batch_notifier.BatchNotifier, 'queue_event',
autospec=True) autospec=True)
@mock.patch.object(client, 'Client', autospec=False) def test_process_port_event_empty_uuid_field(self, mock_queue_event):
def test_process_port_event_empty_uuid_field(self, mock_client,
mock_queue_event):
self.ironic_notifier = ironic.Notifier()
port = get_fake_port() port = get_fake_port()
port.update({'device_id': ''}) port.update({'device_id': ''})
self.ironic_notifier.process_port_delete_event( self.ironic_notifier.process_port_delete_event(
@@ -173,9 +158,7 @@ class TestIronicNotifier(base.BaseTestCase):
'status': 'DELETED'}) 'status': 'DELETED'})
@mock.patch.object(eventlet, 'spawn_n', autospec=True) @mock.patch.object(eventlet, 'spawn_n', autospec=True)
@mock.patch.object(client, 'Client', autospec=False) def test_queue_events(self, mock_spawn_n):
def test_queue_events(self, mock_client, mock_spawn_n):
self.ironic_notifier = ironic.Notifier()
port = get_fake_port() port = get_fake_port()
self.ironic_notifier.process_port_delete_event( self.ironic_notifier.process_port_delete_event(
'fake_resource', 'fake_event', 'fake_trigger', original_port=None, 'fake_resource', 'fake_event', 'fake_trigger', original_port=None,
@@ -193,31 +176,18 @@ class TestIronicNotifier(base.BaseTestCase):
2, len(self.ironic_notifier.batch_notifier._pending_events.queue)) 2, len(self.ironic_notifier.batch_notifier._pending_events.queue))
self.assertEqual(2, mock_spawn_n.call_count) self.assertEqual(2, mock_spawn_n.call_count)
@mock.patch.object(client, 'Client', autospec=False) @mock.patch.object(connection.Connection, 'baremetal', autospec=True)
def test_send_events(self, mock_client): def test_send_events(self, mock_client):
self.ironic_notifier = ironic.Notifier()
self.ironic_notifier.irclient = mock_client self.ironic_notifier.irclient = mock_client
self.ironic_notifier.send_events(['test', 'events']) self.ironic_notifier.send_events(['test', 'events'])
mock_client.events.create.assert_called_with(events=['test', 'events']) mock_client.post.assert_called_with(
'/events', json={'events': ['test', 'events']},
microversion='1.54')
@mock.patch.object(ironic.LOG, 'error', autospec=True) @mock.patch.object(ironic.LOG, 'exception', autospec=True)
@mock.patch.object(client, 'Client', autospec=False) @mock.patch.object(connection.Connection, 'baremetal', autospec=True)
def test_send_event_method_not_found(self, mock_client, mock_log):
self.ironic_notifier = ironic.Notifier()
self.ironic_notifier.irclient = mock_client
exception = ironic_exc.NotFound()
mock_client.events.create.side_effect = exception
self.ironic_notifier.send_events(['test', 'events'])
self.assertEqual(1, mock_log.call_count)
mock_log.assert_called_with('The ironic API appears to not support '
'posting events. The API likely needs to '
'be upgraded.')
@mock.patch.object(ironic.LOG, 'error', autospec=True)
@mock.patch.object(client, 'Client', autospec=False)
def test_send_event_exception(self, mock_client, mock_log): def test_send_event_exception(self, mock_client, mock_log):
self.ironic_notifier = ironic.Notifier()
self.ironic_notifier.irclient = mock_client self.ironic_notifier.irclient = mock_client
mock_client.events.create.side_effect = Exception() mock_client.post.side_effect = Exception()
self.ironic_notifier.send_events(['test', 'events']) self.ironic_notifier.send_events(['test', 'events'])
self.assertEqual(1, mock_log.call_count) self.assertEqual(1, mock_log.call_count)

View File

@@ -52,7 +52,7 @@ pyroute2>=0.5.3;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2)
weakrefmethod>=1.0.2;python_version=='2.7' # PSF weakrefmethod>=1.0.2;python_version=='2.7' # PSF
python-novaclient>=9.1.0 # Apache-2.0 python-novaclient>=9.1.0 # Apache-2.0
python-ironicclient!=2.7.1,>=2.7.0 # Apache-2.0 openstacksdk>=0.31.2 # Apache-2.0
python-designateclient>=2.7.0 # Apache-2.0 python-designateclient>=2.7.0 # Apache-2.0
os-xenapi>=0.3.1 # Apache-2.0 os-xenapi>=0.3.1 # Apache-2.0
os-vif>=1.15.1 # Apache-2.0 os-vif>=1.15.1 # Apache-2.0

View File

@@ -8,7 +8,7 @@ coverage!=4.4,>=4.0 # Apache-2.0
fixtures>=3.0.0 # Apache-2.0/BSD fixtures>=3.0.0 # Apache-2.0/BSD
flake8-import-order==0.12 # LGPLv3 flake8-import-order==0.12 # LGPLv3
pycodestyle>=2.0.0 # MIT pycodestyle>=2.0.0 # MIT
mock>=2.0.0 # BSD mock>=3.0.0 # BSD
python-subunit>=1.0.0 # Apache-2.0/BSD python-subunit>=1.0.0 # Apache-2.0/BSD
testtools>=2.2.0 # MIT testtools>=2.2.0 # MIT
testresources>=2.0.0 # Apache-2.0/BSD testresources>=2.0.0 # Apache-2.0/BSD