Skip to content

Commit

Permalink
Merge "Added volume backup and restore to Ceph RBD driver"
Browse files Browse the repository at this point in the history
  • Loading branch information
Jenkins authored and openstack-gerrit committed Jun 27, 2013
2 parents 5b7ff48 + 756a8d7 commit 35c8643
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 1 deletion.
11 changes: 10 additions & 1 deletion cinder/backup/services/swift.py
Expand Up @@ -383,7 +383,16 @@ def _restore_v1(self, backup, volume_id, metadata, volume_file):

# 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
Expand Down
103 changes: 103 additions & 0 deletions cinder/volume/drivers/rbd.py
Expand Up @@ -18,6 +18,7 @@

from __future__ import absolute_import

import io
import json
import os
import tempfile
Expand All @@ -27,10 +28,13 @@

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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -442,3 +522,26 @@ def copy_volume_to_image(self, context, volume, image_service, image_meta):
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.")

0 comments on commit 35c8643

Please sign in to comment.