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:
@@ -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
|
||||
|
||||
@@ -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.")
|
||||
|
||||
Reference in New Issue
Block a user