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
This commit is contained in:
Andrew Melton
2012-11-12 13:19:19 -05:00
parent 80325d6897
commit 0df60e9879
18 changed files with 268 additions and 35 deletions

View File

@@ -1388,16 +1388,21 @@ class ComputeManager(manager.SchedulerDependentManager):
self._notify_about_instance_usage( self._notify_about_instance_usage(
context, instance, "snapshot.start") context, instance, "snapshot.start")
self.driver.snapshot(context, instance, image_id)
if image_type == 'snapshot': if image_type == 'snapshot':
expected_task_state = task_states.IMAGE_SNAPSHOT expected_task_state = task_states.IMAGE_SNAPSHOT
elif image_type == 'backup': elif image_type == 'backup':
expected_task_state = task_states.IMAGE_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, 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: if image_type == 'snapshot' and rotation:
raise exception.ImageRotationNotAllowed() raise exception.ImageRotationNotAllowed()

View File

@@ -33,6 +33,8 @@ SPAWNING = 'spawning'
# possible task states during snapshot() # possible task states during snapshot()
IMAGE_SNAPSHOT = 'image_snapshot' IMAGE_SNAPSHOT = 'image_snapshot'
IMAGE_PENDING_UPLOAD = 'image_pending_upload'
IMAGE_UPLOADING = 'image_uploading'
# possible task states during backup() # possible task states during backup()
IMAGE_BACKUP = 'image_backup' IMAGE_BACKUP = 'image_backup'

View File

@@ -198,6 +198,21 @@ class IsSubDictOf(object):
return SubDictMismatch(k, sub_value, super_value) 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): class XMLMismatch(object):
"""Superclass for XML mismatch.""" """Superclass for XML mismatch."""

View File

@@ -26,6 +26,7 @@ import sys
import uuid import uuid
from nova.compute import power_state from nova.compute import power_state
from nova.compute import task_states
from nova import context from nova import context
from nova import db from nova import db
from nova.image import glance 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 hypervutils
from nova.tests.hyperv import mockproxy from nova.tests.hyperv import mockproxy
import nova.tests.image.fake as fake_image import nova.tests.image.fake as fake_image
from nova.tests import matchers
from nova.virt.hyperv import constants from nova.virt.hyperv import constants
from nova.virt.hyperv import driver as driver_hyperv from nova.virt.hyperv import driver as driver_hyperv
from nova.virt.hyperv import vmutils from nova.virt.hyperv import vmutils
@@ -407,27 +409,55 @@ class HyperVAPITestCase(basetestcase.BaseTestCase):
self.assertTrue(self._fetched_image is None) self.assertTrue(self._fetched_image is None)
def test_snapshot_with_update_failure(self): 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._spawn_instance(True)
self._update_image_raise_exception = True self._update_image_raise_exception = True
snapshot_name = 'test_snapshot_' + str(uuid.uuid4()) snapshot_name = 'test_snapshot_' + str(uuid.uuid4())
self.assertRaises(vmutils.HyperVException, self._conn.snapshot, 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 # assert VM snapshots have been removed
self.assertEquals(self._hypervutils.get_vm_snapshots_count( self.assertEquals(self._hypervutils.get_vm_snapshots_count(
self._instance_data["name"]), 0) self._instance_data["name"]), 0)
def test_snapshot(self): 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) self._spawn_instance(True)
snapshot_name = 'test_snapshot_' + str(uuid.uuid4()) 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 self.assertTrue(self._image_metadata and
"disk_format" in self._image_metadata and "disk_format" in self._image_metadata and
self._image_metadata["disk_format"] == "vhd") self._image_metadata["disk_format"] == "vhd")
# assert states changed in correct order
self.assertIsNone(func_call_matcher.match())
# assert VM snapshots have been removed # assert VM snapshots have been removed
self.assertEquals(self._hypervutils.get_vm_snapshots_count( self.assertEquals(self._hypervutils.get_vm_snapshots_count(
self._instance_data["name"]), 0) self._instance_data["name"]), 0)

