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
This commit is contained in:
Edward Hope-Morley
2013-06-26 10:25:11 +01:00
parent 6f3b40c59d
commit 756a8d75f1
2 changed files with 113 additions and 1 deletions

View 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

View 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.")