diff --git a/doc/notification_samples/instance-volume_swap-end.json b/doc/notification_samples/instance-volume_swap-end.json new file mode 100644 index 000000000000..88baf9969706 --- /dev/null +++ b/doc/notification_samples/instance-volume_swap-end.json @@ -0,0 +1,64 @@ +{ + "event_type": "instance.volume_swap.end", + "payload": { + "nova_object.data": { + "architecture": "x86_64", + "availability_zone": null, + "created_at": "2012-10-29T13:42:11Z", + "deleted_at": null, + "display_name": "some-server", + "fault": null, + "flavor": { + "nova_object.data": { + "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3", + "root_gb": 1, + "vcpus": 1, + "ephemeral_gb": 0, + "memory_mb": 512 + }, + "nova_object.name": "FlavorPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.0" + }, + "host": "compute", + "host_name": "some-server", + "image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6", + "ip_addresses": [{ + "nova_object.data": { + "address": "192.168.1.3", + "device_name": "tapce531f90-19", + "label": "private-network", + "meta": {}, + "port_uuid": "ce531f90-199f-48c0-816c-13e38010b442", + "version": 4, + "mac": "fa:16:3e:4c:2c:30" + }, + "nova_object.name": "IpPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.0" + }], + "kernel_id": "", + "launched_at": "2012-10-29T13:42:11Z", + "metadata": {}, + "new_volume_id": "227cc671-f30b-4488-96fd-7d0bf13648d8", + "node": "fake-mini", + "old_volume_id": "a07f71dc-8151-4e7d-a0cc-cd24a3f11113", + "os_type": null, + "power_state":"running", + "progress": 0, + "ramdisk_id": "", + "reservation_id": "r-6w6ruqaz", + "state": "active", + "task_state": null, + "tenant_id": "6f70656e737461636b20342065766572", + "terminated_at": null, + "user_id": "fake", + "uuid": "0ab886d0-7443-4107-9265-48371bfa662b" + }, + "nova_object.name": "InstanceActionVolumeSwapPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.0" + }, + "priority": "INFO", + "publisher_id": "nova-compute:compute" +} diff --git a/doc/notification_samples/instance-volume_swap-start.json b/doc/notification_samples/instance-volume_swap-start.json new file mode 100644 index 000000000000..20582af98eaf --- /dev/null +++ b/doc/notification_samples/instance-volume_swap-start.json @@ -0,0 +1,64 @@ +{ + "event_type": "instance.volume_swap.start", + "payload": { + "nova_object.data": { + "architecture": "x86_64", + "availability_zone": null, + "created_at": "2012-10-29T13:42:11Z", + "deleted_at": null, + "display_name": "some-server", + "fault": null, + "flavor": { + "nova_object.data": { + "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3", + "root_gb": 1, + "vcpus": 1, + "ephemeral_gb": 0, + "memory_mb": 512 + }, + "nova_object.name": "FlavorPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.0" + }, + "host": "compute", + "host_name": "some-server", + "image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6", + "ip_addresses": [{ + "nova_object.data": { + "address": "192.168.1.3", + "device_name": "tapce531f90-19", + "label": "private-network", + "meta": {}, + "port_uuid": "ce531f90-199f-48c0-816c-13e38010b442", + "version": 4, + "mac": "fa:16:3e:4c:2c:30" + }, + "nova_object.name": "IpPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.0" + }], + "kernel_id": "", + "launched_at": "2012-10-29T13:42:11Z", + "metadata": {}, + "new_volume_id": "227cc671-f30b-4488-96fd-7d0bf13648d8", + "node": "fake-mini", + "old_volume_id": "a07f71dc-8151-4e7d-a0cc-cd24a3f11113", + "os_type": null, + "power_state":"running", + "progress": 0, + "ramdisk_id": "", + "reservation_id": "r-6w6ruqaz", + "state": "active", + "task_state": null, + "tenant_id": "6f70656e737461636b20342065766572", + "terminated_at": null, + "user_id": "fake", + "uuid": "0ab886d0-7443-4107-9265-48371bfa662b" + }, + "nova_object.name": "InstanceActionVolumeSwapPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.0" + }, + "priority": "INFO", + "publisher_id": "nova-compute:compute" +} diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a9bb9998f301..3954d2cfe90f 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -4916,6 +4916,12 @@ class ComputeManager(manager.Manager): """Swap volume for an instance.""" context = context.elevated() + compute_utils.notify_about_volume_swap( + context, instance, self.host, + fields.NotificationAction.VOLUME_SWAP, + fields.NotificationPhase.START, + old_volume_id, new_volume_id) + bdm = objects.BlockDeviceMapping.get_by_volume_and_instance( context, old_volume_id, instance.uuid) connector = self.driver.get_volume_connector(instance) @@ -4957,6 +4963,12 @@ class ComputeManager(manager.Manager): bdm.update(values) bdm.save() + compute_utils.notify_about_volume_swap( + context, instance, self.host, + fields.NotificationAction.VOLUME_SWAP, + fields.NotificationPhase.END, + old_volume_id, new_volume_id) + @wrap_exception() def remove_volume_connection(self, context, volume_id, instance): """Remove a volume connection using the volume api.""" diff --git a/nova/compute/utils.py b/nova/compute/utils.py index babc8d31c883..68b0d1dabf65 100644 --- a/nova/compute/utils.py +++ b/nova/compute/utils.py @@ -321,15 +321,7 @@ def notify_about_instance_usage(notifier, context, instance, event_suffix, method(context, 'compute.instance.%s' % event_suffix, usage_info) -def notify_about_instance_action(context, instance, host, action, phase=None, - binary='nova-compute'): - """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 action: the name of the action - :param phase: the phase of the action - :param binary: the binary emitting the notification - """ +def _get_instance_ips(instance): network_info = get_nw_info_for_instance(instance) ips = [] if network_info is not None: @@ -343,6 +335,20 @@ def notify_about_instance_action(context, instance, host, action, phase=None, version=ip["version"], address=ip["address"], device_name=vif["devname"])) + return ips + + +def notify_about_instance_action(context, instance, host, action, phase=None, + binary='nova-compute'): + """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 action: the name of the action + :param phase: the phase of the action + :param binary: the binary emitting the notification + """ + ips = _get_instance_ips(instance) + flavor = instance_notification.FlavorPayload(instance=instance) # TODO(gibi): handle fault during the transformation of the first error # notifications @@ -364,6 +370,40 @@ def notify_about_instance_action(context, instance, host, action, phase=None, notification.emit(context) +def notify_about_volume_swap(context, instance, host, action, phase, + old_volume_id, new_volume_id): + """Send versioned notification about the volume swap action + on the instance + + :param context: the request context + :param instance: the instance which the action performed on + :param host: the host emitting the notification + :param action: the name of the action + :param phase: the phase of the action + :param old_volume_id: the ID of the volume that is copied from and detached + :param new_volume_id: the ID of the volume that is copied to and attached + """ + ips = _get_instance_ips(instance) + + flavor = instance_notification.FlavorPayload(instance=instance) + payload = instance_notification.InstanceActionVolumeSwapPayload( + instance=instance, + fault=None, + ip_addresses=ips, + flavor=flavor, + old_volume_id=old_volume_id, + new_volume_id=new_volume_id) + + instance_notification.InstanceActionVolumeSwapNotification( + context=context, + priority=fields.NotificationPriority.INFO, + publisher=notification_base.NotificationPublisher( + context=context, host=host, binary='nova-compute'), + event_type=notification_base.EventType( + object='instance', action=action, phase=phase), + payload=payload).emit(context) + + def notify_about_server_group_update(context, event_suffix, sg_payload): """Send a notification about server group update. diff --git a/nova/notifications/objects/instance.py b/nova/notifications/objects/instance.py index 5efdd4571789..b445bdc45394 100644 --- a/nova/notifications/objects/instance.py +++ b/nova/notifications/objects/instance.py @@ -97,12 +97,34 @@ class InstanceActionPayload(InstancePayload): 'fault': fields.ObjectField('ExceptionPayload', nullable=True), } - def __init__(self, instance, fault, ip_addresses, flavor): + def __init__(self, instance, fault, ip_addresses, flavor, **kwargs): super(InstanceActionPayload, self).__init__( instance=instance, fault=fault, ip_addresses=ip_addresses, - flavor=flavor) + flavor=flavor, + **kwargs) + + +@nova_base.NovaObjectRegistry.register_notification +class InstanceActionVolumeSwapPayload(InstanceActionPayload): + # No SCHEMA as all the additional fields are calculated + + VERSION = '1.0' + fields = { + 'old_volume_id': fields.UUIDField(), + 'new_volume_id': fields.UUIDField(), + } + + def __init__(self, instance, fault, ip_addresses, flavor, + old_volume_id, new_volume_id): + super(InstanceActionVolumeSwapPayload, self).__init__( + instance=instance, + fault=fault, + ip_addresses=ip_addresses, + flavor=flavor, + old_volume_id=old_volume_id, + new_volume_id=new_volume_id) @nova_base.NovaObjectRegistry.register_notification @@ -268,3 +290,15 @@ class InstanceUpdateNotification(base.NotificationBase): fields = { 'payload': fields.ObjectField('InstanceUpdatePayload') } + + +@base.notification_sample('instance-volume_swap-start.json') +@base.notification_sample('instance-volume_swap-end.json') +@nova_base.NovaObjectRegistry.register_notification +class InstanceActionVolumeSwapNotification(base.NotificationBase): + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'payload': fields.ObjectField('InstanceActionVolumeSwapPayload') + } diff --git a/nova/tests/fixtures.py b/nova/tests/fixtures.py index a3ff04ee09b5..be57f332e12f 100644 --- a/nova/tests/fixtures.py +++ b/nova/tests/fixtures.py @@ -790,3 +790,83 @@ class NoopConductorFixture(fixtures.Fixture): 'nova.conductor.ComputeTaskAPI', _NoopConductor)) self.useFixture(fixtures.MonkeyPatch( 'nova.conductor.API', _NoopConductor)) + + +class CinderFixture(fixtures.Fixture): + """A fixture to volume operations""" + + # the default project_id in OSAPIFixtures + tenant_id = '6f70656e737461636b20342065766572' + + SWAP_OLD_VOL = 'a07f71dc-8151-4e7d-a0cc-cd24a3f11113' + SWAP_NEW_VOL = '227cc671-f30b-4488-96fd-7d0bf13648d8' + + def __init__(self, test): + super(CinderFixture, self).__init__() + self.test = test + self.swap_volume_instance_uuid = None + + def setUp(self): + super(CinderFixture, self).setUp() + + def fake_get(self_api, context, volume_id): + if volume_id == CinderFixture.SWAP_OLD_VOL: + volume = { + 'status': 'available', + 'display_name': 'TEST1', + 'attach_status': 'detached', + 'id': volume_id, + 'size': 1 + } + if (self.swap_volume_instance_uuid and + volume_id == CinderFixture.SWAP_OLD_VOL): + instance_uuid = self.swap_volume_instance_uuid + + volume.update({ + 'status': 'in-use', + 'attachments': { + instance_uuid: { + 'mountpoint': '/dev/vdb', + 'attachment_id': volume_id + } + }, + 'attach_status': 'attached' + }) + return volume + else: + return { + 'status': 'available', + 'display_name': 'TEST2', + 'attach_status': 'detached', + 'id': volume_id, + 'size': 1 + } + + def fake_initialize_connection(self, context, volume_id, connector): + return {} + + def fake_migrate_volume_completion(self, context, old_volume_id, + new_volume_id, error): + return {'save_volume_id': new_volume_id} + + self.test.stub_out('nova.volume.cinder.API.attach', + lambda *args, **kwargs: None) + self.test.stub_out('nova.volume.cinder.API.begin_detaching', + lambda *args, **kwargs: None) + self.test.stub_out('nova.volume.cinder.API.check_attach', + lambda *args, **kwargs: None) + self.test.stub_out('nova.volume.cinder.API.check_detach', + lambda *args, **kwargs: None) + self.test.stub_out('nova.volume.cinder.API.get', + fake_get) + self.test.stub_out('nova.volume.cinder.API.initialize_connection', + fake_initialize_connection) + self.test.stub_out( + 'nova.volume.cinder.API.migrate_volume_completion', + fake_migrate_volume_completion) + self.test.stub_out('nova.volume.cinder.API.reserve_volume', + lambda *args, **kwargs: None) + self.test.stub_out('nova.volume.cinder.API.roll_detaching', + lambda *args, **kwargs: None) + self.test.stub_out('nova.volume.cinder.API.terminate_connection', + lambda *args, **kwargs: None) diff --git a/nova/tests/functional/api/client.py b/nova/tests/functional/api/client.py index c0696c81a2da..ff5c629c44a4 100644 --- a/nova/tests/functional/api/client.py +++ b/nova/tests/functional/api/client.py @@ -349,6 +349,11 @@ class TestOpenStackClient(object): (server_id), volume_attachment ).body['volumeAttachment'] + def put_server_volume(self, server_id, attachment_id, volume_id): + return self.api_put('/servers/%s/os-volume_attachments/%s' % + (server_id, attachment_id), + {"volumeAttachment": {"volumeId": volume_id}}) + def delete_server_volume(self, server_id, attachment_id): return self.api_delete('/servers/%s/os-volume_attachments/%s' % (server_id, attachment_id)) diff --git a/nova/tests/functional/notification_sample_tests/test_instance.py b/nova/tests/functional/notification_sample_tests/test_instance.py index 943df07aa5a8..31d44740683a 100644 --- a/nova/tests/functional/notification_sample_tests/test_instance.py +++ b/nova/tests/functional/notification_sample_tests/test_instance.py @@ -9,6 +9,8 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import time + from nova import context from nova.tests import fixtures from nova.tests.functional.notification_sample_tests \ @@ -18,12 +20,26 @@ from nova.tests.unit import fake_notifier class TestInstanceNotificationSample( notification_sample_base.NotificationSampleTestBase): + EVENT_TYPE_SWAP_VOL_START = 'instance-volume_swap-start' + EVENT_TYPE_SWAP_VOL_END = 'instance-volume_swap-end' def setUp(self): self.flags(use_neutron=True) super(TestInstanceNotificationSample, self).setUp() self.neutron = fixtures.NeutronFixture(self) self.useFixture(self.neutron) + self.cinder = fixtures.CinderFixture(self) + self.useFixture(self.cinder) + + def _wait_until_swap_volume(self, server, volume_id): + for i in range(50): + volume_attachments = self.api.get_server_volumes(server['id']) + if len(volume_attachments) > 0: + for volume_attachment in volume_attachments: + if volume_attachment['volumeId'] == volume_id: + return + time.sleep(0.5) + self.fail('Volume swap operation failed.') def test_instance_action(self): # A single test case is used to test most of the instance action @@ -350,3 +366,36 @@ class TestInstanceNotificationSample( actual=fake_notifier.VERSIONED_NOTIFICATIONS[1]) self.flags(reclaim_instance_interval=0) + + def _attach_volume_to_server(self, server, volume_id): + self.api.post_server_volume( + server['id'], {"volumeAttachment": {"volumeId": volume_id}}) + + def _volume_swap_server(self, server, attachement_id, volume_id): + self.api.put_server_volume(server['id'], attachement_id, volume_id) + + def test_volume_swap_server(self): + server = self._boot_a_server( + extra_params={'networks': + [{'port': self.neutron.port_1['id']}]}) + + self._attach_volume_to_server(server, self.cinder.SWAP_OLD_VOL) + self.cinder.swap_volume_instance_uuid = server['id'] + + self._volume_swap_server(server, self.cinder.SWAP_OLD_VOL, + self.cinder.SWAP_NEW_VOL) + self._wait_until_swap_volume(server, self.cinder.SWAP_NEW_VOL) + + self.assertEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS)) + self._verify_notification( + self.EVENT_TYPE_SWAP_VOL_START, + replacements={ + 'reservation_id': server['reservation_id'], + 'uuid': server['id']}, + actual=fake_notifier.VERSIONED_NOTIFICATIONS[0]) + self._verify_notification( + self.EVENT_TYPE_SWAP_VOL_END, + replacements={ + 'reservation_id': server['reservation_id'], + 'uuid': server['id']}, + actual=fake_notifier.VERSIONED_NOTIFICATIONS[1]) diff --git a/nova/tests/unit/compute/test_compute_mgr.py b/nova/tests/unit/compute/test_compute_mgr.py index 18bb50cfd437..49b191894c39 100755 --- a/nova/tests/unit/compute/test_compute_mgr.py +++ b/nova/tests/unit/compute/test_compute_mgr.py @@ -44,6 +44,7 @@ from nova.network import api as network_api from nova.network import model as network_model from nova import objects from nova.objects import block_device as block_device_obj +from nova.objects import fields from nova.objects import instance as instance_obj from nova.objects import migrate_data as migrate_data_obj from nova.objects import network_request as net_req_obj @@ -1741,7 +1742,8 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase): do_test() - def test_swap_volume_volume_api_usage(self): + @mock.patch.object(compute_utils, 'notify_about_volume_swap') + def test_swap_volume_volume_api_usage(self, mock_notify): # This test ensures that volume_id arguments are passed to volume_api # and that volume states are OK volumes = {} @@ -1825,34 +1827,65 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase): '_instance_update', lambda c, u, **k: {}) # Good path + instance1 = fake_instance.fake_instance_obj( + self.context, **{'uuid': uuids.instance}) self.compute.swap_volume(self.context, old_volume_id, new_volume_id, - fake_instance.fake_instance_obj( - self.context, **{'uuid': uuids.instance})) + instance1) self.assertEqual(volumes[old_volume_id]['status'], 'in-use') + self.assertEqual(2, mock_notify.call_count) + mock_notify.assert_any_call(test.MatchType(context.RequestContext), + instance1, self.compute.host, + fields.NotificationAction.VOLUME_SWAP, + fields.NotificationPhase.START, + old_volume_id, new_volume_id) + mock_notify.assert_any_call(test.MatchType(context.RequestContext), + instance1, self.compute.host, + fields.NotificationAction.VOLUME_SWAP, + fields.NotificationPhase.END, + old_volume_id, new_volume_id) # Error paths + mock_notify.reset_mock() volumes[old_volume_id]['status'] = 'detaching' volumes[new_volume_id]['status'] = 'attaching' self.stub_out('nova.virt.fake.FakeDriver.swap_volume', fake_func_exc) + instance2 = fake_instance.fake_instance_obj( + self.context, **{'uuid': uuids.instance}) self.assertRaises(AttributeError, self.compute.swap_volume, self.context, old_volume_id, new_volume_id, - fake_instance.fake_instance_obj( - self.context, **{'uuid': uuids.instance})) + instance2) self.assertEqual(volumes[old_volume_id]['status'], 'in-use') self.assertEqual(volumes[new_volume_id]['status'], 'available') + self.assertEqual(1, mock_notify.call_count) + mock_notify.assert_called_once_with( + test.MatchType(context.RequestContext), instance2, + self.compute.host, + fields.NotificationAction.VOLUME_SWAP, + fields.NotificationPhase.START, + old_volume_id, new_volume_id) + mock_notify.reset_mock() volumes[old_volume_id]['status'] = 'detaching' volumes[new_volume_id]['status'] = 'attaching' self.stub_out('nova.volume.cinder.API.initialize_connection', fake_func_exc) + instance3 = fake_instance.fake_instance_obj( + self.context, **{'uuid': uuids.instance}) self.assertRaises(AttributeError, self.compute.swap_volume, self.context, old_volume_id, new_volume_id, - fake_instance.fake_instance_obj( - self.context, **{'uuid': uuids.instance})) + instance3) self.assertEqual(volumes[old_volume_id]['status'], 'in-use') self.assertEqual(volumes[new_volume_id]['status'], 'available') + self.assertEqual(1, mock_notify.call_count) + mock_notify.assert_called_once_with( + test.MatchType(context.RequestContext), instance3, + self.compute.host, + fields.NotificationAction.VOLUME_SWAP, + fields.NotificationPhase.START, + old_volume_id, new_volume_id) + @mock.patch('nova.compute.utils.notify_about_volume_swap') @mock.patch('nova.db.block_device_mapping_get_by_instance_and_volume_id') @mock.patch('nova.db.block_device_mapping_update') @mock.patch('nova.volume.cinder.API.get') @@ -1862,7 +1895,8 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase): volume_connector_mock, get_volume_mock, update_bdm_mock, - get_bdm_mock): + get_bdm_mock, + notify_mock): # This test ensures that delete_on_termination flag arguments # are reserved volumes = {} diff --git a/nova/tests/unit/compute/test_compute_utils.py b/nova/tests/unit/compute/test_compute_utils.py index 55fb7df2ba92..88bc747f0851 100644 --- a/nova/tests/unit/compute/test_compute_utils.py +++ b/nova/tests/unit/compute/test_compute_utils.py @@ -38,6 +38,7 @@ from nova.network import model from nova import objects from nova.objects import base from nova.objects import block_device as block_device_obj +from nova.objects import fields from nova import rpc from nova import test from nova.tests.unit import fake_block_device @@ -519,6 +520,44 @@ class UsageInfoTestCase(test.TestCase): self.assertEqual(payload['image_uuid'], uuids.fake_image_ref) + def test_notify_about_volume_swap(self): + instance = create_instance(self.context) + + compute_utils.notify_about_volume_swap( + self.context, instance, 'fake-compute', + fields.NotificationAction.VOLUME_SWAP, + fields.NotificationPhase.START, + uuids.old_volume_id, uuids.new_volume_id) + + self.assertEqual(len(fake_notifier.VERSIONED_NOTIFICATIONS), 1) + notification = fake_notifier.VERSIONED_NOTIFICATIONS[0] + + self.assertEqual('INFO', notification['priority']) + self.assertEqual('instance.%s.%s' % + (fields.NotificationAction.VOLUME_SWAP, + fields.NotificationPhase.START), + notification['event_type']) + self.assertEqual('nova-compute:fake-compute', + notification['publisher_id']) + + payload = notification['payload']['nova_object.data'] + self.assertEqual(self.project_id, payload['tenant_id']) + self.assertEqual(self.user_id, payload['user_id']) + self.assertEqual(instance['uuid'], payload['uuid']) + + flavorid = flavors.get_flavor_by_name('m1.tiny')['flavorid'] + flavor = payload['flavor']['nova_object.data'] + self.assertEqual(flavorid, str(flavor['flavorid'])) + + for attr in ('display_name', 'created_at', 'launched_at', + 'state', 'task_state'): + self.assertIn(attr, payload) + + self.assertEqual(uuids.fake_image_ref, payload['image_uuid']) + + self.assertEqual(uuids.old_volume_id, payload['old_volume_id']) + self.assertEqual(uuids.new_volume_id, payload['new_volume_id']) + def test_notify_usage_exists_instance_not_found(self): # Ensure 'exists' notification generates appropriate usage data. instance = create_instance(self.context) diff --git a/nova/tests/unit/notifications/objects/test_notification.py b/nova/tests/unit/notifications/objects/test_notification.py index fca9faeec388..6f4c8732ae95 100644 --- a/nova/tests/unit/notifications/objects/test_notification.py +++ b/nova/tests/unit/notifications/objects/test_notification.py @@ -265,6 +265,9 @@ notification_object_data = { 'FlavorPayload': '1.0-8ad962ab0bafc7270f474c7dda0b7c20', 'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56', 'InstanceActionPayload': '1.0-d94994d6043bb87fde603976ce811cba', + 'InstanceActionVolumeSwapNotification': + '1.0-a73147b93b520ff0061865849d3dfa56', + 'InstanceActionVolumeSwapPayload': '1.0-1043222e12be67403cab471ea1989b76', 'InstancePayload': '1.0-4473793aa2a0a4083d328847f3ab638a', 'InstanceStateUpdatePayload': '1.0-a934d04e1b314318e42e8062647edd11', 'InstanceUpdateNotification': '1.0-a73147b93b520ff0061865849d3dfa56', diff --git a/releasenotes/notes/add-swap-volume-notifications-bb7e14230fccfd6e.yaml b/releasenotes/notes/add-swap-volume-notifications-bb7e14230fccfd6e.yaml new file mode 100644 index 000000000000..2343eacdee48 --- /dev/null +++ b/releasenotes/notes/add-swap-volume-notifications-bb7e14230fccfd6e.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + The following versioned swap volume notifications have been added + in the compute manager: + + * instance.volume_swap.start + * instance.volume_swap.end