View File

@@ -32,6 +32,7 @@ from xml.dom import minidom
from nova.api.ec2 import cloud from nova.api.ec2 import cloud
from nova.compute import instance_types from nova.compute import instance_types
from nova.compute import power_state from nova.compute import power_state
from nova.compute import task_states
from nova.compute import vm_mode from nova.compute import vm_mode
from nova.compute import vm_states from nova.compute import vm_states
from nova import context from nova import context
@@ -1209,6 +1210,16 @@ class LibvirtConnTestCase(test.TestCase):
self.assertEqual(devices, ['vda', 'vdb']) self.assertEqual(devices, ['vda', 'vdb'])
def test_snapshot_in_ami_format(self): 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='./') self.flags(libvirt_snapshots_directory='./')
# Start test # Start test
@@ -1238,15 +1249,28 @@ class LibvirtConnTestCase(test.TestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) 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']) 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['properties']['image_state'], 'available')
self.assertEquals(snapshot['status'], 'active') self.assertEquals(snapshot['status'], 'active')
self.assertEquals(snapshot['disk_format'], 'ami') self.assertEquals(snapshot['disk_format'], 'ami')
self.assertEquals(snapshot['name'], snapshot_name) self.assertEquals(snapshot['name'], snapshot_name)
def test_lxc_snapshot_in_ami_format(self): 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='./', self.flags(libvirt_snapshots_directory='./',
libvirt_type='lxc') libvirt_type='lxc')
@@ -1277,15 +1301,27 @@ class LibvirtConnTestCase(test.TestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) 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']) 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['status'], 'active')
self.assertEquals(snapshot['disk_format'], 'ami') self.assertEquals(snapshot['disk_format'], 'ami')
self.assertEquals(snapshot['name'], snapshot_name) self.assertEquals(snapshot['name'], snapshot_name)
def test_snapshot_in_raw_format(self): 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='./') self.flags(libvirt_snapshots_directory='./')
# Start test # Start test
@@ -1316,15 +1352,27 @@ class LibvirtConnTestCase(test.TestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) 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']) 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['status'], 'active')
self.assertEquals(snapshot['disk_format'], 'raw') self.assertEquals(snapshot['disk_format'], 'raw')
self.assertEquals(snapshot['name'], snapshot_name) self.assertEquals(snapshot['name'], snapshot_name)
def test_lxc_snapshot_in_raw_format(self): 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='./', self.flags(libvirt_snapshots_directory='./',
libvirt_type='lxc') libvirt_type='lxc')
@@ -1356,15 +1404,27 @@ class LibvirtConnTestCase(test.TestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) 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']) 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['status'], 'active')
self.assertEquals(snapshot['disk_format'], 'raw') self.assertEquals(snapshot['disk_format'], 'raw')
self.assertEquals(snapshot['name'], snapshot_name) self.assertEquals(snapshot['name'], snapshot_name)
def test_snapshot_in_qcow2_format(self): 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', self.flags(snapshot_image_format='qcow2',
libvirt_snapshots_directory='./') libvirt_snapshots_directory='./')
@@ -1391,15 +1451,27 @@ class LibvirtConnTestCase(test.TestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) 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']) 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['status'], 'active')
self.assertEquals(snapshot['disk_format'], 'qcow2') self.assertEquals(snapshot['disk_format'], 'qcow2')
self.assertEquals(snapshot['name'], snapshot_name) self.assertEquals(snapshot['name'], snapshot_name)
def test_lxc_snapshot_in_qcow2_format(self): 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', self.flags(snapshot_image_format='qcow2',
libvirt_snapshots_directory='./', libvirt_snapshots_directory='./',
libvirt_type='lxc') libvirt_type='lxc')
@@ -1427,15 +1499,27 @@ class LibvirtConnTestCase(test.TestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) 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']) 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['status'], 'active')
self.assertEquals(snapshot['disk_format'], 'qcow2') self.assertEquals(snapshot['disk_format'], 'qcow2')
self.assertEquals(snapshot['name'], snapshot_name) self.assertEquals(snapshot['name'], snapshot_name)
def test_snapshot_no_image_architecture(self): 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='./') self.flags(libvirt_snapshots_directory='./')
# Start test # Start test
@@ -1465,14 +1549,26 @@ class LibvirtConnTestCase(test.TestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) 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']) 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['status'], 'active')
self.assertEquals(snapshot['name'], snapshot_name) self.assertEquals(snapshot['name'], snapshot_name)
def test_lxc_snapshot_no_image_architecture(self): 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='./', self.flags(libvirt_snapshots_directory='./',
libvirt_type='lxc') libvirt_type='lxc')
@@ -1503,14 +1599,26 @@ class LibvirtConnTestCase(test.TestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) 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']) 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['status'], 'active')
self.assertEquals(snapshot['name'], snapshot_name) self.assertEquals(snapshot['name'], snapshot_name)
def test_snapshot_no_original_image(self): 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='./') self.flags(libvirt_snapshots_directory='./')
# Start test # Start test
@@ -1536,14 +1644,26 @@ class LibvirtConnTestCase(test.TestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) 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']) 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['status'], 'active')
self.assertEquals(snapshot['name'], snapshot_name) self.assertEquals(snapshot['name'], snapshot_name)
def test_lxc_snapshot_no_original_image(self): 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='./', self.flags(libvirt_snapshots_directory='./',
libvirt_type='lxc') libvirt_type='lxc')
@@ -1570,9 +1690,11 @@ class LibvirtConnTestCase(test.TestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
conn = libvirt_driver.LibvirtDriver(False) 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']) 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['status'], 'active')
self.assertEquals(snapshot['name'], snapshot_name) self.assertEquals(snapshot['name'], snapshot_name)

View File

@@ -215,13 +215,15 @@ class _VirtDriverTestCase(_FakeDriverBackendTestCase):
img_ref = self.image_service.create(self.ctxt, {'name': 'snap-1'}) img_ref = self.image_service.create(self.ctxt, {'name': 'snap-1'})
self.assertRaises(exception.InstanceNotRunning, self.assertRaises(exception.InstanceNotRunning,
self.connection.snapshot, self.connection.snapshot,
self.ctxt, instance_ref, img_ref['id']) self.ctxt, instance_ref, img_ref['id'],
lambda *args, **kwargs: None)
@catch_notimplementederror @catch_notimplementederror
def test_snapshot_running(self): def test_snapshot_running(self):
img_ref = self.image_service.create(self.ctxt, {'name': 'snap-1'}) img_ref = self.image_service.create(self.ctxt, {'name': 'snap-1'})
instance_ref, network_info = self._get_running_instance() 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 @catch_notimplementederror
def test_reboot(self): def test_reboot(self):

View File

@@ -20,11 +20,13 @@ Test suite for VMWareAPI.
""" """
from nova.compute import power_state from nova.compute import power_state
from nova.compute import task_states
from nova import context from nova import context
from nova import db from nova import db
from nova import exception from nova import exception
from nova import test from nova import test
import nova.tests.image.fake import nova.tests.image.fake
from nova.tests import matchers
from nova.tests.vmwareapi import db_fakes from nova.tests.vmwareapi import db_fakes
from nova.tests.vmwareapi import stubs from nova.tests.vmwareapi import stubs
from nova.virt.vmwareapi import driver from nova.virt.vmwareapi import driver
@@ -159,17 +161,29 @@ class VMWareAPIVMTestCase(test.TestCase):
self._check_vm_info(info, power_state.RUNNING) self._check_vm_info(info, power_state.RUNNING)
def test_snapshot(self): 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() self._create_vm()
info = self.conn.get_info({'name': 1}) info = self.conn.get_info({'name': 1})
self._check_vm_info(info, power_state.RUNNING) 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}) info = self.conn.get_info({'name': 1})
self._check_vm_info(info, power_state.RUNNING) self._check_vm_info(info, power_state.RUNNING)
self.assertIsNone(func_call_matcher.match())
def test_snapshot_non_existent(self): def test_snapshot_non_existent(self):
self._create_instance_in_the_db() self._create_instance_in_the_db()
self.assertRaises(exception.InstanceNotFound, self.conn.snapshot, 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): def test_reboot(self):
self._create_vm() self._create_vm()

