From 0f07a17fe57d029e22de4a1f02beb700fcb87e0b Mon Sep 17 00:00:00 2001 From: Chris Morrell Date: Mon, 16 Nov 2015 21:45:48 +0000 Subject: [PATCH] Added VAG support to SolidFire This patch adds VAG support to the SolidFire driver. This includes a new config option 'sf_enable_vag'. The driver creates a new VAG based on initiator name during connection initialization. By default VAG support is disabled. When enabled, the driver uses both CHAP and VAG authentication. DocImpact Added configuration option sf_enable_vag. Change-Id: I4ade805640646173c27bc6d969a211189f6301fb --- cinder/tests/unit/test_solidfire.py | 60 ++++++++++++++++++++ cinder/volume/drivers/solidfire.py | 87 ++++++++++++++++++++++++++++- 2 files changed, 146 insertions(+), 1 deletion(-) diff --git a/cinder/tests/unit/test_solidfire.py b/cinder/tests/unit/test_solidfire.py index a3811e90a..b12d91ef1 100644 --- a/cinder/tests/unit/test_solidfire.py +++ b/cinder/tests/unit/test_solidfire.py @@ -52,6 +52,7 @@ class SolidFireVolumeTestCase(test.TestCase): self.configuration.sf_svip = None self.configuration.sf_enable_volume_mapping = True self.configuration.sf_volume_prefix = 'UUID-' + self.configuration.sf_enable_vag = False super(SolidFireVolumeTestCase, self).setUp() self.stubs.Set(solidfire.SolidFireDriver, @@ -1110,3 +1111,62 @@ class SolidFireVolumeTestCase(test.TestCase): with mock.patch.object( sfv, '_issue_api_request', side_effect=_fake_issue_api_req): self.assertEqual(5, sfv._get_sf_volume(test_name, 8)['volumeID']) + + def test_create_vag(self): + global counter + counter = 0 + + def _trick_get_vag(vag_name): + # On the second call to get_vag we want to return a fake VAG + # result as required by logic of _sf_initialize_connection. + global counter + vag = {'attributes': {}, + 'deletedVolumes': [], + 'initiators': [], + 'name': 'TESTIQN', + 'volumeAccessGroupID': 1, + 'volumes': [], + 'virtualNetworkIDs': []} + + if counter == 1: + return [vag] + counter += 1 + + mod_conf = self.configuration + mod_conf.sf_enable_vag = True + sfv = solidfire.SolidFireDriver(configuration=mod_conf) + + 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'} + + def add_volume_to_vag_check(vol_id, vag_id): + self.assertEqual(1, vol_id) + self.assertEqual(1, vag_id) + + with mock.patch.object(sfv, + '_create_vag', + return_value=1), \ + mock.patch.object(sfv, + '_get_vags', + side_effect=_trick_get_vag), \ + mock.patch.object(sfv, + '_add_initiator_to_vag'), \ + mock.patch.object(sfv, + '_add_volume_to_vag', + side_effect=add_volume_to_vag_check): + + sfv.initialize_connection(testvol, connector) diff --git a/cinder/volume/drivers/solidfire.py b/cinder/volume/drivers/solidfire.py index 0bac423a9..3667c9803 100644 --- a/cinder/volume/drivers/solidfire.py +++ b/cinder/volume/drivers/solidfire.py @@ -16,6 +16,7 @@ import json import math import random +import re import socket import string import time @@ -87,7 +88,11 @@ sf_opts = [ cfg.PortOpt('sf_api_port', default=443, help='SolidFire API port. Useful if the device api is behind ' - 'a proxy on a different port.')] + 'a proxy on a different port.'), + + cfg.BoolOpt('sf_enable_vag', + default=False, + help='Utilize volume access groups on a per-tenant basis.')] CONF = cfg.CONF CONF.register_opts(sf_opts) @@ -814,6 +819,45 @@ class SolidFireDriver(san.SanISCSIDriver): vlist = sorted(vlist, key=lambda k: k['volumeID']) return vlist + def _create_vag(self, vag_name): + """Create a volume access group(vag). + + Returns the vag_id. + """ + params = {'name': vag_name} + result = self._issue_api_request('CreateVolumeAccessGroup', + params, + version='7.0') + return result['result']['volumeAccessGroupID'] + + def _get_vags(self, vag_name): + """Retrieve SolidFire volume access group objects by name. + + Returns an array of vags with a matching name value. + Returns an empty array if there are no matches. + """ + params = {} + vags = self._issue_api_request( + 'ListVolumeAccessGroups', + params, + version='7.0')['result']['volumeAccessGroups'] + matching_vags = [vag for vag in vags if vag['name'] == vag_name] + return matching_vags + + def _add_initiator_to_vag(self, iqn, vag_id): + params = {"initiators": [iqn], + "volumeAccessGroupID": vag_id} + self._issue_api_request('AddInitiatorsToVolumeAccessGroup', + params, + version='7.0') + + def _add_volume_to_vag(self, vol_id, vag_id): + params = {"volumeAccessGroupID": vag_id, + "volumes": [vol_id]} + self._issue_api_request('AddVolumesToVolumeAccessGroup', + params, + version='7.0') + def clone_image(self, context, volume, image_location, image_meta, image_service): @@ -1088,6 +1132,15 @@ class SolidFireDriver(san.SanISCSIDriver): results['thinProvisioningPercent']) self.cluster_stats = data + def initialize_connection(self, volume, connector, initiator_data=None): + """Initialize the connection and return connection info. + + Optionally checks and utilizes volume access groups. + """ + return self._sf_initialize_connection(volume, + connector, + initiator_data) + def attach_volume(self, context, volume, instance_uuid, host_name, mountpoint): @@ -1330,3 +1383,35 @@ class SolidFireISCSI(iscsi_driver.SanISCSITarget): def terminate_connection(self, volume, connector, **kwargs): pass + + def _sf_initialize_connection(self, volume, connector, + initiator_data=None): + """Initialize the connection and return connection info. + + Optionally checks and utilizes volume access groups. + """ + if self.configuration.sf_enable_vag: + raw_iqn = connector['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_id = vag[0]['volumeAccessGroupID'] + vag = vag[0] + else: + vag_id = self._create_vag(vag_name) + vag = self._get_vags(vag_name)[0] + + # Verify IQN matches. + if raw_iqn not in vag['initiators']: + self._add_initiator_to_vag(raw_iqn, + vag_id) + # Add volume to vag if not already. + if vol_id not in vag['volumes']: + self._add_volume_to_vag(vol_id, vag_id) + + # Continue along with default behavior + return super(SolidFireISCSI, self).initialize_connection(volume, + connector) -- 2.45.2