diff --git a/cinder/tests/unit/volume/drivers/test_fujitsu_dx.py b/cinder/tests/unit/volume/drivers/test_fujitsu_dx.py index 858ca45dce2..ad62196e695 100644 --- a/cinder/tests/unit/volume/drivers/test_fujitsu_dx.py +++ b/cinder/tests/unit/volume/drivers/test_fujitsu_dx.py @@ -43,6 +43,7 @@ CONF = """ testpass 10.0.0.3 abcd1234_TPP +abcd1234_RG abcd1234_OSVD """ @@ -51,8 +52,19 @@ TEST_VOLUME = { 'name': 'volume1', 'display_name': 'volume1', 'provider_location': None, - 'volume_metadata': [], + 'metadata': {}, 'size': 1, + 'host': 'controller@113#abcd1234_TPP' +} + +TEST_VOLUME2 = { + 'id': '98179912-2495-42e9-97f0-6a0d3511700a', + 'name': 'volume2', + 'display_name': 'volume2', + 'provider_location': None, + 'metadata': {}, + 'size': 1, + 'host': 'controller@113#abcd1234_RG' } TEST_SNAP = { @@ -72,7 +84,8 @@ TEST_CLONE = { 'project_id': 'project', 'display_name': 'clone1', 'display_description': 'volume created from snapshot', - 'volume_metadata': [], + 'metadata': {}, + 'host': 'controller@113#abcd1234_TPP' } ISCSI_INITIATOR = 'iqn.1993-08.org.debian:01:8261afe17e4c' @@ -96,24 +109,76 @@ AUTH_PRIV = 'FUJITSU_AuthorizedPrivilege' STOR_SYNC = 'FUJITSU_StorageSynchronized' PROT_CTRL_UNIT = 'CIM_ProtocolControllerForUnit' STORAGE_TYPE = 'abcd1234_TPP' +STORAGE_TYPE2 = 'abcd1234_RG' LUNMASKCTRL_IDS = ['AFG0010_CM00CA00P00', 'AFG0011_CM01CA00P00'] MAP_STAT = '0' VOL_STAT = '0' FAKE_CAPACITY = 1170368102400 +# Volume1 in pool abcd1234_TPP FAKE_LUN_ID1 = '600000E00D2A0000002A011500140000' FAKE_LUN_NO1 = '0x0014' +# Snapshot1 in pool abcd1234_OSVD FAKE_LUN_ID2 = '600000E00D2A0000002A0115001E0000' FAKE_LUN_NO2 = '0x001E' +# Volume2 in pool abcd1234_RG +FAKE_LUN_ID3 = '600000E00D2800000028075301140000' +FAKE_LUN_NO3 = '0x0114' FAKE_SYSTEM_NAME = 'ET603SA4621302115' +# abcd1234_TPP pool +FAKE_USEGB = 2.0 +# abcd1234_RG pool +FAKE_USEGB2 = 1.0 +FAKE_POOLS = [{ + 'path': {'InstanceID': 'FUJITSU:TPP0004'}, + 'pool_name': 'abcd1234_TPP', + 'useable_capacity_gb': (FAKE_CAPACITY / units.Gi) * 20 - FAKE_USEGB, + 'multiattach': False, + 'thick_provisioning_support': False, + 'provisioned_capacity_gb': FAKE_USEGB, + 'total_volumes': 2, + 'thin_provisioning_support': True, + 'free_capacity_gb': FAKE_CAPACITY / units.Gi - FAKE_USEGB, + 'total_capacity_gb': FAKE_CAPACITY / units.Gi, + 'max_over_subscription_ratio': '20.0', +}, { + 'path': {'InstanceID': 'FUJITSU:RSP0005'}, + 'pool_name': 'abcd1234_RG', + 'useable_capacity_gb': FAKE_CAPACITY / units.Gi - FAKE_USEGB2, + 'multiattach': False, + 'thick_provisioning_support': True, + 'provisioned_capacity_gb': FAKE_USEGB2, + 'total_volumes': 1, + 'thin_provisioning_support': False, + 'free_capacity_gb': FAKE_CAPACITY / units.Gi - FAKE_USEGB2, + 'total_capacity_gb': FAKE_CAPACITY / units.Gi, + 'max_over_subscription_ratio': 1, +}] FAKE_STATS = { + 'driver_version': '1.3.0', + 'storage_protocol': 'iSCSI', 'vendor_name': 'FUJITSU', - 'total_capacity_gb': FAKE_CAPACITY / units.Gi, - 'free_capacity_gb': FAKE_CAPACITY / units.Gi, + 'QoS_support': False, + 'volume_backend_name': 'volume_backend_name', + 'shared_targets': True, + 'backend_state': 'up', + 'pools': FAKE_POOLS, +} +FAKE_STATS2 = { + 'driver_version': '1.3.0', + 'storage_protocol': 'FC', + 'vendor_name': 'FUJITSU', + 'QoS_support': False, + 'volume_backend_name': 'volume_backend_name', + 'shared_targets': True, + 'backend_state': 'up', + 'pools': FAKE_POOLS, } + +# Volume1 in pool abcd1234_TPP FAKE_KEYBIND1 = { 'CreationClassName': 'FUJITSU_StorageVolume', 'SystemName': STORAGE_SYSTEM, @@ -121,11 +186,27 @@ FAKE_KEYBIND1 = { 'SystemCreationClassName': 'FUJITSU_StorageComputerSystem', } +# Volume2 in pool abcd1234_RG +FAKE_KEYBIND3 = { + 'CreationClassName': 'FUJITSU_StorageVolume', + 'SystemName': STORAGE_SYSTEM, + 'DeviceID': FAKE_LUN_ID3, + 'SystemCreationClassName': 'FUJITSU_StorageComputerSystem', +} + +# Volume1 FAKE_LOCATION1 = { 'classname': 'FUJITSU_StorageVolume', 'keybindings': FAKE_KEYBIND1, } +# Volume2 +FAKE_LOCATION3 = { + 'classname': 'FUJITSU_StorageVolume', + 'keybindings': FAKE_KEYBIND3, +} + +# Volume1 metadata info. FAKE_LUN_META1 = { 'FJ_Pool_Type': 'Thinporvisioning_POOL', 'FJ_Volume_No': FAKE_LUN_NO1, @@ -134,10 +215,24 @@ FAKE_LUN_META1 = { 'FJ_Backend': FAKE_SYSTEM_NAME, } +# Volume2 metadata info. +FAKE_LUN_META3 = { + 'FJ_Pool_Type': 'RAID_GROUP', + 'FJ_Volume_No': FAKE_LUN_NO3, + 'FJ_Volume_Name': u'FJosv_4whcadwDac7ANKHA2O719A==', + 'FJ_Pool_Name': STORAGE_TYPE2, + 'FJ_Backend': FAKE_SYSTEM_NAME, +} +# Volume1 FAKE_MODEL_INFO1 = { 'provider_location': six.text_type(FAKE_LOCATION1), 'metadata': FAKE_LUN_META1, } +# Volume2 +FAKE_MODEL_INFO3 = { + 'provider_location': six.text_type(FAKE_LOCATION3), + 'metadata': FAKE_LUN_META3, +} FAKE_KEYBIND2 = { 'CreationClassName': 'FUJITSU_StorageVolume', @@ -151,7 +246,9 @@ FAKE_LOCATION2 = { 'keybindings': FAKE_KEYBIND2, } -FAKE_SNAP_INFO = {'provider_location': six.text_type(FAKE_LOCATION2)} +FAKE_SNAP_INFO = { + 'provider_location': six.text_type(FAKE_LOCATION2) +} FAKE_LUN_META2 = { 'FJ_Pool_Type': 'Thinporvisioning_POOL', @@ -205,7 +302,10 @@ class FakeEternusConnection(object): VOL_STAT = '1' rc = 0 vol = self._enum_volumes() - job = {'TheElement': vol[0].path} + if InPool.get('InstanceID') == 'FUJITSU:RSP0005': + job = {'TheElement': vol[1].path} + else: + job = {'TheElement': vol[0].path} elif MethodName == 'ReturnToStoragePool': VOL_STAT = '0' rc = 0 @@ -262,7 +362,7 @@ class FakeEternusConnection(object): return result - def EnumerateInstances(self, name): + def EnumerateInstances(self, name, **param_dict): result = None if name == 'FUJITSU_StorageProduct': result = self._enum_sysnames() @@ -315,6 +415,8 @@ class FakeEternusConnection(object): result = self._assoc_storagevolume(objectpath) elif ResultClass == 'FUJITSU_AuthorizedPrivilege': result = self._assoc_authpriv() + elif AssocClass == 'FUJITSU_AllocatedFromStoragePool': + result = self._assocnames_pool(objectpath) else: result = self._default_assoc(objectpath) @@ -329,6 +431,9 @@ class FakeEternusConnection(object): result = self._assocnames_tcp_endpoint() elif ResultClass == 'FUJITSU_AffinityGroupController': result = self._assocnames_afngroup() + elif (ResultClass == 'FUJITSU_StorageVolume' and + AssocClass == 'FUJITSU_AllocatedFromStoragePool'): + result = self._assocnames_volumelist(objectpath) else: result = self._default_assocnames(objectpath) @@ -407,6 +512,26 @@ class FakeEternusConnection(object): def _assocnames_afngroup(self): return self._enum_afntyservice() + def _assocnames_volumelist(self, poolpath): + volumelist = self._enum_volumes(force=True) + inpool = [] + for vol in volumelist: + vol_pool = vol.get('poolpath') + if poolpath['InstanceID'] == vol_pool: + inpool.append(vol) + + return inpool + + def _assocnames_pool(self, volumepath): + poollist = self._enum_pool_details('RAID') + poollist += self._enum_pool_details('TPP') + volpool = [] + for pool in poollist: + if volumepath['poolpath'] == pool['InstanceID']: + volpool.append(pool) + + return volpool + def _default_assocnames(self, objectpath): return objectpath @@ -520,34 +645,48 @@ class FakeEternusConnection(object): def _enum_pool_details(self, pooltype): pools = [] pool = FJ_StoragePool() + pool2 = FJ_StoragePool() if pooltype == 'RAID': pool['InstanceID'] = 'FUJITSU:RSP0004' pool['CreationClassName'] = 'FUJITSU_RAIDStoragePool' pool['ElementName'] = 'abcd1234_OSVD' pool['TotalManagedSpace'] = 1170368102400 - pool['RemainingManagedSpace'] = 1170368102400 - pool.path = pool + pool['RemainingManagedSpace'] = 1170368102400 - 1 * units.Gi + pool.path = FJ_StoragePool() + pool.path['InstanceID'] = 'FUJITSU:RSP0004' pool.path.classname = 'FUJITSU_RAIDStoragePool' + pools.append(pool) + + pool2['InstanceID'] = 'FUJITSU:RSP0005' + pool2['CreationClassName'] = 'FUJITSU_RAIDStoragePool' + pool2['ElementName'] = 'abcd1234_RG' + pool2['TotalManagedSpace'] = 1170368102400 + pool2['RemainingManagedSpace'] = 1170368102400 - 1 * units.Gi + pool2.path = FJ_StoragePool() + pool2.path['InstanceID'] = 'FUJITSU:RSP0005' + pool2.path.classname = 'FUJITSU_RAIDStoragePool' + pools.append(pool2) else: - pool = FJ_StoragePool() pool['InstanceID'] = 'FUJITSU:TPP0004' pool['CreationClassName'] = 'FUJITSU_ThinProvisioningPool' pool['ElementName'] = 'abcd1234_TPP' pool['TotalManagedSpace'] = 1170368102400 - pool['RemainingManagedSpace'] = 1170368102400 - pool.path = pool + pool['RemainingManagedSpace'] = 1170368102400 - 2 * units.Gi + pool.path = FJ_StoragePool() + pool.path['InstanceID'] = 'FUJITSU:TPP0004' pool.path.classname = 'FUJITSU_ThinProvisioningPool' + pools.append(pool) - pools.append(pool) return pools - def _enum_volumes(self): + def _enum_volumes(self, force=False): volumes = [] - if VOL_STAT == '0': + if VOL_STAT == '0' and not force: return volumes volume = FJ_StorageVolume() volume['name'] = TEST_VOLUME['name'] + volume['poolpath'] = 'FUJITSU:TPP0004' volume['CreationClassName'] = 'FUJITSU_StorageVolume' volume['Name'] = FAKE_LUN_ID1 volume['DeviceID'] = FAKE_LUN_ID1 @@ -558,20 +697,46 @@ class FakeEternusConnection(object): volume.path = volume volume.path.classname = volume['CreationClassName'] - name = {} - name['classname'] = 'FUJITSU_StorageVolume' - keys = {} - keys['CreationClassName'] = 'FUJITSU_StorageVolume' - keys['SystemName'] = STORAGE_SYSTEM - keys['DeviceID'] = volume['DeviceID'] - keys['SystemCreationClassName'] = 'FUJITSU_StorageComputerSystem' - name['keybindings'] = keys + name = { + 'classname': 'FUJITSU_StorageVolume', + 'keybindings': { + 'CreationClassName': 'FUJITSU_StorageVolume', + 'SystemName': STORAGE_SYSTEM, + 'DeviceID': volume['DeviceID'], + 'SystemCreationClassName': 'FUJITSU_StorageComputerSystem', + }, + } volume['provider_location'] = str(name) - volumes.append(volume) + volume3 = FJ_StorageVolume() + volume3['name'] = TEST_VOLUME2['name'] + volume3['poolpath'] = 'FUJITSU:RSP0005' + volume3['CreationClassName'] = 'FUJITSU_StorageVolume' + volume3['Name'] = FAKE_LUN_ID3 + volume3['DeviceID'] = FAKE_LUN_ID3 + volume3['SystemCreationClassName'] = 'FUJITSU_StorageComputerSystem' + volume3['SystemName'] = STORAGE_SYSTEM + volume3['ElementName'] = 'FJosv_4whcadwDac7ANKHA2O719A==' + volume3['volume_type_id'] = None + volume3.path = volume3 + volume3.path.classname = volume3['CreationClassName'] + + name3 = { + 'classname': 'FUJITSU_StorageVolume', + 'keybindings': { + 'CreationClassName': 'FUJITSU_StorageVolume', + 'SystemName': STORAGE_SYSTEM, + 'DeviceID': volume3['DeviceID'], + 'SystemCreationClassName': 'FUJITSU_StorageComputerSystem', + }, + } + volume3['provider_location'] = str(name3) + volumes.append(volume3) + snap_vol = FJ_StorageVolume() snap_vol['name'] = TEST_SNAP['name'] + snap_vol['poolpath'] = 'FUJITSU:RSP0004' snap_vol['CreationClassName'] = 'FUJITSU_StorageVolume' snap_vol['Name'] = FAKE_LUN_ID2 snap_vol['DeviceID'] = FAKE_LUN_ID2 @@ -581,20 +746,21 @@ class FakeEternusConnection(object): snap_vol.path = snap_vol snap_vol.path.classname = snap_vol['CreationClassName'] - name2 = {} - name2['classname'] = 'FUJITSU_StorageVolume' - keys2 = {} - keys2['CreationClassName'] = 'FUJITSU_StorageVolume' - keys2['SystemName'] = STORAGE_SYSTEM - keys2['DeviceID'] = snap_vol['DeviceID'] - keys2['SystemCreationClassName'] = 'FUJITSU_StorageComputerSystem' - name2['keybindings'] = keys2 + name2 = { + 'classname': 'FUJITSU_StorageVolume', + 'keybindings': { + 'CreationClassName': 'FUJITSU_StorageVolume', + 'SystemName': STORAGE_SYSTEM, + 'DeviceID': snap_vol['DeviceID'], + 'SystemCreationClassName': 'FUJITSU_StorageComputerSystem', + }, + } snap_vol['provider_location'] = str(name2) - volumes.append(snap_vol) clone_vol = FJ_StorageVolume() clone_vol['name'] = TEST_CLONE['name'] + clone_vol['poolpath'] = 'FUJITSU:TPP0004' clone_vol['CreationClassName'] = 'FUJITSU_StorageVolume' clone_vol['ElementName'] = TEST_CLONE['name'] clone_vol['DeviceID'] = FAKE_LUN_ID2 @@ -678,7 +844,6 @@ class FakeEternusConnection(object): return targetlist def _getinstance_storagevolume(self, objpath): - foundinstance = None instance = FJ_StorageVolume() volumes = self._enum_volumes() for volume in volumes: @@ -713,6 +878,8 @@ class FJFCDriverTestCase(test.TestCase): # Make fake Object by using mock as configuration object. self.configuration = mock.Mock(spec=conf.Configuration) self.configuration.cinder_eternus_config_file = self.config_file.name + self.configuration.safe_get = self.fake_safe_get + self.configuration.max_over_subscription_ratio = '20.0' self.mock_object(dx_common.FJDXCommon, '_get_eternus_connection', self.fake_eternus_connection) @@ -725,22 +892,27 @@ class FJFCDriverTestCase(test.TestCase): driver = dx_fc.FJDXFCDriver(configuration=self.configuration) self.driver = driver + def fake_safe_get(self, str=None): + return str + def fake_eternus_connection(self): conn = FakeEternusConnection() return conn def test_get_volume_stats(self): ret = self.driver.get_volume_stats(True) - stats = {'vendor_name': ret['vendor_name'], - 'total_capacity_gb': ret['total_capacity_gb'], - 'free_capacity_gb': ret['free_capacity_gb']} - self.assertEqual(FAKE_STATS, stats) + + self.assertEqual(FAKE_STATS2, ret) def test_create_and_delete_volume(self): model_info = self.driver.create_volume(TEST_VOLUME) self.assertEqual(FAKE_MODEL_INFO1, model_info) + model_info = self.driver.create_volume(TEST_VOLUME2) + self.assertEqual(FAKE_MODEL_INFO3, model_info) + self.driver.delete_volume(TEST_VOLUME) + self.driver.delete_volume(TEST_VOLUME2) @mock.patch.object(dx_common.FJDXCommon, '_get_mapdata') def test_map_unmap(self, mock_mapdata): @@ -816,7 +988,16 @@ class FJFCDriverTestCase(test.TestCase): model_info = self.driver.create_volume(TEST_VOLUME) self.assertEqual(FAKE_MODEL_INFO1, model_info) - self.driver.extend_volume(TEST_VOLUME, 10) + volume_info = {} + for key in TEST_VOLUME: + if key == 'provider_location': + volume_info[key] = model_info[key] + elif key == 'metadata': + volume_info[key] = model_info[key] + else: + volume_info[key] = TEST_VOLUME[key] + + self.driver.extend_volume(volume_info, 10) class FJISCSIDriverTestCase(test.TestCase): @@ -835,6 +1016,8 @@ class FJISCSIDriverTestCase(test.TestCase): # Make fake Object by using mock as configuration object. self.configuration = mock.Mock(spec=conf.Configuration) self.configuration.cinder_eternus_config_file = self.config_file.name + self.configuration.safe_get = self.fake_safe_get + self.configuration.max_over_subscription_ratio = '20.0' self.mock_object(dx_common.FJDXCommon, '_get_eternus_connection', self.fake_eternus_connection) @@ -850,6 +1033,9 @@ class FJISCSIDriverTestCase(test.TestCase): driver = dx_iscsi.FJDXISCSIDriver(configuration=self.configuration) self.driver = driver + def fake_safe_get(self, str=None): + return str + def fake_eternus_connection(self): conn = FakeEternusConnection() return conn @@ -867,16 +1053,18 @@ class FJISCSIDriverTestCase(test.TestCase): def test_get_volume_stats(self): ret = self.driver.get_volume_stats(True) - stats = {'vendor_name': ret['vendor_name'], - 'total_capacity_gb': ret['total_capacity_gb'], - 'free_capacity_gb': ret['free_capacity_gb']} - self.assertEqual(FAKE_STATS, stats) + + self.assertEqual(FAKE_STATS, ret) def test_create_and_delete_volume(self): model_info = self.driver.create_volume(TEST_VOLUME) self.assertEqual(FAKE_MODEL_INFO1, model_info) + model_info = self.driver.create_volume(TEST_VOLUME2) + self.assertEqual(FAKE_MODEL_INFO3, model_info) + self.driver.delete_volume(TEST_VOLUME) + self.driver.delete_volume(TEST_VOLUME2) def test_map_unmap(self): fake_mapdata = self.fake_get_mapdata(None, {}, None) @@ -942,4 +1130,13 @@ class FJISCSIDriverTestCase(test.TestCase): model_info = self.driver.create_volume(TEST_VOLUME) self.assertEqual(FAKE_MODEL_INFO1, model_info) - self.driver.extend_volume(TEST_VOLUME, 10) + volume_info = {} + for key in TEST_VOLUME: + if key == 'provider_location': + volume_info[key] = model_info[key] + elif key == 'metadata': + volume_info[key] = model_info[key] + else: + volume_info[key] = TEST_VOLUME[key] + + self.driver.extend_volume(volume_info, 10) diff --git a/cinder/volume/drivers/fujitsu/eternus_dx/constants.py b/cinder/volume/drivers/fujitsu/eternus_dx/constants.py index 11a1ca7b2fc..d8266c6f716 100644 --- a/cinder/volume/drivers/fujitsu/eternus_dx/constants.py +++ b/cinder/volume/drivers/fujitsu/eternus_dx/constants.py @@ -42,6 +42,10 @@ POOL_TYPE_dic = { RAIDGROUP: 'RAID_GROUP', TPPOOL: 'Thinporvisioning_POOL', } +POOL_TYPE_list = [ + 'RAID', + 'TPP' +] OPERATION_dic = { SNAPOPC: RETURN_TO_RESOURCEPOOL, OPC: DETACH, diff --git a/cinder/volume/drivers/fujitsu/eternus_dx/eternus_dx_common.py b/cinder/volume/drivers/fujitsu/eternus_dx/eternus_dx_common.py index 222d1a425d2..e9a40c24f8c 100644 --- a/cinder/volume/drivers/fujitsu/eternus_dx/eternus_dx_common.py +++ b/cinder/volume/drivers/fujitsu/eternus_dx/eternus_dx_common.py @@ -36,6 +36,7 @@ from cinder.i18n import _ from cinder import utils from cinder.volume import configuration as conf from cinder.volume.drivers.fujitsu.eternus_dx import constants as CONSTANTS +from cinder.volume import volume_utils LOG = logging.getLogger(__name__) CONF = cfg.CONF @@ -61,10 +62,7 @@ class FJDXCommon(object): VERSION = "1.3.0" stats = { 'driver_version': VERSION, - 'free_capacity_gb': 0, - 'reserved_percentage': 0, 'storage_protocol': None, - 'total_capacity_gb': 0, 'vendor_name': 'FUJITSU', 'QoS_support': False, 'volume_backend_name': None, @@ -82,6 +80,7 @@ class FJDXCommon(object): # Get iSCSI ipaddress from driver configuration file. self.configuration.iscsi_ip_address = ( self._get_drvcfg('EternusISCSIIP')) + self.conn = None @staticmethod def get_driver_options(): @@ -103,7 +102,7 @@ class FJDXCommon(object): 'volumesize': volumesize}) # get poolname from driver configuration file - eternus_pool = self._get_drvcfg('EternusPool') + eternus_pool = volume_utils.extract_host(volume['host'], 'pool') # Existence check the pool pool = self._find_pool(eternus_pool) @@ -216,6 +215,47 @@ class FJDXCommon(object): return (element_path, metadata) + def create_pool_info(self, pool_instance, volume_count, pool_type): + """Create pool information from pool instance.""" + LOG.debug('create_pool_info, pool_instance: %(pool)s, ' + 'volume_count: %(volcount)s, pool_type: %(ptype)s.', + {'pool': pool_instance, + 'volcount': volume_count, 'ptype': pool_type}) + + if pool_type not in CONSTANTS.POOL_TYPE_list: + msg = (_('Invalid pool type was specified : %s.') % pool_type) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + total_gb = pool_instance['TotalManagedSpace'] / units.Gi + free_gb = pool_instance['RemainingManagedSpace'] / units.Gi + + if hasattr(pool_instance, 'provisioned_capacity_gb'): + prov_gb = pool_instance.provisioned_capacity_gb + else: + prov_gb = total_gb - free_gb + + if pool_type == 'RAID': + useable_gb = free_gb + else: + max_capacity = total_gb * float( + self.configuration.max_over_subscription_ratio) + useable_gb = max_capacity - prov_gb + + pool = { + 'name': pool_instance['ElementName'], + 'path': pool_instance.path, + 'total_capacity_gb': total_gb, + 'free_capacity_gb': free_gb, + 'type': pool_type, + 'volume_count': volume_count, + 'provisioned_capacity_gb': prov_gb, + 'useable_capacity_gb': useable_gb + } + + LOG.debug('create_pool_info, pool: %s.', pool) + return pool + def create_volume_from_snapshot(self, volume, snapshot): """Creates a volume from a snapshot.""" LOG.debug('create_volume_from_snapshot, ' @@ -716,14 +756,12 @@ class FJDXCommon(object): 'vol_instance': vol_instance.path}) # Get poolname from driver configuration file. - eternus_pool = self._get_drvcfg('EternusPool') - # Check the existence of volume. - pool = self._find_pool(eternus_pool) + pool_name, pool = self._find_pool_from_volume(vol_instance) if pool is None: msg = (_('extend_volume, ' 'eternus_pool: %(eternus_pool)s, ' 'pool not found.') - % {'eternus_pool': eternus_pool}) + % {'eternus_pool': pool_name}) LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) @@ -734,14 +772,14 @@ class FJDXCommon(object): pooltype = CONSTANTS.TPPOOL configservice = self._find_eternus_service(CONSTANTS.STOR_CONF) - if configservice is None: + if not configservice: msg = (_('extend_volume, volume: %(volume)s, ' 'volumename: %(volumename)s, ' 'eternus_pool: %(eternus_pool)s, ' 'Storage Configuration Service not found.') % {'volume': volume, 'volumename': volumename, - 'eternus_pool': eternus_pool}) + 'eternus_pool': pool_name}) LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) @@ -755,7 +793,7 @@ class FJDXCommon(object): 'TheElement: %(vol_instance)s.', {'service': configservice, 'volumename': volumename, - 'eternus_pool': eternus_pool, + 'eternus_pool': pool_name, 'pooltype': pooltype, 'volumesize': volumesize, 'vol_instance': vol_instance.path}) @@ -793,48 +831,21 @@ class FJDXCommon(object): {'volumename': volumename, 'rc': rc, 'errordesc': errordesc, - 'eternus_pool': eternus_pool, + 'eternus_pool': pool_name, 'pooltype': CONSTANTS.POOL_TYPE_dic[pooltype]}) - return eternus_pool + return pool_name @lockutils.synchronized('ETERNUS-update', 'cinder-', True) def update_volume_stats(self): - """get pool capacity.""" + """Get pool capacity.""" self.conn = self._get_eternus_connection() - eternus_pool = self._get_drvcfg('EternusPool') - LOG.debug('update_volume_stats, pool name: %s.', eternus_pool) + poolname_list = self._get_drvcfg('EternusPool', multiple=True) + self._find_pools(poolname_list, self.conn) - pool = self._find_pool(eternus_pool, True) - if pool: - # pool is found - self.stats['total_capacity_gb'] = ( - pool['TotalManagedSpace'] / units.Gi) - - self.stats['free_capacity_gb'] = ( - pool['RemainingManagedSpace'] / units.Gi) - else: - # if pool information is unknown, set 0 GB to capacity information - LOG.warning('update_volume_stats, ' - 'eternus_pool:%(eternus_pool)s, ' - 'specified pool is not found.', - {'eternus_pool': eternus_pool}) - self.stats['total_capacity_gb'] = 0 - self.stats['free_capacity_gb'] = 0 - - self.stats['multiattach'] = False - - LOG.debug('update_volume_stats, ' - 'eternus_pool:%(eternus_pool)s, ' - 'total capacity[%(total)s], ' - 'free capacity[%(free)s].', - {'eternus_pool': eternus_pool, - 'total': self.stats['total_capacity_gb'], - 'free': self.stats['free_capacity_gb']}) - - return (self.stats, eternus_pool) + return (self.stats, poolname_list) def _get_mapdata(self, vol_instance, connector, target_portlist): """return mapping information.""" @@ -1012,9 +1023,9 @@ class FJDXCommon(object): return mapdata def _get_drvcfg(self, tagname, filename=None, multiple=False): - """read from driver configuration file.""" - if filename is None: - # set default configuration file name + """Read from driver configuration file.""" + if not filename: + # Set default configuration file name. filename = self.configuration.cinder_eternus_config_file LOG.debug("_get_drvcfg, input[%(filename)s][%(tagname)s].", @@ -1023,7 +1034,6 @@ class FJDXCommon(object): tree = ET.parse(filename) elem = tree.getroot() - ret = None if not multiple: ret = elem.findtext(".//" + tagname) else: @@ -1141,6 +1151,116 @@ class FJDXCommon(object): LOG.debug('_find_pool, pool: %s.', ret) return ret + def _find_pools(self, poolname_list, conn): + """Find Instance or InstanceName of pool by pool name on ETERNUS.""" + LOG.debug('_find_pool, pool name: %s.', poolname_list) + + target_poolname = list(poolname_list) + pools = [] + + # Get pools info form CIM instance(include info about instance path). + try: + tppoollist = self._enum_eternus_instances( + 'FUJITSU_ThinProvisioningPool', conn=conn) + rgpoollist = self._enum_eternus_instances( + 'FUJITSU_RAIDStoragePool', conn=conn) + except Exception: + msg = (_('_find_pool, ' + 'eternus_pool:%(eternus_pool)s, ' + 'EnumerateInstances, ' + 'cannot connect to ETERNUS.') + % {'eternus_pool': target_poolname}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + # Make total pools list. + tppools = [(tppool, 'TPP') for tppool in tppoollist] + rgpools = [(rgpool, 'RAID') for rgpool in rgpoollist] + poollist = tppools + rgpools + + # One eternus backend has only one special pool name + # so just use pool name can get the target pool. + for pool, ptype in poollist: + poolname = pool['ElementName'] + + LOG.debug('_find_pools, ' + 'pool: %(pool)s, ptype: %(ptype)s.', + {'pool': poolname, 'ptype': ptype}) + if poolname in target_poolname: + try: + volume_list = self._assoc_eternus_names( + pool.path, + conn=conn, + AssocClass='FUJITSU_AllocatedFromStoragePool', + ResultClass='FUJITSU_StorageVolume') + + volume_count = len(volume_list) + except Exception: + msg = (_('_find_pools, ' + 'poolname: %(poolname)s, ' + 'pooltype: %(ptype)s, ' + 'Associator Names, ' + 'cannot connect to ETERNUS.') + % {'ptype': ptype, + 'poolname': poolname}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + poolinfo = self.create_pool_info(pool, volume_count, ptype) + + target_poolname.remove(poolname) + pools.append((poolinfo, poolname)) + + if not target_poolname: + break + + if not pools: + LOG.warning('_find_pools, all the EternusPools in driver ' + 'configuration file do not exist. ' + 'Please edit the driver configuration file ' + 'to include EternusPool names.') + + # Sort pools in the order defined in driver configuration file. + sorted_pools = ( + [pool for name in poolname_list for pool, pname in pools + if name == pname]) + + LOG.debug('_find_pools, ' + 'pools: %(pools)s, ' + 'notfound_pools: %(notfound_pools)s.', + {'pools': pools, + 'notfound_pools': target_poolname}) + pools_stats = {'pools': []} + for pool in sorted_pools: + single_pool = {} + if pool['type'] == 'TPP': + thin_enabled = True + max_ratio = self.configuration.max_over_subscription_ratio + else: + thin_enabled = False + max_ratio = 1 + + single_pool.update(dict( + path=pool['path'], + pool_name=pool['name'], + total_capacity_gb=pool['total_capacity_gb'], + total_volumes=pool['volume_count'], + free_capacity_gb=pool['free_capacity_gb'], + provisioned_capacity_gb=pool['provisioned_capacity_gb'], + useable_capacity_gb=pool['useable_capacity_gb'], + thin_provisioning_support=thin_enabled, + thick_provisioning_support=not thin_enabled, + max_over_subscription_ratio=max_ratio, + )) + single_pool['multiattach'] = False + pools_stats['pools'].append(single_pool) + + self.stats['shared_targets'] = True + self.stats['backend_state'] = 'up' + self.stats['pools'] = pools_stats['pools'] + + return self.stats, target_poolname + def _find_eternus_service(self, classname): """find CIM instance about service information.""" LOG.debug('_find_eternus_service, ' @@ -1176,7 +1296,8 @@ class FJDXCommon(object): {'a': classname, 'b': instanceNameList, 'c': param_dict}) - + rc = None + retdata = None # Use InvokeMethod. try: rc, retdata = self.conn.InvokeMethod( @@ -1223,11 +1344,14 @@ class FJDXCommon(object): @lockutils.synchronized('ETERNUS-SMIS-other', 'cinder-', True) @utils.retry(exception.VolumeBackendAPIException) - def _enum_eternus_instances(self, classname): + def _enum_eternus_instances(self, classname, conn=None, **param_dict): """Enumerate Instances.""" LOG.debug('_enum_eternus_instances, classname: %s.', classname) - ret = self.conn.EnumerateInstances(classname) + if not conn: + conn = self.conn + + ret = conn.EnumerateInstances(classname, **param_dict) LOG.debug('_enum_eternus_instances, enum %d instances.', len(ret)) return ret @@ -1258,26 +1382,32 @@ class FJDXCommon(object): @lockutils.synchronized('ETERNUS-SMIS-other', 'cinder-', True) @utils.retry(exception.VolumeBackendAPIException) - def _assoc_eternus(self, classname, **param_dict): + def _assoc_eternus(self, classname, conn=None, **param_dict): """Associator.""" LOG.debug('_assoc_eternus, ' 'classname: %(cls)s, param: %(param)s.', {'cls': classname, 'param': param_dict}) - ret = self.conn.Associators(classname, **param_dict) + if not conn: + conn = self.conn + + ret = conn.Associators(classname, **param_dict) LOG.debug('_assoc_eternus, enum %d instances.', len(ret)) return ret @lockutils.synchronized('ETERNUS-SMIS-other', 'cinder-', True) @utils.retry(exception.VolumeBackendAPIException) - def _assoc_eternus_names(self, classname, **param_dict): + def _assoc_eternus_names(self, classname, conn=None, **param_dict): """Associator Names.""" LOG.debug('_assoc_eternus_names, ' 'classname: %(cls)s, param: %(param)s.', {'cls': classname, 'param': param_dict}) - ret = self.conn.AssociatorNames(classname, **param_dict) + if not conn: + conn = self.conn + + ret = conn.AssociatorNames(classname, **param_dict) LOG.debug('_assoc_eternus_names, enum %d names.', len(ret)) return ret @@ -2103,3 +2233,66 @@ class FJDXCommon(object): result = num return result + + def _find_pool_from_volume(self, vol_instance, manage_type='volume'): + """Find Instance or InstanceName of pool by volume instance.""" + LOG.debug('_find_pool_from_volume, volume: %(volume)s.', + {'volume': vol_instance}) + poolname = None + target_pool = None + filename = None + conn = self.conn + + # Get poolname of volume on Eternus. + try: + pools = self._assoc_eternus( + vol_instance.path, + conn=conn, + AssocClass='FUJITSU_AllocatedFromStoragePool', + ResultClass='CIM_StoragePool') + except Exception: + msg = (_('_find_pool_from_volume, ' + 'vol_instance: %s, ' + 'Associators: FUJITSU_AllocatedFromStoragePool, ' + 'cannot connect to ETERNUS.') + % vol_instance.path) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + if not pools: + msg = (_('_find_pool_from_volume, ' + 'vol_instance: %s, ' + 'pool not found.') + % vol_instance.path) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + # Get poolname from driver configuration file. + if manage_type == 'volume': + cfgpool_list = list(self._get_drvcfg('EternusPool', + filename=filename, + multiple=True)) + elif manage_type == 'snapshot': + cfgpool_list = list(self._get_drvcfg('EternusSnapPool', + filename=filename, + multiple=True)) + LOG.debug('_find_pool_from_volume, cfgpool_list: %(cfgpool_list)s.', + {'cfgpool_list': cfgpool_list}) + for pool in pools: + if pool['ElementName'] in cfgpool_list: + poolname = pool['ElementName'] + target_pool = pool.path + break + + if not target_pool: + msg = (_('_find_pool_from_volume, ' + 'vol_instance: %s, ' + 'the pool of volume not in driver configuration file.') + % vol_instance.path) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + LOG.debug('_find_pool_from_volume, poolname: %(poolname)s, ' + 'target_pool: %(target_pool)s.', + {'poolname': poolname, 'target_pool': target_pool}) + return poolname, target_pool diff --git a/doc/source/configuration/block-storage/drivers/fujitsu-eternus-dx-driver.rst b/doc/source/configuration/block-storage/drivers/fujitsu-eternus-dx-driver.rst index dbc4b733af2..52e6d5ab961 100644 --- a/doc/source/configuration/block-storage/drivers/fujitsu-eternus-dx-driver.rst +++ b/doc/source/configuration/block-storage/drivers/fujitsu-eternus-dx-driver.rst @@ -132,6 +132,8 @@ Configuration smisuser smispassword raid5_0001 + tpp_0001 + raid_0002 raid5_0001 @@ -146,6 +148,8 @@ Configuration smisuser smispassword raid5_0001 + tpp_0001 + raid_0002 raid5_0001 1.1.1.1 1.1.1.2 @@ -169,7 +173,7 @@ Configuration ``EternusPassword`` Password for the SMI-S connection of the ETERNUS DX. - ``EternusPool`` + ``EternusPool`` (Multiple setting allowed) Storage pool name for volumes. Enter RAID Group name or TPP name in the ETERNUS DX. @@ -188,6 +192,8 @@ Configuration and cannot specify TPP name. * You can specify the same RAID Group name for ``EternusPool`` and ``EternusSnapPool`` if you create volumes and snapshots on a same storage pool. + * For ``EternusPool``, when multiple pools are specified, + cinder-scheduler will select one from multiple pools to create the volume. Configuration example ~~~~~~~~~~~~~~~~~~~~~ diff --git a/releasenotes/notes/fujitsu-multiple-pools-a0dd9197b16b3122.yaml b/releasenotes/notes/fujitsu-multiple-pools-a0dd9197b16b3122.yaml new file mode 100644 index 00000000000..6b8e9f689eb --- /dev/null +++ b/releasenotes/notes/fujitsu-multiple-pools-a0dd9197b16b3122.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Fujitsu Driver: Added multiple pools support.