View File

@@ -397,6 +397,7 @@ class XenAPIVMTestCase(stubs.XenAPITestBase):
self.assertThat(fake_diagnostics, matchers.DictMatches(expected)) self.assertThat(fake_diagnostics, matchers.DictMatches(expected))
def test_instance_snapshot_fails_with_no_primary_vdi(self): def test_instance_snapshot_fails_with_no_primary_vdi(self):
def create_bad_vbd(session, vm_ref, vdi_ref, userdevice, def create_bad_vbd(session, vm_ref, vdi_ref, userdevice,
vbd_type='disk', read_only=False, bootable=False, vbd_type='disk', read_only=False, bootable=False,
osvol=False): osvol=False):
@@ -417,9 +418,20 @@ class XenAPIVMTestCase(stubs.XenAPITestBase):
image_id = "my_snapshot_id" image_id = "my_snapshot_id"
self.assertRaises(exception.NovaException, self.conn.snapshot, 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): 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_instance_snapshot(self.stubs)
stubs.stubout_is_snapshot(self.stubs) stubs.stubout_is_snapshot(self.stubs)
# Stubbing out firewall driver as previous stub sets alters # Stubbing out firewall driver as previous stub sets alters
@@ -428,7 +440,8 @@ class XenAPIVMTestCase(stubs.XenAPITestBase):
instance = self._create_instance() instance = self._create_instance()
image_id = "my_snapshot_id" 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 # Ensure VM was torn down
vm_labels = [] vm_labels = []
@@ -447,6 +460,9 @@ class XenAPIVMTestCase(stubs.XenAPITestBase):
self.assertEquals(vbd_labels, [instance['name']]) self.assertEquals(vbd_labels, [instance['name']])
# Ensure task states changed in correct order
self.assertIsNone(func_call_matcher.match())
# Ensure VDIs were torn down # Ensure VDIs were torn down
for vdi_ref in xenapi_fake.get_all('VDI'): for vdi_ref in xenapi_fake.get_all('VDI'):
vdi_rec = xenapi_fake.get_record('VDI', vdi_ref) vdi_rec = xenapi_fake.get_record('VDI', vdi_ref)

