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"
|
"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
|
ironic-conductor notifications
|
||||||
------------------------------
|
------------------------------
|
||||||
|
@ -1005,17 +1005,22 @@ class NodeVendorPassthruController(rest.RestController):
|
|||||||
class NodeMaintenanceController(rest.RestController):
|
class NodeMaintenanceController(rest.RestController):
|
||||||
|
|
||||||
def _set_maintenance(self, node_ident, maintenance_mode, reason=None):
|
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 = api_utils.get_rpc_node(node_ident)
|
||||||
rpc_node.maintenance = maintenance_mode
|
rpc_node.maintenance = maintenance_mode
|
||||||
rpc_node.maintenance_reason = reason
|
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:
|
new_node = pecan.request.rpcapi.update_node(context, rpc_node,
|
||||||
topic = pecan.request.rpcapi.get_topic_for(rpc_node)
|
topic=topic)
|
||||||
except exception.NoValidHost as e:
|
notify.emit_end_notification(context, new_node, 'maintenance_set')
|
||||||
e.code = http_client.BAD_REQUEST
|
|
||||||
raise
|
|
||||||
pecan.request.rpcapi.update_node(pecan.request.context,
|
|
||||||
rpc_node, topic=topic)
|
|
||||||
|
|
||||||
@METRICS.timer('NodeMaintenanceController.put')
|
@METRICS.timer('NodeMaintenanceController.put')
|
||||||
@expose.expose(None, types.uuid_or_name, wtypes.text,
|
@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()}
|
for k, v in kwargs.items()}
|
||||||
try:
|
try:
|
||||||
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")
|
notification_name = payload_name = _("is not defined")
|
||||||
raise KeyError(_("Unsupported resource: %s") % resource)
|
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__
|
notification_name = notification_method.__name__
|
||||||
payload_name = payload_method.__name__
|
payload_name = payload_method.__name__
|
||||||
finally:
|
finally:
|
||||||
|
@ -614,3 +614,14 @@ class NodeCRUDPayload(NodePayload):
|
|||||||
|
|
||||||
def __init__(self, node, chassis_uuid):
|
def __init__(self, node, chassis_uuid):
|
||||||
super(NodeCRUDPayload, self).__init__(node, chassis_uuid=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,
|
mock_update.assert_called_once_with(mock.ANY, mock.ANY,
|
||||||
topic='test-topic')
|
topic='test-topic')
|
||||||
|
|
||||||
|
@mock.patch.object(notification_utils, '_emit_api_notification')
|
||||||
@mock.patch.object(objects.Node, 'get_by_uuid')
|
@mock.patch.object(objects.Node, 'get_by_uuid')
|
||||||
@mock.patch.object(rpcapi.ConductorAPI, 'update_node')
|
@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,
|
self._test_set_node_maintenance_mode(mock_update, mock_get,
|
||||||
'fake_reason', self.node.uuid)
|
'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(objects.Node, 'get_by_uuid')
|
||||||
@mock.patch.object(rpcapi.ConductorAPI, 'update_node')
|
@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._test_set_node_maintenance_mode(mock_update, mock_get, None,
|
||||||
self.node.name, is_by_name=True)
|
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):
|
class TestCheckCleanSteps(base.TestCase):
|
||||||
def test__check_clean_steps_not_list(self):
|
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
|
from ironic.tests.unit.objects import utils as obj_utils
|
||||||
|
|
||||||
|
|
||||||
class CRUDNotifyTestCase(tests_base.TestCase):
|
class APINotifyTestCase(tests_base.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(CRUDNotifyTestCase, self).setUp()
|
super(APINotifyTestCase, self).setUp()
|
||||||
self.node_notify_mock = mock.Mock()
|
self.node_notify_mock = mock.Mock()
|
||||||
self.port_notify_mock = mock.Mock()
|
self.port_notify_mock = mock.Mock()
|
||||||
self.chassis_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({'a': 25}, payload.local_link_connection)
|
||||||
self.assertEqual({'as': 34}, payload.extra)
|
self.assertEqual({'as': 34}, payload.extra)
|
||||||
self.assertEqual(False, payload.pxe_enabled)
|
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',
|
'NodeCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||||
'NodeCRUDPayload': '1.0-37bb4cdd2c84b59fd6ad0547dbf713a0',
|
'NodeCRUDPayload': '1.0-37bb4cdd2c84b59fd6ad0547dbf713a0',
|
||||||
'PortCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
|
'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