Merge "Add node maintenance notifications"
This commit is contained in:
commit
d0450c1f5f
@ -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
|
||||
------------------------------
|
||||
|
@ -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
|
||||
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,
|
||||
|
@ -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)
|
||||
else:
|
||||
notification_method, payload_method = CRUD_NOTIFY_OBJ[resource]
|
||||
|
||||
notification_name = notification_method.__name__
|
||||
payload_name = payload_method.__name__
|
||||
finally:
|
||||
|
@ -614,3 +614,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')
|
||||
}
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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'
|
||||
}
|
||||
|
||||
|
||||
|
@ -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.
|
Loading…
x
Reference in New Issue
Block a user