View File

@@ -280,7 +280,7 @@ class ComputeDriver(object):
""" """
raise NotImplementedError() raise NotImplementedError()
def snapshot(self, context, instance, image_id): def snapshot(self, context, instance, image_id, update_task_state):
""" """
Snapshots the specified instance. Snapshots the specified instance.

View File

@@ -26,6 +26,7 @@ semantics of real hypervisor connections.
""" """
from nova.compute import power_state from nova.compute import power_state
from nova.compute import task_states
from nova import db from nova import db
from nova import exception from nova import exception
from nova.openstack.common import log as logging from nova.openstack.common import log as logging
@@ -122,9 +123,10 @@ class FakeDriver(driver.ComputeDriver):
fake_instance = FakeInstance(name, state) fake_instance = FakeInstance(name, state)
self.instances[name] = fake_instance 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: if not instance['name'] in self.instances:
raise exception.InstanceNotRunning(instance_id=instance['uuid']) raise exception.InstanceNotRunning(instance_id=instance['uuid'])
update_task_state(task_state=task_states.IMAGE_UPLOADING)
def reboot(self, instance, network_info, reboot_type, def reboot(self, instance, network_info, reboot_type,
block_device_info=None): block_device_info=None):

View File

@@ -128,8 +128,8 @@ class HyperVDriver(driver.ComputeDriver):
def host_power_action(self, host, action): def host_power_action(self, host, action):
return self._hostops.host_power_action(host, action) return self._hostops.host_power_action(host, action)
def snapshot(self, context, instance, name): def snapshot(self, context, instance, name, update_task_state):
self._snapshotops.snapshot(context, instance, name) self._snapshotops.snapshot(context, instance, name, update_task_state)
def pause(self, instance): def pause(self, instance):
self._vmops.pause(instance) self._vmops.pause(instance)

