diff options
| author | Boris Filippov <bfilippov@griddynamics.com> | 2012-09-26 04:55:03 +0400 |
|---|---|---|
| committer | Gerrit Code Review <review@openstack.org> | 2012-10-12 10:25:53 +0000 |
| commit | 0df84e9bba8d5a77680cc65fd5488e9a5298f2cd (patch) | |
| tree | c0383398ec144b6f78436594f1956b6c647e6587 /nova/virt | |
| parent | 4f3930632ca0cde0afa233d7345f8ddb19c4c37c (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.py | 16 | ||||
| -rw-r--r-- | nova/virt/libvirt/imagebackend.py | 81 | ||||
| -rw-r--r-- | nova/virt/libvirt/snapshots.py | 89 | ||||
| -rw-r--r-- | nova/virt/libvirt/utils.py | 16 |
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: |
