From: Chris Morrell Date: Wed, 18 Nov 2015 22:14:15 +0000 (+0000) Subject: Additional VAG support for SolidFire X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=23bfbd04070c31c83d3831dc8c64c1fc4ba79ac2;p=openstack-build%2Fcinder-build.git Additional VAG support for SolidFire This patch removes volumes from volume access groups, and potentially volume access groups, when a connection is terminated. When the previously added 'sf_enable_vag' option is enabled and a volume connection is terminated, if the volume was part of a VAG then the volume is removed from the VAG. If the volume is the only remaining volume in the VAG then the VAG is also removed. Change-Id: Ib508a67f012f6f186abba0d381ab104edc163fa9 --- diff --git a/cinder/tests/unit/test_solidfire.py b/cinder/tests/unit/test_solidfire.py index b12d91ef1..369a17f65 100644 --- a/cinder/tests/unit/test_solidfire.py +++ b/cinder/tests/unit/test_solidfire.py @@ -1170,3 +1170,79 @@ class SolidFireVolumeTestCase(test.TestCase): side_effect=add_volume_to_vag_check): sfv.initialize_connection(testvol, connector) + + def test_remove_vag(self): + vag = {'attributes': {}, + 'deletedVolumes': [], + 'initiators': [], + 'name': 'TESTIQN', + 'volumeAccessGroupID': 1, + 'volumes': [1], + 'virtualNetworkIDs': []} + testvol = {'project_id': 'testprjid', + 'name': 'testvol', + 'size': 1, + 'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66', + 'volume_type_id': None, + 'provider_location': '10.10.7.1:3260 iqn.2010-01.com.' + 'solidfire:87hg.uuid-2cc06226-cc' + '74-4cb7-bd55-14aed659a0cc.4060 0', + 'provider_auth': 'CHAP stack-1-a60e2611875f40199931f2' + 'c76370d66b 2FE0CQ8J196R', + 'provider_geometry': '4096 4096', + 'created_at': timeutils.utcnow(), + 'provider_id': "1 1 1" + } + connector = {'initiator': 'iqn.2012-07.org.fake:01'} + mod_conf = self.configuration + mod_conf.sf_enable_vag = True + sfv = solidfire.SolidFireDriver(configuration=mod_conf) + + with mock.patch.object(sfv, + '_get_vags', + return_value=[vag]), \ + mock.patch.object(sfv, + '_remove_vag') as mock_rem_vag: + sfv.terminate_connection(testvol, connector, force=False) + mock_rem_vag.assert_called_with(vag['volumeAccessGroupID']) + + def test_remove_volume_from_vag(self): + vag = {'attributes': {}, + 'deletedVolumes': [], + 'initiators': [], + 'name': 'TESTIQN', + 'volumeAccessGroupID': 1, + 'volumes': [1, 2], + 'virtualNetworkIDs': []} + testvol = {'project_id': 'testprjid', + 'name': 'testvol', + 'size': 1, + 'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66', + 'volume_type_id': None, + 'provider_location': '10.10.7.1:3260 iqn.2010-01.com.' + 'solidfire:87hg.uuid-2cc06226-cc' + '74-4cb7-bd55-14aed659a0cc.4060 0', + 'provider_auth': 'CHAP stack-1-a60e2611875f40199931f2' + 'c76370d66b 2FE0CQ8J196R', + 'provider_geometry': '4096 4096', + 'created_at': timeutils.utcnow(), + 'provider_id': "1 1 1" + } + connector = {'initiator': 'iqn.2012-07.org.fake:01'} + mod_conf = self.configuration + mod_conf.sf_enable_vag = True + sfv = solidfire.SolidFireDriver(configuration=mod_conf) + provider_id = testvol['provider_id'] + vol_id = int(sfv._parse_provider_id_string(provider_id)[0]) + + with mock.patch.object(sfv, + '_get_vags', + return_value=[vag]), \ + mock.patch.object(sfv, + '_remove_vag') as mock_rem_vag, \ + mock.patch.object(sfv, + '_remove_volume_from_vag') as mock_rem_vol_vag: + sfv.terminate_connection(testvol, connector, force=False) + mock_rem_vol_vag.assert_called_with(vol_id, + vag['volumeAccessGroupID']) + mock_rem_vag.assert_not_called() diff --git a/cinder/volume/drivers/solidfire.py b/cinder/volume/drivers/solidfire.py index 3667c9803..2021ea2a9 100644 --- a/cinder/volume/drivers/solidfire.py +++ b/cinder/volume/drivers/solidfire.py @@ -858,6 +858,19 @@ class SolidFireDriver(san.SanISCSIDriver): params, version='7.0') + def _remove_volume_from_vag(self, vol_id, vag_id): + params = {"volumeAccessGroupID": vag_id, + "volumes": [vol_id]} + self._issue_api_request('RemoveVolumesFromVolumeAccessGroup', + params, + version='7.0') + + def _remove_vag(self, vag_id): + params = {"volumeAccessGroupID": vag_id} + self._issue_api_request('DeleteVolumeAccessGroup', + params, + version='7.0') + def clone_image(self, context, volume, image_location, image_meta, image_service): @@ -1165,6 +1178,11 @@ class SolidFireDriver(san.SanISCSIDriver): self._issue_api_request('ModifyVolume', params) + def terminate_connection(self, volume, properties, force): + return self._sf_terminate_connection(volume, + properties, + force) + def detach_volume(self, context, volume, attachment=None): sfaccount = self._get_sfaccount(volume['project_id']) params = {'accountID': sfaccount['accountID']} @@ -1404,6 +1422,13 @@ class SolidFireISCSI(iscsi_driver.SanISCSITarget): vag_id = self._create_vag(vag_name) vag = self._get_vags(vag_name)[0] + # TODO(chrismorrell): There is a potential race condition if a + # volume is attached and another is detached on the same + # host/initiator. The detach may purge the VAG before the attach + # has a chance to add the volume to the VAG. Propose combining + # add_initiator_to_vag and add_volume_to_vag with a retry on + # SFAPI exception regarding nonexistent VAG. + # Verify IQN matches. if raw_iqn not in vag['initiators']: self._add_initiator_to_vag(raw_iqn, @@ -1415,3 +1440,28 @@ class SolidFireISCSI(iscsi_driver.SanISCSITarget): # Continue along with default behavior return super(SolidFireISCSI, self).initialize_connection(volume, connector) + + def _sf_terminate_connection(self, volume, properties, force): + """Terminate the volume connection. + + Optionally remove volume from volume access group. + If the VAG is empty then the VAG is also removed. + """ + if self.configuration.sf_enable_vag: + raw_iqn = properties['initiator'] + vag_name = re.sub('[^0-9a-zA-Z]+', '-', raw_iqn) + vag = self._get_vags(vag_name) + provider_id = volume['provider_id'] + vol_id = int(self._parse_provider_id_string(provider_id)[0]) + + if vag: + vag = vag[0] + vag_id = vag['volumeAccessGroupID'] + if [vol_id] == vag['volumes']: + self._remove_vag(vag_id) + elif vol_id in vag['volumes']: + self._remove_volume_from_vag(vol_id, vag_id) + + return super(SolidFireISCSI, self).terminate_connection(volume, + properties, + force=force)