diff --git a/nova/cells/messaging.py b/nova/cells/messaging.py index f7e7c575fcae..d0d11dd5a637 100644 --- a/nova/cells/messaging.py +++ b/nova/cells/messaging.py @@ -909,6 +909,11 @@ class _TargetedMessageMethods(_BaseMessageMethods): instance.refresh() instance.task_state = task_states.IMAGE_SNAPSHOT_PENDING instance.save(expected_task_state=[None]) + + objects.InstanceAction.action_start( + message.ctxt, instance.uuid, instance_actions.CREATE_IMAGE, + want_result=False) + self.compute_rpcapi.snapshot_instance(message.ctxt, instance, image_id) diff --git a/nova/compute/api.py b/nova/compute/api.py index 7b1b94b37ba0..05f28f0952e4 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -2658,6 +2658,9 @@ class API(base.Base): state=state, method='snapshot') + self._record_action_start(context, instance, + instance_actions.CREATE_IMAGE) + self.compute_rpcapi.snapshot_instance(context, instance, image_meta['id']) @@ -2767,29 +2770,39 @@ class API(base.Base): bdms = objects.BlockDeviceMappingList.get_by_instance_uuid( context, instance.uuid) - mapping = [] - for bdm in bdms: - if bdm.no_device: - continue + @wrap_instance_event(prefix='api') + def snapshot_instance(self, context, instance, bdms): + mapping = [] + for bdm in bdms: + if bdm.no_device: + continue - if bdm.is_volume: - # create snapshot based on volume_id - volume = self.volume_api.get(context, bdm.volume_id) - # NOTE(yamahata): Should we wait for snapshot creation? - # Linux LVM snapshot creation completes in - # short time, it doesn't matter for now. - name = _('snapshot for %s') % image_meta['name'] - LOG.debug('Creating snapshot from volume %s.', volume['id'], - instance=instance) - snapshot = self.volume_api.create_snapshot_force( - context, volume['id'], name, volume['display_description']) - mapping_dict = block_device.snapshot_from_bdm(snapshot['id'], - bdm) - mapping_dict = mapping_dict.get_image_mapping() - else: - mapping_dict = bdm.get_image_mapping() + if bdm.is_volume: + # create snapshot based on volume_id + volume = self.volume_api.get(context, bdm.volume_id) + # NOTE(yamahata): Should we wait for snapshot creation? + # Linux LVM snapshot creation completes in + # short time, it doesn't matter for now. + name = _('snapshot for %s') % image_meta['name'] + LOG.debug('Creating snapshot from volume %s.', + volume['id'], + instance=instance) + snapshot = self.volume_api.create_snapshot_force( + context, volume['id'], name, + volume['display_description']) + mapping_dict = block_device.snapshot_from_bdm( + snapshot['id'], + bdm) + mapping_dict = mapping_dict.get_image_mapping() + else: + mapping_dict = bdm.get_image_mapping() - mapping.append(mapping_dict) + mapping.append(mapping_dict) + return mapping + + self._record_action_start(context, instance, + instance_actions.CREATE_IMAGE) + mapping = snapshot_instance(self, context, instance, bdms) if quiesced: self.compute_rpcapi.unquiesce_instance(context, instance, mapping) diff --git a/nova/compute/instance_actions.py b/nova/compute/instance_actions.py index ec6e0e77a341..e6e51c95e46f 100644 --- a/nova/compute/instance_actions.py +++ b/nova/compute/instance_actions.py @@ -69,3 +69,4 @@ SWAP_VOLUME = 'swap_volume' LOCK = 'lock' UNLOCK = 'unlock' BACKUP = 'createBackup' +CREATE_IMAGE = 'createImage' diff --git a/nova/compute/manager.py b/nova/compute/manager.py index fbb19dde65fd..d3ca84b9bb71 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -3229,6 +3229,7 @@ class ComputeManager(manager.Manager): @wrap_exception() @reverts_task_state + @wrap_instance_event(prefix='compute') @wrap_instance_fault @delete_image_on_error def snapshot_instance(self, context, image_id, instance): diff --git a/nova/tests/unit/cells/test_cells_messaging.py b/nova/tests/unit/cells/test_cells_messaging.py index 5d6d5f2cd63a..bf76736e7acc 100644 --- a/nova/tests/unit/cells/test_cells_messaging.py +++ b/nova/tests/unit/cells/test_cells_messaging.py @@ -1343,8 +1343,9 @@ class CellsTargetedMethodsTestCase(test.NoDBTestCase): self._test_instance_action_method('inject_network_info', (), {}, (), {}, False) - def test_snapshot_instance(self): - inst = objects.Instance() + @mock.patch.object(objects.InstanceAction, 'action_start') + def test_snapshot_instance(self, action_start): + inst = objects.Instance(uuid=uuids.instance) meth_cls = self.tgt_methods_cls self.mox.StubOutWithMock(inst, 'refresh') @@ -1371,6 +1372,9 @@ class CellsTargetedMethodsTestCase(test.NoDBTestCase): message.need_response = False meth_cls.snapshot_instance(message, inst, image_id='image-id') + action_start.assert_called_once_with( + message.ctxt, inst.uuid, instance_actions.CREATE_IMAGE, + want_result=False) @mock.patch.object(objects.InstanceAction, 'action_start') def test_backup_instance(self, action_start): diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py index 3f74ca25f6ea..a0d1c744c5ca 100644 --- a/nova/tests/unit/compute/test_compute.py +++ b/nova/tests/unit/compute/test_compute.py @@ -3392,14 +3392,17 @@ class ComputeTestCase(BaseTestCase, fake_delete) inst_obj = self._get_snapshotting_instance() - if method == 'snapshot': - self.assertRaises(test.TestingException, - self.compute.snapshot_instance, - self.context, image_id='fakesnap', - instance=inst_obj) - else: - with mock.patch.object(compute_utils, - 'EventReporter') as mock_event: + with mock.patch.object(compute_utils, + 'EventReporter') as mock_event: + if method == 'snapshot': + self.assertRaises(test.TestingException, + self.compute.snapshot_instance, + self.context, image_id='fakesnap', + instance=inst_obj) + mock_event.assert_called_once_with(self.context, + 'compute_snapshot_instance', + inst_obj.uuid) + else: self.assertRaises(test.TestingException, self.compute.backup_instance, self.context, image_id='fakesnap', diff --git a/nova/tests/unit/compute/test_compute_api.py b/nova/tests/unit/compute/test_compute_api.py index 8eb4aee88800..0515b5ebfd32 100644 --- a/nova/tests/unit/compute/test_compute_api.py +++ b/nova/tests/unit/compute/test_compute_api.py @@ -2594,6 +2594,8 @@ class _ComputeAPIUnitTestMixIn(object): res = self.compute_api.snapshot(self.context, instance, 'fake-name', extra_properties=extra_props) + mock_record.assert_called_once_with( + self.context, instance, instance_actions.CREATE_IMAGE) else: res = self.compute_api.backup(self.context, instance, 'fake-name', @@ -2821,9 +2823,20 @@ class _ComputeAPIUnitTestMixIn(object): fake_unquiesce_instance) fake_image.stub_out_image_service(self) - # No block devices defined - self.compute_api.snapshot_volume_backed( - self.context, instance, 'test-snapshot') + with test.nested( + mock.patch.object(compute_api.API, '_record_action_start'), + mock.patch.object(compute_utils, 'EventReporter')) as ( + mock_record, mock_event): + # No block devices defined + self.compute_api.snapshot_volume_backed( + self.context, instance, 'test-snapshot') + + mock_record.assert_called_once_with(self.context, + instance, + instance_actions.CREATE_IMAGE) + mock_event.assert_called_once_with(self.context, + 'api_snapshot_instance', + instance.uuid) bdm = fake_block_device.FakeDbBlockDeviceDict( {'no_device': False, 'volume_id': '1', 'boot_index': 0, @@ -2843,13 +2856,24 @@ class _ComputeAPIUnitTestMixIn(object): 'destination_type': 'volume', 'delete_on_termination': False, 'tag': None}) - # All the db_only fields and the volume ones are removed - self.compute_api.snapshot_volume_backed( - self.context, instance, 'test-snapshot') + with test.nested( + mock.patch.object(compute_api.API, '_record_action_start'), + mock.patch.object(compute_utils, 'EventReporter')) as ( + mock_record, mock_event): + # All the db_only fields and the volume ones are removed + self.compute_api.snapshot_volume_backed( + self.context, instance, 'test-snapshot') self.assertEqual(quiesce_expected, quiesced[0]) self.assertEqual(quiesce_expected, quiesced[1]) + mock_record.assert_called_once_with(self.context, + instance, + instance_actions.CREATE_IMAGE) + mock_event.assert_called_once_with(self.context, + 'api_snapshot_instance', + instance.uuid) + instance.system_metadata['image_mappings'] = jsonutils.dumps( [{'virtual': 'ami', 'device': 'vda'}, {'device': 'vda', 'virtual': 'ephemeral0'}, @@ -2882,13 +2906,25 @@ class _ComputeAPIUnitTestMixIn(object): quiesced = [False, False] - # Check that the mappings from the image properties are not included - self.compute_api.snapshot_volume_backed( - self.context, instance, 'test-snapshot') + with test.nested( + mock.patch.object(compute_api.API, '_record_action_start'), + mock.patch.object(compute_utils, 'EventReporter')) as ( + mock_record, mock_event): + # Check that the mappings from the image properties are not + # included + self.compute_api.snapshot_volume_backed( + self.context, instance, 'test-snapshot') self.assertEqual(quiesce_expected, quiesced[0]) self.assertEqual(quiesce_expected, quiesced[1]) + mock_record.assert_called_once_with(self.context, + instance, + instance_actions.CREATE_IMAGE) + mock_event.assert_called_once_with(self.context, + 'api_snapshot_instance', + instance.uuid) + def test_snapshot_volume_backed(self): self._test_snapshot_volume_backed(False, False) diff --git a/releasenotes/notes/fill-instance-action-record-gaps-14b36eba313d6d87.yaml b/releasenotes/notes/fill-instance-action-record-gaps-14b36eba313d6d87.yaml index 817117d91ee1..4bf41e738a80 100644 --- a/releasenotes/notes/fill-instance-action-record-gaps-14b36eba313d6d87.yaml +++ b/releasenotes/notes/fill-instance-action-record-gaps-14b36eba313d6d87.yaml @@ -12,3 +12,4 @@ features: * unlock * shelveOffload * createBackup + * createImage