Add node maintenance notifications

This patch adds node maintenance notifications, event types are:
"baremetal.node.maintenance_set.{start, end, error}".
Developer documentation updated.

Change-Id: I9105821ed0a4db614ea3e1c73ad563b82b1c6082
Partial-Bug: #1606520
This commit is contained in:
Yuriy Zveryanskyy 2016-11-10 16:29:27 +02:00
parent 499ef55dd1
commit 876db5a613
8 changed files with 150 additions and 13 deletions

View File

@ -205,6 +205,59 @@ Example of port CRUD notification::
"publisher_id":"ironic-api.hostname02"
}
Node maintenance notifications
------------------------------
These notifications are emitted from API service when maintenance mode is
changed via API service. List of maintenance notifications for a node:
* ``baremetal.node.maintenance_set.start``
* ``baremetal.node.maintenance_set.end``
* ``baremetal.node.maintenance_set.error``
"start" and "end" notifications have INFO level, "error" has ERROR. Example of
node maintenance notification::
{
"priority": "info",
"payload":{
"ironic_object.namespace":"ironic",
"ironic_object.name":"NodePayload",
"ironic_object.version":"1.0",
"ironic_object.data":{
"clean_step": None,
"console_enabled": False,
"created_at": "2016-01-26T20:41:03+00:00",
"driver": "fake",
"extra": {},
"inspection_finished_at": None,
"inspection_started_at": None,
"instance_info": {},
"instance_uuid": None,
"last_error": None,
"maintenance": True,
"maintenance_reason": "hw upgrade",
"network_interface": "flat",
"name": None,
"power_state": "power off",
"properties": {
"memory_mb": 4096,
"cpu_arch": "x86_64",
"local_gb": 10,
"cpus": 8},
"provision_state": "available",
"provision_updated_at": "2016-01-27T20:41:03+00:00",
"resource_class": None,
"target_power_state": None,
"target_provision_state": None,
"updated_at": "2016-01-27T20:41:03+00:00",
"uuid": "1be26c0b-03f2-4d2e-ae87-c02d7f33c123",
}
},
"event_type":"baremetal.node.maintenance_set.start",
"publisher_id":"ironic-api.hostname02"
}
------------------------------
ironic-conductor notifications
------------------------------

View File

@ -1005,17 +1005,22 @@ class NodeVendorPassthruController(rest.RestController):
class NodeMaintenanceController(rest.RestController):
def _set_maintenance(self, node_ident, maintenance_mode, reason=None):
context = pecan.request.context
rpc_node = api_utils.get_rpc_node(node_ident)
rpc_node.maintenance = maintenance_mode
rpc_node.maintenance_reason = reason
notify.emit_start_notification(context, rpc_node, 'maintenance_set')
with notify.handle_error_notification(context, rpc_node,
'maintenance_set'):
try:
topic = pecan.request.rpcapi.get_topic_for(rpc_node)
except exception.NoValidHost as e:
e.code = http_client.BAD_REQUEST
raise
try:
topic = pecan.request.rpcapi.get_topic_for(rpc_node)
except exception.NoValidHost as e:
e.code = http_client.BAD_REQUEST
raise
pecan.request.rpcapi.update_node(pecan.request.context,
rpc_node, topic=topic)
new_node = pecan.request.rpcapi.update_node(context, rpc_node,
topic=topic)
notify.emit_end_notification(context, new_node, 'maintenance_set')
@METRICS.timer('NodeMaintenanceController.put')
@expose.expose(None, types.uuid_or_name, wtypes.text,

View File

@ -59,10 +59,15 @@ def _emit_api_notification(context, obj, action, level, status, **kwargs):
for k, v in kwargs.items()}
try:
try:
if resource not in CRUD_NOTIFY_OBJ:
if action == 'maintenance_set':
notification_method = node_objects.NodeMaintenanceNotification
payload_method = node_objects.NodePayload
elif resource not in CRUD_NOTIFY_OBJ:
notification_name = payload_name = _("is not defined")
raise KeyError(_("Unsupported resource: %s") % resource)
notification_method, payload_method = CRUD_NOTIFY_OBJ[resource]
else:
notification_method, payload_method = CRUD_NOTIFY_OBJ[resource]
notification_name = notification_method.__name__
payload_name = payload_method.__name__
finally:

View File

@ -607,3 +607,14 @@ class NodeCRUDPayload(NodePayload):
def __init__(self, node, chassis_uuid):
super(NodeCRUDPayload, self).__init__(node, chassis_uuid=chassis_uuid)
@base.IronicObjectRegistry.register
class NodeMaintenanceNotification(notification.NotificationBase):
"""Notification emitted when maintenance state changed via API."""
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'payload': object_fields.ObjectField('NodePayload')
}

View File

