From: Edward Hope-Morley Date: Wed, 26 Jun 2013 09:25:11 +0000 (+0100) Subject: Added volume backup and restore to Ceph RBD driver X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=756a8d75f1d8696f0796e4730877298be00d2b5f;p=openstack-build%2Fcinder-build.git Added volume backup and restore to Ceph RBD driver It is now possible to backup and restore volumes when using volume.drivers.rbd.RBDDriver Implements: blueprint cinder-backup-to-ceph Change-Id: Ic1b8db8f0acd7974423414171b8fb45197d05dc6 --- diff --git a/cinder/backup/services/swift.py b/cinder/backup/services/swift.py index 4b3ed22a1..31c44e979 100644 --- a/cinder/backup/services/swift.py +++ b/cinder/backup/services/swift.py @@ -381,7 +381,16 @@ class SwiftBackupService(base.Base): # force flush every write to avoid long blocking write on close volume_file.flush() - os.fsync(volume_file.fileno()) + + # Be tolerant to IO implementations that do not support fileno() + try: + fileno = volume_file.fileno() + except IOError: + LOG.info("volume_file does not support fileno() so skipping " + "fsync()") + else: + os.fsync(fileno) + # Restoring a backup to a volume can take some time. Yield so other # threads can run, allowing for among other things the service # status to be updated diff --git a/cinder/volume/drivers/rbd.py b/cinder/volume/drivers/rbd.py index d8dbd3895..533d58441 100644 --- a/cinder/volume/drivers/rbd.py +++ b/cinder/volume/drivers/rbd.py @@ -18,6 +18,7 @@ RADOS Block Device Driver from __future__ import absolute_import +import io import json import os import tempfile @@ -27,10 +28,13 @@ from oslo.config import cfg from cinder import exception from cinder.image import image_utils +from cinder import utils + from cinder.openstack.common import fileutils from cinder.openstack.common import log as logging from cinder.volume import driver + try: import rados import rbd @@ -80,6 +84,82 @@ def ascii_str(string): return str(string) +class RBDImageIOWrapper(io.RawIOBase): + """ + Wrapper to provide standard Python IO interface to RBD images so that they + can be treated as files. + """ + + def __init__(self, rbd_image): + super(RBDImageIOWrapper, self).__init__() + self.rbd_image = rbd_image + self._offset = 0 + + def _inc_offset(self, length): + self._offset += length + + def read(self, length=None): + offset = self._offset + total = self.rbd_image.size() + + # (dosaboy): posix files do not barf if you read beyond their length + # (they just return nothing) but rbd images do so we need to return + # empty string if we are at the end of the image + if (offset == total): + return '' + + if length is None: + length = total + + if (offset + length) > total: + length = total - offset + + self._inc_offset(length) + return self.rbd_image.read(int(offset), int(length)) + + def write(self, data): + self.rbd_image.write(data, self._offset) + self._inc_offset(len(data)) + + def seekable(self): + return True + + def seek(self, offset, whence=0): + if whence == 0: + new_offset = offset + elif whence == 1: + new_offset = self._offset + offset + elif whence == 2: + new_offset = self.volume.size() - 1 + new_offset += offset + else: + raise IOError("Invalid argument - whence=%s not supported" % + (whence)) + + if (new_offset < 0): + raise IOError("Invalid argument") + + self._offset = new_offset + + def tell(self): + return self._offset + + def flush(self): + try: + self.rbd_image.flush() + except AttributeError as exc: + LOG.warning("flush() not supported in this version of librbd - " + "%s" % (str(rbd.RBD().version()))) + + def fileno(self): + """ + Since rbd image does not have a fileno we raise an IOError (recommended + for IOBase class implementations - see + http://docs.python.org/2/library/io.html#io.IOBase) + """ + raise IOError("fileno() not supported by RBD()") + + class RBDVolumeProxy(object): """ Context manager for dealing with an existing rbd volume. @@ -442,3 +522,26 @@ class RBDDriver(driver.VolumeDriver): image_utils.upload_volume(context, image_service, image_meta, tmp_file) os.unlink(tmp_file) + + def backup_volume(self, context, backup, backup_service): + """Create a new backup from an existing volume.""" + volume = self.db.volume_get(context, backup['volume_id']) + pool = self.configuration.rbd_pool + volname = volume['name'] + + with RBDVolumeProxy(self, volname, pool, read_only=True) as rbd_image: + rbd_fd = RBDImageIOWrapper(rbd_image) + backup_service.backup(backup, rbd_fd) + + LOG.debug("volume backup complete.") + + def restore_backup(self, context, backup, volume, backup_service): + """Restore an existing backup to a new or existing volume.""" + volume = self.db.volume_get(context, backup['volume_id']) + pool = self.configuration.rbd_pool + + with RBDVolumeProxy(self, volume['name'], pool) as rbd_image: + rbd_fd = RBDImageIOWrapper(rbd_image) + backup_service.restore(backup, volume['id'], rbd_fd) + + LOG.debug("volume restore complete.")