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.