View File

@@ -22,6 +22,7 @@ import os
import shutil import shutil
import sys import sys
from nova.compute import task_states
from nova import exception from nova import exception
from nova.image import glance from nova.image import glance
from nova.openstack.common import cfg from nova.openstack.common import cfg
@@ -45,7 +46,7 @@ class SnapshotOps(baseops.BaseOps):
super(SnapshotOps, self).__init__() super(SnapshotOps, self).__init__()
self._vmutils = vmutils.VMUtils() 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.""" """Create snapshot from a running VM instance."""
instance_name = instance["name"] instance_name = instance["name"]
vm = self._vmutils.lookup(self._conn, instance_name) vm = self._vmutils.lookup(self._conn, instance_name)
@@ -70,6 +71,8 @@ class SnapshotOps(baseops.BaseOps):
raise vmutils.HyperVException( raise vmutils.HyperVException(
_('Failed to create snapshot for VM %s') % _('Failed to create snapshot for VM %s') %
instance_name) instance_name)
else:
update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD)
export_folder = None export_folder = None
f = None f = None
@@ -164,6 +167,8 @@ class SnapshotOps(baseops.BaseOps):
_("Updating Glance image %(image_id)s with content from " _("Updating Glance image %(image_id)s with content from "
"merged disk %(image_vhd_path)s"), "merged disk %(image_vhd_path)s"),
locals()) 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) glance_image_service.update(context, image_id, image_metadata, f)
LOG.debug(_("Snapshot image %(image_id)s updated for VM " LOG.debug(_("Snapshot image %(image_id)s updated for VM "

View File

@@ -57,6 +57,7 @@ from xml.dom import minidom
from nova.api.metadata import base as instance_metadata from nova.api.metadata import base as instance_metadata
from nova import block_device from nova import block_device
from nova.compute import power_state from nova.compute import power_state
from nova.compute import task_states
from nova.compute import vm_mode from nova.compute import vm_mode
from nova import context as nova_context from nova import context as nova_context
from nova import exception from nova import exception
@@ -736,7 +737,7 @@ class LibvirtDriver(driver.ComputeDriver):
mount_device) mount_device)
@exception.wrap_exception() @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. """Create snapshot from a running VM instance.
This command only works with qemu 0.14+ This command only works with qemu 0.14+
@@ -804,6 +805,7 @@ class LibvirtDriver(driver.ComputeDriver):
image_type=source_format) image_type=source_format)
snapshot.create() snapshot.create()
update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD)
# Export the snapshot to a raw image # Export the snapshot to a raw image
snapshot_directory = CONF.libvirt_snapshots_directory snapshot_directory = CONF.libvirt_snapshots_directory
@@ -821,6 +823,9 @@ class LibvirtDriver(driver.ComputeDriver):
self._create_domain(domain=virt_dom) self._create_domain(domain=virt_dom)
# Upload that image to the image service # 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: with libvirt_utils.file_open(out_path) as image_file:
image_service.update(context, image_service.update(context,
image_href, image_href,

View File

@@ -130,9 +130,9 @@ class VMWareESXDriver(driver.ComputeDriver):
"""Create VM instance.""" """Create VM instance."""
self._vmops.spawn(context, instance, image_meta, network_info) 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.""" """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, def reboot(self, instance, network_info, reboot_type,
block_device_info=None): block_device_info=None):

View File

@@ -27,6 +27,7 @@ import urllib2
import uuid import uuid
from nova.compute import power_state from nova.compute import power_state
from nova.compute import task_states
from nova import exception from nova import exception
from nova.openstack.common import cfg from nova.openstack.common import cfg
from nova.openstack.common import importutils from nova.openstack.common import importutils
@@ -338,7 +339,7 @@ class VMWareVMOps(object):
LOG.debug(_("Powered on the VM instance"), instance=instance) LOG.debug(_("Powered on the VM instance"), instance=instance)
_power_on_vm() _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. """Create snapshot from a running VM instance.
Steps followed are: Steps followed are:
@@ -395,6 +396,7 @@ class VMWareVMOps(object):
instance=instance) instance=instance)
_create_vm_snapshot() _create_vm_snapshot()
update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD)
def _check_if_tmp_folder_exists(): def _check_if_tmp_folder_exists():
# Copy the contents of the VM that were there just before the # 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, LOG.debug(_("Uploaded image %s") % snapshot_name,
instance=instance) instance=instance)
update_task_state(task_state=task_states.IMAGE_UPLOADING,
expected_state=task_states.IMAGE_PENDING_UPLOAD)
_upload_vmdk_to_image_repository() _upload_vmdk_to_image_repository()
def _clean_temp_data(): def _clean_temp_data():

View File

@@ -188,9 +188,9 @@ class XenAPIDriver(driver.ComputeDriver):
network_info, image_meta, resize_instance, network_info, image_meta, resize_instance,
block_device_info) 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 """ """ 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, def reboot(self, instance, network_info, reboot_type,
block_device_info=None): block_device_info=None):

View File

@@ -36,6 +36,7 @@ from eventlet import greenthread
from nova import block_device from nova import block_device
from nova.compute import power_state from nova.compute import power_state
from nova.compute import task_states
from nova import exception from nova import exception
from nova.image import glance from nova.image import glance
from nova.openstack.common import cfg from nova.openstack.common import cfg
@@ -604,7 +605,11 @@ def get_vdi_for_vm_safely(session, vm_ref):
@contextlib.contextmanager @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 """Snapshot the root disk only. Return a list of uuids for the vhds
in the chain. in the chain.
""" """
@@ -616,6 +621,8 @@ def snapshot_attached_here(session, instance, vm_ref, label):
sr_ref = vm_vdi_rec["SR"] sr_ref = vm_vdi_rec["SR"]
snapshot_ref = session.call_xenapi("VDI.snapshot", vm_vdi_ref, {}) 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: try:
snapshot_rec = session.call_xenapi("VDI.get_record", snapshot_ref) snapshot_rec = session.call_xenapi("VDI.get_record", snapshot_ref)
_wait_for_vhd_coalesce(session, instance, sr_ref, vm_vdi_ref, _wait_for_vhd_coalesce(session, instance, sr_ref, vm_vdi_ref,

View File

@@ -28,6 +28,7 @@ import netaddr
from nova.compute import api as compute from nova.compute import api as compute
from nova.compute import power_state from nova.compute import power_state
from nova.compute import task_states
from nova.compute import vm_mode from nova.compute import vm_mode
from nova.compute import vm_states from nova.compute import vm_states
from nova import context as nova_context from nova import context as nova_context
@@ -626,7 +627,7 @@ class VMOps(object):
vm, vm,
"start") "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. """Create snapshot from a running VM instance.
:param context: request context :param context: request context
@@ -654,7 +655,10 @@ class VMOps(object):
label = "%s-snapshot" % instance['name'] label = "%s-snapshot" % instance['name']
with vm_utils.snapshot_attached_here( 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( vm_utils.upload_image(
context, self._session, instance, vdi_uuids, image_id) context, self._session, instance, vdi_uuids, image_id)