Add node console notifications

This patch adds node console notifications, event types are:
"baremetal.node.console_{set, restore}.{start, end, error}".
Developer documentation updated.

Change-Id: I3b3ac74607fd6e218fdf0ea3ff30964e527db399
Partial-Bug: #1606520
This commit is contained in:
Yuriy Zveryanskyy 2016-11-15 18:17:55 +02:00
parent 876db5a613
commit 294f974fe7
10 changed files with 274 additions and 17 deletions

View File

@ -262,6 +262,69 @@ node maintenance notification::
ironic-conductor notifications ironic-conductor notifications
------------------------------ ------------------------------
Node console notifications
------------------------------
These notifications are emitted by the ironic-conductor service when conductor
service starts or stops console for the node. The notification event types for
a node console are:
* ``baremetal.node.console_set.start``
* ``baremetal.node.console_set.end``
* ``baremetal.node.console_set.error``
* ``baremetal.node.console_restore.start``
* ``baremetal.node.console_restore.end``
* ``baremetal.node.console_restore.error``
``console_set`` action is used when start or stop console is initiated via API
request. The ``console_restore`` action is used when the console was already
enabled, but a driver must restart the console because an ironic-conductor was
restarted. This may also be sent when an ironic-conductor takes over a node
that was being managed by another ironic-conductor. "start" and "end"
notifications have INFO level, "error" has ERROR. Example of node console
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": True,
"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": False,
"maintenance_reason": None,
"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.console_set.end",
"publisher_id":"ironic-conductor.hostname01"
}
baremetal.node.power_set baremetal.node.power_set
------------------------ ------------------------

View File