@ -2985,11 +2985,21 @@ class TestPut(test_api_base.BaseApiTest):
mock_update.assert_called_once_with(mock.ANY, mock.ANY,
topic='test-topic')
@mock.patch.object(notification_utils, '_emit_api_notification')
@mock.patch.object(objects.Node, 'get_by_uuid')
@mock.patch.object(rpcapi.ConductorAPI, 'update_node')
def test_set_node_maintenance_mode(self, mock_update, mock_get):
def test_set_node_maintenance_mode(self, mock_update, mock_get,
mock_notify):
self._test_set_node_maintenance_mode(mock_update, mock_get,
'fake_reason', self.node.uuid)
mock_notify.assert_has_calls([mock.call(mock.ANY, mock.ANY,
'maintenance_set',
obj_fields.NotificationLevel.INFO,
obj_fields.NotificationStatus.START),
mock.call(mock.ANY, mock.ANY,
'maintenance_set',
obj_fields.NotificationLevel.INFO,
obj_fields.NotificationStatus.END)])
@mock.patch.object(objects.Node, 'get_by_uuid')
@mock.patch.object(rpcapi.ConductorAPI, 'update_node')
@ -3011,6 +3021,24 @@ class TestPut(test_api_base.BaseApiTest):
self._test_set_node_maintenance_mode(mock_update, mock_get, None,
self.node.name, is_by_name=True)
@mock.patch.object(notification_utils, '_emit_api_notification')
@mock.patch.object(objects.Node, 'get_by_uuid')
@mock.patch.object(rpcapi.ConductorAPI, 'update_node')
def test_set_node_maintenance_mode_error(self, mock_update, mock_get,
mock_notify):
mock_get.return_value = self.node
mock_update.side_effect = Exception()
self.put_json('/nodes/%s/maintenance' % self.node.uuid,
{'reason': 'fake'}, expect_errors=True)
mock_notify.assert_has_calls([mock.call(mock.ANY, mock.ANY,
'maintenance_set',
obj_fields.NotificationLevel.INFO,
obj_fields.NotificationStatus.START),
mock.call(mock.ANY, mock.ANY,
'maintenance_set',
obj_fields.NotificationLevel.ERROR,
obj_fields.NotificationStatus.ERROR)])
class TestCheckCleanSteps(base.TestCase):
def test__check_clean_steps_not_list(self):

View File

@ -23,10 +23,10 @@ from ironic.tests import base as tests_base
from ironic.tests.unit.objects import utils as obj_utils
class CRUDNotifyTestCase(tests_base.TestCase):
class APINotifyTestCase(tests_base.TestCase):
def setUp(self):
super(CRUDNotifyTestCase, self).setUp()
super(APINotifyTestCase, self).setUp()
self.node_notify_mock = mock.Mock()
self.port_notify_mock = mock.Mock()
self.chassis_notify_mock = mock.Mock()
@ -141,3 +141,32 @@ class CRUDNotifyTestCase(tests_base.TestCase):
self.assertEqual({'a': 25}, payload.local_link_connection)
self.assertEqual({'as': 34}, payload.extra)
self.assertEqual(False, payload.pxe_enabled)
@mock.patch('ironic.objects.node.NodeMaintenanceNotification')
def test_node_maintenance_notification(self, maintenance_mock):
maintenance_mock.__name__ = 'NodeMaintenanceNotification'
node = obj_utils.get_test_node(self.context,
maintenance=True,
maintenance_reason='test reason')
test_level = fields.NotificationLevel.INFO
test_status = fields.NotificationStatus.START
notif_utils._emit_api_notification(self.context, node,
'maintenance_set',
test_level, test_status)
init_kwargs = maintenance_mock.call_args[1]
payload = init_kwargs['payload']
event_type = init_kwargs['event_type']
self.assertEqual('node', event_type.object)
self.assertEqual(node.uuid, payload.uuid)
self.assertEqual(True, payload.maintenance)
self.assertEqual('test reason', payload.maintenance_reason)
@mock.patch.object(notification.NotificationBase, 'emit')
def test_emit_maintenance_notification(self, emit_mock):
node = obj_utils.get_test_node(self.context)
test_level = fields.NotificationLevel.INFO
test_status = fields.NotificationStatus.START
notif_utils._emit_api_notification(self.context, node,
'maintenance_set',
test_level, test_status)
emit_mock.assert_called_once_with(self.context)

View File

@ -428,7 +428,8 @@ expected_object_fingerprints = {
'NodeCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
'NodeCRUDPayload': '1.0-37bb4cdd2c84b59fd6ad0547dbf713a0',
'PortCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
'PortCRUDPayload': '1.0-88acd98c9b08b4c8810e77793152057b'
'PortCRUDPayload': '1.0-88acd98c9b08b4c8810e77793152057b',
'NodeMaintenanceNotification': '1.0-59acc533c11d306f149846f922739c15'
}

View File

@ -0,0 +1,5 @@
---
features:
- Add notifications for node maintenance. Event types are
"baremetal.node.maintenance_set.{start, end, error}"
For more details, see the developer documentation.