Skip to content

Commit

Permalink
Added copy-on-write support for all RBD cloning
Browse files Browse the repository at this point in the history
Up till now we only had copy-on-write for cloning from snapshot. This
change optionally allows clone from volume to use copy-on-write
instead of a doing a full copy each time. This should increase speed
and reduce nearterm storage consumtion but could introduce some new
risks e.g. excessively long clone chains and flatten storms. To avoid
this, a new config option has been providedons are provided -
rbd_max_clone_depth - which allows the user to limit the depth of a
chain of clones i.e.

    a->b->c->d as opposed to a->b
                              ->c
                              ->d

This will avoid flatten storms by breaking chains as they are formed
and at an early, predefined stage.

A second option - rbd_clone_from_volume_force_copy - allows the user
to use a full copy as before i.e. disable COW for volume clones.

Implements: blueprint use-copy-on-write-for-all-volume-cloning
Fixes: bug #1209199

Change-Id: Ia4a8a10c797cda2cf1ef3a2e9bd49f8c084ec977
  • Loading branch information
dosaboy committed Sep 5, 2013
1 parent 5994133 commit 52291d6
Show file tree
Hide file tree
Showing 4 changed files with 311 additions and 49 deletions.
29 changes: 29 additions & 0 deletions cinder/tests/backup/fake_rados.py
Expand Up @@ -16,13 +16,30 @@

class mock_rados(object):

class ObjectNotFound(Exception):
pass

class ioctx(object):
def __init__(self, *args, **kwargs):
pass

def close(self, *args, **kwargs):
pass

class Object(object):

def __init__(self, *args, **kwargs):
pass

def read(self, *args):
raise NotImplementedError()

def write(self, *args):
raise NotImplementedError()

def seek(self, *args):
raise NotImplementedError()

class Rados(object):

def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -63,6 +80,12 @@ def create_snap(self, *args, **kwargs):
def remove_snap(self, *args, **kwargs):
pass

def protect_snap(self, *args, **kwargs):
pass

def unprotect_snap(self, *args, **kwargs):
pass

def read(self, *args, **kwargs):
raise NotImplementedError()

Expand All @@ -78,6 +101,9 @@ def close(self):
def list_snaps(self):
raise NotImplementedError()

def parent_info(self):
raise NotImplementedError()

def size(self):
raise NotImplementedError()

Expand All @@ -94,3 +120,6 @@ def remove(self, *args, **kwargs):

def list(self, *args, **kwargs):
raise NotImplementedError()

def clone(self, *args, **kwargs):
raise NotImplementedError()
84 changes: 57 additions & 27 deletions cinder/tests/test_rbd.py
Expand Up @@ -27,6 +27,8 @@
from cinder.openstack.common import log as logging
from cinder.openstack.common import timeutils
from cinder import test
from cinder.tests.backup.fake_rados import mock_rados
from cinder.tests.backup.fake_rados import mock_rbd
from cinder.tests.image import fake as fake_image
from cinder.tests.test_volume import DriverTestCase
from cinder import units
Expand Down Expand Up @@ -111,11 +113,11 @@ def test_create_volume(self):
driver.RADOSClient(self.driver).AndReturn(mock_client)
mock_client.__enter__().AndReturn(mock_client)
self.rbd.RBD_FEATURE_LAYERING = 1
mock_rbd = self.mox.CreateMockAnything()
self.rbd.RBD().AndReturn(mock_rbd)
mock_rbd.create(mox.IgnoreArg(), str(name), size * 1024 ** 3,
old_format=False,
features=self.rbd.RBD_FEATURE_LAYERING)
_mock_rbd = self.mox.CreateMockAnything()
self.rbd.RBD().AndReturn(_mock_rbd)
_mock_rbd.create(mox.IgnoreArg(), str(name), size * 1024 ** 3,
old_format=False,
features=self.rbd.RBD_FEATURE_LAYERING)
mock_client.__exit__(None, None, None).AndReturn(None)

self.mox.ReplayAll()
Expand All @@ -125,21 +127,31 @@ def test_create_volume(self):
def test_delete_volume(self):
name = u'volume-00000001'
volume = dict(name=name)
mock_client = self.mox.CreateMockAnything()
self.mox.StubOutWithMock(driver, 'RADOSClient')
self.stubs.Set(self.driver, '_get_backup_snaps', lambda *args: None)

driver.RADOSClient(self.driver).AndReturn(mock_client)
mock_client.__enter__().AndReturn(mock_client)
mock_image = self.mox.CreateMockAnything()
self.rbd.Image(mox.IgnoreArg(), str(name)).AndReturn(mock_image)
mock_image.close()
mock_rbd = self.mox.CreateMockAnything()
self.rbd.RBD().AndReturn(mock_rbd)
mock_rbd.remove(mox.IgnoreArg(), str(name))
mock_client.__exit__(None, None, None).AndReturn(None)
# Setup librbd stubs
self.stubs.Set(self.driver, 'rados', mock_rados)
self.stubs.Set(self.driver, 'rbd', mock_rbd)

self.mox.ReplayAll()
class mock_client(object):
def __init__(self, *args, **kwargs):
self.ioctx = None

def __enter__(self, *args, **kwargs):
return self

def __exit__(self, type_, value, traceback):
pass

self.stubs.Set(driver, 'RADOSClient', mock_client)

self.stubs.Set(self.driver, '_get_backup_snaps',
lambda *args: None)
self.stubs.Set(self.driver.rbd.Image, 'list_snaps',
lambda *args: [])
self.stubs.Set(self.driver.rbd.Image, 'parent_info',
lambda *args: (None, None, None))
self.stubs.Set(self.driver.rbd.Image, 'unprotect_snap',
lambda *args: None)

self.driver.delete_volume(volume)

Expand Down Expand Up @@ -184,17 +196,35 @@ def test_delete_snapshot(self):
def test_create_cloned_volume(self):
src_name = u'volume-00000001'
dst_name = u'volume-00000002'
mock_proxy = self.mox.CreateMockAnything()
mock_proxy.ioctx = self.mox.CreateMockAnything()
self.mox.StubOutWithMock(driver, 'RBDVolumeProxy')

driver.RBDVolumeProxy(self.driver, src_name, read_only=True) \
.AndReturn(mock_proxy)
mock_proxy.__enter__().AndReturn(mock_proxy)
mock_proxy.copy(mock_proxy.ioctx, str(dst_name))
mock_proxy.__exit__(None, None, None).AndReturn(None)
# Setup librbd stubs
self.stubs.Set(self.driver, 'rados', mock_rados)
self.stubs.Set(self.driver, 'rbd', mock_rbd)

self.mox.ReplayAll()
self.driver.rbd.RBD_FEATURE_LAYERING = 1

class mock_client(object):
def __init__(self, *args, **kwargs):
self.ioctx = None

def __enter__(self, *args, **kwargs):
return self

def __exit__(self, type_, value, traceback):
pass

self.stubs.Set(driver, 'RADOSClient', mock_client)

def mock_clone(*args, **kwargs):
pass

self.stubs.Set(self.driver.rbd.RBD, 'clone', mock_clone)
self.stubs.Set(self.driver.rbd.Image, 'list_snaps',
lambda *args: [{'name': 'snap1'}, {'name': 'snap2'}])
self.stubs.Set(self.driver.rbd.Image, 'parent_info',
lambda *args: (None, None, None))
self.stubs.Set(self.driver.rbd.Image, 'protect_snap',
lambda *args: None)

self.driver.create_cloned_volume(dict(name=dst_name),
dict(name=src_name))
Expand Down

0 comments on commit 52291d6

Please sign in to comment.