Transform rescue/unrescue instance notifications
The rescue (instance.rescue.start and instance.rescue.end) and unrescue (instance.unrescue.start and instance.unrescue.end) notifications are transformed to the versioned framework. This patch also fixes the power state of the server when unrescuing it with the fake compute driver. Co-Authored-By: Takashi Natsume <natsume.takashi@lab.ntt.co.jp> Change-Id: Ib1d03c6d693e3b04886c638c956e35809fed8fc2 Implements: bp versioned-notification-transformation-queens Closes-Bug: #1742133
This commit is contained in:
parent
e66fc365c8
commit
df7442ee5a
@ -59,7 +59,7 @@
|
||||
"OS-EXT-SRV-ATTR:host": "b8b357f7100d4391828f2177c922ef93",
|
||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
|
||||
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
|
||||
"OS-EXT-STS:power_state": 4,
|
||||
"OS-EXT-STS:power_state": 1,
|
||||
"OS-EXT-STS:task_state": null,
|
||||
"OS-EXT-STS:vm_state": "active",
|
||||
"os-extended-volumes:volumes_attached": [],
|
||||
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"$ref": "InstanceActionPayload.json",
|
||||
"nova_object.data": {
|
||||
"rescue_image_ref": "a2459075-d96c-40d5-893e-577ff92e721c"
|
||||
},
|
||||
"nova_object.name": "InstanceActionRescuePayload",
|
||||
"nova_object.version": "1.0"
|
||||
}
|
12
doc/notification_samples/instance-rescue-end.json
Normal file
12
doc/notification_samples/instance-rescue-end.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"event_type": "instance.rescue.end",
|
||||
"payload": {
|
||||
"$ref": "common_payloads/InstanceActionRescuePayload.json#",
|
||||
"nova_object.data": {
|
||||
"state": "rescued",
|
||||
"power_state": "shutdown"
|
||||
}
|
||||
},
|
||||
"priority":"INFO",
|
||||
"publisher_id":"nova-compute:compute"
|
||||
}
|
11
doc/notification_samples/instance-rescue-start.json
Normal file
11
doc/notification_samples/instance-rescue-start.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"event_type": "instance.rescue.start",
|
||||
"payload": {
|
||||
"$ref": "common_payloads/InstanceActionRescuePayload.json#",
|
||||
"nova_object.data": {
|
||||
"task_state": "rescuing"
|
||||
}
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "nova-compute:compute"
|
||||
}
|
6
doc/notification_samples/instance-unrescue-end.json
Normal file
6
doc/notification_samples/instance-unrescue-end.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"event_type": "instance.unrescue.end",
|
||||
"payload":{"$ref": "common_payloads/InstanceActionPayload.json#"},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "nova-compute:compute"
|
||||
}
|
13
doc/notification_samples/instance-unrescue-start.json
Normal file
13
doc/notification_samples/instance-unrescue-start.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"event_type": "instance.unrescue.start",
|
||||
"payload":{
|
||||
"$ref": "common_payloads/InstanceActionPayload.json#",
|
||||
"nova_object.data": {
|
||||
"power_state": "shutdown",
|
||||
"task_state": "unrescuing",
|
||||
"state": "rescued"
|
||||
}
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "nova-compute:compute"
|
||||
}
|
@ -3549,6 +3549,10 @@ class ComputeManager(manager.Manager):
|
||||
self._notify_about_instance_usage(context, instance,
|
||||
"rescue.start", extra_usage_info=extra_usage_info,
|
||||
network_info=network_info)
|
||||
compute_utils.notify_about_instance_rescue_action(
|
||||
context, instance, self.host, rescue_image_ref,
|
||||
action=fields.NotificationAction.RESCUE,
|
||||
phase=fields.NotificationPhase.START)
|
||||
|
||||
try:
|
||||
self._power_off_instance(context, instance, clean_shutdown)
|
||||
@ -3576,6 +3580,10 @@ class ComputeManager(manager.Manager):
|
||||
self._notify_about_instance_usage(context, instance,
|
||||
"rescue.end", extra_usage_info=extra_usage_info,
|
||||
network_info=network_info)
|
||||
compute_utils.notify_about_instance_rescue_action(
|
||||
context, instance, self.host, rescue_image_ref,
|
||||
action=fields.NotificationAction.RESCUE,
|
||||
phase=fields.NotificationPhase.END)
|
||||
|
||||
@wrap_exception()
|
||||
@reverts_task_state
|
||||
@ -3588,6 +3596,10 @@ class ComputeManager(manager.Manager):
|
||||
network_info = self.network_api.get_instance_nw_info(context, instance)
|
||||
self._notify_about_instance_usage(context, instance,
|
||||
"unrescue.start", network_info=network_info)
|
||||
compute_utils.notify_about_instance_action(context, instance,
|
||||
self.host, action=fields.NotificationAction.UNRESCUE,
|
||||
phase=fields.NotificationPhase.START)
|
||||
|
||||
with self._error_out_instance_on_exception(context, instance):
|
||||
self.driver.unrescue(instance,
|
||||
network_info)
|
||||
@ -3601,6 +3613,9 @@ class ComputeManager(manager.Manager):
|
||||
instance,
|
||||
"unrescue.end",
|
||||
network_info=network_info)
|
||||
compute_utils.notify_about_instance_action(context, instance,
|
||||
self.host, action=fields.NotificationAction.UNRESCUE,
|
||||
phase=fields.NotificationPhase.END)
|
||||
|
||||
@wrap_exception()
|
||||
@wrap_instance_fault
|
||||
|
@ -445,6 +445,39 @@ def notify_about_volume_attach_detach(context, instance, host, action, phase,
|
||||
notification.emit(context)
|
||||
|
||||
|
||||
@rpc.if_notifications_enabled
|
||||
def notify_about_instance_rescue_action(
|
||||
context, instance, host, rescue_image_ref, action, phase=None,
|
||||
source=fields.NotificationSource.COMPUTE, exception=None):
|
||||
"""Send versioned notification about the action made on the instance
|
||||
|
||||
:param instance: the instance which the action performed on
|
||||
:param host: the host emitting the notification
|
||||
:param rescue_image_ref: the rescue image ref
|
||||
:param action: the name of the action
|
||||
:param phase: the phase of the action
|
||||
:param source: the source of the notification
|
||||
:param exception: the thrown exception (used in error notifications)
|
||||
"""
|
||||
fault, priority = _get_fault_and_priority_from_exc(exception)
|
||||
payload = instance_notification.InstanceActionRescuePayload(
|
||||
instance=instance,
|
||||
fault=fault,
|
||||
rescue_image_ref=rescue_image_ref)
|
||||
|
||||
notification = instance_notification.InstanceActionRescueNotification(
|
||||
context=context,
|
||||
priority=priority,
|
||||
publisher=notification_base.NotificationPublisher(
|
||||
host=host, source=source),
|
||||
event_type=notification_base.EventType(
|
||||
object='instance',
|
||||
action=action,
|
||||
phase=phase),
|
||||
payload=payload)
|
||||
notification.emit(context)
|
||||
|
||||
|
||||
@rpc.if_notifications_enabled
|
||||
def notify_about_keypair_action(context, keypair, action, phase):
|
||||
"""Send versioned notification about the keypair action on the instance
|
||||
|
@ -244,6 +244,21 @@ class InstanceUpdatePayload(InstancePayload):
|
||||
for instance_tag in instance.tags.objects]
|
||||
|
||||
|
||||
@nova_base.NovaObjectRegistry.register_notification
|
||||
class InstanceActionRescuePayload(InstanceActionPayload):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
fields = {
|
||||
'rescue_image_ref': fields.UUIDField(nullable=True)
|
||||
}
|
||||
|
||||
def __init__(self, instance, fault, rescue_image_ref):
|
||||
super(InstanceActionRescuePayload, self).__init__(
|
||||
instance=instance,
|
||||
fault=fault)
|
||||
self.rescue_image_ref = rescue_image_ref
|
||||
|
||||
|
||||
@nova_base.NovaObjectRegistry.register_notification
|
||||
class IpPayload(base.NotificationPayloadBase):
|
||||
# Version 1.0: Initial version
|
||||
@ -451,8 +466,8 @@ class InstanceStateUpdatePayload(base.NotificationPayloadBase):
|
||||
@base.notification_sample('instance-soft_delete-end.json')
|
||||
@base.notification_sample('instance-trigger_crash_dump-start.json')
|
||||
@base.notification_sample('instance-trigger_crash_dump-end.json')
|
||||
# @base.notification_sample('instance-unrescue-start.json')
|
||||
# @base.notification_sample('instance-unrescue-end.json')
|
||||
@base.notification_sample('instance-unrescue-start.json')
|
||||
@base.notification_sample('instance-unrescue-end.json')
|
||||
@base.notification_sample('instance-unshelve-start.json')
|
||||
@base.notification_sample('instance-unshelve-end.json')
|
||||
@nova_base.NovaObjectRegistry.register_notification
|
||||
@ -529,6 +544,18 @@ class InstanceActionSnapshotNotification(base.NotificationBase):
|
||||
}
|
||||
|
||||
|
||||
@base.notification_sample('instance-rescue-start.json')
|
||||
@base.notification_sample('instance-rescue-end.json')
|
||||
@nova_base.NovaObjectRegistry.register_notification
|
||||
class InstanceActionRescueNotification(base.NotificationBase):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'payload': fields.ObjectField('InstanceActionRescuePayload')
|
||||
}
|
||||
|
||||
|
||||
@nova_base.NovaObjectRegistry.register_notification
|
||||
class InstanceActionSnapshotPayload(InstanceActionPayload):
|
||||
# Version 1.6: Initial version. It starts at version 1.6 as
|
||||
|
@ -1464,6 +1464,8 @@ class CinderFixture(fixtures.Fixture):
|
||||
lambda *args, **kwargs: None)
|
||||
self.test.stub_out('nova.volume.cinder.API.unreserve_volume',
|
||||
fake_unreserve_volume)
|
||||
self.test.stub_out('nova.volume.cinder.API.check_attached',
|
||||
lambda *args, **kwargs: None)
|
||||
|
||||
|
||||
# TODO(mriedem): We can probably pull some of the common parts from the
|
||||
@ -1638,6 +1640,8 @@ class CinderFixtureNewAttachFlow(fixtures.Fixture):
|
||||
lambda *args, **kwargs: None)
|
||||
self.test.stub_out('nova.volume.cinder.is_microversion_supported',
|
||||
lambda *args, **kwargs: None)
|
||||
self.test.stub_out('nova.volume.cinder.API.check_attached',
|
||||
lambda *args, **kwargs: None)
|
||||
|
||||
|
||||
class PlacementApiClient(object):
|
||||
|
@ -59,7 +59,7 @@
|
||||
"OS-EXT-SRV-ATTR:host": "%(compute_host)s",
|
||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "%(hypervisor_hostname)s",
|
||||
"OS-EXT-SRV-ATTR:instance_name": "%(instance_name)s",
|
||||
"OS-EXT-STS:power_state": 4,
|
||||
"OS-EXT-STS:power_state": 1,
|
||||
"OS-EXT-STS:task_state": null,
|
||||
"OS-EXT-STS:vm_state": "active",
|
||||
"os-extended-volumes:volumes_attached": [],
|
||||
|
@ -263,8 +263,7 @@ class TestInstanceNotificationSample(
|
||||
self._test_reboot_server_error,
|
||||
self._test_trigger_crash_dump,
|
||||
self._test_volume_detach_attach_server,
|
||||
self._test_rescue_server,
|
||||
self._test_unrescue_server,
|
||||
self._test_rescue_unrescue_server,
|
||||
self._test_soft_delete_server,
|
||||
self._test_attach_volume_error,
|
||||
self._test_interface_attach_and_detach,
|
||||
@ -1217,11 +1216,52 @@ class TestInstanceNotificationSample(
|
||||
'uuid': server['id']},
|
||||
actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
|
||||
|
||||
def _test_rescue_server(self, server):
|
||||
pass
|
||||
def _test_rescue_unrescue_server(self, server):
|
||||
# Both "rescue" and "unrescue" notification asserts are made here
|
||||
# rescue notification asserts
|
||||
post = {
|
||||
"rescue": {
|
||||
"rescue_image_ref": 'a2459075-d96c-40d5-893e-577ff92e721c'
|
||||
}
|
||||
}
|
||||
self.api.post_server_action(server['id'], post)
|
||||
self._wait_for_state_change(self.admin_api, server, 'RESCUE')
|
||||
|
||||
def _test_unrescue_server(self, server):
|
||||
pass
|
||||
self.assertEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS))
|
||||
self._verify_notification(
|
||||
'instance-rescue-start',
|
||||
replacements={
|
||||
'reservation_id': server['reservation_id'],
|
||||
'uuid': server['id']},
|
||||
actual=fake_notifier.VERSIONED_NOTIFICATIONS[0])
|
||||
self._verify_notification(
|
||||
'instance-rescue-end',
|
||||
replacements={
|
||||
'reservation_id': server['reservation_id'],
|
||||
'uuid': server['id']},
|
||||
actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
|
||||
fake_notifier.reset()
|
||||
|
||||
# unrescue notification asserts
|
||||
post = {
|
||||
'unrescue': None
|
||||
}
|
||||
self.api.post_server_action(server['id'], post)
|
||||
self._wait_for_state_change(self.admin_api, server, 'ACTIVE')
|
||||
|
||||
self.assertEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS))
|
||||
self._verify_notification(
|
||||
'instance-unrescue-start',
|
||||
replacements={
|
||||
'reservation_id': server['reservation_id'],
|
||||
'uuid': server['id']},
|
||||
actual=fake_notifier.VERSIONED_NOTIFICATIONS[0])
|
||||
self._verify_notification(
|
||||
'instance-unrescue-end',
|
||||
replacements={
|
||||
'reservation_id': server['reservation_id'],
|
||||
'uuid': server['id']},
|
||||
actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
|
||||
|
||||
def _test_soft_delete_server(self, server):
|
||||
self.flags(reclaim_instance_interval=30)
|
||||
|
@ -2389,13 +2389,17 @@ class ComputeTestCase(BaseTestCase,
|
||||
|
||||
self.compute.terminate_instance(self.context, instance, [], [])
|
||||
|
||||
def test_rescue_notifications(self):
|
||||
@mock.patch.object(nova.compute.utils,
|
||||
'notify_about_instance_rescue_action')
|
||||
@mock.patch('nova.context.RequestContext.elevated')
|
||||
def test_rescue_notifications(self, mock_context, mock_notify):
|
||||
# Ensure notifications on instance rescue.
|
||||
def fake_rescue(self, context, instance_ref, network_info, image_meta,
|
||||
rescue_password):
|
||||
pass
|
||||
self.stub_out('nova.virt.fake.FakeDriver.rescue', fake_rescue)
|
||||
|
||||
mock_context.return_value = self.context
|
||||
instance = self._create_fake_instance_obj()
|
||||
self.compute.build_and_run_instance(self.context, instance, {}, {}, {},
|
||||
block_device_mapping=[])
|
||||
@ -2412,6 +2416,14 @@ class ComputeTestCase(BaseTestCase,
|
||||
'compute.instance.rescue.end']
|
||||
self.assertEqual([m.event_type for m in fake_notifier.NOTIFICATIONS],
|
||||
expected_notifications)
|
||||
mock_notify.assert_has_calls([
|
||||
mock.call(self.context, instance, 'fake-mini',
|
||||
uuids.fake_image_ref_1,
|
||||
action='rescue', phase='start'),
|
||||
mock.call(self.context, instance, 'fake-mini',
|
||||
uuids.fake_image_ref_1,
|
||||
action='rescue', phase='end')])
|
||||
|
||||
for n, msg in enumerate(fake_notifier.NOTIFICATIONS):
|
||||
self.assertEqual(msg.event_type, expected_notifications[n])
|
||||
self.assertEqual(msg.priority, 'INFO')
|
||||
@ -2433,12 +2445,16 @@ class ComputeTestCase(BaseTestCase,
|
||||
|
||||
self.compute.terminate_instance(self.context, instance, [], [])
|
||||
|
||||
def test_unrescue_notifications(self):
|
||||
@mock.patch.object(nova.compute.utils, 'notify_about_instance_action')
|
||||
@mock.patch('nova.context.RequestContext.elevated')
|
||||
def test_unrescue_notifications(self, mock_context, mock_notify):
|
||||
# Ensure notifications on instance rescue.
|
||||
def fake_unrescue(self, instance_ref, network_info):
|
||||
pass
|
||||
self.stub_out('nova.virt.fake.FakeDriver.unrescue',
|
||||
fake_unrescue)
|
||||
context = self.context
|
||||
mock_context.return_value = context
|
||||
|
||||
instance = self._create_fake_instance_obj()
|
||||
self.compute.build_and_run_instance(self.context, instance, {}, {}, {},
|
||||
@ -2453,6 +2469,12 @@ class ComputeTestCase(BaseTestCase,
|
||||
'compute.instance.unrescue.end']
|
||||
self.assertEqual([m.event_type for m in fake_notifier.NOTIFICATIONS],
|
||||
expected_notifications)
|
||||
mock_notify.assert_has_calls([
|
||||
mock.call(context, instance, 'fake-mini',
|
||||
action='unrescue', phase='start'),
|
||||
mock.call(context, instance, 'fake-mini',
|
||||
action='unrescue', phase='end')])
|
||||
|
||||
for n, msg in enumerate(fake_notifier.NOTIFICATIONS):
|
||||
self.assertEqual(msg.event_type, expected_notifications[n])
|
||||
self.assertEqual(msg.priority, 'INFO')
|
||||
|
@ -3166,7 +3166,8 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
|
||||
volume_id=volume_id),
|
||||
])
|
||||
|
||||
def _test_rescue(self, clean_shutdown=True):
|
||||
@mock.patch('nova.compute.utils.notify_about_instance_rescue_action')
|
||||
def _test_rescue(self, mock_notify, clean_shutdown=True):
|
||||
instance = fake_instance.fake_instance_obj(
|
||||
self.context, vm_state=vm_states.ACTIVE)
|
||||
fake_nw_info = network_model.NetworkInfo()
|
||||
@ -3226,6 +3227,11 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
|
||||
|
||||
notify_usage_exists.assert_called_once_with(self.compute.notifier,
|
||||
self.context, instance, current_period=True)
|
||||
mock_notify.assert_has_calls([
|
||||
mock.call(self.context, instance, 'fake-mini', None,
|
||||
action='rescue', phase='start'),
|
||||
mock.call(self.context, instance, 'fake-mini', None,
|
||||
action='rescue', phase='end')])
|
||||
|
||||
instance_save.assert_called_once_with(
|
||||
expected_task_state=task_states.RESCUING)
|
||||
@ -3236,7 +3242,8 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
|
||||
def test_rescue_forced_shutdown(self):
|
||||
self._test_rescue(clean_shutdown=False)
|
||||
|
||||
def test_unrescue(self):
|
||||
@mock.patch('nova.compute.utils.notify_about_instance_action')
|
||||
def test_unrescue(self, mock_notify):
|
||||
instance = fake_instance.fake_instance_obj(
|
||||
self.context, vm_state=vm_states.RESCUED)
|
||||
fake_nw_info = network_model.NetworkInfo()
|
||||
@ -3273,6 +3280,11 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
|
||||
notify_instance_usage.assert_has_calls(notify_calls)
|
||||
|
||||
driver_unrescue.assert_called_once_with(instance, fake_nw_info)
|
||||
mock_notify.assert_has_calls([
|
||||
mock.call(self.context, instance, 'fake-mini',
|
||||
action='unrescue', phase='start'),
|
||||
mock.call(self.context, instance, 'fake-mini',
|
||||
action='unrescue', phase='end')])
|
||||
|
||||
instance_save.assert_called_once_with(
|
||||
expected_task_state=task_states.UNRESCUING)
|
||||
|
@ -754,6 +754,42 @@ class UsageInfoTestCase(test.TestCase):
|
||||
self.assertEqual('nova.tests.unit.compute.test_compute_utils',
|
||||
exception_payload['module_name'])
|
||||
|
||||
def test_notify_about_instance_rescue_action(self):
|
||||
instance = create_instance(self.context)
|
||||
|
||||
compute_utils.notify_about_instance_rescue_action(
|
||||
self.context,
|
||||
instance,
|
||||
'fake-compute',
|
||||
uuids.rescue_image_ref,
|
||||
action='rescue',
|
||||
phase='start')
|
||||
|
||||
self.assertEqual(len(fake_notifier.VERSIONED_NOTIFICATIONS), 1)
|
||||
notification = fake_notifier.VERSIONED_NOTIFICATIONS[0]
|
||||
|
||||
self.assertEqual(notification['priority'], 'INFO')
|
||||
self.assertEqual(notification['event_type'], 'instance.rescue.start')
|
||||
self.assertEqual(notification['publisher_id'],
|
||||
'nova-compute:fake-compute')
|
||||
|
||||
payload = notification['payload']['nova_object.data']
|
||||
self.assertEqual(payload['tenant_id'], self.project_id)
|
||||
self.assertEqual(payload['user_id'], self.user_id)
|
||||
self.assertEqual(payload['uuid'], instance['uuid'])
|
||||
|
||||
flavorid = flavors.get_flavor_by_name('m1.tiny')['flavorid']
|
||||
flavor = payload['flavor']['nova_object.data']
|
||||
self.assertEqual(str(flavor['flavorid']), flavorid)
|
||||
|
||||
for attr in ('display_name', 'created_at', 'launched_at',
|
||||
'state', 'task_state', 'display_description', 'locked',
|
||||
'auto_disk_config', 'key_name'):
|
||||
self.assertIn(attr, payload, "Key %s not in payload" % attr)
|
||||
|
||||
self.assertEqual(payload['image_uuid'], uuids.fake_image_ref)
|
||||
self.assertEqual(payload['rescue_image_ref'], uuids.rescue_image_ref)
|
||||
|
||||
def test_notify_usage_exists_instance_not_found(self):
|
||||
# Ensure 'exists' notification generates appropriate usage data.
|
||||
instance = create_instance(self.context)
|
||||
|
@ -380,6 +380,8 @@ notification_object_data = {
|
||||
'FlavorPayload': '1.4-2e7011b8b4e59167fe8b7a0a81f0d452',
|
||||
'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceActionPayload': '1.5-fb2804ce9b681bfb217e729153c22611',
|
||||
'InstanceActionRescueNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceActionRescuePayload': '1.0-a29f3339d0b8c3bcc997ab5d19d898d5',
|
||||
'InstanceActionVolumeNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceActionVolumePayload': '1.3-f175b22ac6d6d0aea2bac21e12156e77',
|
||||
'InstanceActionVolumeSwapNotification':
|
||||
|
@ -225,7 +225,7 @@ class FakeDriver(driver.ComputeDriver):
|
||||
pass
|
||||
|
||||
def unrescue(self, instance, network_info):
|
||||
pass
|
||||
self.instances[instance.uuid].state = power_state.RUNNING
|
||||
|
||||
def poll_rebooting_instances(self, timeout, instances):
|
||||
pass
|
||||
|
Loading…
Reference in New Issue
Block a user