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> <EternusPassword>testpass</EternusPassword>
<EternusISCSIIP>10.0.0.3</EternusISCSIIP> <EternusISCSIIP>10.0.0.3</EternusISCSIIP>
<EternusPool>abcd1234_TPP</EternusPool> <EternusPool>abcd1234_TPP</EternusPool>
<EternusPool>abcd1234_RG</EternusPool>
<EternusSnapPool>abcd1234_OSVD</EternusSnapPool> <EternusSnapPool>abcd1234_OSVD</EternusSnapPool>
</FUJITSU>""" </FUJITSU>"""
@ -51,8 +52,19 @@ TEST_VOLUME = {
'name': 'volume1', 'name': 'volume1',
'display_name': 'volume1', 'display_name': 'volume1',
'provider_location': None, 'provider_location': None,
'volume_metadata': [], 'metadata': {},
'size': 1, '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 = { TEST_SNAP = {
@ -72,7 +84,8 @@ TEST_CLONE = {
'project_id': 'project', 'project_id': 'project',
'display_name': 'clone1', 'display_name': 'clone1',
'display_description': 'volume created from snapshot', 'display_description': 'volume created from snapshot',
'volume_metadata': [], 'metadata': {},
'host': 'controller@113#abcd1234_TPP'
} }
ISCSI_INITIATOR = 'iqn.1993-08.org.debian:01:8261afe17e4c' ISCSI_INITIATOR = 'iqn.1993-08.org.debian:01:8261afe17e4c'
@ -96,24 +109,76 @@ AUTH_PRIV = 'FUJITSU_AuthorizedPrivilege'
STOR_SYNC = 'FUJITSU_StorageSynchronized' STOR_SYNC = 'FUJITSU_StorageSynchronized'
PROT_CTRL_UNIT = 'CIM_ProtocolControllerForUnit' PROT_CTRL_UNIT = 'CIM_ProtocolControllerForUnit'
STORAGE_TYPE = 'abcd1234_TPP' STORAGE_TYPE = 'abcd1234_TPP'
STORAGE_TYPE2 = 'abcd1234_RG'
LUNMASKCTRL_IDS = ['AFG0010_CM00CA00P00', 'AFG0011_CM01CA00P00'] LUNMASKCTRL_IDS = ['AFG0010_CM00CA00P00', 'AFG0011_CM01CA00P00']
MAP_STAT = '0' MAP_STAT = '0'
VOL_STAT = '0' VOL_STAT = '0'
FAKE_CAPACITY = 1170368102400 FAKE_CAPACITY = 1170368102400
# Volume1 in pool abcd1234_TPP
FAKE_LUN_ID1 = '600000E00D2A0000002A011500140000' FAKE_LUN_ID1 = '600000E00D2A0000002A011500140000'
FAKE_LUN_NO1 = '0x0014' FAKE_LUN_NO1 = '0x0014'
# Snapshot1 in pool abcd1234_OSVD
FAKE_LUN_ID2 = '600000E00D2A0000002A0115001E0000' FAKE_LUN_ID2 = '600000E00D2A0000002A0115001E0000'
FAKE_LUN_NO2 = '0x001E' FAKE_LUN_NO2 = '0x001E'
# Volume2 in pool abcd1234_RG
FAKE_LUN_ID3 = '600000E00D2800000028075301140000'
FAKE_LUN_NO3 = '0x0114'
FAKE_SYSTEM_NAME = 'ET603SA4621302115' 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 = { FAKE_STATS = {
'driver_version': '1.3.0',
'storage_protocol': 'iSCSI',
'vendor_name': 'FUJITSU', 'vendor_name': 'FUJITSU',
'total_capacity_gb': FAKE_CAPACITY / units.Gi, 'QoS_support': False,
'free_capacity_gb': FAKE_CAPACITY / units.Gi, '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 = { FAKE_KEYBIND1 = {
'CreationClassName': 'FUJITSU_StorageVolume', 'CreationClassName': 'FUJITSU_StorageVolume',
'SystemName': STORAGE_SYSTEM, 'SystemName': STORAGE_SYSTEM,
@ -121,11 +186,27 @@ FAKE_KEYBIND1 = {
'SystemCreationClassName': 'FUJITSU_StorageComputerSystem', '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 = { FAKE_LOCATION1 = {
'classname': 'FUJITSU_StorageVolume', 'classname': 'FUJITSU_StorageVolume',
'keybindings': FAKE_KEYBIND1, 'keybindings': FAKE_KEYBIND1,
} }
# Volume2
FAKE_LOCATION3 = {
'classname': 'FUJITSU_StorageVolume',
'keybindings': FAKE_KEYBIND3,
}
# Volume1 metadata info.
FAKE_LUN_META1 = { FAKE_LUN_META1 = {
'FJ_Pool_Type': 'Thinporvisioning_POOL', 'FJ_Pool_Type': 'Thinporvisioning_POOL',
'FJ_Volume_No': FAKE_LUN_NO1, 'FJ_Volume_No': FAKE_LUN_NO1,
@ -134,10 +215,24 @@ FAKE_LUN_META1 = {
'FJ_Backend': FAKE_SYSTEM_NAME, '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 = { FAKE_MODEL_INFO1 = {
'provider_location': six.text_type(FAKE_LOCATION1), 'provider_location': six.text_type(FAKE_LOCATION1),
'metadata': FAKE_LUN_META1, 'metadata': FAKE_LUN_META1,
} }
# Volume2
FAKE_MODEL_INFO3 = {
'provider_location': six.text_type(FAKE_LOCATION3),
'metadata': FAKE_LUN_META3,
}
FAKE_KEYBIND2 = { FAKE_KEYBIND2 = {
'CreationClassName': 'FUJITSU_StorageVolume', 'CreationClassName': 'FUJITSU_StorageVolume',
@ -151,7 +246,9 @@ FAKE_LOCATION2 = {
'keybindings': FAKE_KEYBIND2, '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 = { FAKE_LUN_META2 = {
'FJ_Pool_Type': 'Thinporvisioning_POOL', 'FJ_Pool_Type': 'Thinporvisioning_POOL',
@ -205,6 +302,9 @@ class FakeEternusConnection(object):
VOL_STAT = '1' VOL_STAT = '1'
rc = 0 rc = 0
vol = self._enum_volumes() vol = self._enum_volumes()
if InPool.get('InstanceID') == 'FUJITSU:RSP0005':
job = {'TheElement': vol[1].path}
else:
job = {'TheElement': vol[0].path} job = {'TheElement': vol[0].path}
elif MethodName == 'ReturnToStoragePool': elif MethodName == 'ReturnToStoragePool':
VOL_STAT = '0' VOL_STAT = '0'
@ -262,7 +362,7 @@ class FakeEternusConnection(object):
return result return result
def EnumerateInstances(self, name): def EnumerateInstances(self, name, **param_dict):
result = None result = None
if name == 'FUJITSU_StorageProduct': if name == 'FUJITSU_StorageProduct':
result = self._enum_sysnames() result = self._enum_sysnames()
@ -315,6 +415,8 @@ class FakeEternusConnection(object):
result = self._assoc_storagevolume(objectpath) result = self._assoc_storagevolume(objectpath)
elif ResultClass == 'FUJITSU_AuthorizedPrivilege': elif ResultClass == 'FUJITSU_AuthorizedPrivilege':
result = self._assoc_authpriv() result = self._assoc_authpriv()
elif AssocClass == 'FUJITSU_AllocatedFromStoragePool':
result = self._assocnames_pool(objectpath)
else: else:
result = self._default_assoc(objectpath) result = self._default_assoc(objectpath)
@ -329,6 +431,9 @@ class FakeEternusConnection(object):
result = self._assocnames_tcp_endpoint() result = self._assocnames_tcp_endpoint()
elif ResultClass == 'FUJITSU_AffinityGroupController': elif ResultClass == 'FUJITSU_AffinityGroupController':
result = self._assocnames_afngroup() result = self._assocnames_afngroup()
elif (ResultClass == 'FUJITSU_StorageVolume' and
AssocClass == 'FUJITSU_AllocatedFromStoragePool'):
result = self._assocnames_volumelist(objectpath)
else: else:
result = self._default_assocnames(objectpath) result = self._default_assocnames(objectpath)
@ -407,6 +512,26 @@ class FakeEternusConnection(object):
def _assocnames_afngroup(self): def _assocnames_afngroup(self):
return self._enum_afntyservice() 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): def _default_assocnames(self, objectpath):
return objectpath return objectpath
@ -520,34 +645,48 @@ class FakeEternusConnection(object):
def _enum_pool_details(self, pooltype): def _enum_pool_details(self, pooltype):
pools = [] pools = []
pool = FJ_StoragePool() pool = FJ_StoragePool()
pool2 = FJ_StoragePool()
if pooltype == 'RAID': if pooltype == 'RAID':
pool['InstanceID'] = 'FUJITSU:RSP0004' pool['InstanceID'] = 'FUJITSU:RSP0004'
pool['CreationClassName'] = 'FUJITSU_RAIDStoragePool' pool['CreationClassName'] = 'FUJITSU_RAIDStoragePool'
pool['ElementName'] = 'abcd1234_OSVD' pool['ElementName'] = 'abcd1234_OSVD'
pool['TotalManagedSpace'] = 1170368102400 pool['TotalManagedSpace'] = 1170368102400
pool['RemainingManagedSpace'] = 1170368102400 pool['RemainingManagedSpace'] = 1170368102400 - 1 * units.Gi
pool.path = pool pool.path = FJ_StoragePool()
pool.path['InstanceID'] = 'FUJITSU:RSP0004'
pool.path.classname = 'FUJITSU_RAIDStoragePool' 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: else:
pool = FJ_StoragePool()
pool['InstanceID'] = 'FUJITSU:TPP0004' pool['InstanceID'] = 'FUJITSU:TPP0004'
pool['CreationClassName'] = 'FUJITSU_ThinProvisioningPool' pool['CreationClassName'] = 'FUJITSU_ThinProvisioningPool'
pool['ElementName'] = 'abcd1234_TPP' pool['ElementName'] = 'abcd1234_TPP'
pool['TotalManagedSpace'] = 1170368102400 pool['TotalManagedSpace'] = 1170368102400
pool['RemainingManagedSpace'] = 1170368102400 pool['RemainingManagedSpace'] = 1170368102400 - 2 * units.Gi
pool.path = pool pool.path = FJ_StoragePool()
pool.path['InstanceID'] = 'FUJITSU:TPP0004'
pool.path.classname = 'FUJITSU_ThinProvisioningPool' pool.path.classname = 'FUJITSU_ThinProvisioningPool'
pools.append(pool) pools.append(pool)
return pools return pools
def _enum_volumes(self): def _enum_volumes(self, force=False):
volumes = [] volumes = []
if VOL_STAT == '0': if VOL_STAT == '0' and not force:
return volumes return volumes
volume = FJ_StorageVolume() volume = FJ_StorageVolume()
volume['name'] = TEST_VOLUME['name'] volume['name'] = TEST_VOLUME['name']
volume['poolpath'] = 'FUJITSU:TPP0004'
volume['CreationClassName'] = 'FUJITSU_StorageVolume' volume['CreationClassName'] = 'FUJITSU_StorageVolume'
volume['Name'] = FAKE_LUN_ID1 volume['Name'] = FAKE_LUN_ID1
volume['DeviceID'] = FAKE_LUN_ID1 volume['DeviceID'] = FAKE_LUN_ID1
@ -558,20 +697,46 @@ class FakeEternusConnection(object):
volume.path = volume volume.path = volume
volume.path.classname = volume['CreationClassName'] volume.path.classname = volume['CreationClassName']
name = {} name = {
name['classname'] = 'FUJITSU_StorageVolume' 'classname': 'FUJITSU_StorageVolume',
keys = {} 'keybindings': {
keys['CreationClassName'] = 'FUJITSU_StorageVolume' 'CreationClassName': 'FUJITSU_StorageVolume',
keys['SystemName'] = STORAGE_SYSTEM 'SystemName': STORAGE_SYSTEM,
keys['DeviceID'] = volume['DeviceID'] 'DeviceID': volume['DeviceID'],
keys['SystemCreationClassName'] = 'FUJITSU_StorageComputerSystem' 'SystemCreationClassName': 'FUJITSU_StorageComputerSystem',
name['keybindings'] = keys },
}
volume['provider_location'] = str(name) volume['provider_location'] = str(name)
volumes.append(volume) 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 = FJ_StorageVolume()
snap_vol['name'] = TEST_SNAP['name'] snap_vol['name'] = TEST_SNAP['name']
snap_vol['poolpath'] = 'FUJITSU:RSP0004'
snap_vol['CreationClassName'] = 'FUJITSU_StorageVolume' snap_vol['CreationClassName'] = 'FUJITSU_StorageVolume'
snap_vol['Name'] = FAKE_LUN_ID2 snap_vol['Name'] = FAKE_LUN_ID2
snap_vol['DeviceID'] = FAKE_LUN_ID2 snap_vol['DeviceID'] = FAKE_LUN_ID2
@ -581,20 +746,21 @@ class FakeEternusConnection(object):
snap_vol.path = snap_vol snap_vol.path = snap_vol
snap_vol.path.classname = snap_vol['CreationClassName'] snap_vol.path.classname = snap_vol['CreationClassName']
name2 = {} name2 = {
name2['classname'] = 'FUJITSU_StorageVolume' 'classname': 'FUJITSU_StorageVolume',
keys2 = {} 'keybindings': {
keys2['CreationClassName'] = 'FUJITSU_StorageVolume' 'CreationClassName': 'FUJITSU_StorageVolume',
keys2['SystemName'] = STORAGE_SYSTEM 'SystemName': STORAGE_SYSTEM,
keys2['DeviceID'] = snap_vol['DeviceID'] 'DeviceID': snap_vol['DeviceID'],
keys2['SystemCreationClassName'] = 'FUJITSU_StorageComputerSystem' 'SystemCreationClassName': 'FUJITSU_StorageComputerSystem',
name2['keybindings'] = keys2 },
}
snap_vol['provider_location'] = str(name2) snap_vol['provider_location'] = str(name2)
volumes.append(snap_vol) volumes.append(snap_vol)
clone_vol = FJ_StorageVolume() clone_vol = FJ_StorageVolume()
clone_vol['name'] = TEST_CLONE['name'] clone_vol['name'] = TEST_CLONE['name']
clone_vol['poolpath'] = 'FUJITSU:TPP0004'
clone_vol['CreationClassName'] = 'FUJITSU_StorageVolume' clone_vol['CreationClassName'] = 'FUJITSU_StorageVolume'
clone_vol['ElementName'] = TEST_CLONE['name'] clone_vol['ElementName'] = TEST_CLONE['name']
clone_vol['DeviceID'] = FAKE_LUN_ID2 clone_vol['DeviceID'] = FAKE_LUN_ID2
@ -678,7 +844,6 @@ class FakeEternusConnection(object):
return targetlist return targetlist
def _getinstance_storagevolume(self, objpath): def _getinstance_storagevolume(self, objpath):
foundinstance = None
instance = FJ_StorageVolume() instance = FJ_StorageVolume()
volumes = self._enum_volumes() volumes = self._enum_volumes()
for volume in volumes: for volume in volumes:
@ -713,6 +878,8 @@ class FJFCDriverTestCase(test.TestCase):
# Make fake Object by using mock as configuration object. # Make fake Object by using mock as configuration object.
self.configuration = mock.Mock(spec=conf.Configuration) self.configuration = mock.Mock(spec=conf.Configuration)
self.configuration.cinder_eternus_config_file = self.config_file.name 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.mock_object(dx_common.FJDXCommon, '_get_eternus_connection',
self.fake_eternus_connection) self.fake_eternus_connection)
@ -725,22 +892,27 @@ class FJFCDriverTestCase(test.TestCase):
driver = dx_fc.FJDXFCDriver(configuration=self.configuration) driver = dx_fc.FJDXFCDriver(configuration=self.configuration)
self.driver = driver self.driver = driver
def fake_safe_get(self, str=None):
return str
def fake_eternus_connection(self): def fake_eternus_connection(self):
conn = FakeEternusConnection() conn = FakeEternusConnection()
return conn return conn
def test_get_volume_stats(self): def test_get_volume_stats(self):
ret = self.driver.get_volume_stats(True) ret = self.driver.get_volume_stats(True)
stats = {'vendor_name': ret['vendor_name'],
'total_capacity_gb': ret['total_capacity_gb'], self.assertEqual(FAKE_STATS2, ret)
'free_capacity_gb': ret['free_capacity_gb']}
self.assertEqual(FAKE_STATS, stats)
def test_create_and_delete_volume(self): def test_create_and_delete_volume(self):
model_info = self.driver.create_volume(TEST_VOLUME) model_info = self.driver.create_volume(TEST_VOLUME)
self.assertEqual(FAKE_MODEL_INFO1, model_info) 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_VOLUME)
self.driver.delete_volume(TEST_VOLUME2)
@mock.patch.object(dx_common.FJDXCommon, '_get_mapdata') @mock.patch.object(dx_common.FJDXCommon, '_get_mapdata')
def test_map_unmap(self, mock_mapdata): def test_map_unmap(self, mock_mapdata):
@ -816,7 +988,16 @@ class FJFCDriverTestCase(test.TestCase):
model_info = self.driver.create_volume(TEST_VOLUME) model_info = self.driver.create_volume(TEST_VOLUME)
self.assertEqual(FAKE_MODEL_INFO1, model_info) 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): class FJISCSIDriverTestCase(test.TestCase):
@ -835,6 +1016,8 @@ class FJISCSIDriverTestCase(test.TestCase):
# Make fake Object by using mock as configuration object. # Make fake Object by using mock as configuration object.
self.configuration = mock.Mock(spec=conf.Configuration) self.configuration = mock.Mock(spec=conf.Configuration)
self.configuration.cinder_eternus_config_file = self.config_file.name 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.mock_object(dx_common.FJDXCommon, '_get_eternus_connection',
self.fake_eternus_connection) self.fake_eternus_connection)
@ -850,6 +1033,9 @@ class FJISCSIDriverTestCase(test.TestCase):
driver = dx_iscsi.FJDXISCSIDriver(configuration=self.configuration) driver = dx_iscsi.FJDXISCSIDriver(configuration=self.configuration)
self.driver = driver self.driver = driver
def fake_safe_get(self, str=None):
return str
def fake_eternus_connection(self): def fake_eternus_connection(self):
conn = FakeEternusConnection() conn = FakeEternusConnection()
return conn return conn
@ -867,16 +1053,18 @@ class FJISCSIDriverTestCase(test.TestCase):
def test_get_volume_stats(self): def test_get_volume_stats(self):
ret = self.driver.get_volume_stats(True) ret = self.driver.get_volume_stats(True)
stats = {'vendor_name': ret['vendor_name'],
'total_capacity_gb': ret['total_capacity_gb'], self.assertEqual(FAKE_STATS, ret)
'free_capacity_gb': ret['free_capacity_gb']}
self.assertEqual(FAKE_STATS, stats)
def test_create_and_delete_volume(self): def test_create_and_delete_volume(self):
model_info = self.driver.create_volume(TEST_VOLUME) model_info = self.driver.create_volume(TEST_VOLUME)
self.assertEqual(FAKE_MODEL_INFO1, model_info) 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_VOLUME)
self.driver.delete_volume(TEST_VOLUME2)
def test_map_unmap(self): def test_map_unmap(self):
fake_mapdata = self.fake_get_mapdata(None, {}, None) fake_mapdata = self.fake_get_mapdata(None, {}, None)
@ -942,4 +1130,13 @@ class FJISCSIDriverTestCase(test.TestCase):
model_info = self.driver.create_volume(TEST_VOLUME) model_info = self.driver.create_volume(TEST_VOLUME)
self.assertEqual(FAKE_MODEL_INFO1, model_info) 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', RAIDGROUP: 'RAID_GROUP',
TPPOOL: 'Thinporvisioning_POOL', TPPOOL: 'Thinporvisioning_POOL',
} }
POOL_TYPE_list = [
'RAID',
'TPP'
]
OPERATION_dic = { OPERATION_dic = {
SNAPOPC: RETURN_TO_RESOURCEPOOL, SNAPOPC: RETURN_TO_RESOURCEPOOL,
OPC: DETACH, OPC: DETACH,

View File

@ -36,6 +36,7 @@ from cinder.i18n import _
from cinder import utils from cinder import utils
from cinder.volume import configuration as conf from cinder.volume import configuration as conf
from cinder.volume.drivers.fujitsu.eternus_dx import constants as CONSTANTS from cinder.volume.drivers.fujitsu.eternus_dx import constants as CONSTANTS
from cinder.volume import volume_utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONF = cfg.CONF CONF = cfg.CONF
@ -61,10 +62,7 @@ class FJDXCommon(object):
VERSION = "1.3.0" VERSION = "1.3.0"
stats = { stats = {
'driver_version': VERSION, 'driver_version': VERSION,
'free_capacity_gb': 0,
'reserved_percentage': 0,
'storage_protocol': None, 'storage_protocol': None,
'total_capacity_gb': 0,
'vendor_name': 'FUJITSU', 'vendor_name': 'FUJITSU',
'QoS_support': False, 'QoS_support': False,
'volume_backend_name': None, 'volume_backend_name': None,
@ -82,6 +80,7 @@ class FJDXCommon(object):
# Get iSCSI ipaddress from driver configuration file. # Get iSCSI ipaddress from driver configuration file.
self.configuration.iscsi_ip_address = ( self.configuration.iscsi_ip_address = (
self._get_drvcfg('EternusISCSIIP')) self._get_drvcfg('EternusISCSIIP'))
self.conn = None
@staticmethod @staticmethod
def get_driver_options(): def get_driver_options():
@ -103,7 +102,7 @@ class FJDXCommon(object):
'volumesize': volumesize}) 'volumesize': volumesize})
# get poolname from driver configuration file # 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 # Existence check the pool
pool = self._find_pool(eternus_pool) pool = self._find_pool(eternus_pool)
@ -216,6 +215,47 @@ class FJDXCommon(object):
return (element_path, metadata) 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): def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot.""" """Creates a volume from a snapshot."""
LOG.debug('create_volume_from_snapshot, ' LOG.debug('create_volume_from_snapshot, '
@ -716,14 +756,12 @@ class FJDXCommon(object):
'vol_instance': vol_instance.path}) 'vol_instance': vol_instance.path})
# Get poolname from driver configuration file. # Get poolname from driver configuration file.
eternus_pool = self._get_drvcfg('EternusPool') pool_name, pool = self._find_pool_from_volume(vol_instance)
# Check the existence of volume.
pool = self._find_pool(eternus_pool)
if pool is None: if pool is None:
msg = (_('extend_volume, ' msg = (_('extend_volume, '
'eternus_pool: %(eternus_pool)s, ' 'eternus_pool: %(eternus_pool)s, '
'pool not found.') 'pool not found.')
% {'eternus_pool': eternus_pool}) % {'eternus_pool': pool_name})
LOG.error(msg) LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg) raise exception.VolumeBackendAPIException(data=msg)
@ -734,14 +772,14 @@ class FJDXCommon(object):
pooltype = CONSTANTS.TPPOOL pooltype = CONSTANTS.TPPOOL
configservice = self._find_eternus_service(CONSTANTS.STOR_CONF) configservice = self._find_eternus_service(CONSTANTS.STOR_CONF)
if configservice is None: if not configservice:
msg = (_('extend_volume, volume: %(volume)s, ' msg = (_('extend_volume, volume: %(volume)s, '
'volumename: %(volumename)s, ' 'volumename: %(volumename)s, '
'eternus_pool: %(eternus_pool)s, ' 'eternus_pool: %(eternus_pool)s, '
'Storage Configuration Service not found.') 'Storage Configuration Service not found.')
% {'volume': volume, % {'volume': volume,
'volumename': volumename, 'volumename': volumename,
'eternus_pool': eternus_pool}) 'eternus_pool': pool_name})
LOG.error(msg) LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg) raise exception.VolumeBackendAPIException(data=msg)
@ -755,7 +793,7 @@ class FJDXCommon(object):
'TheElement: %(vol_instance)s.', 'TheElement: %(vol_instance)s.',
{'service': configservice, {'service': configservice,
'volumename': volumename, 'volumename': volumename,
'eternus_pool': eternus_pool, 'eternus_pool': pool_name,
'pooltype': pooltype, 'pooltype': pooltype,
'volumesize': volumesize, 'volumesize': volumesize,
'vol_instance': vol_instance.path}) 'vol_instance': vol_instance.path})
@ -793,48 +831,21 @@ class FJDXCommon(object):
{'volumename': volumename, {'volumename': volumename,
'rc': rc, 'rc': rc,
'errordesc': errordesc, 'errordesc': errordesc,
'eternus_pool': eternus_pool, 'eternus_pool': pool_name,
'pooltype': CONSTANTS.POOL_TYPE_dic[pooltype]}) 'pooltype': CONSTANTS.POOL_TYPE_dic[pooltype]})
return eternus_pool return pool_name
@lockutils.synchronized('ETERNUS-update', 'cinder-', True) @lockutils.synchronized('ETERNUS-update', 'cinder-', True)
def update_volume_stats(self): def update_volume_stats(self):
"""get pool capacity.""" """Get pool capacity."""
self.conn = self._get_eternus_connection() 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) return (self.stats, poolname_list)
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)
def _get_mapdata(self, vol_instance, connector, target_portlist): def _get_mapdata(self, vol_instance, connector, target_portlist):
"""return mapping information.""" """return mapping information."""
@ -1012,9 +1023,9 @@ class FJDXCommon(object):
return mapdata return mapdata
def _get_drvcfg(self, tagname, filename=None, multiple=False): def _get_drvcfg(self, tagname, filename=None, multiple=False):
"""read from driver configuration file.""" """Read from driver configuration file."""
if filename is None: if not filename:
# set default configuration file name # Set default configuration file name.
filename = self.configuration.cinder_eternus_config_file filename = self.configuration.cinder_eternus_config_file
LOG.debug("_get_drvcfg, input[%(filename)s][%(tagname)s].", LOG.debug("_get_drvcfg, input[%(filename)s][%(tagname)s].",
@ -1023,7 +1034,6 @@ class FJDXCommon(object):
tree = ET.parse(filename) tree = ET.parse(filename)
elem = tree.getroot() elem = tree.getroot()
ret = None
if not multiple: if not multiple:
ret = elem.findtext(".//" + tagname) ret = elem.findtext(".//" + tagname)
else: else:
@ -1141,6 +1151,116 @@ class FJDXCommon(object):
LOG.debug('_find_pool, pool: %s.', ret) LOG.debug('_find_pool, pool: %s.', ret)
return 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): def _find_eternus_service(self, classname):
"""find CIM instance about service information.""" """find CIM instance about service information."""
LOG.debug('_find_eternus_service, ' LOG.debug('_find_eternus_service, '
@ -1176,7 +1296,8 @@ class FJDXCommon(object):
{'a': classname, {'a': classname,
'b': instanceNameList, 'b': instanceNameList,
'c': param_dict}) 'c': param_dict})
rc = None
retdata = None
# Use InvokeMethod. # Use InvokeMethod.
try: try:
rc, retdata = self.conn.InvokeMethod( rc, retdata = self.conn.InvokeMethod(
@ -1223,11 +1344,14 @@ class FJDXCommon(object):
@lockutils.synchronized('ETERNUS-SMIS-other', 'cinder-', True) @lockutils.synchronized('ETERNUS-SMIS-other', 'cinder-', True)
@utils.retry(exception.VolumeBackendAPIException) @utils.retry(exception.VolumeBackendAPIException)
def _enum_eternus_instances(self, classname): def _enum_eternus_instances(self, classname, conn=None, **param_dict):
"""Enumerate Instances.""" """Enumerate Instances."""
LOG.debug('_enum_eternus_instances, classname: %s.', classname) 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)) LOG.debug('_enum_eternus_instances, enum %d instances.', len(ret))
return ret return ret
@ -1258,26 +1382,32 @@ class FJDXCommon(object):
@lockutils.synchronized('ETERNUS-SMIS-other', 'cinder-', True) @lockutils.synchronized('ETERNUS-SMIS-other', 'cinder-', True)
@utils.retry(exception.VolumeBackendAPIException) @utils.retry(exception.VolumeBackendAPIException)
def _assoc_eternus(self, classname, **param_dict): def _assoc_eternus(self, classname, conn=None, **param_dict):
"""Associator.""" """Associator."""
LOG.debug('_assoc_eternus, ' LOG.debug('_assoc_eternus, '
'classname: %(cls)s, param: %(param)s.', 'classname: %(cls)s, param: %(param)s.',
{'cls': classname, 'param': param_dict}) {'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)) LOG.debug('_assoc_eternus, enum %d instances.', len(ret))
return ret return ret
@lockutils.synchronized('ETERNUS-SMIS-other', 'cinder-', True) @lockutils.synchronized('ETERNUS-SMIS-other', 'cinder-', True)
@utils.retry(exception.VolumeBackendAPIException) @utils.retry(exception.VolumeBackendAPIException)
def _assoc_eternus_names(self, classname, **param_dict): def _assoc_eternus_names(self, classname, conn=None, **param_dict):
"""Associator Names.""" """Associator Names."""
LOG.debug('_assoc_eternus_names, ' LOG.debug('_assoc_eternus_names, '
'classname: %(cls)s, param: %(param)s.', 'classname: %(cls)s, param: %(param)s.',
{'cls': classname, 'param': param_dict}) {'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)) LOG.debug('_assoc_eternus_names, enum %d names.', len(ret))
return ret return ret
@ -2103,3 +2233,66 @@ class FJDXCommon(object):
result = num result = num
return result 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> <EternusUser>smisuser</EternusUser>
<EternusPassword>smispassword</EternusPassword> <EternusPassword>smispassword</EternusPassword>
<EternusPool>raid5_0001</EternusPool> <EternusPool>raid5_0001</EternusPool>
<EternusPool>tpp_0001</EternusPool>
<EternusPool>raid_0002</EternusPool>
<EternusSnapPool>raid5_0001</EternusSnapPool> <EternusSnapPool>raid5_0001</EternusSnapPool>
</FUJITSU> </FUJITSU>
@ -146,6 +148,8 @@ Configuration
<EternusUser>smisuser</EternusUser> <EternusUser>smisuser</EternusUser>
<EternusPassword>smispassword</EternusPassword> <EternusPassword>smispassword</EternusPassword>
<EternusPool>raid5_0001</EternusPool> <EternusPool>raid5_0001</EternusPool>
<EternusPool>tpp_0001</EternusPool>
<EternusPool>raid_0002</EternusPool>
<EternusSnapPool>raid5_0001</EternusSnapPool> <EternusSnapPool>raid5_0001</EternusSnapPool>
<EternusISCSIIP>1.1.1.1</EternusISCSIIP> <EternusISCSIIP>1.1.1.1</EternusISCSIIP>
<EternusISCSIIP>1.1.1.2</EternusISCSIIP> <EternusISCSIIP>1.1.1.2</EternusISCSIIP>
@ -169,7 +173,7 @@ Configuration
``EternusPassword`` ``EternusPassword``
Password for the SMI-S connection of the ETERNUS DX. Password for the SMI-S connection of the ETERNUS DX.
``EternusPool`` ``EternusPool`` (Multiple setting allowed)
Storage pool name for volumes. Storage pool name for volumes.
Enter RAID Group name or TPP name in the ETERNUS DX. Enter RAID Group name or TPP name in the ETERNUS DX.
@ -188,6 +192,8 @@ Configuration
and cannot specify TPP name. and cannot specify TPP name.
* You can specify the same RAID Group name for ``EternusPool`` and ``EternusSnapPool`` * You can specify the same RAID Group name for ``EternusPool`` and ``EternusSnapPool``
if you create volumes and snapshots on a same storage pool. 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 Configuration example
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~

View File

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