summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Melton <andrew.melton@rackspace.com>2012-11-12 13:19:19 -0500
committerAndrew Melton <andrew.melton@rackspace.com>2013-01-04 14:17:12 -0500
commit0df60e98790c722aef59d0015c209ea0944e62c0 (patch)
treebfec8852d3e828dce8c2615f964cd90c51312d7f
parent80325d6897e9aadd0287e5e4e3fc3ada03448dac (diff)
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
-rw-r--r--nova/compute/manager.py11
-rw-r--r--nova/compute/task_states.py2
-rw-r--r--nova/tests/matchers.py15
-rw-r--r--nova/tests/test_hypervapi.py34
-rw-r--r--nova/tests/test_libvirt.py142
-rw-r--r--nova/tests/test_virt_drivers.py6
-rw-r--r--nova/tests/test_vmwareapi.py18
-rw-r--r--nova/tests/test_xenapi.py20
-rw-r--r--nova/virt/driver.py2
-rw-r--r--nova/virt/fake.py4
-rw-r--r--nova/virt/hyperv/driver.py4
-rw-r--r--nova/virt/hyperv/snapshotops.py7
-rw-r--r--nova/virt/libvirt/driver.py7
-rw-r--r--nova/virt/vmwareapi/driver.py4
-rw-r--r--nova/virt/vmwareapi/vmops.py6
-rw-r--r--nova/virt/xenapi/driver.py4
-rw-r--r--nova/virt/xenapi/vm_utils.py9
-rw-r--r--nova/virt/xenapi/vmops.py8
18 files changed, 268 insertions, 35 deletions
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 489deef42..1a9fef448 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 c2966d554..8e2b8344a 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 a421cc056..be65da823 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 eae3c0151..cab877da9 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 fc8e01728..3943e8c46 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 b0e25d095..9d9ebcad9 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 3a404a122..86b3a5730 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 3ca69dc4c..8b57dfef4 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 9291ac6f8..7d627e80c 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 c9cd41680..5d3b3c926 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 4359b1007..2b57ba0b1 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 5dc19ebb1..cdc6e45a4 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 7b338353d..9a30ae386 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 f6aa91e85..d34c690c8 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 97270fc06..cb4cdd836 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 10bc99fef..d3047d364 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 ee36cea0b..adb43a743 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 a96a90f0e..fbf3e0599 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)