diff --git a/nova/compute/api.py b/nova/compute/api.py index 6401b5829a..7ede3c8eca 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1349,6 +1349,32 @@ class API(BaseAPI): if instance_type['root_gb'] < int(image.get('min_disk') or 0): raise exception.InstanceTypeDiskTooSmall() + def _reset_image_metadata(): + """ + Remove old image properties that we're storing as instance + system metadata. These properties start with 'image_'. + Then add the properites for the new image. + """ + + # FIXME(comstud): There's a race condition here in that + # if the system_metadata for this instance is updated + # after we do the get and before we update.. those other + # updates will be lost. Since this problem exists in a lot + # of other places, I think it should be addressed in a DB + # layer overhaul. + sys_metadata = self.db.instance_system_metadata_get(context, + instance['uuid']) + # Remove the old keys + for key in sys_metadata.keys(): + if key.startswith('image_'): + del sys_metadata[key] + # Add the new ones + for key, value in image['properties'].iteritems(): + new_value = str(value)[:255] + sys_metadata['image_%s' % key] = new_value + self.db.instance_system_metadata_update(context, + instance['uuid'], sys_metadata, True) + self.update(context, instance, vm_state=vm_states.REBUILDING, @@ -1359,6 +1385,11 @@ class API(BaseAPI): progress=0, **kwargs) + # On a rebuild, since we're potentially changing images, we need to + # wipe out the old image properties that we're storing as instance + # system metadata... and copy in the properties for the new image. + _reset_image_metadata() + rebuild_params = { "new_pass": admin_password, "injected_files": files_to_inject, diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index f806e7516d..a401626138 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -115,7 +115,7 @@ class BaseTestCase(test.TestCase): test_notifier.NOTIFICATIONS = [] def fake_show(meh, context, id): - return {'id': 1, 'min_disk': None, 'min_ram': None, + return {'id': id, 'min_disk': None, 'min_ram': None, 'properties': {'kernel_id': 'fake_kernel_id', 'ramdisk_id': 'fake_ramdisk_id', 'something_else': 'meow'}} @@ -2307,6 +2307,15 @@ class ComputeAPITestCase(BaseTestCase): instance = db.instance_get_by_uuid(self.context, instance_uuid) self.assertEqual(instance['task_state'], None) + # Set some image metadata that should get wiped out and reset + # as well as some other metadata that should be preserved. + db.instance_system_metadata_update(self.context, instance_uuid, + {'image_kernel_id': 'old-data', + 'image_ramdisk_id': 'old_data', + 'image_something_else': 'old-data', + 'image_should_remove': 'bye-bye', + 'preserved': 'preserve this!'}, + True) # Make sure Compute API updates the image_ref before casting to # compute manager. @@ -2327,7 +2336,13 @@ class ComputeAPITestCase(BaseTestCase): instance = db.instance_get_by_uuid(self.context, instance_uuid) self.assertEqual(instance['vm_state'], vm_states.REBUILDING) - + sys_metadata = db.instance_system_metadata_get(self.context, + instance_uuid) + self.assertEqual(sys_metadata, + {'image_kernel_id': 'fake_kernel_id', + 'image_ramdisk_id': 'fake_ramdisk_id', + 'image_something_else': 'meow', + 'preserved': 'preserve this!'}) db.instance_destroy(self.context, instance['id']) def test_reboot_soft(self):