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:
parent
80325d6897
commit
0df60e9879
@ -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()
|
||||
|
@ -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'
|
||||
|
@ -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."""
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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 "
|
||||
|
@ -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,
|
||||
|
@ -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):
|
||||
|
@ -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():
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user