Fujitsu Driver: Multiple pools support

Add multiple pools support for Fujitsu driver.

Change-Id: Icb8e04a0b623d46ec97c85bfe61d466ff983d9e5
This commit is contained in:
Mingyue Qian 2019-09-25 18:00:36 +08:00
parent fb0bebe788
commit d988ee3468
5 changed files with 504 additions and 100 deletions

View File

@ -43,6 +43,7 @@ CONF = """<?xml version='1.0' encoding='UTF-8'?>
<EternusPassword>testpass</EternusPassword>
<EternusISCSIIP>10.0.0.3</EternusISCSIIP>
<EternusPool>abcd1234_TPP</EternusPool>
<EternusPool>abcd1234_RG</EternusPool>
<EternusSnapPool>abcd1234_OSVD</EternusSnapPool>
</FUJITSU>"""
@ -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,6 +302,9 @@ class FakeEternusConnection(object):
VOL_STAT = '1'
rc = 0
vol = self._enum_volumes()
if InPool.get('InstanceID') == 'FUJITSU:RSP0005':
job = {'TheElement': vol[1].path}
else:
job = {'TheElement': vol[0].path}
elif MethodName == 'ReturnToStoragePool':
VOL_STAT = '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)
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)

View File

@ -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,

View File

@ -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

View File

@ -132,6 +132,8 @@ Configuration
<EternusUser>smisuser</EternusUser>
<EternusPassword>smispassword</EternusPassword>
<EternusPool>raid5_0001</EternusPool>
<EternusPool>tpp_0001</EternusPool>
<EternusPool>raid_0002</EternusPool>
<EternusSnapPool>raid5_0001</EternusSnapPool>
</FUJITSU>
@ -146,6 +148,8 @@ Configuration
<EternusUser>smisuser</EternusUser>
<EternusPassword>smispassword</EternusPassword>
<EternusPool>raid5_0001</EternusPool>
<EternusPool>tpp_0001</EternusPool>
<EternusPool>raid_0002</EternusPool>
<EternusSnapPool>raid5_0001</EternusSnapPool>
<EternusISCSIIP>1.1.1.1</EternusISCSIIP>
<EternusISCSIIP>1.1.1.2</EternusISCSIIP>
@ -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
~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,4 @@
---
features:
- |
Fujitsu Driver: Added multiple pools support.