Add ironic port group CRUD notifications
This patch adds notifications for create, update or delete port groups. Event types are: baremetal.portgroup.{create, update, delete}.{start,end,error}. Developer documentation updated. "portgroup_uuid" field added to port payload. Closes-Bug: #1660292 Change-Id: I9a8ce6c34e9c704b1aeeb526babcb20a5b1261db
This commit is contained in:
parent
57cd68caf8
commit
f9d9b334da
@ -197,13 +197,14 @@ Example of port CRUD notification::
|
||||
"payload":{
|
||||
"ironic_object.namespace":"ironic",
|
||||
"ironic_object.name":"PortCRUDPayload",
|
||||
"ironic_object.version":"1.0",
|
||||
"ironic_object.version":"1.1",
|
||||
"ironic_object.data":{
|
||||
"address": "77:66:23:34:11:b7",
|
||||
"created_at": "2016-02-11T15:23:03+00:00",
|
||||
"node_uuid": "5b236cab-ad4e-4220-b57c-e827e858745a",
|
||||
"extra": {},
|
||||
"local_link_connection": {},
|
||||
"portgroup_uuid": "bd2f385e-c51c-4752-82d1-7a9ec2c25f24",
|
||||
"pxe_enabled": True,
|
||||
"updated_at": "2016-03-27T20:41:03+00:00",
|
||||
"uuid": "1be26c0b-03f2-4d2e-ae87-c02d7f33c123"
|
||||
@ -213,6 +214,43 @@ Example of port CRUD notification::
|
||||
"publisher_id":"ironic-api.hostname02"
|
||||
}
|
||||
|
||||
List of CRUD notifications for port group:
|
||||
|
||||
* ``baremetal.portgroup.create.start``
|
||||
* ``baremetal.portgroup.create.end``
|
||||
* ``baremetal.portgroup.create.error``
|
||||
* ``baremetal.portgroup.update.start``
|
||||
* ``baremetal.portgroup.update.end``
|
||||
* ``baremetal.portgroup.update.error``
|
||||
* ``baremetal.portgroup.delete.start``
|
||||
* ``baremetal.portgroup.delete.end``
|
||||
* ``baremetal.portgroup.delete.error``
|
||||
|
||||
Example of portgroup CRUD notification::
|
||||
|
||||
{
|
||||
"priority": "info",
|
||||
"payload":{
|
||||
"ironic_object.namespace":"ironic",
|
||||
"ironic_object.name":"PortgroupCRUDPayload",
|
||||
"ironic_object.version":"1.0",
|
||||
"ironic_object.data":{
|
||||
"address": "11:44:32:87:61:e5",
|
||||
"created_at": "2017-01-11T11:33:03+00:00",
|
||||
"node_uuid": "5b236cab-ad4e-4220-b57c-e827e858745a",
|
||||
"extra": {},
|
||||
"mode": "7",
|
||||
"name": "portgroup-node-18",
|
||||
"properties": {},
|
||||
"standalone_ports_supported": True,
|
||||
"updated_at": "2017-01-31T11:41:07+00:00",
|
||||
"uuid": "db033a40-bfed-4c84-815a-3db26bb268bb",
|
||||
}
|
||||
},
|
||||
"event_type":"baremetal.portgroup.update.end",
|
||||
"publisher_id":"ironic-api.hostname02"
|
||||
}
|
||||
|
||||
Node maintenance notifications
|
||||
------------------------------
|
||||
|
||||
|
@ -26,6 +26,7 @@ from ironic.objects import fields
|
||||
from ironic.objects import node as node_objects
|
||||
from ironic.objects import notification
|
||||
from ironic.objects import port as port_objects
|
||||
from ironic.objects import portgroup as portgroup_objects
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
@ -37,7 +38,9 @@ CRUD_NOTIFY_OBJ = {
|
||||
'node': (node_objects.NodeCRUDNotification,
|
||||
node_objects.NodeCRUDPayload),
|
||||
'port': (port_objects.PortCRUDNotification,
|
||||
port_objects.PortCRUDPayload)
|
||||
port_objects.PortCRUDPayload),
|
||||
'portgroup': (portgroup_objects.PortgroupCRUDNotification,
|
||||
portgroup_objects.PortgroupCRUDPayload)
|
||||
}
|
||||
|
||||
|
||||
|
@ -533,13 +533,15 @@ class PortsController(rest.RestController):
|
||||
|
||||
new_port = objects.Port(context, **pdict)
|
||||
|
||||
notify_extra = {'node_uuid': port.node_uuid,
|
||||
'portgroup_uuid': port.portgroup_uuid}
|
||||
notify.emit_start_notification(context, new_port, 'create',
|
||||
node_uuid=port.node_uuid)
|
||||
**notify_extra)
|
||||
with notify.handle_error_notification(context, new_port, 'create',
|
||||
node_uuid=port.node_uuid):
|
||||
**notify_extra):
|
||||
new_port.create()
|
||||
notify.emit_end_notification(context, new_port, 'create',
|
||||
node_uuid=port.node_uuid)
|
||||
**notify_extra)
|
||||
# Set the HTTP Location Header
|
||||
pecan.response.location = link.build_url('ports', new_port.uuid)
|
||||
return Port.convert_with_links(new_port)
|
||||
@ -607,17 +609,19 @@ class PortsController(rest.RestController):
|
||||
rpc_port[field] = patch_val
|
||||
|
||||
rpc_node = objects.Node.get_by_id(context, rpc_port.node_id)
|
||||
notify_extra = {'node_uuid': rpc_node.uuid,
|
||||
'portgroup_uuid': port.portgroup_uuid}
|
||||
notify.emit_start_notification(context, rpc_port, 'update',
|
||||
node_uuid=rpc_node.uuid)
|
||||
**notify_extra)
|
||||
with notify.handle_error_notification(context, rpc_port, 'update',
|
||||
node_uuid=rpc_node.uuid):
|
||||
**notify_extra):
|
||||
topic = pecan.request.rpcapi.get_topic_for(rpc_node)
|
||||
new_port = pecan.request.rpcapi.update_port(context, rpc_port,
|
||||
topic)
|
||||
|
||||
api_port = Port.convert_with_links(new_port)
|
||||
notify.emit_end_notification(context, new_port, 'update',
|
||||
node_uuid=api_port.node_uuid)
|
||||
**notify_extra)
|
||||
|
||||
return api_port
|
||||
|
||||
@ -638,11 +642,20 @@ class PortsController(rest.RestController):
|
||||
|
||||
rpc_port = objects.Port.get_by_uuid(context, port_uuid)
|
||||
rpc_node = objects.Node.get_by_id(context, rpc_port.node_id)
|
||||
|
||||
portgroup_uuid = None
|
||||
if rpc_port.portgroup_id:
|
||||
portgroup = objects.Portgroup.get_by_id(context,
|
||||
rpc_port.portgroup_id)
|
||||
portgroup_uuid = portgroup.uuid
|
||||
|
||||
notify_extra = {'node_uuid': rpc_node.uuid,
|
||||
'portgroup_uuid': portgroup_uuid}
|
||||
notify.emit_start_notification(context, rpc_port, 'delete',
|
||||
node_uuid=rpc_node.uuid)
|
||||
**notify_extra)
|
||||
with notify.handle_error_notification(context, rpc_port, 'delete',
|
||||
node_uuid=rpc_node.uuid):
|
||||
**notify_extra):
|
||||
topic = pecan.request.rpcapi.get_topic_for(rpc_node)
|
||||
pecan.request.rpcapi.destroy_port(context, rpc_port, topic)
|
||||
notify.emit_end_notification(context, rpc_port, 'delete',
|
||||
node_uuid=rpc_node.uuid)
|
||||
**notify_extra)
|
||||
|
@ -13,6 +13,7 @@
|
||||
import datetime
|
||||
|
||||
from ironic_lib import metrics_utils
|
||||
from oslo_utils import uuidutils
|
||||
import pecan
|
||||
from six.moves import http_client
|
||||
import wsme
|
||||
@ -21,6 +22,7 @@ from wsme import types as wtypes
|
||||
from ironic.api.controllers import base
|
||||
from ironic.api.controllers import link
|
||||
from ironic.api.controllers.v1 import collection
|
||||
from ironic.api.controllers.v1 import notification_utils as notify
|
||||
from ironic.api.controllers.v1 import port
|
||||
from ironic.api.controllers.v1 import types
|
||||
from ironic.api.controllers.v1 import utils as api_utils
|
||||
@ -429,7 +431,8 @@ class PortgroupsController(pecan.rest.RestController):
|
||||
if not api_utils.allow_portgroups():
|
||||
raise exception.NotFound()
|
||||
|
||||
cdict = pecan.request.context.to_policy_values()
|
||||
context = pecan.request.context
|
||||
cdict = context.to_policy_values()
|
||||
policy.authorize('baremetal:portgroup:create', cdict, cdict)
|
||||
|
||||
if self.parent_node_ident:
|
||||
@ -452,9 +455,20 @@ class PortgroupsController(pecan.rest.RestController):
|
||||
if vif:
|
||||
common_utils.warn_about_deprecated_extra_vif_port_id()
|
||||
|
||||
new_portgroup = objects.Portgroup(pecan.request.context,
|
||||
**pg_dict)
|
||||
new_portgroup.create()
|
||||
# NOTE(yuriyz): UUID is mandatory for notifications payload
|
||||
if not pg_dict.get('uuid'):
|
||||
pg_dict['uuid'] = uuidutils.generate_uuid()
|
||||
|
||||
new_portgroup = objects.Portgroup(context, **pg_dict)
|
||||
|
||||
notify.emit_start_notification(context, new_portgroup, 'create',
|
||||
node_uuid=portgroup.node_uuid)
|
||||
with notify.handle_error_notification(context, new_portgroup, 'create',
|
||||
node_uuid=portgroup.node_uuid):
|
||||
new_portgroup.create()
|
||||
notify.emit_end_notification(context, new_portgroup, 'create',
|
||||
node_uuid=portgroup.node_uuid)
|
||||
|
||||
# Set the HTTP Location Header
|
||||
pecan.response.location = link.build_url('portgroups',
|
||||
new_portgroup.uuid)
|
||||
@ -472,7 +486,8 @@ class PortgroupsController(pecan.rest.RestController):
|
||||
if not api_utils.allow_portgroups():
|
||||
raise exception.NotFound()
|
||||
|
||||
cdict = pecan.request.context.to_policy_values()
|
||||
context = pecan.request.context
|
||||
cdict = context.to_policy_values()
|
||||
policy.authorize('baremetal:portgroup:update', cdict, cdict)
|
||||
|
||||
if self.parent_node_ident:
|
||||
@ -520,14 +535,21 @@ class PortgroupsController(pecan.rest.RestController):
|
||||
if rpc_portgroup[field] != patch_val:
|
||||
rpc_portgroup[field] = patch_val
|
||||
|
||||
rpc_node = objects.Node.get_by_id(pecan.request.context,
|
||||
rpc_portgroup.node_id)
|
||||
topic = pecan.request.rpcapi.get_topic_for(rpc_node)
|
||||
rpc_node = objects.Node.get_by_id(context, rpc_portgroup.node_id)
|
||||
|
||||
new_portgroup = pecan.request.rpcapi.update_portgroup(
|
||||
pecan.request.context, rpc_portgroup, topic)
|
||||
notify.emit_start_notification(context, rpc_portgroup, 'update',
|
||||
node_uuid=rpc_node.uuid)
|
||||
with notify.handle_error_notification(context, rpc_portgroup, 'update',
|
||||
node_uuid=rpc_node.uuid):
|
||||
topic = pecan.request.rpcapi.get_topic_for(rpc_node)
|
||||
new_portgroup = pecan.request.rpcapi.update_portgroup(
|
||||
context, rpc_portgroup, topic)
|
||||
|
||||
return Portgroup.convert_with_links(new_portgroup)
|
||||
api_portgroup = Portgroup.convert_with_links(new_portgroup)
|
||||
notify.emit_end_notification(context, new_portgroup, 'update',
|
||||
node_uuid=api_portgroup.node_uuid)
|
||||
|
||||
return api_portgroup
|
||||
|
||||
@METRICS.timer('PortgroupsController.delete')
|
||||
@expose.expose(None, types.uuid_or_name,
|
||||
@ -540,7 +562,8 @@ class PortgroupsController(pecan.rest.RestController):
|
||||
if not api_utils.allow_portgroups():
|
||||
raise exception.NotFound()
|
||||
|
||||
cdict = pecan.request.context.to_policy_values()
|
||||
context = pecan.request.context
|
||||
cdict = context.to_policy_values()
|
||||
policy.authorize('baremetal:portgroup:delete', cdict, cdict)
|
||||
|
||||
if self.parent_node_ident:
|
||||
@ -549,6 +572,13 @@ class PortgroupsController(pecan.rest.RestController):
|
||||
rpc_portgroup = api_utils.get_rpc_portgroup(portgroup_ident)
|
||||
rpc_node = objects.Node.get_by_id(pecan.request.context,
|
||||
rpc_portgroup.node_id)
|
||||
topic = pecan.request.rpcapi.get_topic_for(rpc_node)
|
||||
pecan.request.rpcapi.destroy_portgroup(pecan.request.context,
|
||||
rpc_portgroup, topic)
|
||||
|
||||
notify.emit_start_notification(context, rpc_portgroup, 'delete',
|
||||
node_uuid=rpc_node.uuid)
|
||||
with notify.handle_error_notification(context, rpc_portgroup, 'delete',
|
||||
node_uuid=rpc_node.uuid):
|
||||
topic = pecan.request.rpcapi.get_topic_for(rpc_node)
|
||||
pecan.request.rpcapi.destroy_portgroup(context, rpc_portgroup,
|
||||
topic)
|
||||
notify.emit_end_notification(context, rpc_portgroup, 'delete',
|
||||
node_uuid=rpc_node.uuid)
|
||||
|
@ -307,7 +307,8 @@ class PortCRUDNotification(notification.NotificationBase):
|
||||
@base.IronicObjectRegistry.register
|
||||
class PortCRUDPayload(notification.NotificationPayloadBase):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
# Version 1.1: Add "portgroup_uuid" field
|
||||
VERSION = '1.1'
|
||||
|
||||
SCHEMA = {
|
||||
'address': ('port', 'address'),
|
||||
@ -326,12 +327,13 @@ class PortCRUDPayload(notification.NotificationPayloadBase):
|
||||
nullable=True),
|
||||
'pxe_enabled': object_fields.BooleanField(nullable=True),
|
||||
'node_uuid': object_fields.UUIDField(),
|
||||
'portgroup_uuid': object_fields.UUIDField(nullable=True),
|
||||
'created_at': object_fields.DateTimeField(nullable=True),
|
||||
'updated_at': object_fields.DateTimeField(nullable=True),
|
||||
'uuid': object_fields.UUIDField()
|
||||
# TODO(yuriyz): add "portgroup_uuid" field with portgroup notifications
|
||||
}
|
||||
|
||||
def __init__(self, port, node_uuid):
|
||||
super(PortCRUDPayload, self).__init__(node_uuid=node_uuid)
|
||||
def __init__(self, port, node_uuid, portgroup_uuid):
|
||||
super(PortCRUDPayload, self).__init__(node_uuid=node_uuid,
|
||||
portgroup_uuid=portgroup_uuid)
|
||||
self.populate_schema(port=port)
|
||||
|
@ -23,6 +23,7 @@ from ironic.common import utils
|
||||
from ironic.db import api as dbapi
|
||||
from ironic.objects import base
|
||||
from ironic.objects import fields as object_fields
|
||||
from ironic.objects import notification
|
||||
|
||||
|
||||
@base.IronicObjectRegistry.register
|
||||
@ -280,3 +281,51 @@ class Portgroup(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
current = self.get_by_uuid(self._context, uuid=self.uuid)
|
||||
self.obj_refresh(current)
|
||||
self.obj_reset_changes()
|
||||
|
||||
|
||||
@base.IronicObjectRegistry.register
|
||||
class PortgroupCRUDNotification(notification.NotificationBase):
|
||||
"""Notification when ironic creates, updates or deletes a portgroup."""
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'payload': object_fields.ObjectField('PortgroupCRUDPayload')
|
||||
}
|
||||
|
||||
|
||||
@base.IronicObjectRegistry.register
|
||||
class PortgroupCRUDPayload(notification.NotificationPayloadBase):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
SCHEMA = {
|
||||
'address': ('portgroup', 'address'),
|
||||
'extra': ('portgroup', 'extra'),
|
||||
'mode': ('portgroup', 'mode'),
|
||||
'name': ('portgroup', 'name'),
|
||||
'properties': ('portgroup', 'properties'),
|
||||
'standalone_ports_supported': ('portgroup',
|
||||
'standalone_ports_supported'),
|
||||
'created_at': ('portgroup', 'created_at'),
|
||||
'updated_at': ('portgroup', 'updated_at'),
|
||||
'uuid': ('portgroup', 'uuid')
|
||||
}
|
||||
|
||||
fields = {
|
||||
'address': object_fields.MACAddressField(nullable=True),
|
||||
'extra': object_fields.FlexibleDictField(nullable=True),
|
||||
'mode': object_fields.StringField(nullable=True),
|
||||
'name': object_fields.StringField(nullable=True),
|
||||
'node_uuid': object_fields.UUIDField(),
|
||||
'properties': object_fields.FlexibleDictField(nullable=True),
|
||||
'standalone_ports_supported': object_fields.BooleanField(
|
||||
nullable=True),
|
||||
'created_at': object_fields.DateTimeField(nullable=True),
|
||||
'updated_at': object_fields.DateTimeField(nullable=True),
|
||||
'uuid': object_fields.UUIDField()
|
||||
}
|
||||
|
||||
def __init__(self, portgroup, node_uuid):
|
||||
super(PortgroupCRUDPayload, self).__init__(node_uuid=node_uuid)
|
||||
self.populate_schema(portgroup=portgroup)
|
||||
|
@ -30,16 +30,20 @@ class APINotifyTestCase(tests_base.TestCase):
|
||||
self.node_notify_mock = mock.Mock()
|
||||
self.port_notify_mock = mock.Mock()
|
||||
self.chassis_notify_mock = mock.Mock()
|
||||
self.portgroup_notify_mock = mock.Mock()
|
||||
self.node_notify_mock.__name__ = 'NodeCRUDNotification'
|
||||
self.port_notify_mock.__name__ = 'PortCRUDNotification'
|
||||
self.chassis_notify_mock.__name__ = 'ChassisCRUDNotification'
|
||||
self.portgroup_notify_mock.__name__ = 'PortgroupCRUDNotification'
|
||||
_notification_mocks = {
|
||||
'chassis': (self.chassis_notify_mock,
|
||||
notif_utils.CRUD_NOTIFY_OBJ['chassis'][1]),
|
||||
'node': (self.node_notify_mock,
|
||||
notif_utils.CRUD_NOTIFY_OBJ['node'][1]),
|
||||
'port': (self.port_notify_mock,
|
||||
notif_utils.CRUD_NOTIFY_OBJ['port'][1])
|
||||
notif_utils.CRUD_NOTIFY_OBJ['port'][1]),
|
||||
'portgroup': (self.portgroup_notify_mock,
|
||||
notif_utils.CRUD_NOTIFY_OBJ['portgroup'][1])
|
||||
}
|
||||
self.addCleanup(self._restore, notif_utils.CRUD_NOTIFY_OBJ.copy())
|
||||
notif_utils.CRUD_NOTIFY_OBJ = _notification_mocks
|
||||
@ -121,6 +125,7 @@ class APINotifyTestCase(tests_base.TestCase):
|
||||
|
||||
def test_port_notification(self):
|
||||
node_uuid = uuidutils.generate_uuid()
|
||||
portgroup_uuid = uuidutils.generate_uuid()
|
||||
port = obj_utils.get_test_port(self.context,
|
||||
address='11:22:33:77:88:99',
|
||||
local_link_connection={'a': 25},
|
||||
@ -130,18 +135,45 @@ class APINotifyTestCase(tests_base.TestCase):
|
||||
test_status = fields.NotificationStatus.SUCCESS
|
||||
notif_utils._emit_api_notification(self.context, port, 'create',
|
||||
test_level, test_status,
|
||||
node_uuid=node_uuid)
|
||||
node_uuid=node_uuid,
|
||||
portgroup_uuid=portgroup_uuid)
|
||||
init_kwargs = self.port_notify_mock.call_args[1]
|
||||
payload = init_kwargs['payload']
|
||||
event_type = init_kwargs['event_type']
|
||||
self.assertEqual('port', event_type.object)
|
||||
self.assertEqual(port.uuid, payload.uuid)
|
||||
self.assertEqual(node_uuid, payload.node_uuid)
|
||||
self.assertEqual(portgroup_uuid, payload.portgroup_uuid)
|
||||
self.assertEqual('11:22:33:77:88:99', payload.address)
|
||||
self.assertEqual({'a': 25}, payload.local_link_connection)
|
||||
self.assertEqual({'as': 34}, payload.extra)
|
||||
self.assertEqual(False, payload.pxe_enabled)
|
||||
|
||||
def test_portgroup_notification(self):
|
||||
node_uuid = uuidutils.generate_uuid()
|
||||
portgroup = obj_utils.get_test_portgroup(self.context,
|
||||
address='22:55:88:AA:BB:99',
|
||||
name='new01',
|
||||
mode='mode2',
|
||||
extra={'bs': 11})
|
||||
test_level = fields.NotificationLevel.INFO
|
||||
test_status = fields.NotificationStatus.SUCCESS
|
||||
notif_utils._emit_api_notification(self.context, portgroup, 'create',
|
||||
test_level, test_status,
|
||||
node_uuid=node_uuid)
|
||||
init_kwargs = self.portgroup_notify_mock.call_args[1]
|
||||
payload = init_kwargs['payload']
|
||||
event_type = init_kwargs['event_type']
|
||||
self.assertEqual('portgroup', event_type.object)
|
||||
self.assertEqual(portgroup.uuid, payload.uuid)
|
||||
self.assertEqual(node_uuid, payload.node_uuid)
|
||||
self.assertEqual(portgroup.address, payload.address)
|
||||
self.assertEqual(portgroup.name, payload.name)
|
||||
self.assertEqual(portgroup.mode, payload.mode)
|
||||
self.assertEqual(portgroup.extra, payload.extra)
|
||||
self.assertEqual(portgroup.standalone_ports_supported,
|
||||
payload.standalone_ports_supported)
|
||||
|
||||
@mock.patch('ironic.objects.node.NodeMaintenanceNotification')
|
||||
def test_node_maintenance_notification(self, maintenance_mock):
|
||||
maintenance_mock.__name__ = 'NodeMaintenanceNotification'
|
||||
|
@ -27,11 +27,14 @@ from wsme import types as wtypes
|
||||
|
||||
from ironic.api.controllers import base as api_base
|
||||
from ironic.api.controllers import v1 as api_v1
|
||||
from ironic.api.controllers.v1 import notification_utils
|
||||
from ironic.api.controllers.v1 import portgroup as api_portgroup
|
||||
from ironic.api.controllers.v1 import utils as api_utils
|
||||
from ironic.common import exception
|
||||
from ironic.common import utils as common_utils
|
||||
from ironic.conductor import rpcapi
|
||||
from ironic import objects
|
||||
from ironic.objects import fields as obj_fields
|
||||
from ironic.tests import base
|
||||
from ironic.tests.unit.api import base as test_api_base
|
||||
from ironic.tests.unit.api import utils as apiutils
|
||||
@ -443,7 +446,8 @@ class TestPatch(test_api_base.BaseApiTest):
|
||||
self.mock_gtf.return_value = 'test-topic'
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
def test_update_byid(self, mock_upd):
|
||||
@mock.patch.object(notification_utils, '_emit_api_notification')
|
||||
def test_update_byid(self, mock_notify, mock_upd):
|
||||
extra = {'foo': 'bar'}
|
||||
mock_upd.return_value = self.portgroup
|
||||
mock_upd.return_value.extra = extra
|
||||
@ -458,6 +462,14 @@ class TestPatch(test_api_base.BaseApiTest):
|
||||
|
||||
kargs = mock_upd.call_args[0][1]
|
||||
self.assertEqual(extra, kargs.extra)
|
||||
mock_notify.assert_has_calls([mock.call(mock.ANY, mock.ANY, 'update',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.START,
|
||||
node_uuid=self.node.uuid),
|
||||
mock.call(mock.ANY, mock.ANY, 'update',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.END,
|
||||
node_uuid=self.node.uuid)])
|
||||
|
||||
def test_update_byname(self, mock_upd):
|
||||
extra = {'foo': 'bar'}
|
||||
@ -540,7 +552,8 @@ class TestPatch(test_api_base.BaseApiTest):
|
||||
kargs = mock_upd.call_args[0][1]
|
||||
self.assertEqual(address, kargs.address)
|
||||
|
||||
def test_replace_address_already_exist(self, mock_upd):
|
||||
@mock.patch.object(notification_utils, '_emit_api_notification')
|
||||
def test_replace_address_already_exist(self, mock_notify, mock_upd):
|
||||
address = 'aa:aa:aa:aa:aa:aa'
|
||||
mock_upd.side_effect = exception.MACAlreadyExists(mac=address)
|
||||
response = self.patch_json('/portgroups/%s' % self.portgroup.uuid,
|
||||
@ -556,6 +569,14 @@ class TestPatch(test_api_base.BaseApiTest):
|
||||
|
||||
kargs = mock_upd.call_args[0][1]
|
||||
self.assertEqual(address, kargs.address)
|
||||
mock_notify.assert_has_calls([mock.call(mock.ANY, mock.ANY, 'update',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.START,
|
||||
node_uuid=self.node.uuid),
|
||||
mock.call(mock.ANY, mock.ANY, 'update',
|
||||
obj_fields.NotificationLevel.ERROR,
|
||||
obj_fields.NotificationStatus.ERROR,
|
||||
node_uuid=self.node.uuid)])
|
||||
|
||||
def test_replace_node_uuid(self, mock_upd):
|
||||
mock_upd.return_value = self.portgroup
|
||||
@ -876,10 +897,11 @@ class TestPost(test_api_base.BaseApiTest):
|
||||
super(TestPost, self).setUp()
|
||||
self.node = obj_utils.create_test_node(self.context)
|
||||
|
||||
@mock.patch.object(notification_utils, '_emit_api_notification')
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
@mock.patch.object(timeutils, 'utcnow', autospec=True)
|
||||
def test_create_portgroup(self, mock_utcnow, mock_warn):
|
||||
def test_create_portgroup(self, mock_utcnow, mock_warn, mock_notify):
|
||||
pdict = apiutils.post_get_test_portgroup()
|
||||
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
||||
mock_utcnow.return_value = test_time
|
||||
@ -899,6 +921,14 @@ class TestPost(test_api_base.BaseApiTest):
|
||||
self.assertEqual(urlparse.urlparse(response.location).path,
|
||||
expected_location)
|
||||
self.assertEqual(0, mock_warn.call_count)
|
||||
mock_notify.assert_has_calls([mock.call(mock.ANY, mock.ANY, 'create',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.START,
|
||||
node_uuid=self.node.uuid),
|
||||
mock.call(mock.ANY, mock.ANY, 'create',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.END,
|
||||
node_uuid=self.node.uuid)])
|
||||
|
||||
@mock.patch.object(timeutils, 'utcnow', autospec=True)
|
||||
def test_create_portgroup_v123(self, mock_utcnow):
|
||||
@ -942,7 +972,9 @@ class TestPost(test_api_base.BaseApiTest):
|
||||
# Check that 'id' is not in first arg of positional args
|
||||
self.assertNotIn('id', cp_mock.call_args[0][0])
|
||||
|
||||
def test_create_portgroup_generate_uuid(self):
|
||||
@mock.patch.object(notification_utils.LOG, 'exception', autospec=True)
|
||||
@mock.patch.object(notification_utils.LOG, 'warning', autospec=True)
|
||||
def test_create_portgroup_generate_uuid(self, mock_warn, mock_except):
|
||||
pdict = apiutils.post_get_test_portgroup()
|
||||
del pdict['uuid']
|
||||
response = self.post_json('/portgroups', pdict, headers=self.headers)
|
||||
@ -950,6 +982,24 @@ class TestPost(test_api_base.BaseApiTest):
|
||||
headers=self.headers)
|
||||
self.assertEqual(pdict['address'], result['address'])
|
||||
self.assertTrue(uuidutils.is_uuid_like(result['uuid']))
|
||||
self.assertFalse(mock_warn.called)
|
||||
self.assertFalse(mock_except.called)
|
||||
|
||||
@mock.patch.object(notification_utils, '_emit_api_notification')
|
||||
@mock.patch.object(objects.Portgroup, 'create')
|
||||
def test_create_portgroup_error(self, mock_create, mock_notify):
|
||||
mock_create.side_effect = Exception()
|
||||
pdict = apiutils.post_get_test_portgroup()
|
||||
self.post_json('/portgroups', pdict, headers=self.headers,
|
||||
expect_errors=True)
|
||||
mock_notify.assert_has_calls([mock.call(mock.ANY, mock.ANY, 'create',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.START,
|
||||
node_uuid=self.node.uuid),
|
||||
mock.call(mock.ANY, mock.ANY, 'create',
|
||||
obj_fields.NotificationLevel.ERROR,
|
||||
obj_fields.NotificationStatus.ERROR,
|
||||
node_uuid=self.node.uuid)])
|
||||
|
||||
def test_create_portgroup_valid_extra(self):
|
||||
pdict = apiutils.post_get_test_portgroup(
|
||||
@ -1137,12 +1187,22 @@ class TestDelete(test_api_base.BaseApiTest):
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertIn(self.portgroup.address, response.json['error_message'])
|
||||
|
||||
def test_delete_portgroup_byid(self, mock_dpt):
|
||||
@mock.patch.object(notification_utils, '_emit_api_notification')
|
||||
def test_delete_portgroup_byid(self, mock_notify, mock_dpt):
|
||||
self.delete('/portgroups/%s' % self.portgroup.uuid,
|
||||
headers=self.headers)
|
||||
self.assertTrue(mock_dpt.called)
|
||||
mock_notify.assert_has_calls([mock.call(mock.ANY, mock.ANY, 'delete',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.START,
|
||||
node_uuid=self.node.uuid),
|
||||
mock.call(mock.ANY, mock.ANY, 'delete',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.END,
|
||||
node_uuid=self.node.uuid)])
|
||||
|
||||
def test_delete_portgroup_node_locked(self, mock_dpt):
|
||||
@mock.patch.object(notification_utils, '_emit_api_notification')
|
||||
def test_delete_portgroup_node_locked(self, mock_notify, mock_dpt):
|
||||
self.node.reserve(self.context, 'fake', self.node.uuid)
|
||||
mock_dpt.side_effect = exception.NodeLocked(node='fake-node',
|
||||
host='fake-host')
|
||||
@ -1151,6 +1211,14 @@ class TestDelete(test_api_base.BaseApiTest):
|
||||
self.assertEqual(http_client.CONFLICT, ret.status_code)
|
||||
self.assertTrue(ret.json['error_message'])
|
||||
self.assertTrue(mock_dpt.called)
|
||||
mock_notify.assert_has_calls([mock.call(mock.ANY, mock.ANY, 'delete',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.START,
|
||||
node_uuid=self.node.uuid),
|
||||
mock.call(mock.ANY, mock.ANY, 'delete',
|
||||
obj_fields.NotificationLevel.ERROR,
|
||||
obj_fields.NotificationStatus.ERROR,
|
||||
node_uuid=self.node.uuid)])
|
||||
|
||||
def test_delete_portgroup_invalid_api_version(self, mock_dpt):
|
||||
response = self.delete('/portgroups/%s' % self.portgroup.uuid,
|
||||
|
@ -489,11 +489,13 @@ class TestPatch(test_api_base.BaseApiTest):
|
||||
mock_notify.assert_has_calls([mock.call(mock.ANY, mock.ANY, 'update',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.START,
|
||||
node_uuid=self.node.uuid),
|
||||
node_uuid=self.node.uuid,
|
||||
portgroup_uuid=wtypes.Unset),
|
||||
mock.call(mock.ANY, mock.ANY, 'update',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.END,
|
||||
node_uuid=self.node.uuid)])
|
||||
node_uuid=self.node.uuid,
|
||||
portgroup_uuid=wtypes.Unset)])
|
||||
|
||||
def test_update_byaddress_not_allowed(self, mock_upd):
|
||||
extra = {'foo': 'bar'}
|
||||
@ -556,11 +558,13 @@ class TestPatch(test_api_base.BaseApiTest):
|
||||
mock_notify.assert_has_calls([mock.call(mock.ANY, mock.ANY, 'update',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.START,
|
||||
node_uuid=self.node.uuid),
|
||||
node_uuid=self.node.uuid,
|
||||
portgroup_uuid=wtypes.Unset),
|
||||
mock.call(mock.ANY, mock.ANY, 'update',
|
||||
obj_fields.NotificationLevel.ERROR,
|
||||
obj_fields.NotificationStatus.ERROR,
|
||||
node_uuid=self.node.uuid)])
|
||||
node_uuid=self.node.uuid,
|
||||
portgroup_uuid=wtypes.Unset)])
|
||||
|
||||
def test_replace_node_uuid(self, mock_upd):
|
||||
mock_upd.return_value = self.port
|
||||
@ -982,11 +986,13 @@ class TestPost(test_api_base.BaseApiTest):
|
||||
mock_notify.assert_has_calls([mock.call(mock.ANY, mock.ANY, 'create',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.START,
|
||||
node_uuid=self.node.uuid),
|
||||
node_uuid=self.node.uuid,
|
||||
portgroup_uuid=self.portgroup.uuid),
|
||||
mock.call(mock.ANY, mock.ANY, 'create',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.END,
|
||||
node_uuid=self.node.uuid)])
|
||||
node_uuid=self.node.uuid,
|
||||
portgroup_uuid=self.portgroup.uuid)])
|
||||
self.assertEqual(0, mock_warn.call_count)
|
||||
|
||||
def test_create_port_min_api_version(self):
|
||||
@ -1036,11 +1042,13 @@ class TestPost(test_api_base.BaseApiTest):
|
||||
mock_notify.assert_has_calls([mock.call(mock.ANY, mock.ANY, 'create',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.START,
|
||||
node_uuid=self.node.uuid),
|
||||
node_uuid=self.node.uuid,
|
||||
portgroup_uuid=self.portgroup.uuid),
|
||||
mock.call(mock.ANY, mock.ANY, 'create',
|
||||
obj_fields.NotificationLevel.ERROR,
|
||||
obj_fields.NotificationStatus.ERROR,
|
||||
node_uuid=self.node.uuid)])
|
||||
node_uuid=self.node.uuid,
|
||||
portgroup_uuid=self.portgroup.uuid)])
|
||||
|
||||
def test_create_port_valid_extra(self):
|
||||
pdict = post_get_test_port(extra={'str': 'foo', 'int': 123,
|
||||
@ -1396,11 +1404,13 @@ class TestDelete(test_api_base.BaseApiTest):
|
||||
mock_notify.assert_has_calls([mock.call(mock.ANY, mock.ANY, 'delete',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.START,
|
||||
node_uuid=self.node.uuid),
|
||||
node_uuid=self.node.uuid,
|
||||
portgroup_uuid=None),
|
||||
mock.call(mock.ANY, mock.ANY, 'delete',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.END,
|
||||
node_uuid=self.node.uuid)])
|
||||
node_uuid=self.node.uuid,
|
||||
portgroup_uuid=None)])
|
||||
|
||||
@mock.patch.object(notification_utils, '_emit_api_notification')
|
||||
def test_delete_port_node_locked(self, mock_notify, mock_dpt):
|
||||
@ -1414,11 +1424,13 @@ class TestDelete(test_api_base.BaseApiTest):
|
||||
mock_notify.assert_has_calls([mock.call(mock.ANY, mock.ANY, 'delete',
|
||||
obj_fields.NotificationLevel.INFO,
|
||||
obj_fields.NotificationStatus.START,
|
||||
node_uuid=self.node.uuid),
|
||||
node_uuid=self.node.uuid,
|
||||
portgroup_uuid=None),
|
||||
mock.call(mock.ANY, mock.ANY, 'delete',
|
||||
obj_fields.NotificationLevel.ERROR,
|
||||
obj_fields.NotificationStatus.ERROR,
|
||||
node_uuid=self.node.uuid)])
|
||||
node_uuid=self.node.uuid,
|
||||
portgroup_uuid=None)])
|
||||
|
||||
def test_portgroups_subresource_delete(self, mock_dpt):
|
||||
portgroup = obj_utils.create_test_portgroup(self.context,
|
||||
|
@ -25,7 +25,7 @@ from ironic.tests.unit.db import utils
|
||||
from ironic.tests.unit.objects import utils as obj_utils
|
||||
|
||||
|
||||
class TestChassisObject(base.DbTestCase):
|
||||
class TestChassisObject(base.DbTestCase, obj_utils.SchemasTestMixIn):
|
||||
|
||||
def setUp(self):
|
||||
super(TestChassisObject, self).setUp()
|
||||
@ -121,21 +121,4 @@ class TestChassisObject(base.DbTestCase):
|
||||
self.assertEqual(self.context, chassis[0]._context)
|
||||
|
||||
def test_payload_schemas(self):
|
||||
"""Assert that the chassis' Payload SCHEMAs have the expected properties.
|
||||
|
||||
A payload's SCHEMA should:
|
||||
|
||||
1. Have each of its keys in the payload's fields
|
||||
2. Have each member of the schema match with a corresponding field
|
||||
in the Chassis object
|
||||
"""
|
||||
payloads = obj_utils.get_payloads_with_schemas(objects.chassis)
|
||||
for payload in payloads:
|
||||
for schema_key in payload.SCHEMA:
|
||||
self.assertIn(schema_key, payload.fields,
|
||||
"for %s, schema key %s is not in fields"
|
||||
% (payload, schema_key))
|
||||
chassis_key = payload.SCHEMA[schema_key][1]
|
||||
self.assertIn(chassis_key, objects.Chassis.fields,
|
||||
"for %s, schema key %s has invalid chassis "
|
||||
"field %s" % (payload, schema_key, chassis_key))
|
||||
self._check_payload_schemas(objects.chassis, objects.Chassis.fields)
|
||||
|
@ -25,7 +25,7 @@ from ironic.tests.unit.db import utils
|
||||
from ironic.tests.unit.objects import utils as obj_utils
|
||||
|
||||
|
||||
class TestNodeObject(base.DbTestCase):
|
||||
class TestNodeObject(base.DbTestCase, obj_utils.SchemasTestMixIn):
|
||||
|
||||
def setUp(self):
|
||||
super(TestNodeObject, self).setUp()
|
||||
@ -244,21 +244,4 @@ class TestNodeObject(base.DbTestCase):
|
||||
self.assertEqual(expect, values['properties'])
|
||||
|
||||
def test_payload_schemas(self):
|
||||
"""Assert that the node's Payload SCHEMAs have the expected properties.
|
||||
|
||||
A payload's SCHEMA should:
|
||||
|
||||
1. Have each of its keys in the payload's fields
|
||||
2. Have each member of the schema match with a corresponding field
|
||||
in the Node object
|
||||
"""
|
||||
payloads = obj_utils.get_payloads_with_schemas(objects.node)
|
||||
for payload in payloads:
|
||||
for schema_key in payload.SCHEMA:
|
||||
self.assertIn(schema_key, payload.fields,
|
||||
"for %s, schema key %s is not in fields"
|
||||
% (payload, schema_key))
|
||||
node_key = payload.SCHEMA[schema_key][1]
|
||||
self.assertIn(node_key, objects.Node.fields,
|
||||
"for %s, schema key %s has invalid node field %s"
|
||||
% (payload, schema_key, node_key))
|
||||
self._check_payload_schemas(objects.node, objects.Node.fields)
|
||||
|
@ -428,9 +428,11 @@ expected_object_fingerprints = {
|
||||
'NodeCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||
'NodeCRUDPayload': '1.1-35c16dd49d75812763e4e99bfebc3191',
|
||||
'PortCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||
'PortCRUDPayload': '1.0-88acd98c9b08b4c8810e77793152057b',
|
||||
'PortCRUDPayload': '1.1-1ecf2d63b68014c52cb52d0227f8b5b8',
|
||||
'NodeMaintenanceNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||
'NodeConsoleNotification': '1.0-59acc533c11d306f149846f922739c15'
|
||||
'NodeConsoleNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||
'PortgroupCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||
'PortgroupCRUDPayload': '1.0-b73c1fecf0cef3aa56bbe3c7e2275018',
|
||||
}
|
||||
|
||||
|
||||
|
@ -24,7 +24,7 @@ from ironic.tests.unit.db import utils
|
||||
from ironic.tests.unit.objects import utils as obj_utils
|
||||
|
||||
|
||||
class TestPortObject(base.DbTestCase):
|
||||
class TestPortObject(base.DbTestCase, obj_utils.SchemasTestMixIn):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPortObject, self).setUp()
|
||||
@ -129,21 +129,4 @@ class TestPortObject(base.DbTestCase):
|
||||
self.assertEqual(self.context, ports[0]._context)
|
||||
|
||||
def test_payload_schemas(self):
|
||||
"""Assert that the port's Payload SCHEMAs have the expected properties.
|
||||
|
||||
A payload's SCHEMA should:
|
||||
|
||||
1. Have each of its keys in the payload's fields
|
||||
2. Have each member of the schema match with a corresponding field
|
||||
in the Port object
|
||||
"""
|
||||
payloads = obj_utils.get_payloads_with_schemas(objects.port)
|
||||
for payload in payloads:
|
||||
for schema_key in payload.SCHEMA:
|
||||
self.assertIn(schema_key, payload.fields,
|
||||
"for %s, schema key %s is not in fields"
|
||||
% (payload, schema_key))
|
||||
port_key = payload.SCHEMA[schema_key][1]
|
||||
self.assertIn(port_key, objects.Port.fields,
|
||||
"for %s, schema key %s has invalid port field %s"
|
||||
% (payload, schema_key, port_key))
|
||||
self._check_payload_schemas(objects.port, objects.Port.fields)
|
||||
|
@ -18,9 +18,10 @@ from ironic.common import exception
|
||||
from ironic import objects
|
||||
from ironic.tests.unit.db import base
|
||||
from ironic.tests.unit.db import utils
|
||||
from ironic.tests.unit.objects import utils as obj_utils
|
||||
|
||||
|
||||
class TestPortgroupObject(base.DbTestCase):
|
||||
class TestPortgroupObject(base.DbTestCase, obj_utils.SchemasTestMixIn):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPortgroupObject, self).setUp()
|
||||
@ -147,3 +148,7 @@ class TestPortgroupObject(base.DbTestCase):
|
||||
self.assertThat(portgroups, matchers.HasLength(1))
|
||||
self.assertIsInstance(portgroups[0], objects.Portgroup)
|
||||
self.assertEqual(self.context, portgroups[0]._context)
|
||||
|
||||
def test_payload_schemas(self):
|
||||
self._check_payload_schemas(objects.portgroup,
|
||||
objects.Portgroup.fields)
|
||||
|
@ -245,3 +245,27 @@ def get_payloads_with_schemas(from_module):
|
||||
payloads.append(payload)
|
||||
|
||||
return payloads
|
||||
|
||||
|
||||
class SchemasTestMixIn(object):
|
||||
def _check_payload_schemas(self, from_module, fields):
|
||||
"""Assert that the Payload SCHEMAs have the expected properties.
|
||||
|
||||
A payload's SCHEMA should:
|
||||
|
||||
1. Have each of its keys in the payload's fields
|
||||
2. Have each member of the schema match with a corresponding field
|
||||
in the object
|
||||
"""
|
||||
resource = from_module.__name__.split('.')[-1]
|
||||
payloads = get_payloads_with_schemas(from_module)
|
||||
for payload in payloads:
|
||||
for schema_key in payload.SCHEMA:
|
||||
self.assertIn(schema_key, payload.fields,
|
||||
"for %s, schema key %s is not in fields"
|
||||
% (payload, schema_key))
|
||||
key = payload.SCHEMA[schema_key][1]
|
||||
self.assertIn(key, fields,
|
||||
"for %s, schema key %s has invalid %s "
|
||||
"field %s" % (payload, schema_key, resource,
|
||||
key))
|
||||
|
@ -0,0 +1,10 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds notifications for creation, updates, or deletions of port groups.
|
||||
Event types are formatted as follows:
|
||||
|
||||
* baremetal.portgroup.{create, update, delete}.{start,end,error}
|
||||
|
||||
Also adds portgroup_uuid field to port notifications, port payload version
|
||||
bumped to 1.1.
|
Loading…
Reference in New Issue
Block a user