Add swap volume notifications (start, end)
Add the following notifications when swapping volumes. * 'instance.volume_swap.start' * 'instance.volume_swap.end' A subsequent patch will add 'instance.volume_swap.error'. Change-Id: Ic4d9d25bdc611221157e4663817e918b8d667ce9 Implements: blueprint add-swap-volume-notifications
This commit is contained in:
parent
3d7ff766a7
commit
47fb8b7579
64
doc/notification_samples/instance-volume_swap-end.json
Normal file
64
doc/notification_samples/instance-volume_swap-end.json
Normal file
@ -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"
|
||||
}
|
64
doc/notification_samples/instance-volume_swap-start.json
Normal file
64
doc/notification_samples/instance-volume_swap-start.json
Normal file
@ -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"
|
||||
}
|
@ -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."""
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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')
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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])
|
||||
|
@ -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 = {}
|
||||
|
@ -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)
|
||||
|
@ -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',
|
||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user