@ -30,10 +30,12 @@ from ironic.common import hash_ring as hash
from ironic.common.i18n import _, _LC, _LE, _LI, _LW from ironic.common.i18n import _, _LC, _LE, _LI, _LW
from ironic.common import rpc from ironic.common import rpc
from ironic.common import states from ironic.common import states
from ironic.conductor import notification_utils as notify_utils
from ironic.conductor import task_manager from ironic.conductor import task_manager
from ironic.conf import CONF from ironic.conf import CONF
from ironic.db import api as dbapi from ironic.db import api as dbapi
from ironic import objects from ironic import objects
from ironic.objects import fields as obj_fields
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -383,12 +385,18 @@ class BaseConductorManager(object):
try: try:
with task_manager.acquire(context, node_uuid, shared=False, with task_manager.acquire(context, node_uuid, shared=False,
purpose='start console') as task: purpose='start console') as task:
notify_utils.emit_console_notification(
task, 'console_restore',
obj_fields.NotificationStatus.START)
try: try:
LOG.debug('Trying to start console of node %(node)s', LOG.debug('Trying to start console of node %(node)s',
{'node': node_uuid}) {'node': node_uuid})
task.driver.console.start_console(task) task.driver.console.start_console(task)
LOG.info(_LI('Successfully started console of node ' LOG.info(_LI('Successfully started console of node '
'%(node)s'), {'node': node_uuid}) '%(node)s'), {'node': node_uuid})
notify_utils.emit_console_notification(
task, 'console_restore',
obj_fields.NotificationStatus.END)
except Exception as err: except Exception as err:
msg = (_('Failed to start console of node %(node)s ' msg = (_('Failed to start console of node %(node)s '
'while starting the conductor, so changing ' 'while starting the conductor, so changing '
@ -401,6 +409,9 @@ class BaseConductorManager(object):
task.node.last_error = msg task.node.last_error = msg
task.node.console_enabled = False task.node.console_enabled = False
task.node.save() task.node.save()
notify_utils.emit_console_notification(
task, 'console_restore',
obj_fields.NotificationStatus.ERROR)
except exception.NodeLocked: except exception.NodeLocked:
LOG.warning(_LW('Node %(node)s is locked while trying to ' LOG.warning(_LW('Node %(node)s is locked while trying to '
'start console on conductor startup'), 'start console on conductor startup'),

View File

@ -69,6 +69,7 @@ from ironic.conf import CONF
from ironic.drivers import base as drivers_base from ironic.drivers import base as drivers_base
from ironic import objects from ironic import objects
from ironic.objects import base as objects_base from ironic.objects import base as objects_base
from ironic.objects import fields
MANAGER_TOPIC = 'ironic.conductor_manager' MANAGER_TOPIC = 'ironic.conductor_manager'
@ -1335,7 +1336,10 @@ class ConductorManager(base_manager.BaseConductorManager):
task.driver.deploy.take_over(task) task.driver.deploy.take_over(task)
# NOTE(zhenguo): If console enabled, take over the console session # NOTE(zhenguo): If console enabled, take over the console session
# as well. # as well.
console_error = None
if task.node.console_enabled: if task.node.console_enabled:
notify_utils.emit_console_notification(
task, 'console_restore', fields.NotificationStatus.START)
try: try:
task.driver.console.start_console(task) task.driver.console.start_console(task)
except Exception as err: except Exception as err:
@ -1347,10 +1351,17 @@ class ConductorManager(base_manager.BaseConductorManager):
# back to False and set node's last error. # back to False and set node's last error.
task.node.last_error = msg task.node.last_error = msg
task.node.console_enabled = False task.node.console_enabled = False
console_error = True
else:
notify_utils.emit_console_notification(
task, 'console_restore', fields.NotificationStatus.END)
# NOTE(lucasagomes): Set the ID of the new conductor managing # NOTE(lucasagomes): Set the ID of the new conductor managing
# this node # this node
task.node.conductor_affinity = self.conductor.id task.node.conductor_affinity = self.conductor.id
task.node.save() task.node.save()
if console_error:
notify_utils.emit_console_notification(
task, 'console_restore', fields.NotificationStatus.ERROR)
@METRICS.timer('ConductorManager._check_cleanwait_timeouts') @METRICS.timer('ConductorManager._check_cleanwait_timeouts')
@periodics.periodic(spacing=CONF.conductor.check_provision_state_interval) @periodics.periodic(spacing=CONF.conductor.check_provision_state_interval)
@ -1510,12 +1521,20 @@ class ConductorManager(base_manager.BaseConductorManager):
'valid_states': states.DELETE_ALLOWED_STATES}) 'valid_states': states.DELETE_ALLOWED_STATES})
raise exception.InvalidState(msg) raise exception.InvalidState(msg)
if node.console_enabled: if node.console_enabled:
notify_utils.emit_console_notification(
task, 'console_set', fields.NotificationStatus.START)
try: try:
task.driver.console.stop_console(task) task.driver.console.stop_console(task)
except Exception as err: except Exception as err:
LOG.error(_LE('Failed to stop console while deleting ' LOG.error(_LE('Failed to stop console while deleting '
'the node %(node)s: %(err)s.'), 'the node %(node)s: %(err)s.'),
{'node': node.uuid, 'err': err}) {'node': node.uuid, 'err': err})
notify_utils.emit_console_notification(
task, 'console_set', fields.NotificationStatus.ERROR)
else:
node.console_enabled = False
notify_utils.emit_console_notification(
task, 'console_set', fields.NotificationStatus.END)
node.destroy() node.destroy()
LOG.info(_LI('Successfully deleted node %(node)s.'), LOG.info(_LI('Successfully deleted node %(node)s.'),
{'node': node.uuid}) {'node': node.uuid})
@ -1699,6 +1718,8 @@ class ConductorManager(base_manager.BaseConductorManager):
def _set_console_mode(self, task, enabled): def _set_console_mode(self, task, enabled):
"""Internal method to set console mode on a node.""" """Internal method to set console mode on a node."""
node = task.node node = task.node
notify_utils.emit_console_notification(
task, 'console_set', fields.NotificationStatus.START)
try: try:
if enabled: if enabled:
task.driver.console.start_console(task) task.driver.console.start_console(task)
@ -1715,11 +1736,15 @@ class ConductorManager(base_manager.BaseConductorManager):
'error': e}) 'error': e})
node.last_error = msg node.last_error = msg
LOG.error(msg) LOG.error(msg)
node.save()
notify_utils.emit_console_notification(
task, 'console_set', fields.NotificationStatus.ERROR)
else: else:
node.console_enabled = enabled node.console_enabled = enabled
node.last_error = None node.last_error = None
finally:
node.save() node.save()
notify_utils.emit_console_notification(
task, 'console_set', fields.NotificationStatus.END)
@METRICS.timer('ConductorManager.update_port') @METRICS.timer('ConductorManager.update_port')
@messaging.expected_exceptions(exception.NodeLocked, @messaging.expected_exceptions(exception.NodeLocked,

View File

@ -152,3 +152,27 @@ def emit_provision_set_notification(task, level, status, prev_state,
prev_target=prev_target, prev_target=prev_target,
event=event event=event
) )
def emit_console_notification(task, action, status):
"""Helper for conductor sending a set console state notification.
:param task: a TaskManager instance.
:param action: Action string to go in the EventType. Must be either
'console_set' or 'console_restore'.
:param status: One of `ironic.objects.fields.NotificationStatus.START`,
END or ERROR.
"""
if status == fields.NotificationStatus.ERROR:
level = fields.NotificationLevel.ERROR
else:
level = fields.NotificationLevel.INFO
_emit_conductor_node_notification(
task,
node_objects.NodeConsoleNotification,
node_objects.NodePayload,
action,
level,
status,
)

View File

@ -618,3 +618,14 @@ class NodeMaintenanceNotification(notification.NotificationBase):
fields = { fields = {
'payload': object_fields.ObjectField('NodePayload') 'payload': object_fields.ObjectField('NodePayload')
} }
@base.IronicObjectRegistry.register
class NodeConsoleNotification(notification.NotificationBase):
"""Notification emitted when node console state changed."""
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'payload': object_fields.ObjectField('NodePayload')
}

