summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorBoris Filippov <bfilippov@griddynamics.com>2012-09-26 04:55:03 +0400
committerGerrit Code Review <review@openstack.org>2012-10-12 10:25:53 +0000
commit0df84e9bba8d5a77680cc65fd5488e9a5298f2cd (patch)
treec0383398ec144b6f78436594f1956b6c647e6587 /nova
parent4f3930632ca0cde0afa233d7345f8ddb19c4c37c (diff)
downloadnova-0df84e9bba8d5a77680cc65fd5488e9a5298f2cd.tar.gz
nova-0df84e9bba8d5a77680cc65fd5488e9a5298f2cd.tar.xz
nova-0df84e9bba8d5a77680cc65fd5488e9a5298f2cd.zip
Implement snapshots for raw backend
blueprint snapshots-for-everyone Use simple qemu-img convert for raw snapshots. Polymorphically select snapshot behavior for raw and qcow2. Allow images to be constructed from actual device/file path. Change-Id: I6a57e43c6775c144c41a53382dcc7504ce6d4c43
Diffstat (limited to 'nova')
-rw-r--r--nova/tests/fake_imagebackend.py8
-rw-r--r--nova/tests/fake_libvirt_utils.py6
-rw-r--r--nova/tests/test_libvirt.py11
-rw-r--r--nova/virt/libvirt/driver.py16
-rw-r--r--nova/virt/libvirt/imagebackend.py81
-rw-r--r--nova/virt/libvirt/snapshots.py89
-rw-r--r--nova/virt/libvirt/utils.py16
7 files changed, 199 insertions, 28 deletions
diff --git a/nova/tests/fake_imagebackend.py b/nova/tests/fake_imagebackend.py
index b6a052f2a..978c879fd 100644
--- a/nova/tests/fake_imagebackend.py
+++ b/nova/tests/fake_imagebackend.py
@@ -37,6 +37,9 @@ class Backend(object):
def cache(self, fetch_func, filename, size=None, *args, **kwargs):
pass
+ def snapshot(self, name):
+ pass
+
def libvirt_info(self, disk_bus, disk_dev, device_type,
cache_mode):
info = config.LibvirtConfigGuestDisk()
@@ -50,3 +53,8 @@ class Backend(object):
return info
return FakeImage(instance, name)
+
+ def snapshot(self, path, name, image_type=''):
+ #NOTE(bfilippov): this is done in favor for
+ # snapshot tests in test_libvirt.LibvirtConnTestCase
+ return imagebackend.Backend(True).snapshot(path, name, image_type)
diff --git a/nova/tests/fake_libvirt_utils.py b/nova/tests/fake_libvirt_utils.py
index 1862521c1..4f7c96d4f 100644
--- a/nova/tests/fake_libvirt_utils.py
+++ b/nova/tests/fake_libvirt_utils.py
@@ -104,7 +104,7 @@ def file_open(path, mode=None):
def find_disk(virt_dom):
- return "some/path"
+ return "filename"
def load_file(path):
@@ -115,6 +115,10 @@ def load_file(path):
return ''
+def logical_volume_info(path):
+ return {}
+
+
def file_delete(path):
return True
diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py
index 7af877f22..7de72266b 100644
--- a/nova/tests/test_libvirt.py
+++ b/nova/tests/test_libvirt.py
@@ -53,6 +53,7 @@ from nova.virt.libvirt import config
from nova.virt.libvirt import driver as libvirt_driver
from nova.virt.libvirt import firewall
from nova.virt.libvirt import imagebackend
+from nova.virt.libvirt import snapshots
from nova.virt.libvirt import utils as libvirt_utils
from nova.virt.libvirt import volume
from nova.virt.libvirt import volume_nfs
@@ -484,6 +485,7 @@ class LibvirtConnTestCase(test.TestCase):
self.flags(libvirt_snapshots_directory='')
self.call_libvirt_dependant_setup = False
libvirt_driver.libvirt_utils = fake_libvirt_utils
+ snapshots.libvirt_utils = fake_libvirt_utils
def fake_extend(image, size):
pass
@@ -532,7 +534,7 @@ class LibvirtConnTestCase(test.TestCase):
def fake_lookup(self, instance_name):
return FakeVirtDomain()
- def fake_execute(self, *args):
+ def fake_execute(self, *args, **kwargs):
open(args[-1], "a").close()
def create_service(self, **kwargs):
@@ -1129,6 +1131,7 @@ class LibvirtConnTestCase(test.TestCase):
libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup
self.mox.StubOutWithMock(libvirt_driver.utils, 'execute')
libvirt_driver.utils.execute = self.fake_execute
+ libvirt_driver.libvirt_utils.disk_type = "qcow2"
self.mox.ReplayAll()
@@ -1164,6 +1167,11 @@ class LibvirtConnTestCase(test.TestCase):
libvirt_driver.utils.execute = self.fake_execute
self.stubs.Set(libvirt_driver.libvirt_utils, 'disk_type', 'raw')
+ def convert_image(source, dest, out_format):
+ libvirt_driver.libvirt_utils.files[dest] = ''
+
+ images.convert_image = convert_image
+
self.mox.ReplayAll()
conn = libvirt_driver.LibvirtDriver(False)
@@ -1197,6 +1205,7 @@ class LibvirtConnTestCase(test.TestCase):
libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup
self.mox.StubOutWithMock(libvirt_driver.utils, 'execute')
libvirt_driver.utils.execute = self.fake_execute
+ libvirt_driver.libvirt_utils.disk_type = "qcow2"
self.mox.ReplayAll()
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index 50227ae6d..763a98e80 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -813,6 +813,10 @@ class LibvirtDriver(driver.ComputeDriver):
image_format = FLAGS.snapshot_image_format or source_format
+ # NOTE(bfilippov): save lvm as raw
+ if image_format == 'lvm':
+ image_format = 'raw'
+
# NOTE(vish): glance forces ami disk format to be ami
if base.get('disk_format') == 'ami':
metadata['disk_format'] = 'ami'
@@ -828,8 +832,12 @@ class LibvirtDriver(driver.ComputeDriver):
if state == power_state.RUNNING:
virt_dom.managedSave(0)
+
# Make the snapshot
- libvirt_utils.create_snapshot(disk_path, snapshot_name)
+ snapshot = self.image_backend.snapshot(disk_path, snapshot_name,
+ image_type=source_format)
+
+ snapshot.create()
# Export the snapshot to a raw image
snapshot_directory = FLAGS.libvirt_snapshots_directory
@@ -837,11 +845,9 @@ class LibvirtDriver(driver.ComputeDriver):
with utils.tempdir(dir=snapshot_directory) as tmpdir:
try:
out_path = os.path.join(tmpdir, snapshot_name)
- libvirt_utils.extract_snapshot(disk_path, source_format,
- snapshot_name, out_path,
- image_format)
+ snapshot.extract(out_path, image_format)
finally:
- libvirt_utils.delete_snapshot(disk_path, snapshot_name)
+ snapshot.delete()
if state == power_state.RUNNING:
self._create_domain(domain=virt_dom)
diff --git a/nova/virt/libvirt/imagebackend.py b/nova/virt/libvirt/imagebackend.py
index 0f2f044d7..040884e17 100644
--- a/nova/virt/libvirt/imagebackend.py
+++ b/nova/virt/libvirt/imagebackend.py
@@ -25,6 +25,7 @@ from nova.openstack.common import excutils
from nova import utils
from nova.virt.disk import api as disk
from nova.virt.libvirt import config
+from nova.virt.libvirt import snapshots
from nova.virt.libvirt import utils as libvirt_utils
__imagebackend_opts = [
@@ -125,13 +126,21 @@ class Image(object):
self.create_image(call_if_not_exists, base, size,
*args, **kwargs)
+ @abc.abstractmethod
+ def snapshot(self, name):
+ """Create snapshot object for this image
+
+ :name: snapshot name
+ """
+ pass
+
class Raw(Image):
- def __init__(self, instance, name):
+ def __init__(self, instance=None, name=None, path=None):
super(Raw, self).__init__("file", "raw", is_block_dev=False)
- self.path = os.path.join(FLAGS.instances_path,
- instance, name)
+ self.path = path or os.path.join(FLAGS.instances_path,
+ instance, name)
def create_image(self, prepare_template, base, size, *args, **kwargs):
@utils.synchronized(base, external=True, lock_path=self.lock_path)
@@ -149,13 +158,16 @@ class Raw(Image):
with utils.remove_path_on_error(self.path):
copy_raw_image(base, self.path, size)
+ def snapshot(self, name):
+ return snapshots.RawSnapshot(self.path, name)
+
class Qcow2(Image):
- def __init__(self, instance, name):
+ def __init__(self, instance=None, name=None, path=None):
super(Qcow2, self).__init__("file", "qcow2", is_block_dev=False)
- self.path = os.path.join(FLAGS.instances_path,
- instance, name)
+ self.path = path or os.path.join(FLAGS.instances_path,
+ instance, name)
def create_image(self, prepare_template, base, size, *args, **kwargs):
@utils.synchronized(base, external=True, lock_path=self.lock_path)
@@ -174,23 +186,33 @@ class Qcow2(Image):
with utils.remove_path_on_error(self.path):
copy_qcow2_image(base, self.path, size)
+ def snapshot(self, name):
+ return snapshots.Qcow2Snapshot(self.path, name)
+
class Lvm(Image):
@staticmethod
def escape(filename):
return filename.replace('_', '__')
- def __init__(self, instance, name):
+ def __init__(self, instance=None, name=None, path=None):
super(Lvm, self).__init__("block", "raw", is_block_dev=True)
- if not FLAGS.libvirt_images_volume_group:
- raise RuntimeError(_('You should specify'
- ' libvirt_images_volume_group'
- ' flag to use LVM images.'))
- self.vg = FLAGS.libvirt_images_volume_group
- self.lv = '%s_%s' % (self.escape(instance),
- self.escape(name))
- self.path = os.path.join('/dev', self.vg, self.lv)
+ if path:
+ info = libvirt_utils.logical_volume_info(path)
+ self.vg = info['VG']
+ self.lv = info['LV']
+ self.path = path
+ else:
+ if not FLAGS.libvirt_images_volume_group:
+ raise RuntimeError(_('You should specify'
+ ' libvirt_images_volume_group'
+ ' flag to use LVM images.'))
+ self.vg = FLAGS.libvirt_images_volume_group
+ self.lv = '%s_%s' % (self.escape(instance),
+ self.escape(name))
+ self.path = os.path.join('/dev', self.vg, self.lv)
+
self.sparse = FLAGS.libvirt_sparse_logical_volumes
def create_image(self, prepare_template, base, size, *args, **kwargs):
@@ -227,6 +249,9 @@ class Lvm(Image):
with excutils.save_and_reraise_exception():
libvirt_utils.remove_logical_volumes(path)
+ def snapshot(self, name):
+ return snapshots.LvmSnapshot(self.path, name)
+
class Backend(object):
def __init__(self, use_cow):
@@ -237,6 +262,14 @@ class Backend(object):
'default': Qcow2 if use_cow else Raw
}
+ def backend(self, image_type=None):
+ if not image_type:
+ image_type = FLAGS.libvirt_images_type
+ image = self.BACKEND.get(image_type)
+ if not image:
+ raise RuntimeError(_('Unknown image_type=%s') % image_type)
+ return image
+
def image(self, instance, name, image_type=None):
"""Constructs image for selected backend
@@ -245,9 +278,15 @@ class Backend(object):
:image_type: Image type.
Optional, is FLAGS.libvirt_images_type by default.
"""
- if not image_type:
- image_type = FLAGS.libvirt_images_type
- image = self.BACKEND.get(image_type)
- if not image:
- raise RuntimeError(_('Unknown image_type=%s') % image_type)
- return image(instance, name)
+ backend = self.backend(image_type)
+ return backend(instance=instance, name=name)
+
+ def snapshot(self, path, snapshot_name, image_type=None):
+ """Returns snapshot for given image
+
+ :path: path to image
+ :snapshot_name: snapshot name
+ :image_type: type of image
+ """
+ backend = self.backend(image_type)
+ return backend(path=path).snapshot(snapshot_name)
diff --git a/nova/virt/libvirt/snapshots.py b/nova/virt/libvirt/snapshots.py
new file mode 100644
index 000000000..37933876d
--- /dev/null
+++ b/nova/virt/libvirt/snapshots.py
@@ -0,0 +1,89 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Grid Dynamics
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import abc
+
+from nova.virt import images
+from nova.virt.libvirt import utils as libvirt_utils
+
+
+class Snapshot(object):
+ @abc.abstractmethod
+ def create(self):
+ """Create new snapshot"""
+ pass
+
+ @abc.abstractmethod
+ def extract(self, target, out_format):
+ """Extract snapshot content to file
+
+ :target: path to extraction
+ :out_format: format of extraction (raw, qcow2, ...)
+ """
+ pass
+
+ @abc.abstractmethod
+ def delete(self):
+ """Delete snapshot"""
+ pass
+
+
+class RawSnapshot(object):
+ def __init__(self, path, name):
+ self.path = path
+ self.name = name
+
+ def create(self):
+ pass
+
+ def extract(self, target, out_format):
+ images.convert_image(self.path, target, out_format)
+
+ def delete(self):
+ pass
+
+
+class Qcow2Snapshot(object):
+ def __init__(self, path, name):
+ self.path = path
+ self.name = name
+
+ def create(self):
+ libvirt_utils.create_snapshot(self.path, self.name)
+
+ def extract(self, target, out_format):
+ libvirt_utils.extract_snapshot(self.path, 'qcow2',
+ self.name, target,
+ out_format)
+
+ def delete(self):
+ libvirt_utils.delete_snapshot(self.path, self.name)
+
+
+class LvmSnapshot(object):
+ def __init__(self, path, name):
+ self.path = path
+ self.name = name
+
+ def create(self):
+ raise NotImplementedError(_("LVM snapshots not implemented"))
+
+ def extract(self, target, out_format):
+ raise NotImplementedError(_("LVM snapshots not implemented"))
+
+ def delete(self):
+ raise NotImplementedError(_("LVM snapshots not implemented"))
diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py
index 2d1b5558c..17c9efdda 100644
--- a/nova/virt/libvirt/utils.py
+++ b/nova/virt/libvirt/utils.py
@@ -172,6 +172,22 @@ def list_logical_volumes(vg):
return [line.strip() for line in out.splitlines()]
+def logical_volume_info(path):
+ """Get logical volume info.
+
+ :param path: logical volume path
+ """
+ out, err = execute('lvs', '-o', 'vg_all,lv_all',
+ '--separator', '|', path, run_as_root=True)
+
+ info = [line.split('|') for line in out.splitlines()]
+
+ if len(info) != 2:
+ raise RuntimeError(_("Path %s must be LVM logical volume") % path)
+
+ return dict(zip(*info))
+
+
def remove_logical_volumes(*paths):
"""Remove one or more logical volume."""
if paths: