]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Added volume backup and restore to Ceph RBD driver
authorEdward Hope-Morley <edward.hope-morley@canonical.com>
Wed, 26 Jun 2013 09:25:11 +0000 (10:25 +0100)
committerEdward Hope-Morley <edward.hope-morley@canonical.com>
Wed, 26 Jun 2013 13:03:29 +0000 (14:03 +0100)
It is now possible to backup and restore volumes
when using volume.drivers.rbd.RBDDriver

Implements: blueprint cinder-backup-to-ceph

Change-Id: Ic1b8db8f0acd7974423414171b8fb45197d05dc6

cinder/backup/services/swift.py
cinder/volume/drivers/rbd.py

index 4b3ed22a13911b696062324ed4b0030416d7db1d..31c44e979115593c666cb9016abdcc7969e268ec 100644 (file)
@@ -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
index d8dbd3895c18b920c454b8cd9b0cb6eb39ced076..533d58441de168fa47a97d332f0423c9d86013db 100644 (file)
@@ -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.")