From 8e90227f533d58a0ad0c3c1fbb1386bd0f331bb4 Mon Sep 17 00:00:00 2001 From: Chao Zheng CZ Li Date: Thu, 9 Apr 2015 15:10:39 +0800 Subject: [PATCH] Add consistency group support for XIV/DS8K cinder driver proxy Change-Id: I0850255a59997dbe44d9a7202ab5f947a3d91a3b Implements: blueprint xiv-ds8k-consistency-group --- cinder/tests/test_ibm_xiv_ds8k.py | 291 +++++++++++++++++++++++++- cinder/volume/drivers/ibm/xiv_ds8k.py | 20 ++ 2 files changed, 306 insertions(+), 5 deletions(-) diff --git a/cinder/tests/test_ibm_xiv_ds8k.py b/cinder/tests/test_ibm_xiv_ds8k.py index 2bb57b9d5..88db060ae 100644 --- a/cinder/tests/test_ibm_xiv_ds8k.py +++ b/cinder/tests/test_ibm_xiv_ds8k.py @@ -32,11 +32,16 @@ from cinder.volume import configuration as conf from cinder.volume.drivers.ibm import xiv_ds8k from cinder.volume import volume_types - FAKE = "fake" +CANNOT_DELETE = "Can not delete" +TOO_BIG_VOLUME_SIZE = 12000 +POOL_SIZE = 100 +CONSISTGROUP_ID = 1 VOLUME = {'size': 16, 'name': FAKE, - 'id': 1} + 'id': 1, + 'consistencygroup_id': CONSISTGROUP_ID, + 'status': 'available'} MANAGED_FAKE = "managed_fake" MANAGED_VOLUME = {'size': 16, @@ -50,6 +55,11 @@ REPLICATED_VOLUME = {'size': 64, CONTEXT = {} +CONSISTGROUP = {'id': CONSISTGROUP_ID, } +CG_SNAPSHOT_ID = 1 +CG_SNAPSHOT = {'id': CG_SNAPSHOT_ID, + 'consistencygroup_id': CONSISTGROUP_ID} + CONNECTOR = {'initiator': "iqn.2012-07.org.fake:01:948f189c4695", } CONF = cfg.CONF @@ -68,6 +78,7 @@ class XIVDS8KFakeProxyDriver(object): self.xiv_ds8k_iqn = FAKE self.volumes = {} + self.snapshots = {} self.driver = driver def setup(self, context): @@ -80,7 +91,7 @@ class XIVDS8KFakeProxyDriver(object): raise self.exception.HostNotFound(host='fake') def create_volume(self, volume): - if volume['size'] > 100: + if volume['size'] > POOL_SIZE: raise self.exception.VolumeBackendAPIException(data='blah') self.volumes[volume['name']] = volume @@ -172,6 +183,83 @@ class XIVDS8KFakeProxyDriver(object): volume['easytier'] = new_type['extra_specs']['easytier'] return True, volume + def create_consistencygroup(self, ctxt, group): + + volumes = [volume for k, volume in self.volumes.iteritems() + if volume['consistencygroup_id'] == group['id']] + + if volumes: + raise exception.CinderException( + message='The consistency group id of volume may be wrong.') + + return {'status': 'available'} + + def delete_consistencygroup(self, ctxt, group): + volumes = [] + for volume in self.volumes.values(): + if (group.get('id', None) + == volume.get('consistencygroup_id', None)): + if volume['name'] == CANNOT_DELETE: + raise exception.VolumeBackendAPIException( + message='Volume can not be deleted') + else: + volume['status'] = 'deleted' + volumes.append(volume) + + # Delete snapshots in consistency group + self.snapshots = {k: snap for k, snap in self.snapshots.iteritems() + if not(snap.get('consistencygroup_id', None) + == group.get('id', None))} + + # Delete volume in consistency group + self.volumes = {k: vol for k, vol in self.volumes.iteritems() + if not(vol.get('consistencygroup_id', None) + == group.get('id', None))} + + return {'status': 'deleted'}, volumes + + def create_cgsnapshot(self, ctxt, cgsnapshot): + snapshots = [] + for volume in self.volumes.values(): + if (cgsnapshot.get('consistencygroup_id', None) + == volume.get('consistencygroup_id', None)): + + if volume['size'] > POOL_SIZE / 2: + raise self.exception.VolumeBackendAPIException(data='blah') + + snapshot = copy.deepcopy(volume) + snapshot['name'] = CANNOT_DELETE \ + if snapshot['name'] == CANNOT_DELETE \ + else snapshot['name'] + 'Snapshot' + snapshot['status'] = 'available' + snapshot['cgsnapshot_id'] = cgsnapshot.get('id', None) + snapshot['consistencygroup_id'] = \ + cgsnapshot.get('consistencygroup_id', None) + self.snapshots[snapshot['name']] = snapshot + snapshots.append(snapshot) + + return {'status': 'available'}, snapshots + + def delete_cgsnapshot(self, ctxt, cgsnapshot): + snapshots = [] + for snapshot in self.snapshots.values(): + if (cgsnapshot.get('id', None) + == snapshot.get('cgsnapshot_id', None)): + + if snapshot['name'] == CANNOT_DELETE: + raise exception.VolumeBackendAPIException( + message='Snapshot can not be deleted') + else: + snapshot['status'] = 'deleted' + snapshots.append(snapshot) + + # Delete snapshots in consistency group + self.snapshots = {k: snap for k, snap in self.snapshots.iteritems() + if not(snap.get('consistencygroup_id', None) + == cgsnapshot.get('cgsnapshot_id', None))} + + return {'status': 'deleted'}, snapshots + class XIVDS8KVolumeDriverTest(test.TestCase): """Test IBM XIV and DS8K volume driver.""" @@ -237,8 +325,10 @@ class XIVDS8KVolumeDriverTest(test.TestCase): """Test the volume exist method with a volume that doesn't exist.""" self.driver.do_setup(None) + self.assertFalse( - self.driver.xiv_ds8k_proxy.volume_exists({'name': FAKE})) + self.driver.xiv_ds8k_proxy.volume_exists({'name': FAKE}) + ) def test_delete_volume(self): """Verify that a volume is deleted.""" @@ -263,7 +353,7 @@ class XIVDS8KVolumeDriverTest(test.TestCase): self.driver.create_volume, {'name': FAKE, 'id': 1, - 'size': 12000}) + 'size': TOO_BIG_VOLUME_SIZE}) def test_initialize_connection(self): """Test that inititialize connection attaches volume to host.""" @@ -563,3 +653,194 @@ class XIVDS8KVolumeDriverTest(test.TestCase): self.driver.retype, ctxt, volume, new_type, diff, host ) + + def test_create_consistencygroup(self): + """Test that create_consistencygroup return successfully.""" + + self.driver.do_setup(None) + + ctxt = context.get_admin_context() + + # Create consistency group + model_update = self.driver.create_consistencygroup(ctxt, CONSISTGROUP) + + self.assertEqual('available', + model_update['status'], + "Consistency Group created failed") + + def test_create_consistencygroup_fail_on_cg_not_empty(self): + """Test that create_consistencygroup fail + when consistency group is not empty. + """ + + self.driver.do_setup(None) + + ctxt = context.get_admin_context() + + # Create volumes + # And add the volumes into the consistency group before creating cg + self.driver.create_volume(VOLUME) + + self.assertRaises(exception.CinderException, + self.driver.create_consistencygroup, + ctxt, CONSISTGROUP) + + def test_delete_consistencygroup(self): + """Test that delete_consistencygroup return successfully.""" + + self.driver.do_setup(None) + + ctxt = context.get_admin_context() + + # Create consistency group + self.driver.create_consistencygroup(ctxt, CONSISTGROUP) + + # Create volumes and add them to consistency group + self.driver.create_volume(VOLUME) + + # Delete consistency group + model_update, volumes = \ + self.driver.delete_consistencygroup(ctxt, CONSISTGROUP) + + # Verify the result + self.assertEqual('deleted', + model_update['status'], + 'Consistency Group deleted failed') + for volume in volumes: + self.assertEqual('deleted', + volume['status'], + 'Consistency Group deleted failed') + + def test_delete_consistencygroup_fail_on_volume_not_delete(self): + """Test that delete_consistencygroup return fail + when the volume can not be deleted. + """ + + self.driver.do_setup(None) + + ctxt = context.get_admin_context() + + # Create consistency group + self.driver.create_consistencygroup(ctxt, CONSISTGROUP) + + # Set the volume not to be deleted + volume = copy.deepcopy(VOLUME) + volume['name'] = CANNOT_DELETE + + # Create volumes and add them to consistency group + self.driver.create_volume(volume) + + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.delete_consistencygroup, + ctxt, CONSISTGROUP) + + def test_create_cgsnapshot(self): + """Test that create_cgsnapshot return successfully.""" + + self.driver.do_setup(None) + + ctxt = context.get_admin_context() + + # Create consistency group + self.driver.create_consistencygroup(ctxt, CONSISTGROUP) + + # Create volumes and add them to consistency group + self.driver.create_volume(VOLUME) + + # Create consistency group snapshot + model_update, snapshots = \ + self.driver.create_cgsnapshot(ctxt, CG_SNAPSHOT) + + # Verify the result + self.assertEqual('available', + model_update['status'], + 'Consistency Group Snapshot created failed') + for snap in snapshots: + self.assertEqual('available', + snap['status']) + + # Clean the environment + self.driver.delete_cgsnapshot(ctxt, CG_SNAPSHOT) + self.driver.delete_consistencygroup(ctxt, CONSISTGROUP) + + def test_create_cgsnapshot_fail_on_no_pool_space_left(self): + """Test that create_cgsnapshot return fail when no pool space left.""" + + self.driver.do_setup(None) + + ctxt = context.get_admin_context() + + # Create consistency group + self.driver.create_consistencygroup(ctxt, CONSISTGROUP) + + # Set the volume size + volume = copy.deepcopy(VOLUME) + volume['size'] = POOL_SIZE / 2 + 1 + + # Create volumes and add them to consistency group + self.driver.create_volume(volume) + + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_cgsnapshot, + ctxt, CG_SNAPSHOT) + + # Clean the environment + self.driver.volumes = None + self.driver.delete_consistencygroup(ctxt, CONSISTGROUP) + + def test_delete_cgsnapshot(self): + """Test that delete_cgsnapshot return successfully.""" + + self.driver.do_setup(None) + + ctxt = context.get_admin_context() + + # Create consistency group + self.driver.create_consistencygroup(ctxt, CONSISTGROUP) + + # Create volumes and add them to consistency group + self.driver.create_volume(VOLUME) + + # Create consistency group snapshot + self.driver.create_cgsnapshot(ctxt, CG_SNAPSHOT) + + # Delete consistency group snapshot + model_update, snapshots = \ + self.driver.delete_cgsnapshot(ctxt, CG_SNAPSHOT) + + # Verify the result + self.assertEqual('deleted', + model_update['status'], + 'Consistency Group Snapshot deleted failed') + for snap in snapshots: + self.assertEqual('deleted', + snap['status']) + + # Clean the environment + self.driver.delete_consistencygroup(ctxt, CONSISTGROUP) + + def test_delete_cgsnapshot_fail_on_snapshot_not_delete(self): + """Test that delete_cgsnapshot return fail + when the snapshot can not be deleted. + """ + + self.driver.do_setup(None) + + ctxt = context.get_admin_context() + + # Create consistency group + self.driver.create_consistencygroup(ctxt, CONSISTGROUP) + + # Set the snapshot not to be deleted + volume = copy.deepcopy(VOLUME) + volume['name'] = CANNOT_DELETE + + # Create volumes and add them to consistency group + self.driver.create_volume(volume) + + # Create consistency group snapshot + self.driver.create_cgsnapshot(ctxt, CG_SNAPSHOT) + + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.delete_cgsnapshot, + ctxt, CG_SNAPSHOT) diff --git a/cinder/volume/drivers/ibm/xiv_ds8k.py b/cinder/volume/drivers/ibm/xiv_ds8k.py index 56747d324..93228a940 100644 --- a/cinder/volume/drivers/ibm/xiv_ds8k.py +++ b/cinder/volume/drivers/ibm/xiv_ds8k.py @@ -227,3 +227,23 @@ class XIVDS8KDriver(san.SanDriver): """Convert the volume to be of the new type.""" return self.xiv_ds8k_proxy.retype(ctxt, volume, new_type, diff, host) + + def create_consistencygroup(self, context, group): + """Creates a consistency group.""" + + return self.xiv_ds8k_proxy.create_consistencygroup(context, group) + + def delete_consistencygroup(self, context, group): + """Deletes a consistency group.""" + + return self.xiv_ds8k_proxy.delete_consistencygroup(context, group) + + def create_cgsnapshot(self, context, cgsnapshot): + """Creates a consistency group snapshot.""" + + return self.xiv_ds8k_proxy.create_cgsnapshot(context, cgsnapshot) + + def delete_cgsnapshot(self, context, cgsnapshot): + """Deletes a consistency group snapshot.""" + + return self.xiv_ds8k_proxy.delete_cgsnapshot(context, cgsnapshot) -- 2.45.2