From 0df60e98790c722aef59d0015c209ea0944e62c0 Mon Sep 17 00:00:00 2001 From: Andrew Melton Date: Mon, 12 Nov 2012 13:19:19 -0500 Subject: [PATCH] Adding two snapshot related task states The first, 'image_pending_upload', indicates that the snapshot of a given instance has been taken and it is being prepared for uploading to the image service. The second, 'image_uploading', indicates that the compute manager has initiated upload to the image service. Implements blueprint snapshot-task-states Change-Id: I256c5d21a1d23b87d2060cca99eb9839c5b89161 --- nova/compute/manager.py | 11 ++- nova/compute/task_states.py | 2 + nova/tests/matchers.py | 15 ++++ nova/tests/test_hypervapi.py | 34 +++++++- nova/tests/test_libvirt.py | 142 +++++++++++++++++++++++++++++--- nova/tests/test_virt_drivers.py | 6 +- nova/tests/test_vmwareapi.py | 18 +++- nova/tests/test_xenapi.py | 20 ++++- nova/virt/driver.py | 2 +- nova/virt/fake.py | 4 +- nova/virt/hyperv/driver.py | 4 +- nova/virt/hyperv/snapshotops.py | 7 +- nova/virt/libvirt/driver.py | 7 +- nova/virt/vmwareapi/driver.py | 4 +- nova/virt/vmwareapi/vmops.py | 6 +- nova/virt/xenapi/driver.py | 4 +- nova/virt/xenapi/vm_utils.py | 9 +- nova/virt/xenapi/vmops.py | 8 +- 18 files changed, 268 insertions(+), 35 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 489deef42f59..1a9fef448d11 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1388,16 +1388,21 @@ class ComputeManager(manager.SchedulerDependentManager): self._notify_about_instance_usage( context, instance, "snapshot.start") - self.driver.snapshot(context, instance, image_id) - if image_type == 'snapshot': expected_task_state = task_states.IMAGE_SNAPSHOT elif image_type == 'backup': expected_task_state = task_states.IMAGE_BACKUP + def update_task_state(task_state, expected_state=expected_task_state): + self._instance_update(context, instance['uuid'], + task_state=task_state, + expected_task_state=expected_state) + + self.driver.snapshot(context, instance, image_id, update_task_state) + self._instance_update(context, instance['uuid'], task_state=None, - expected_task_state=expected_task_state) + expected_task_state=task_states.IMAGE_UPLOADING) if image_type == 'snapshot' and rotation: raise exception.ImageRotationNotAllowed() diff --git a/nova/compute/task_states.py b/nova/compute/task_states.py index c2966d5541cc..8e2b8344a052 100644 --- a/nova/compute/task_states.py +++ b/nova/compute/task_states.py @@ -33,6 +33,8 @@ SPAWNING = 'spawning' # possible task states during snapshot() IMAGE_SNAPSHOT = 'image_snapshot' +IMAGE_PENDING_UPLOAD = 'image_pending_upload' +IMAGE_UPLOADING = 'image_uploading' # possible task states during backup() IMAGE_BACKUP = 'image_backup' diff --git a/nova/tests/matchers.py b/nova/tests/matchers.py index a421cc056864..be65da823a8d 100644 --- a/nova/tests/matchers.py +++ b/nova/tests/matchers.py @@ -198,6 +198,21 @@ class IsSubDictOf(object): return SubDictMismatch(k, sub_value, super_value) +class FunctionCallMatcher(object): + + def __init__(self, expected_func_calls): + self.expected_func_calls = expected_func_calls + self.actual_func_calls = [] + + def call(self, *args, **kwargs): + func_call = {'args': args, 'kwargs': kwargs} + self.actual_func_calls.append(func_call) + + def match(self): + dict_list_matcher = DictListMatches(self.expected_func_calls) + return dict_list_matcher.match(self.actual_func_calls) + + class XMLMismatch(object): """Superclass for XML mismatch.""" diff --git a/nova/tests/test_hypervapi.py b/nova/tests/test_hypervapi.py index eae3c0151094..cab877da9ec2 100644 --- a/nova/tests/test_hypervapi.py +++ b/nova/tests/test_hypervapi.py @@ -26,6 +26,7 @@ import sys import uuid from nova.compute import power_state +from nova.compute import task_states from nova import context from nova import db from nova.image import glance @@ -36,6 +37,7 @@ from nova.tests.hyperv import db_fakes from nova.tests.hyperv import hypervutils from nova.tests.hyperv import mockproxy import nova.tests.image.fake as fake_image +from nova.tests import matchers from nova.virt.hyperv import constants from nova.virt.hyperv import driver as driver_hyperv from nova.virt.hyperv import vmutils @@ -407,27 +409,55 @@ class HyperVAPITestCase(basetestcase.BaseTestCase): self.assertTrue(self._fetched_image is None) def test_snapshot_with_update_failure(self): + expected_calls = [ + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + self._spawn_instance(True) self._update_image_raise_exception = True snapshot_name = 'test_snapshot_' + str(uuid.uuid4()) self.assertRaises(vmutils.HyperVException, self._conn.snapshot, - self._context, self._instance_data, snapshot_name) + self._context, self._instance_data, snapshot_name, + func_call_matcher.call) + + # assert states changed in correct order + self.assertIsNone(func_call_matcher.match()) # assert VM snapshots have been removed self.assertEquals(self._hypervutils.get_vm_snapshots_count( self._instance_data["name"]), 0) def test_snapshot(self): + expected_calls = [ + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + self._spawn_instance(True) snapshot_name = 'test_snapshot_' + str(uuid.uuid4()) - self._conn.snapshot(self._context, self._instance_data, snapshot_name) + self._conn.snapshot(self._context, self._instance_data, snapshot_name, + func_call_matcher.call) self.assertTrue(self._image_metadata and "disk_format" in self._image_metadata and self._image_metadata["disk_format"] == "vhd") + # assert states changed in correct order + self.assertIsNone(func_call_matcher.match()) + # assert VM snapshots have been removed self.assertEquals(self._hypervutils.get_vm_snapshots_count( self._instance_data["name"]), 0) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index fc8e01728be1..3943e8c4617e 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -32,6 +32,7 @@ from xml.dom import minidom from nova.api.ec2 import cloud from nova.compute import instance_types from nova.compute import power_state +from nova.compute import task_states from nova.compute import vm_mode from nova.compute import vm_states from nova import context @@ -1209,6 +1210,16 @@ class LibvirtConnTestCase(test.TestCase): self.assertEqual(devices, ['vda', 'vdb']) def test_snapshot_in_ami_format(self): + expected_calls = [ + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + self.flags(libvirt_snapshots_directory='./') # Start test @@ -1238,15 +1249,28 @@ class LibvirtConnTestCase(test.TestCase): self.mox.ReplayAll() conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) - conn.snapshot(self.context, instance_ref, recv_meta['id']) + conn.snapshot(self.context, instance_ref, recv_meta['id'], + func_call_matcher.call) snapshot = image_service.show(context, recv_meta['id']) + self.assertIsNone(func_call_matcher.match()) + self.assertEquals(snapshot['properties']['image_state'], 'available') self.assertEquals(snapshot['properties']['image_state'], 'available') self.assertEquals(snapshot['status'], 'active') self.assertEquals(snapshot['disk_format'], 'ami') self.assertEquals(snapshot['name'], snapshot_name) def test_lxc_snapshot_in_ami_format(self): + expected_calls = [ + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + self.flags(libvirt_snapshots_directory='./', libvirt_type='lxc') @@ -1277,15 +1301,27 @@ class LibvirtConnTestCase(test.TestCase): self.mox.ReplayAll() conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) - conn.snapshot(self.context, instance_ref, recv_meta['id']) + conn.snapshot(self.context, instance_ref, recv_meta['id'], + func_call_matcher.call) snapshot = image_service.show(context, recv_meta['id']) + self.assertIsNone(func_call_matcher.match()) self.assertEquals(snapshot['properties']['image_state'], 'available') self.assertEquals(snapshot['status'], 'active') self.assertEquals(snapshot['disk_format'], 'ami') self.assertEquals(snapshot['name'], snapshot_name) def test_snapshot_in_raw_format(self): + expected_calls = [ + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + self.flags(libvirt_snapshots_directory='./') # Start test @@ -1316,15 +1352,27 @@ class LibvirtConnTestCase(test.TestCase): self.mox.ReplayAll() conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) - conn.snapshot(self.context, instance_ref, recv_meta['id']) + conn.snapshot(self.context, instance_ref, recv_meta['id'], + func_call_matcher.call) snapshot = image_service.show(context, recv_meta['id']) + self.assertIsNone(func_call_matcher.match()) self.assertEquals(snapshot['properties']['image_state'], 'available') self.assertEquals(snapshot['status'], 'active') self.assertEquals(snapshot['disk_format'], 'raw') self.assertEquals(snapshot['name'], snapshot_name) def test_lxc_snapshot_in_raw_format(self): + expected_calls = [ + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + self.flags(libvirt_snapshots_directory='./', libvirt_type='lxc') @@ -1356,15 +1404,27 @@ class LibvirtConnTestCase(test.TestCase): self.mox.ReplayAll() conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) - conn.snapshot(self.context, instance_ref, recv_meta['id']) + conn.snapshot(self.context, instance_ref, recv_meta['id'], + func_call_matcher.call) snapshot = image_service.show(context, recv_meta['id']) + self.assertIsNone(func_call_matcher.match()) self.assertEquals(snapshot['properties']['image_state'], 'available') self.assertEquals(snapshot['status'], 'active') self.assertEquals(snapshot['disk_format'], 'raw') self.assertEquals(snapshot['name'], snapshot_name) def test_snapshot_in_qcow2_format(self): + expected_calls = [ + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + self.flags(snapshot_image_format='qcow2', libvirt_snapshots_directory='./') @@ -1391,15 +1451,27 @@ class LibvirtConnTestCase(test.TestCase): self.mox.ReplayAll() conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) - conn.snapshot(self.context, instance_ref, recv_meta['id']) + conn.snapshot(self.context, instance_ref, recv_meta['id'], + func_call_matcher.call) snapshot = image_service.show(context, recv_meta['id']) + self.assertIsNone(func_call_matcher.match()) self.assertEquals(snapshot['properties']['image_state'], 'available') self.assertEquals(snapshot['status'], 'active') self.assertEquals(snapshot['disk_format'], 'qcow2') self.assertEquals(snapshot['name'], snapshot_name) def test_lxc_snapshot_in_qcow2_format(self): + expected_calls = [ + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + self.flags(snapshot_image_format='qcow2', libvirt_snapshots_directory='./', libvirt_type='lxc') @@ -1427,15 +1499,27 @@ class LibvirtConnTestCase(test.TestCase): self.mox.ReplayAll() conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) - conn.snapshot(self.context, instance_ref, recv_meta['id']) + conn.snapshot(self.context, instance_ref, recv_meta['id'], + func_call_matcher.call) snapshot = image_service.show(context, recv_meta['id']) + self.assertIsNone(func_call_matcher.match()) self.assertEquals(snapshot['properties']['image_state'], 'available') self.assertEquals(snapshot['status'], 'active') self.assertEquals(snapshot['disk_format'], 'qcow2') self.assertEquals(snapshot['name'], snapshot_name) def test_snapshot_no_image_architecture(self): + expected_calls = [ + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + self.flags(libvirt_snapshots_directory='./') # Start test @@ -1465,14 +1549,26 @@ class LibvirtConnTestCase(test.TestCase): self.mox.ReplayAll() conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) - conn.snapshot(self.context, instance_ref, recv_meta['id']) + conn.snapshot(self.context, instance_ref, recv_meta['id'], + func_call_matcher.call) snapshot = image_service.show(context, recv_meta['id']) + self.assertIsNone(func_call_matcher.match()) self.assertEquals(snapshot['properties']['image_state'], 'available') self.assertEquals(snapshot['status'], 'active') self.assertEquals(snapshot['name'], snapshot_name) def test_lxc_snapshot_no_image_architecture(self): + expected_calls = [ + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + self.flags(libvirt_snapshots_directory='./', libvirt_type='lxc') @@ -1503,14 +1599,26 @@ class LibvirtConnTestCase(test.TestCase): self.mox.ReplayAll() conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) - conn.snapshot(self.context, instance_ref, recv_meta['id']) + conn.snapshot(self.context, instance_ref, recv_meta['id'], + func_call_matcher.call) snapshot = image_service.show(context, recv_meta['id']) + self.assertIsNone(func_call_matcher.match()) self.assertEquals(snapshot['properties']['image_state'], 'available') self.assertEquals(snapshot['status'], 'active') self.assertEquals(snapshot['name'], snapshot_name) def test_snapshot_no_original_image(self): + expected_calls = [ + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + self.flags(libvirt_snapshots_directory='./') # Start test @@ -1536,14 +1644,26 @@ class LibvirtConnTestCase(test.TestCase): self.mox.ReplayAll() conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) - conn.snapshot(self.context, instance_ref, recv_meta['id']) + conn.snapshot(self.context, instance_ref, recv_meta['id'], + func_call_matcher.call) snapshot = image_service.show(context, recv_meta['id']) + self.assertIsNone(func_call_matcher.match()) self.assertEquals(snapshot['properties']['image_state'], 'available') self.assertEquals(snapshot['status'], 'active') self.assertEquals(snapshot['name'], snapshot_name) def test_lxc_snapshot_no_original_image(self): + expected_calls = [ + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + self.flags(libvirt_snapshots_directory='./', libvirt_type='lxc') @@ -1570,9 +1690,11 @@ class LibvirtConnTestCase(test.TestCase): self.mox.ReplayAll() conn = libvirt_driver.LibvirtDriver(False) - conn.snapshot(self.context, instance_ref, recv_meta['id']) + conn.snapshot(self.context, instance_ref, recv_meta['id'], + func_call_matcher.call) snapshot = image_service.show(context, recv_meta['id']) + self.assertIsNone(func_call_matcher.match()) self.assertEquals(snapshot['properties']['image_state'], 'available') self.assertEquals(snapshot['status'], 'active') self.assertEquals(snapshot['name'], snapshot_name) diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py index b0e25d0955da..9d9ebcad9a12 100644 --- a/nova/tests/test_virt_drivers.py +++ b/nova/tests/test_virt_drivers.py @@ -215,13 +215,15 @@ class _VirtDriverTestCase(_FakeDriverBackendTestCase): img_ref = self.image_service.create(self.ctxt, {'name': 'snap-1'}) self.assertRaises(exception.InstanceNotRunning, self.connection.snapshot, - self.ctxt, instance_ref, img_ref['id']) + self.ctxt, instance_ref, img_ref['id'], + lambda *args, **kwargs: None) @catch_notimplementederror def test_snapshot_running(self): img_ref = self.image_service.create(self.ctxt, {'name': 'snap-1'}) instance_ref, network_info = self._get_running_instance() - self.connection.snapshot(self.ctxt, instance_ref, img_ref['id']) + self.connection.snapshot(self.ctxt, instance_ref, img_ref['id'], + lambda *args, **kwargs: None) @catch_notimplementederror def test_reboot(self): diff --git a/nova/tests/test_vmwareapi.py b/nova/tests/test_vmwareapi.py index 3a404a122263..86b3a5730647 100644 --- a/nova/tests/test_vmwareapi.py +++ b/nova/tests/test_vmwareapi.py @@ -20,11 +20,13 @@ Test suite for VMWareAPI. """ from nova.compute import power_state +from nova.compute import task_states from nova import context from nova import db from nova import exception from nova import test import nova.tests.image.fake +from nova.tests import matchers from nova.tests.vmwareapi import db_fakes from nova.tests.vmwareapi import stubs from nova.virt.vmwareapi import driver @@ -159,17 +161,29 @@ class VMWareAPIVMTestCase(test.TestCase): self._check_vm_info(info, power_state.RUNNING) def test_snapshot(self): + expected_calls = [ + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + func_call_matcher = matchers.FunctionCallMatcher(expected_calls) self._create_vm() info = self.conn.get_info({'name': 1}) self._check_vm_info(info, power_state.RUNNING) - self.conn.snapshot(self.context, self.instance, "Test-Snapshot") + self.conn.snapshot(self.context, self.instance, "Test-Snapshot", + func_call_matcher.call) info = self.conn.get_info({'name': 1}) self._check_vm_info(info, power_state.RUNNING) + self.assertIsNone(func_call_matcher.match()) def test_snapshot_non_existent(self): self._create_instance_in_the_db() self.assertRaises(exception.InstanceNotFound, self.conn.snapshot, - self.context, self.instance, "Test-Snapshot") + self.context, self.instance, "Test-Snapshot", + lambda *args, **kwargs: None) def test_reboot(self): self._create_vm() diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 3ca69dc4cff9..8b57dfef4195 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -397,6 +397,7 @@ class XenAPIVMTestCase(stubs.XenAPITestBase): self.assertThat(fake_diagnostics, matchers.DictMatches(expected)) def test_instance_snapshot_fails_with_no_primary_vdi(self): + def create_bad_vbd(session, vm_ref, vdi_ref, userdevice, vbd_type='disk', read_only=False, bootable=False, osvol=False): @@ -417,9 +418,20 @@ class XenAPIVMTestCase(stubs.XenAPITestBase): image_id = "my_snapshot_id" self.assertRaises(exception.NovaException, self.conn.snapshot, - self.context, instance, image_id) + self.context, instance, image_id, + lambda *args, **kwargs: None) def test_instance_snapshot(self): + expected_calls = [ + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': + {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + stubs.stubout_instance_snapshot(self.stubs) stubs.stubout_is_snapshot(self.stubs) # Stubbing out firewall driver as previous stub sets alters @@ -428,7 +440,8 @@ class XenAPIVMTestCase(stubs.XenAPITestBase): instance = self._create_instance() image_id = "my_snapshot_id" - self.conn.snapshot(self.context, instance, image_id) + self.conn.snapshot(self.context, instance, image_id, + func_call_matcher.call) # Ensure VM was torn down vm_labels = [] @@ -447,6 +460,9 @@ class XenAPIVMTestCase(stubs.XenAPITestBase): self.assertEquals(vbd_labels, [instance['name']]) + # Ensure task states changed in correct order + self.assertIsNone(func_call_matcher.match()) + # Ensure VDIs were torn down for vdi_ref in xenapi_fake.get_all('VDI'): vdi_rec = xenapi_fake.get_record('VDI', vdi_ref) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 9291ac6f80a3..7d627e80c826 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -280,7 +280,7 @@ class ComputeDriver(object): """ raise NotImplementedError() - def snapshot(self, context, instance, image_id): + def snapshot(self, context, instance, image_id, update_task_state): """ Snapshots the specified instance. diff --git a/nova/virt/fake.py b/nova/virt/fake.py index c9cd41680d64..5d3b3c9269fb 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -26,6 +26,7 @@ semantics of real hypervisor connections. """ from nova.compute import power_state +from nova.compute import task_states from nova import db from nova import exception from nova.openstack.common import log as logging @@ -122,9 +123,10 @@ class FakeDriver(driver.ComputeDriver): fake_instance = FakeInstance(name, state) self.instances[name] = fake_instance - def snapshot(self, context, instance, name): + def snapshot(self, context, instance, name, update_task_state): if not instance['name'] in self.instances: raise exception.InstanceNotRunning(instance_id=instance['uuid']) + update_task_state(task_state=task_states.IMAGE_UPLOADING) def reboot(self, instance, network_info, reboot_type, block_device_info=None): diff --git a/nova/virt/hyperv/driver.py b/nova/virt/hyperv/driver.py index 4359b1007718..2b57ba0b11fb 100644 --- a/nova/virt/hyperv/driver.py +++ b/nova/virt/hyperv/driver.py @@ -128,8 +128,8 @@ class HyperVDriver(driver.ComputeDriver): def host_power_action(self, host, action): return self._hostops.host_power_action(host, action) - def snapshot(self, context, instance, name): - self._snapshotops.snapshot(context, instance, name) + def snapshot(self, context, instance, name, update_task_state): + self._snapshotops.snapshot(context, instance, name, update_task_state) def pause(self, instance): self._vmops.pause(instance) diff --git a/nova/virt/hyperv/snapshotops.py b/nova/virt/hyperv/snapshotops.py index 5dc19ebb1cc2..cdc6e45a47ac 100644 --- a/nova/virt/hyperv/snapshotops.py +++ b/nova/virt/hyperv/snapshotops.py @@ -22,6 +22,7 @@ import os import shutil import sys +from nova.compute import task_states from nova import exception from nova.image import glance from nova.openstack.common import cfg @@ -45,7 +46,7 @@ class SnapshotOps(baseops.BaseOps): super(SnapshotOps, self).__init__() self._vmutils = vmutils.VMUtils() - def snapshot(self, context, instance, name): + def snapshot(self, context, instance, name, update_task_state): """Create snapshot from a running VM instance.""" instance_name = instance["name"] vm = self._vmutils.lookup(self._conn, instance_name) @@ -70,6 +71,8 @@ class SnapshotOps(baseops.BaseOps): raise vmutils.HyperVException( _('Failed to create snapshot for VM %s') % instance_name) + else: + update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD) export_folder = None f = None @@ -164,6 +167,8 @@ class SnapshotOps(baseops.BaseOps): _("Updating Glance image %(image_id)s with content from " "merged disk %(image_vhd_path)s"), locals()) + update_task_state(task_state=task_states.IMAGE_UPLOADING, + expected_state=task_states.IMAGE_PENDING_UPLOAD) glance_image_service.update(context, image_id, image_metadata, f) LOG.debug(_("Snapshot image %(image_id)s updated for VM " diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 7b338353da1a..9a30ae386ed7 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -57,6 +57,7 @@ from xml.dom import minidom from nova.api.metadata import base as instance_metadata from nova import block_device from nova.compute import power_state +from nova.compute import task_states from nova.compute import vm_mode from nova import context as nova_context from nova import exception @@ -736,7 +737,7 @@ class LibvirtDriver(driver.ComputeDriver): mount_device) @exception.wrap_exception() - def snapshot(self, context, instance, image_href): + def snapshot(self, context, instance, image_href, update_task_state): """Create snapshot from a running VM instance. This command only works with qemu 0.14+ @@ -804,6 +805,7 @@ class LibvirtDriver(driver.ComputeDriver): image_type=source_format) snapshot.create() + update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD) # Export the snapshot to a raw image snapshot_directory = CONF.libvirt_snapshots_directory @@ -821,6 +823,9 @@ class LibvirtDriver(driver.ComputeDriver): self._create_domain(domain=virt_dom) # Upload that image to the image service + + update_task_state(task_state=task_states.IMAGE_UPLOADING, + expected_state=task_states.IMAGE_PENDING_UPLOAD) with libvirt_utils.file_open(out_path) as image_file: image_service.update(context, image_href, diff --git a/nova/virt/vmwareapi/driver.py b/nova/virt/vmwareapi/driver.py index f6aa91e8500c..d34c690c804d 100644 --- a/nova/virt/vmwareapi/driver.py +++ b/nova/virt/vmwareapi/driver.py @@ -130,9 +130,9 @@ class VMWareESXDriver(driver.ComputeDriver): """Create VM instance.""" self._vmops.spawn(context, instance, image_meta, network_info) - def snapshot(self, context, instance, name): + def snapshot(self, context, instance, name, update_task_state): """Create snapshot from a running VM instance.""" - self._vmops.snapshot(context, instance, name) + self._vmops.snapshot(context, instance, name, update_task_state) def reboot(self, instance, network_info, reboot_type, block_device_info=None): diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index 97270fc06302..cb4cdd83696d 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -27,6 +27,7 @@ import urllib2 import uuid from nova.compute import power_state +from nova.compute import task_states from nova import exception from nova.openstack.common import cfg from nova.openstack.common import importutils @@ -338,7 +339,7 @@ class VMWareVMOps(object): LOG.debug(_("Powered on the VM instance"), instance=instance) _power_on_vm() - def snapshot(self, context, instance, snapshot_name): + def snapshot(self, context, instance, snapshot_name, update_task_state): """Create snapshot from a running VM instance. Steps followed are: @@ -395,6 +396,7 @@ class VMWareVMOps(object): instance=instance) _create_vm_snapshot() + update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD) def _check_if_tmp_folder_exists(): # Copy the contents of the VM that were there just before the @@ -473,6 +475,8 @@ class VMWareVMOps(object): LOG.debug(_("Uploaded image %s") % snapshot_name, instance=instance) + update_task_state(task_state=task_states.IMAGE_UPLOADING, + expected_state=task_states.IMAGE_PENDING_UPLOAD) _upload_vmdk_to_image_repository() def _clean_temp_data(): diff --git a/nova/virt/xenapi/driver.py b/nova/virt/xenapi/driver.py index 10bc99fef575..d3047d364142 100644 --- a/nova/virt/xenapi/driver.py +++ b/nova/virt/xenapi/driver.py @@ -188,9 +188,9 @@ class XenAPIDriver(driver.ComputeDriver): network_info, image_meta, resize_instance, block_device_info) - def snapshot(self, context, instance, image_id): + def snapshot(self, context, instance, image_id, update_task_state): """ Create snapshot from a running VM instance """ - self._vmops.snapshot(context, instance, image_id) + self._vmops.snapshot(context, instance, image_id, update_task_state) def reboot(self, instance, network_info, reboot_type, block_device_info=None): diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index ee36cea0bbb3..adb43a743059 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -36,6 +36,7 @@ from eventlet import greenthread from nova import block_device from nova.compute import power_state +from nova.compute import task_states from nova import exception from nova.image import glance from nova.openstack.common import cfg @@ -604,7 +605,11 @@ def get_vdi_for_vm_safely(session, vm_ref): @contextlib.contextmanager -def snapshot_attached_here(session, instance, vm_ref, label): +def snapshot_attached_here(session, instance, vm_ref, label, *args): + update_task_state = None + if len(args) == 1: + update_task_state = args[0] + """Snapshot the root disk only. Return a list of uuids for the vhds in the chain. """ @@ -616,6 +621,8 @@ def snapshot_attached_here(session, instance, vm_ref, label): sr_ref = vm_vdi_rec["SR"] snapshot_ref = session.call_xenapi("VDI.snapshot", vm_vdi_ref, {}) + if update_task_state is not None: + update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD) try: snapshot_rec = session.call_xenapi("VDI.get_record", snapshot_ref) _wait_for_vhd_coalesce(session, instance, sr_ref, vm_vdi_ref, diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index a96a90f0e11d..fbf3e0599efb 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -28,6 +28,7 @@ import netaddr from nova.compute import api as compute from nova.compute import power_state +from nova.compute import task_states from nova.compute import vm_mode from nova.compute import vm_states from nova import context as nova_context @@ -626,7 +627,7 @@ class VMOps(object): vm, "start") - def snapshot(self, context, instance, image_id): + def snapshot(self, context, instance, image_id, update_task_state): """Create snapshot from a running VM instance. :param context: request context @@ -654,7 +655,10 @@ class VMOps(object): label = "%s-snapshot" % instance['name'] with vm_utils.snapshot_attached_here( - self._session, instance, vm_ref, label) as vdi_uuids: + self._session, instance, vm_ref, label, + update_task_state) as vdi_uuids: + update_task_state(task_state=task_states.IMAGE_UPLOADING, + expected_state=task_states.IMAGE_PENDING_UPLOAD) vm_utils.upload_image( context, self._session, instance, vdi_uuids, image_id)