View File

@ -24,8 +24,10 @@ from ironic.common import driver_factory
from ironic.common import exception from ironic.common import exception
from ironic.conductor import base_manager from ironic.conductor import base_manager
from ironic.conductor import manager from ironic.conductor import manager
from ironic.conductor import notification_utils
from ironic.conductor import task_manager from ironic.conductor import task_manager
from ironic import objects from ironic import objects
from ironic.objects import fields
from ironic.tests import base as tests_base from ironic.tests import base as tests_base
from ironic.tests.unit.conductor import mgr_utils from ironic.tests.unit.conductor import mgr_utils
from ironic.tests.unit.db import base as tests_db_base from ironic.tests.unit.db import base as tests_db_base
@ -221,7 +223,8 @@ class ManagerSpawnWorkerTestCase(tests_base.TestCase):
class StartConsolesTestCase(mgr_utils.ServiceSetUpMixin, class StartConsolesTestCase(mgr_utils.ServiceSetUpMixin,
tests_db_base.DbTestCase): tests_db_base.DbTestCase):
def test__start_consoles(self): @mock.patch.object(notification_utils, 'emit_console_notification')
def test__start_consoles(self, mock_notify):
obj_utils.create_test_node(self.context, obj_utils.create_test_node(self.context,
driver='fake', driver='fake',
console_enabled=True) console_enabled=True)
@ -241,8 +244,14 @@ class StartConsolesTestCase(mgr_utils.ServiceSetUpMixin,
'start_console') as mock_start_console: 'start_console') as mock_start_console:
self.service._start_consoles(self.context) self.service._start_consoles(self.context)
self.assertEqual(2, mock_start_console.call_count) self.assertEqual(2, mock_start_console.call_count)
mock_notify.assert_has_calls(
[mock.call(mock.ANY, 'console_restore',
fields.NotificationStatus.START),
mock.call(mock.ANY, 'console_restore',
fields.NotificationStatus.END)])
def test__start_consoles_no_console_enabled(self): @mock.patch.object(notification_utils, 'emit_console_notification')
def test__start_consoles_no_console_enabled(self, mock_notify):
obj_utils.create_test_node(self.context, obj_utils.create_test_node(self.context,
driver='fake', driver='fake',
console_enabled=False) console_enabled=False)
@ -251,8 +260,10 @@ class StartConsolesTestCase(mgr_utils.ServiceSetUpMixin,
'start_console') as mock_start_console: 'start_console') as mock_start_console:
self.service._start_consoles(self.context) self.service._start_consoles(self.context)
self.assertFalse(mock_start_console.called) self.assertFalse(mock_start_console.called)
self.assertFalse(mock_notify.called)
def test__start_consoles_failed(self): @mock.patch.object(notification_utils, 'emit_console_notification')
def test__start_consoles_failed(self, mock_notify):
test_node = obj_utils.create_test_node(self.context, test_node = obj_utils.create_test_node(self.context,
driver='fake', driver='fake',
console_enabled=True) console_enabled=True)
@ -265,9 +276,15 @@ class StartConsolesTestCase(mgr_utils.ServiceSetUpMixin,
test_node.refresh() test_node.refresh()
self.assertFalse(test_node.console_enabled) self.assertFalse(test_node.console_enabled)
self.assertIsNotNone(test_node.last_error) self.assertIsNotNone(test_node.last_error)
mock_notify.assert_has_calls(
[mock.call(mock.ANY, 'console_restore',
fields.NotificationStatus.START),
mock.call(mock.ANY, 'console_restore',
fields.NotificationStatus.ERROR)])
@mock.patch.object(notification_utils, 'emit_console_notification')
@mock.patch.object(base_manager, 'LOG') @mock.patch.object(base_manager, 'LOG')
def test__start_consoles_node_locked(self, log_mock): def test__start_consoles_node_locked(self, log_mock, mock_notify):
test_node = obj_utils.create_test_node(self.context, test_node = obj_utils.create_test_node(self.context,
driver='fake', driver='fake',
console_enabled=True, console_enabled=True,
@ -281,9 +298,11 @@ class StartConsolesTestCase(mgr_utils.ServiceSetUpMixin,
self.assertTrue(test_node.console_enabled) self.assertTrue(test_node.console_enabled)
self.assertIsNone(test_node.last_error) self.assertIsNone(test_node.last_error)
self.assertTrue(log_mock.warning.called) self.assertTrue(log_mock.warning.called)
self.assertFalse(mock_notify.called)
@mock.patch.object(notification_utils, 'emit_console_notification')
@mock.patch.object(base_manager, 'LOG') @mock.patch.object(base_manager, 'LOG')
def test__start_consoles_node_not_found(self, log_mock): def test__start_consoles_node_not_found(self, log_mock, mock_notify):
test_node = obj_utils.create_test_node(self.context, test_node = obj_utils.create_test_node(self.context,
driver='fake', driver='fake',
console_enabled=True) console_enabled=True)
@ -298,3 +317,4 @@ class StartConsolesTestCase(mgr_utils.ServiceSetUpMixin,
self.assertTrue(test_node.console_enabled) self.assertTrue(test_node.console_enabled)
self.assertIsNone(test_node.last_error) self.assertIsNone(test_node.last_error)
self.assertTrue(log_mock.warning.called) self.assertTrue(log_mock.warning.called)
self.assertFalse(mock_notify.called)

View File

@ -36,6 +36,7 @@ from ironic.common import images
from ironic.common import states from ironic.common import states
from ironic.common import swift from ironic.common import swift
from ironic.conductor import manager from ironic.conductor import manager
from ironic.conductor import notification_utils
from ironic.conductor import task_manager from ironic.conductor import task_manager
from ironic.conductor import utils as conductor_utils from ironic.conductor import utils as conductor_utils
from ironic.db import api as dbapi from ironic.db import api as dbapi
@ -2652,21 +2653,34 @@ class ConsoleTestCase(mgr_utils.ServiceSetUpMixin, tests_db_base.DbTestCase):
self._stop_service() self._stop_service()
spawn_mock.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY) spawn_mock.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY)
def test_set_console_mode_enabled(self): @mock.patch.object(notification_utils, 'emit_console_notification')
def test_set_console_mode_enabled(self, mock_notify):
node = obj_utils.create_test_node(self.context, driver='fake') node = obj_utils.create_test_node(self.context, driver='fake')
self._start_service() self._start_service()
self.service.set_console_mode(self.context, node.uuid, True) self.service.set_console_mode(self.context, node.uuid, True)
self._stop_service() self._stop_service()
node.refresh() node.refresh()
self.assertTrue(node.console_enabled) self.assertTrue(node.console_enabled)
mock_notify.assert_has_calls(
[mock.call(mock.ANY, 'console_set',
obj_fields.NotificationStatus.START),
mock.call(mock.ANY, 'console_set',
obj_fields.NotificationStatus.END)])
def test_set_console_mode_disabled(self): @mock.patch.object(notification_utils, 'emit_console_notification')
node = obj_utils.create_test_node(self.context, driver='fake') def test_set_console_mode_disabled(self, mock_notify):
node = obj_utils.create_test_node(self.context, driver='fake',
console_enabled=True)
self._start_service() self._start_service()
self.service.set_console_mode(self.context, node.uuid, False) self.service.set_console_mode(self.context, node.uuid, False)
self._stop_service() self._stop_service()
node.refresh() node.refresh()
self.assertFalse(node.console_enabled) self.assertFalse(node.console_enabled)
mock_notify.assert_has_calls(
[mock.call(mock.ANY, 'console_set',
obj_fields.NotificationStatus.START),
mock.call(mock.ANY, 'console_set',
obj_fields.NotificationStatus.END)])
def test_set_console_mode_not_supported(self): def test_set_console_mode_not_supported(self):
node = obj_utils.create_test_node(self.context, driver='fake', node = obj_utils.create_test_node(self.context, driver='fake',
@ -2695,7 +2709,8 @@ class ConsoleTestCase(mgr_utils.ServiceSetUpMixin, tests_db_base.DbTestCase):
# Compare true exception hidden by @messaging.expected_exceptions # Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidParameterValue, exc.exc_info[0]) self.assertEqual(exception.InvalidParameterValue, exc.exc_info[0])
def test_set_console_mode_start_fail(self): @mock.patch.object(notification_utils, 'emit_console_notification')
def test_set_console_mode_start_fail(self, mock_notify):
node = obj_utils.create_test_node(self.context, driver='fake', node = obj_utils.create_test_node(self.context, driver='fake',
last_error=None, last_error=None,
console_enabled=False) console_enabled=False)
@ -2708,8 +2723,14 @@ class ConsoleTestCase(mgr_utils.ServiceSetUpMixin, tests_db_base.DbTestCase):
mock_sc.assert_called_once_with(mock.ANY) mock_sc.assert_called_once_with(mock.ANY)
node.refresh() node.refresh()
self.assertIsNotNone(node.last_error) self.assertIsNotNone(node.last_error)
mock_notify.assert_has_calls(
[mock.call(mock.ANY, 'console_set',
obj_fields.NotificationStatus.START),
mock.call(mock.ANY, 'console_set',
obj_fields.NotificationStatus.ERROR)])
def test_set_console_mode_stop_fail(self): @mock.patch.object(notification_utils, 'emit_console_notification')
def test_set_console_mode_stop_fail(self, mock_notify):
node = obj_utils.create_test_node(self.context, driver='fake', node = obj_utils.create_test_node(self.context, driver='fake',
last_error=None, last_error=None,
console_enabled=True) console_enabled=True)
@ -2722,8 +2743,14 @@ class ConsoleTestCase(mgr_utils.ServiceSetUpMixin, tests_db_base.DbTestCase):
mock_sc.assert_called_once_with(mock.ANY) mock_sc.assert_called_once_with(mock.ANY)
node.refresh() node.refresh()
self.assertIsNotNone(node.last_error) self.assertIsNotNone(node.last_error)
mock_notify.assert_has_calls(
[mock.call(mock.ANY, 'console_set',
obj_fields.NotificationStatus.START),
mock.call(mock.ANY, 'console_set',
obj_fields.NotificationStatus.ERROR)])
def test_enable_console_already_enabled(self): @mock.patch.object(notification_utils, 'emit_console_notification')
def test_enable_console_already_enabled(self, mock_notify):
node = obj_utils.create_test_node(self.context, driver='fake', node = obj_utils.create_test_node(self.context, driver='fake',
console_enabled=True) console_enabled=True)
self._start_service() self._start_service()
@ -2732,8 +2759,10 @@ class ConsoleTestCase(mgr_utils.ServiceSetUpMixin, tests_db_base.DbTestCase):
self.service.set_console_mode(self.context, node.uuid, True) self.service.set_console_mode(self.context, node.uuid, True)
self._stop_service() self._stop_service()
self.assertFalse(mock_sc.called) self.assertFalse(mock_sc.called)
self.assertFalse(mock_notify.called)
def test_disable_console_already_disabled(self): @mock.patch.object(notification_utils, 'emit_console_notification')
def test_disable_console_already_disabled(self, mock_notify):
node = obj_utils.create_test_node(self.context, driver='fake', node = obj_utils.create_test_node(self.context, driver='fake',
console_enabled=False) console_enabled=False)
self._start_service() self._start_service()
@ -2742,6 +2771,7 @@ class ConsoleTestCase(mgr_utils.ServiceSetUpMixin, tests_db_base.DbTestCase):
self.service.set_console_mode(self.context, node.uuid, False) self.service.set_console_mode(self.context, node.uuid, False)
self._stop_service() self._stop_service()
self.assertFalse(mock_sc.called) self.assertFalse(mock_sc.called)
self.assertFalse(mock_notify.called)
def test_get_console(self): def test_get_console(self):
node = obj_utils.create_test_node(self.context, driver='fake', node = obj_utils.create_test_node(self.context, driver='fake',
@ -2860,7 +2890,8 @@ class DestroyNodeTestCase(mgr_utils.ServiceSetUpMixin,
power_state=states.POWER_OFF) power_state=states.POWER_OFF)
self.service.destroy_node(self.context, node.uuid) self.service.destroy_node(self.context, node.uuid)
def test_destroy_node_console_enabled(self): @mock.patch.object(notification_utils, 'emit_console_notification')
def test_destroy_node_console_enabled(self, mock_notify):
self._start_service() self._start_service()
node = obj_utils.create_test_node(self.context, driver='fake', node = obj_utils.create_test_node(self.context, driver='fake',
console_enabled=True) console_enabled=True)
@ -2871,6 +2902,30 @@ class DestroyNodeTestCase(mgr_utils.ServiceSetUpMixin,
self.assertRaises(exception.NodeNotFound, self.assertRaises(exception.NodeNotFound,
self.dbapi.get_node_by_uuid, self.dbapi.get_node_by_uuid,
node.uuid) node.uuid)
mock_notify.assert_has_calls(
[mock.call(mock.ANY, 'console_set',
obj_fields.NotificationStatus.START),
mock.call(mock.ANY, 'console_set',
obj_fields.NotificationStatus.END)])
@mock.patch.object(notification_utils, 'emit_console_notification')
def test_destroy_node_console_disable_fail(self, mock_notify):
self._start_service()
node = obj_utils.create_test_node(self.context, driver='fake',
console_enabled=True)
with mock.patch.object(self.driver.console,
'stop_console') as mock_sc:
mock_sc.side_effect = Exception()
self.service.destroy_node(self.context, node.uuid)
mock_sc.assert_called_once_with(mock.ANY)
self.assertRaises(exception.NodeNotFound,
self.dbapi.get_node_by_uuid,
node.uuid)
mock_notify.assert_has_calls(
[mock.call(mock.ANY, 'console_set',
obj_fields.NotificationStatus.START),
mock.call(mock.ANY, 'console_set',
obj_fields.NotificationStatus.ERROR)])
def test_destroy_node_adopt_failed_no_power_change(self): def test_destroy_node_adopt_failed_no_power_change(self):
self._start_service() self._start_service()
@ -5522,12 +5577,14 @@ class DoNodeTakeOverTestCase(mgr_utils.ServiceSetUpMixin,
mock_take_over.assert_called_once_with(mock.ANY) mock_take_over.assert_called_once_with(mock.ANY)
self.assertFalse(mock_start_console.called) self.assertFalse(mock_start_console.called)
@mock.patch.object(notification_utils, 'emit_console_notification')
@mock.patch('ironic.drivers.modules.fake.FakeConsole.start_console') @mock.patch('ironic.drivers.modules.fake.FakeConsole.start_console')
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.take_over') @mock.patch('ironic.drivers.modules.fake.FakeDeploy.take_over')
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare') @mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare')
def test__do_takeover_with_console_enabled(self, mock_prepare, def test__do_takeover_with_console_enabled(self, mock_prepare,
mock_take_over, mock_take_over,
mock_start_console): mock_start_console,
mock_notify):
self._start_service() self._start_service()
node = obj_utils.create_test_node(self.context, driver='fake', node = obj_utils.create_test_node(self.context, driver='fake',
console_enabled=True) console_enabled=True)
@ -5540,13 +5597,20 @@ class DoNodeTakeOverTestCase(mgr_utils.ServiceSetUpMixin,
mock_prepare.assert_called_once_with(mock.ANY) mock_prepare.assert_called_once_with(mock.ANY)
mock_take_over.assert_called_once_with(mock.ANY) mock_take_over.assert_called_once_with(mock.ANY)
mock_start_console.assert_called_once_with(mock.ANY) mock_start_console.assert_called_once_with(mock.ANY)
mock_notify.assert_has_calls(
[mock.call(task, 'console_restore',
obj_fields.NotificationStatus.START),
mock.call(task, 'console_restore',
obj_fields.NotificationStatus.END)])
@mock.patch.object(notification_utils, 'emit_console_notification')
@mock.patch('ironic.drivers.modules.fake.FakeConsole.start_console') @mock.patch('ironic.drivers.modules.fake.FakeConsole.start_console')
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.take_over') @mock.patch('ironic.drivers.modules.fake.FakeDeploy.take_over')
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare') @mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare')
def test__do_takeover_with_console_exception(self, mock_prepare, def test__do_takeover_with_console_exception(self, mock_prepare,
mock_take_over, mock_take_over,
mock_start_console): mock_start_console,
mock_notify):
self._start_service() self._start_service()
mock_start_console.side_effect = Exception() mock_start_console.side_effect = Exception()
node = obj_utils.create_test_node(self.context, driver='fake', node = obj_utils.create_test_node(self.context, driver='fake',
@ -5560,6 +5624,11 @@ class DoNodeTakeOverTestCase(mgr_utils.ServiceSetUpMixin,
mock_prepare.assert_called_once_with(mock.ANY) mock_prepare.assert_called_once_with(mock.ANY)
mock_take_over.assert_called_once_with(mock.ANY) mock_take_over.assert_called_once_with(mock.ANY)
mock_start_console.assert_called_once_with(mock.ANY) mock_start_console.assert_called_once_with(mock.ANY)
mock_notify.assert_has_calls(
[mock.call(task, 'console_restore',
obj_fields.NotificationStatus.START),
mock.call(task, 'console_restore',
obj_fields.NotificationStatus.ERROR)])
@mgr_utils.mock_record_keepalive @mgr_utils.mock_record_keepalive

View File

@ -70,6 +70,32 @@ class TestNotificationUtils(base.DbTestCase):
to_power=states.POWER_ON to_power=states.POWER_ON
) )
@mock.patch.object(notif_utils, '_emit_conductor_node_notification')
def test_emit_console_notification(self, mock_cond_emit):
notif_utils.emit_console_notification(
self.task, 'console_set', fields.NotificationStatus.END)
mock_cond_emit.assert_called_once_with(
self.task,
node_objects.NodeConsoleNotification,
node_objects.NodePayload,
'console_set',
fields.NotificationLevel.INFO,
fields.NotificationStatus.END,
)
@mock.patch.object(notif_utils, '_emit_conductor_node_notification')
def test_emit_console_notification_error_status(self, mock_cond_emit):
notif_utils.emit_console_notification(
self.task, 'console_set', fields.NotificationStatus.ERROR)
mock_cond_emit.assert_called_once_with(
self.task,
node_objects.NodeConsoleNotification,
node_objects.NodePayload,
'console_set',
fields.NotificationLevel.ERROR,
fields.NotificationStatus.ERROR,
)
@mock.patch.object(notification, 'mask_secrets') @mock.patch.object(notification, 'mask_secrets')
def test__emit_conductor_node_notification(self, mock_secrets): def test__emit_conductor_node_notification(self, mock_secrets):
mock_notify_method = mock.Mock() mock_notify_method = mock.Mock()

View File

@ -428,8 +428,10 @@ 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' 'NodeMaintenanceNotification': '1.0-59acc533c11d306f149846f922739c15',
'NodeConsoleNotification': '1.0-59acc533c11d306f149846f922739c15'
} }

View File

@ -0,0 +1,6 @@
---
features:
- Add notifications for start and stop console on the node.
Event types are
"baremetal.node.console_{set, restore}.{start, end, error}"
For more details, see the developer documentation.