summaryrefslogtreecommitdiffstats
path: root/nova/virt
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/virt
parent4f3930632ca0cde0afa233d7345f8ddb19c4c37c (diff)
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/virt')
-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
4 files changed, 176 insertions, 26 deletions
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: