Fujitsu Driver: Add QoS support
Added support for QoS in the Fujitsu driver. The supported qos specs are: * maxBWS * read_bytes_sec * write_bytes_sec * total_bytes_sec * read_iops_sec * write_iops_sec * total_iops_sec Change-Id: I0f12c90f9483393501cf5788b66f724d47e72c8e
This commit is contained in:
parent
f79048d282
commit
e954ba02de
@ -91,6 +91,16 @@ TEST_CLONE = {
|
||||
'host': 'controller@113#abcd1234_TPP'
|
||||
}
|
||||
|
||||
TEST_VOLUME_QOS = {
|
||||
'id': '7bd8b81f-137d-4140-85ce-d00281c91c84',
|
||||
'name': 'qos',
|
||||
'display_name': 'qos',
|
||||
'provider_location': None,
|
||||
'metadata': {},
|
||||
'size': 1,
|
||||
'host': 'controller@113#abcd1234_TPP'
|
||||
}
|
||||
|
||||
ISCSI_INITIATOR = 'iqn.1993-08.org.debian:01:8261afe17e4c'
|
||||
ISCSI_TARGET_IP = '10.0.0.3'
|
||||
ISCSI_TARGET_IQN = 'iqn.2000-09.com.fujitsu:storage-system.eternus-dxl:0'
|
||||
@ -98,6 +108,9 @@ FC_TARGET_WWN = ['500000E0DA000001', '500000E0DA000002']
|
||||
TEST_WWPN = ['0123456789111111', '0123456789222222']
|
||||
TEST_CONNECTOR = {'initiator': ISCSI_INITIATOR, 'wwpns': TEST_WWPN}
|
||||
|
||||
STORAGE_IP = '172.16.0.2'
|
||||
TEST_USER = 'testuser'
|
||||
TEST_PASSWORD = 'testpassword'
|
||||
|
||||
STOR_CONF_SVC = 'FUJITSU_StorageConfigurationService'
|
||||
CTRL_CONF_SVC = 'FUJITSU_ControllerConfigurationService'
|
||||
@ -128,6 +141,9 @@ FAKE_LUN_NO2 = '0x001E'
|
||||
# Volume2 in pool abcd1234_RG
|
||||
FAKE_LUN_ID3 = '600000E00D2800000028075301140000'
|
||||
FAKE_LUN_NO3 = '0x0114'
|
||||
# VolumeQoS in pool abcd1234_TPP
|
||||
FAKE_LUN_ID_QOS = '600000E00D2A0000002A011500140000'
|
||||
FAKE_LUN_NO_QOS = '0x0014'
|
||||
FAKE_SYSTEM_NAME = 'ET603SA4621302115'
|
||||
# abcd1234_TPP pool
|
||||
FAKE_USEGB = 2.0
|
||||
@ -160,20 +176,20 @@ FAKE_POOLS = [{
|
||||
}]
|
||||
|
||||
FAKE_STATS = {
|
||||
'driver_version': '1.3.0',
|
||||
'driver_version': '1.4.0',
|
||||
'storage_protocol': 'iSCSI',
|
||||
'vendor_name': 'FUJITSU',
|
||||
'QoS_support': False,
|
||||
'QoS_support': True,
|
||||
'volume_backend_name': 'volume_backend_name',
|
||||
'shared_targets': True,
|
||||
'backend_state': 'up',
|
||||
'pools': FAKE_POOLS,
|
||||
}
|
||||
FAKE_STATS2 = {
|
||||
'driver_version': '1.3.0',
|
||||
'driver_version': '1.4.0',
|
||||
'storage_protocol': 'FC',
|
||||
'vendor_name': 'FUJITSU',
|
||||
'QoS_support': False,
|
||||
'QoS_support': True,
|
||||
'volume_backend_name': 'volume_backend_name',
|
||||
'shared_targets': True,
|
||||
'backend_state': 'up',
|
||||
@ -183,37 +199,58 @@ FAKE_STATS2 = {
|
||||
|
||||
# Volume1 in pool abcd1234_TPP
|
||||
FAKE_KEYBIND1 = {
|
||||
'CreationClassName': 'FUJITSU_StorageVolume',
|
||||
'SystemName': STORAGE_SYSTEM,
|
||||
'DeviceID': FAKE_LUN_ID1,
|
||||
'SystemCreationClassName': 'FUJITSU_StorageComputerSystem',
|
||||
}
|
||||
|
||||
# Volume2 in pool abcd1234_RG
|
||||
FAKE_KEYBIND3 = {
|
||||
'CreationClassName': 'FUJITSU_StorageVolume',
|
||||
'SystemName': STORAGE_SYSTEM,
|
||||
'DeviceID': FAKE_LUN_ID3,
|
||||
'SystemCreationClassName': 'FUJITSU_StorageComputerSystem',
|
||||
}
|
||||
|
||||
# Volume QOS in pool abcd1234_TPP
|
||||
FAKE_KEYBIND_QOS = {
|
||||
'SystemName': STORAGE_SYSTEM,
|
||||
'DeviceID': FAKE_LUN_ID_QOS,
|
||||
}
|
||||
|
||||
# Volume1
|
||||
FAKE_LOCATION1 = {
|
||||
'classname': 'FUJITSU_StorageVolume',
|
||||
'keybindings': FAKE_KEYBIND1,
|
||||
'vol_name': 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
||||
}
|
||||
|
||||
# Clone Volume
|
||||
FAKE_CLONE_LOCATION = {
|
||||
'classname': 'FUJITSU_StorageVolume',
|
||||
'keybindings': FAKE_KEYBIND1,
|
||||
'vol_name': 'FJosv_UkCZqMFZW3SU_JzxjHiKfg=='
|
||||
}
|
||||
|
||||
# Volume2
|
||||
FAKE_LOCATION3 = {
|
||||
'classname': 'FUJITSU_StorageVolume',
|
||||
'keybindings': FAKE_KEYBIND3,
|
||||
'vol_name': 'FJosv_4whcadwDac7ANKHA2O719A=='
|
||||
}
|
||||
|
||||
# VolumeQOS
|
||||
FAKE_LOCATION_QOS = {
|
||||
'classname': 'FUJITSU_StorageVolume',
|
||||
'keybindings': FAKE_KEYBIND_QOS,
|
||||
'vol_name': 'FJosv_mIsapeuZOaSXz4LYTqFcug=='
|
||||
}
|
||||
|
||||
# Volume1 metadata info.
|
||||
# Here is a misspelling, and the right value should be "Thinprovisioning_POOL".
|
||||
# It would not be compatible with the metadata of the legacy volumes,
|
||||
# so this spelling mistake needs to be retained.
|
||||
FAKE_LUN_META1 = {
|
||||
'FJ_Pool_Type': 'Thinporvisioning_POOL',
|
||||
'FJ_Volume_No': FAKE_LUN_NO1,
|
||||
'FJ_Volume_Name': u'FJosv_0qJ4rpOHgFE8ipcJOMfBmg==',
|
||||
'FJ_Volume_Name': 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg==',
|
||||
'FJ_Pool_Name': STORAGE_TYPE,
|
||||
'FJ_Backend': FAKE_SYSTEM_NAME,
|
||||
}
|
||||
@ -222,10 +259,20 @@ FAKE_LUN_META1 = {
|
||||
FAKE_LUN_META3 = {
|
||||
'FJ_Pool_Type': 'RAID_GROUP',
|
||||
'FJ_Volume_No': FAKE_LUN_NO3,
|
||||
'FJ_Volume_Name': u'FJosv_4whcadwDac7ANKHA2O719A==',
|
||||
'FJ_Volume_Name': 'FJosv_4whcadwDac7ANKHA2O719A==',
|
||||
'FJ_Pool_Name': STORAGE_TYPE2,
|
||||
'FJ_Backend': FAKE_SYSTEM_NAME,
|
||||
}
|
||||
|
||||
# VolumeQOS metadata info
|
||||
FAKE_LUN_META_QOS = {
|
||||
'FJ_Pool_Type': 'Thinporvisioning_POOL',
|
||||
'FJ_Volume_No': FAKE_LUN_NO_QOS,
|
||||
'FJ_Volume_Name': 'FJosv_mIsapeuZOaSXz4LYTqFcug==',
|
||||
'FJ_Pool_Name': STORAGE_TYPE,
|
||||
'FJ_Backend': FAKE_SYSTEM_NAME,
|
||||
}
|
||||
|
||||
# Volume1
|
||||
FAKE_MODEL_INFO1 = {
|
||||
'provider_location': six.text_type(FAKE_LOCATION1),
|
||||
@ -236,17 +283,21 @@ FAKE_MODEL_INFO3 = {
|
||||
'provider_location': six.text_type(FAKE_LOCATION3),
|
||||
'metadata': FAKE_LUN_META3,
|
||||
}
|
||||
# VoluemQOS
|
||||
FAKE_MODEL_INFO_QOS = {
|
||||
'provider_location': six.text_type(FAKE_LOCATION_QOS),
|
||||
'metadata': FAKE_LUN_META_QOS,
|
||||
}
|
||||
|
||||
FAKE_KEYBIND2 = {
|
||||
'CreationClassName': 'FUJITSU_StorageVolume',
|
||||
'SystemName': STORAGE_SYSTEM,
|
||||
'DeviceID': FAKE_LUN_ID2,
|
||||
'SystemCreationClassName': 'FUJITSU_StorageComputerSystem',
|
||||
}
|
||||
|
||||
FAKE_LOCATION2 = {
|
||||
'classname': 'FUJITSU_StorageVolume',
|
||||
'keybindings': FAKE_KEYBIND2,
|
||||
'vol_name': 'FJosv_OgEZj1mSvKRvIKOExKktlg=='
|
||||
}
|
||||
|
||||
FAKE_SNAP_INFO = {
|
||||
@ -256,16 +307,36 @@ FAKE_SNAP_INFO = {
|
||||
FAKE_LUN_META2 = {
|
||||
'FJ_Pool_Type': 'Thinporvisioning_POOL',
|
||||
'FJ_Volume_No': FAKE_LUN_NO1,
|
||||
'FJ_Volume_Name': u'FJosv_UkCZqMFZW3SU_JzxjHiKfg==',
|
||||
'FJ_Volume_Name': 'FJosv_OgEZj1mSvKRvIKOExKktlg==',
|
||||
'FJ_Pool_Name': STORAGE_TYPE,
|
||||
'FJ_Backend': FAKE_SYSTEM_NAME,
|
||||
}
|
||||
|
||||
FAKE_CLONE_LUN_META = {
|
||||
'FJ_Pool_Type': 'Thinporvisioning_POOL',
|
||||
'FJ_Volume_No': FAKE_LUN_NO1,
|
||||
'FJ_Volume_Name': 'FJosv_UkCZqMFZW3SU_JzxjHiKfg==',
|
||||
'FJ_Pool_Name': STORAGE_TYPE,
|
||||
'FJ_Backend': FAKE_SYSTEM_NAME,
|
||||
}
|
||||
|
||||
FAKE_MODEL_INFO2 = {
|
||||
'provider_location': six.text_type(FAKE_LOCATION1),
|
||||
'metadata': FAKE_LUN_META2,
|
||||
'provider_location': six.text_type(FAKE_CLONE_LOCATION),
|
||||
'metadata': FAKE_CLONE_LUN_META,
|
||||
}
|
||||
|
||||
FAKE_CLI_OUTPUT = {
|
||||
"result": 0,
|
||||
'rc': '0',
|
||||
"message": 'TEST_MESSAGE'
|
||||
}
|
||||
|
||||
# Constants for QOS
|
||||
MAX_IOPS = 4294967295
|
||||
MAX_THROUGHPUT = 2097151
|
||||
MIN_IOPS = 1
|
||||
MIN_THROUGHPUT = 1
|
||||
|
||||
|
||||
class FJ_StorageVolume(dict):
|
||||
pass
|
||||
@ -289,6 +360,32 @@ class FakeCIMInstanceName(dict):
|
||||
instancename.namespace = 'root/eternus'
|
||||
return instancename
|
||||
|
||||
def fake_enumerateinstances(self):
|
||||
instancename_1 = FakeCIMInstanceName()
|
||||
|
||||
ret = []
|
||||
instancename_1['ElementName'] = 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
||||
instancename_1['Purpose'] = '00228+0x06'
|
||||
instancename_1['Name'] = None
|
||||
instancename_1['DeviceID'] = FAKE_LUN_ID1
|
||||
instancename_1['SystemName'] = STORAGE_SYSTEM
|
||||
ret.append(instancename_1)
|
||||
instancename_1.path = ''
|
||||
instancename_1.classname = 'FUJITSU_StorageVolume'
|
||||
|
||||
snaps = FakeCIMInstanceName()
|
||||
snaps['ElementName'] = 'FJosv_OgEZj1mSvKRvIKOExKktlg=='
|
||||
snaps['Name'] = None
|
||||
ret.append(snaps)
|
||||
snaps.path = ''
|
||||
|
||||
map = FakeCIMInstanceName()
|
||||
map['ElementName'] = 'FJosv_hhJsV9lcMBvAPADrGqucwg=='
|
||||
map['Name'] = None
|
||||
ret.append(map)
|
||||
map.path = ''
|
||||
return ret
|
||||
|
||||
|
||||
class FakeEternusConnection(object):
|
||||
def InvokeMethod(self, MethodName, Service, ElementName=None, InPool=None,
|
||||
@ -383,6 +480,9 @@ class FakeEternusConnection(object):
|
||||
result = self._enum_scsiport_endpoint()
|
||||
elif name == 'FUJITSU_StorageHardwareID':
|
||||
result = None
|
||||
elif name == 'FUJITSU_StorageVolume':
|
||||
instancename_1 = FakeCIMInstanceName()
|
||||
result = instancename_1.fake_enumerateinstances()
|
||||
else:
|
||||
result = None
|
||||
|
||||
@ -892,6 +992,10 @@ class FJFCDriverTestCase(test.TestCase):
|
||||
instancename.fake_create_eternus_instance_name)
|
||||
|
||||
self.mock_object(ssh_utils, 'SSHPool', mock.Mock())
|
||||
|
||||
self.mock_object(dx_common.FJDXCommon, '_get_qos_specs',
|
||||
return_value={})
|
||||
|
||||
self.mock_object(eternus_dx_cli.FJDXCLI, '_exec_cli_with_eternus',
|
||||
self.fake_exec_cli_with_eternus)
|
||||
# Set fc driver to self.driver.
|
||||
@ -900,11 +1004,12 @@ class FJFCDriverTestCase(test.TestCase):
|
||||
|
||||
def fake_exec_cli_with_eternus(self, exec_cmdline):
|
||||
if exec_cmdline == "show users":
|
||||
ret = ('\r\nCLI> show users\r\n00\r\n'
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n'
|
||||
'3B\r\nf.ce\tMaintainer\t01\t00'
|
||||
'\t00\t00\r\ntestuser\tSoftware'
|
||||
'\t01\t01\t00\t00\r\nCLI> ')
|
||||
return ret
|
||||
'\t01\t01\t00\t00\r\nCLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('set volume-qos'):
|
||||
ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
|
||||
elif exec_cmdline.startswith('show volumes'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n0560\r\n0000'
|
||||
'\tFJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
||||
@ -914,14 +1019,69 @@ class FJFCDriverTestCase(test.TestCase):
|
||||
'\tFF\t20\tFF\tFFFF\t00'
|
||||
'\t600000E00D2A0000002A011500140000'
|
||||
'\t00\t00\tFF\tFF\tFFFFFFFF\t00'
|
||||
'\t00\tFF\r\n0001\tFJosv_UkCZqMFZW3SU_JzxjHiKfg=='
|
||||
'\t00\tFF\r\n0001\tFJosv_OgEZj1mSvKRvIKOExKktlg=='
|
||||
'\tA001\t0B\t00\t0000\tabcd1234_OSVD'
|
||||
'\t0000000000200000\t00\t00\t00000000'
|
||||
'\t0050\tFF\t00\tFF\tFF\t20\tFF\tFFFF'
|
||||
'\t00\t600000E00D2A0000002A0115001E0000'
|
||||
'\t00\t00\tFF\tFF\tFFFFFFFF\t00'
|
||||
'\t00\tFF' % exec_cmdline)
|
||||
return ret
|
||||
elif exec_cmdline.startswith('show enclosure-status'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n'
|
||||
'ETDX200S3_1\t01\tET203ACU\t4601417434\t280753\t20'
|
||||
'\t00\t00\t01\t02\t01001000\tV10L87-9000\t91\r\n02'
|
||||
'\r\n70000000\t30\r\nD0000100\t30\r\nCLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('show volume-qos'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n'
|
||||
'0002\t\r\n0000\tFJosv_0qJ4rpOHgFE8ipcJOMfBmg==\t0F'
|
||||
'\t\r\n0001\tFJosv_OgEZj1mSvKRvIKOExKktlg==\t0D'
|
||||
'\t\r\nCLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('show qos-bandwidth-limit'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n0010\t\r\n00\t0000ffff\t0000ffff'
|
||||
'\t0000ffff\t0000ffff\t0000ffff\t0000ffff\t0000ffff'
|
||||
'\t0000ffff\t0000ffff\t0000ffff\t0000ffff\t0000ffff\r\n'
|
||||
'01\t00000001\t00000001\t00000001\t00000001\t00000001'
|
||||
'\t00000001\t00000001\t00000001\t00000001\t00000001'
|
||||
'\t00000001\t00000001\r\n02\t00000002\t00000002\t00000002'
|
||||
'\t00000002\t00000002\t00000002\t00000002\t00000002'
|
||||
'\t00000002\t00000002\t00000002\t00000002\r\n03\t00000003'
|
||||
'\t00000003\t00000003\t00000003\t00000003\t00000003'
|
||||
'\t00000003\t00000003\t00000003\t00000003\t00000003'
|
||||
'\t00000003\r\n04\t00000004\t00000004\t00000004\t00000004'
|
||||
'\t00000004\t00000004\t00000004\t00000004\t00000004'
|
||||
'\t00000004\t00000004\t00000004\r\n05\t00000005\t00000005'
|
||||
'\t00000005\t00000005\t00000005\t00000005\t00000005'
|
||||
'\t00000005\t00000005\t00000005\t00000005\t00000005\r\n06'
|
||||
'\t00000006\t00000006\t00000006\t00000006\t00000006'
|
||||
'\t00000006\t00000006\t00000006\t00000006\t00000006'
|
||||
'\t00000006\t00000006\r\n07\t00000007\t00000007\t00000007'
|
||||
'\t00000007\t00000007\t00000007\t00000007\t00000007'
|
||||
'\t00000007\t00000007\t00000007\t00000007\r\n08\t00000008'
|
||||
'\t00000008\t00000008\t00000008\t00000008\t00000008'
|
||||
'\t00000008\t00000008\t00000008\t00000008\t00000008'
|
||||
'\t00000008\r\n09\t00000009\t00000009\t00000009\t00000009'
|
||||
'\t00000009\t00000009\t00000009\t00000009\t00000009'
|
||||
'\t00000009\t00000009\t00000009\r\n0a\t0000000a\t0000000a'
|
||||
'\t0000000a\t0000000a\t0000000a\t0000000a\t0000000a'
|
||||
'\t0000000a\t0000000a\t0000000a\t0000000a\t0000000a\r\n0b'
|
||||
'\t0000000b\t0000000b\t0000000b\t0000000b\t0000000b'
|
||||
'\t0000000b\t0000000b\t0000000b\t0000000b\t0000000b'
|
||||
'\t0000000b\t0000000b\r\n0c\t0000000c\t0000000c\t0000000c'
|
||||
'\t0000000c\t0000000c\t0000000c\t0000000c\t0000000c'
|
||||
'\t0000000c\t0000000c\t0000000c\t0000000c\r\n0d\t0000000d'
|
||||
'\t0000000d\t0000000d\t0000000d\t0000000d\t0000000d'
|
||||
'\t0000000d\t0000000d\t0000000d\t0000000d\t0000000d'
|
||||
'\t0000000d\r\n0e\t0000000e\t0000000e\t0000000e\t0000000e'
|
||||
'\t0000000e\t0000000e\t0000000e\t0000000e\t0000000e'
|
||||
'\t0000000e\t0000000e\t0000000e\r\n0f\t0000000f\t0000000f'
|
||||
'\t0000000f\t0000000f\t0000000f\t0000000f\t0000000f'
|
||||
'\t0000000f\t0000000f\t0000000f\t0000000f\t0000000f'
|
||||
'\r\nCLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('set qos-bandwidth-limit'):
|
||||
ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
|
||||
else:
|
||||
ret = None
|
||||
return ret
|
||||
|
||||
def fake_safe_get(self, str=None):
|
||||
return str
|
||||
@ -1030,6 +1190,14 @@ class FJFCDriverTestCase(test.TestCase):
|
||||
|
||||
self.driver.extend_volume(volume_info, 10)
|
||||
|
||||
def test_create_volume_with_qos(self):
|
||||
self.driver.common._get_qos_specs = mock.Mock()
|
||||
self.driver.common._get_qos_specs.return_value = {'maxBWS': '700'}
|
||||
self.driver.common._set_qos = mock.Mock()
|
||||
model_info = self.driver.create_volume(TEST_VOLUME_QOS)
|
||||
self.assertEqual(FAKE_MODEL_INFO_QOS, model_info)
|
||||
self.driver.common._set_qos.assert_called()
|
||||
|
||||
|
||||
class FJISCSIDriverTestCase(test.TestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -1061,6 +1229,10 @@ class FJISCSIDriverTestCase(test.TestCase):
|
||||
self.fake_get_mapdata)
|
||||
|
||||
self.mock_object(ssh_utils, 'SSHPool', mock.Mock())
|
||||
|
||||
self.mock_object(dx_common.FJDXCommon, '_get_qos_specs',
|
||||
return_value={})
|
||||
|
||||
self.mock_object(eternus_dx_cli.FJDXCLI, '_exec_cli_with_eternus',
|
||||
self.fake_exec_cli_with_eternus)
|
||||
# Set iscsi driver to self.driver.
|
||||
@ -1069,11 +1241,12 @@ class FJISCSIDriverTestCase(test.TestCase):
|
||||
|
||||
def fake_exec_cli_with_eternus(self, exec_cmdline):
|
||||
if exec_cmdline == "show users":
|
||||
ret = ('\r\nCLI> show users\r\n00\r\n'
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n'
|
||||
'3B\r\nf.ce\tMaintainer\t01\t00'
|
||||
'\t00\t00\r\ntestuser\tSoftware'
|
||||
'\t01\t01\t00\t00\r\nCLI> ')
|
||||
return ret
|
||||
'\t01\t01\t00\t00\r\nCLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('set volume-qos'):
|
||||
ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
|
||||
elif exec_cmdline.startswith('show volumes'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n0560\r\n0000'
|
||||
'\tFJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
||||
@ -1083,14 +1256,69 @@ class FJISCSIDriverTestCase(test.TestCase):
|
||||
'\tFF\t20\tFF\tFFFF\t00'
|
||||
'\t600000E00D2A0000002A011500140000'
|
||||
'\t00\t00\tFF\tFF\tFFFFFFFF\t00'
|
||||
'\t00\tFF\r\n0001\tFJosv_UkCZqMFZW3SU_JzxjHiKfg=='
|
||||
'\t00\tFF\r\n0001\tFJosv_OgEZj1mSvKRvIKOExKktlg=='
|
||||
'\tA001\t0B\t00\t0000\tabcd1234_OSVD'
|
||||
'\t0000000000200000\t00\t00\t00000000'
|
||||
'\t0050\tFF\t00\tFF\tFF\t20\tFF\tFFFF'
|
||||
'\t00\t600000E00D2A0000002A0115001E0000'
|
||||
'\t00\t00\tFF\tFF\tFFFFFFFF\t00'
|
||||
'\t00\tFF' % exec_cmdline)
|
||||
return ret
|
||||
elif exec_cmdline.startswith('show enclosure-status'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n'
|
||||
'ETDX200S3_1\t01\tET203ACU\t4601417434\t280753\t20'
|
||||
'\t00\t00\t01\t02\t01001000\tV10L87-9000\t91\r\n02'
|
||||
'\r\n70000000\t30\r\nD0000100\t30\r\nCLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('show volume-qos'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n'
|
||||
'0002\t\r\n0000\tFJosv_0qJ4rpOHgFE8ipcJOMfBmg==\t0F'
|
||||
'\t\r\n0001\tFJosv_OgEZj1mSvKRvIKOExKktlg==\t0D'
|
||||
'\t\r\nCLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('show qos-bandwidth-limit'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n0010\t\r\n00\t0000ffff\t0000ffff'
|
||||
'\t0000ffff\t0000ffff\t0000ffff\t0000ffff\t0000ffff'
|
||||
'\t0000ffff\t0000ffff\t0000ffff\t0000ffff\t0000ffff\r\n'
|
||||
'01\t00000001\t00000001\t00000001\t00000001\t00000001'
|
||||
'\t00000001\t00000001\t00000001\t00000001\t00000001'
|
||||
'\t00000001\t00000001\r\n02\t00000002\t00000002\t00000002'
|
||||
'\t00000002\t00000002\t00000002\t00000002\t00000002'
|
||||
'\t00000002\t00000002\t00000002\t00000002\r\n03\t00000003'
|
||||
'\t00000003\t00000003\t00000003\t00000003\t00000003'
|
||||
'\t00000003\t00000003\t00000003\t00000003\t00000003'
|
||||
'\t00000003\r\n04\t00000004\t00000004\t00000004\t00000004'
|
||||
'\t00000004\t00000004\t00000004\t00000004\t00000004'
|
||||
'\t00000004\t00000004\t00000004\r\n05\t00000005\t00000005'
|
||||
'\t00000005\t00000005\t00000005\t00000005\t00000005'
|
||||
'\t00000005\t00000005\t00000005\t00000005\t00000005\r\n06'
|
||||
'\t00000006\t00000006\t00000006\t00000006\t00000006'
|
||||
'\t00000006\t00000006\t00000006\t00000006\t00000006'
|
||||
'\t00000006\t00000006\r\n07\t00000007\t00000007\t00000007'
|
||||
'\t00000007\t00000007\t00000007\t00000007\t00000007'
|
||||
'\t00000007\t00000007\t00000007\t00000007\r\n08\t00000008'
|
||||
'\t00000008\t00000008\t00000008\t00000008\t00000008'
|
||||
'\t00000008\t00000008\t00000008\t00000008\t00000008'
|
||||
'\t00000008\r\n09\t00000009\t00000009\t00000009\t00000009'
|
||||
'\t00000009\t00000009\t00000009\t00000009\t00000009'
|
||||
'\t00000009\t00000009\t00000009\r\n0a\t0000000a\t0000000a'
|
||||
'\t0000000a\t0000000a\t0000000a\t0000000a\t0000000a'
|
||||
'\t0000000a\t0000000a\t0000000a\t0000000a\t0000000a\r\n0b'
|
||||
'\t0000000b\t0000000b\t0000000b\t0000000b\t0000000b'
|
||||
'\t0000000b\t0000000b\t0000000b\t0000000b\t0000000b'
|
||||
'\t0000000b\t0000000b\r\n0c\t0000000c\t0000000c\t0000000c'
|
||||
'\t0000000c\t0000000c\t0000000c\t0000000c\t0000000c'
|
||||
'\t0000000c\t0000000c\t0000000c\t0000000c\r\n0d\t0000000d'
|
||||
'\t0000000d\t0000000d\t0000000d\t0000000d\t0000000d'
|
||||
'\t0000000d\t0000000d\t0000000d\t0000000d\t0000000d'
|
||||
'\t0000000d\r\n0e\t0000000e\t0000000e\t0000000e\t0000000e'
|
||||
'\t0000000e\t0000000e\t0000000e\t0000000e\t0000000e'
|
||||
'\t0000000e\t0000000e\t0000000e\r\n0f\t0000000f\t0000000f'
|
||||
'\t0000000f\t0000000f\t0000000f\t0000000f\t0000000f'
|
||||
'\t0000000f\t0000000f\t0000000f\t0000000f\t0000000f'
|
||||
'\r\nCLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('set qos-bandwidth-limit'):
|
||||
ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
|
||||
else:
|
||||
ret = None
|
||||
return ret
|
||||
|
||||
def fake_safe_get(self, str=None):
|
||||
return str
|
||||
@ -1199,3 +1427,388 @@ class FJISCSIDriverTestCase(test.TestCase):
|
||||
volume_info[key] = TEST_VOLUME[key]
|
||||
|
||||
self.driver.extend_volume(volume_info, 10)
|
||||
|
||||
def test_create_volume_with_qos(self):
|
||||
self.driver.common._get_qos_specs = mock.Mock()
|
||||
self.driver.common._get_qos_specs.return_value = {'maxBWS': '700'}
|
||||
self.driver.common._set_qos = mock.Mock()
|
||||
model_info = self.driver.create_volume(TEST_VOLUME_QOS)
|
||||
self.assertEqual(FAKE_MODEL_INFO_QOS, model_info)
|
||||
self.driver.common._set_qos.assert_called()
|
||||
|
||||
|
||||
class FJCLITestCase(test.TestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FJCLITestCase, self).__init__(*args, **kwargs)
|
||||
|
||||
def setUp(self):
|
||||
super(FJCLITestCase, self).setUp()
|
||||
self.mock_object(ssh_utils, 'SSHPool', mock.Mock())
|
||||
self.mock_object(eternus_dx_cli.FJDXCLI, '_exec_cli_with_eternus',
|
||||
self.fake_exec_cli_with_eternus)
|
||||
|
||||
cli = eternus_dx_cli.FJDXCLI(user=TEST_USER,
|
||||
storage_ip=STORAGE_IP,
|
||||
password=TEST_PASSWORD)
|
||||
self.cli = cli
|
||||
|
||||
def create_fake_options(self, **kwargs):
|
||||
# Create options for CLI command.
|
||||
FAKE_OPTION_DICT = {}
|
||||
for key, value in kwargs.items():
|
||||
processed_key = key.replace('_', '-')
|
||||
FAKE_OPTION_DICT[processed_key] = value
|
||||
FAKE_OPTION = {**FAKE_OPTION_DICT}
|
||||
return FAKE_OPTION
|
||||
|
||||
def fake_exec_cli_with_eternus(self, exec_cmdline):
|
||||
if exec_cmdline == "show users":
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n'
|
||||
'3B\r\nf.ce\tMaintainer\t01\t00'
|
||||
'\t00\t00\r\ntestuser\tSoftware'
|
||||
'\t01\t01\t00\t00\r\nCLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('set volume-qos'):
|
||||
ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
|
||||
elif exec_cmdline.startswith('show volumes'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n0560\r\n0000'
|
||||
'\tFJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
||||
'\tA001\t0B\t00\t0000\tabcd1234_TPP'
|
||||
'\t0000000000200000\t00\t00'
|
||||
'\t00000000\t0050\tFF\t00\tFF'
|
||||
'\tFF\t20\tFF\tFFFF\t00'
|
||||
'\t600000E00D2A0000002A011500140000'
|
||||
'\t00\t00\tFF\tFF\tFFFFFFFF\t00'
|
||||
'\t00\tFF\r\n0001\tFJosv_OgEZj1mSvKRvIKOExKktlg=='
|
||||
'\tA001\t0B\t00\t0000\tabcd1234_OSVD'
|
||||
'\t0000000000200000\t00\t00\t00000000'
|
||||
'\t0050\tFF\t00\tFF\tFF\t20\tFF\tFFFF'
|
||||
'\t00\t600000E00D2A0000002A0115001E0000'
|
||||
'\t00\t00\tFF\tFF\tFFFFFFFF\t00'
|
||||
'\t00\tFF' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('show enclosure-status'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n'
|
||||
'ETDX200S3_1\t01\tET203ACU\t4601417434\t280753\t20'
|
||||
'\t00\t00\t01\t02\t01001000\tV10L87-9000\t91\r\n02'
|
||||
'\r\n70000000\t30\r\nD0000100\t30\r\nCLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('show volume-qos'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n'
|
||||
'0001\r\n0000\tFJosv_0qJ4rpOHgFE8ipcJOMfBmg==\t01\t00\t00'
|
||||
'\r\nCLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('show qos-bandwidth-limit'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n0001\t\r\n00\t0000ffff\t0000ffff'
|
||||
'\t0000ffff\t0000ffff\t0000ffff\t0000ffff\t0000ffff'
|
||||
'\t0000ffff\t0000ffff\t0000ffff\t0000ffff\t0000ffff\r\n'
|
||||
'CLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('set qos-bandwidth-limit'):
|
||||
ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
|
||||
elif exec_cmdline.startswith('delete volume'):
|
||||
ret = '%s\r\n00\r\nCLI> ' % exec_cmdline
|
||||
else:
|
||||
ret = None
|
||||
return ret
|
||||
|
||||
@mock.patch.object(eternus_dx_cli.FJDXCLI, '_exec_cli_with_eternus')
|
||||
def test_create_error_message(self, mock_exec_cli_with_eternus):
|
||||
expected_error_value = {'message': ['-bandwidth-limit', 'asdf'],
|
||||
'rc': 'E8101',
|
||||
'result': 0}
|
||||
|
||||
FAKE_VOLUME_NAME = 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
||||
FAKE_BANDWIDTH_LIMIT = 'abcd'
|
||||
FAKE_QOS_OPTION = self.create_fake_options(
|
||||
volume_name=FAKE_VOLUME_NAME,
|
||||
bandwidth_limit=FAKE_BANDWIDTH_LIMIT)
|
||||
|
||||
error_cli_output = ('\r\nCLI> set volume-qos -volume-name %s '
|
||||
'-bandwidth-limit %s\r\n'
|
||||
'01\r\n8101\r\n-bandwidth-limit\r\nasdf\r\n'
|
||||
'CLI> ' % (FAKE_VOLUME_NAME, FAKE_BANDWIDTH_LIMIT))
|
||||
mock_exec_cli_with_eternus.return_value = error_cli_output
|
||||
|
||||
error_qos_output = self.cli._set_volume_qos(**FAKE_QOS_OPTION)
|
||||
|
||||
self.assertEqual(expected_error_value, error_qos_output)
|
||||
|
||||
def test_get_options(self):
|
||||
expected_option = " -bandwidth-limit 2"
|
||||
option = {"bandwidth-limit": 2}
|
||||
ret = self.cli._get_option(**option)
|
||||
self.assertEqual(expected_option, ret)
|
||||
|
||||
def test_done_and_default_func(self):
|
||||
# Test function 'done' and '_default_func' in CLI file.
|
||||
self.cli.CMD_dic['check_user_role'] = mock.Mock()
|
||||
self.cli._default_func = mock.Mock(
|
||||
side_effect=Exception('Invalid function is specified'))
|
||||
|
||||
cmd1 = 'check_user_role'
|
||||
self.cli.done(cmd1)
|
||||
self.cli.CMD_dic['check_user_role'].assert_called_with()
|
||||
|
||||
cmd2 = 'test_run_cmd'
|
||||
cli_ex = None
|
||||
try:
|
||||
self.cli.done(cmd2)
|
||||
except Exception as ex:
|
||||
cli_ex = ex
|
||||
finally:
|
||||
self.cli._default_func.assert_called()
|
||||
self.assertEqual(str(cli_ex), "Invalid function is specified")
|
||||
|
||||
def test_check_user_role(self):
|
||||
FAKE_ROLE = {**FAKE_CLI_OUTPUT, 'message': 'Software'}
|
||||
|
||||
role = self.cli._check_user_role()
|
||||
self.assertEqual(FAKE_ROLE, role)
|
||||
|
||||
def test_set_volume_qos(self):
|
||||
FAKE_VOLUME_NAME = 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
||||
FAKE_BANDWIDTH_LIMIT = 2
|
||||
FAKE_QOS_OPTION = self.create_fake_options(
|
||||
volume_name=FAKE_VOLUME_NAME,
|
||||
bandwidth_limit=FAKE_BANDWIDTH_LIMIT)
|
||||
|
||||
FAKE_VOLUME_NUMBER = ['0001']
|
||||
FAKE_QOS_OUTPUT = {**FAKE_CLI_OUTPUT, 'message': FAKE_VOLUME_NUMBER}
|
||||
|
||||
volume_number = self.cli._set_volume_qos(**FAKE_QOS_OPTION)
|
||||
self.assertEqual(FAKE_QOS_OUTPUT, volume_number)
|
||||
|
||||
def test_show_pool_provision(self):
|
||||
FAKE_POOL_PROVIOSN_OPTION = self.create_fake_options(
|
||||
pool_name='abcd1234_TPP')
|
||||
|
||||
FAKE_PROVISION = {**FAKE_CLI_OUTPUT, 'message': FAKE_USEGB}
|
||||
|
||||
proviosn = self.cli._show_pool_provision(**FAKE_POOL_PROVIOSN_OPTION)
|
||||
self.assertEqual(FAKE_PROVISION, proviosn)
|
||||
|
||||
def test_show_qos_bandwidth_limit(self):
|
||||
FAKE_QOS_BANDWIDTH_LIMIT = {'read_bytes_sec': 65535,
|
||||
'read_iops_sec': 65535,
|
||||
'read_limit': 0,
|
||||
'total_bytes_sec': 65535,
|
||||
'total_iops_sec': 65535,
|
||||
'total_limit': 0,
|
||||
'write_bytes_sec': 65535,
|
||||
'write_iops_sec': 65535,
|
||||
'write_limit': 0}
|
||||
FAKE_QOS_LIST = {**FAKE_CLI_OUTPUT,
|
||||
'message': [FAKE_QOS_BANDWIDTH_LIMIT]}
|
||||
|
||||
qos_list = self.cli._show_qos_bandwidth_limit()
|
||||
self.assertEqual(FAKE_QOS_LIST, qos_list)
|
||||
|
||||
def test_set_qos_bandwidth_limit(self):
|
||||
FAKE_VOLUME_NAME = 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
||||
FAKE_READ_BANDWIDTH_LIMIT = 2
|
||||
FAKE_WRITE_BANDWIDTH_LIMIT = 3
|
||||
FAKE_QOS_OPTION = self.create_fake_options(
|
||||
volume_name=FAKE_VOLUME_NAME,
|
||||
read_bandwidth_limit=FAKE_READ_BANDWIDTH_LIMIT,
|
||||
write_bandwidth_limit=FAKE_WRITE_BANDWIDTH_LIMIT)
|
||||
|
||||
FAKE_VOLUME_NUMBER = ['0001']
|
||||
FAKE_QOS_OUTPUT = {**FAKE_CLI_OUTPUT, 'message': FAKE_VOLUME_NUMBER}
|
||||
|
||||
volume_number = self.cli._set_qos_bandwidth_limit(**FAKE_QOS_OPTION)
|
||||
self.assertEqual(FAKE_QOS_OUTPUT, volume_number)
|
||||
|
||||
def test_show_volume_qos(self):
|
||||
FAKE_VOLUME_QOS = {'total_limit': 1,
|
||||
'read_limit': 0,
|
||||
'write_limit': 0}
|
||||
FAKE_VQOS_DATA_LIST = {**FAKE_CLI_OUTPUT,
|
||||
'message': [FAKE_VOLUME_QOS]}
|
||||
|
||||
vqos_datalist = self.cli._show_volume_qos()
|
||||
self.assertEqual(FAKE_VQOS_DATA_LIST, vqos_datalist)
|
||||
|
||||
def test_show_enclosure_status(self):
|
||||
FAKE_VERSION = 'V10L87-9000'
|
||||
FAKE_VERSION_INFO = {**FAKE_CLI_OUTPUT,
|
||||
'message': {'version': FAKE_VERSION}}
|
||||
|
||||
versioninfo = self.cli._show_enclosure_status()
|
||||
self.assertEqual(FAKE_VERSION_INFO, versioninfo)
|
||||
|
||||
def test_delete_volume(self):
|
||||
FAKE_VOLUME_NAME = 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
||||
FAKE_DELETE_OUTPUT = {**FAKE_CLI_OUTPUT, 'message': []}
|
||||
FAKE_DELETE_VOLUME_OPTION = self.create_fake_options(
|
||||
volume_name=FAKE_VOLUME_NAME)
|
||||
|
||||
delete_output = self.cli._delete_volume(**FAKE_DELETE_VOLUME_OPTION)
|
||||
self.assertEqual(FAKE_DELETE_OUTPUT, delete_output)
|
||||
|
||||
|
||||
class FJCommonTestCase(test.TestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FJCommonTestCase, self).__init__(*args, **kwargs)
|
||||
|
||||
def setUp(self):
|
||||
super(FJCommonTestCase, self).setUp()
|
||||
|
||||
# Make fake xml-configuration file.
|
||||
self.config_file = tempfile.NamedTemporaryFile("w+", suffix='.xml')
|
||||
self.addCleanup(self.config_file.close)
|
||||
self.config_file.write(CONF)
|
||||
self.config_file.flush()
|
||||
|
||||
# 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)
|
||||
|
||||
instancename = FakeCIMInstanceName()
|
||||
self.mock_object(dx_common.FJDXCommon, '_create_eternus_instance_name',
|
||||
instancename.fake_create_eternus_instance_name)
|
||||
|
||||
self.mock_object(ssh_utils, 'SSHPool', mock.Mock())
|
||||
|
||||
self.mock_object(dx_common.FJDXCommon, '_get_qos_specs',
|
||||
return_value={})
|
||||
|
||||
self.mock_object(eternus_dx_cli.FJDXCLI, '_exec_cli_with_eternus',
|
||||
self.fake_exec_cli_with_eternus)
|
||||
# Set iscsi driver to self.driver.
|
||||
driver = dx_iscsi.FJDXISCSIDriver(configuration=self.configuration)
|
||||
self.driver = driver
|
||||
|
||||
def fake_exec_cli_with_eternus(self, exec_cmdline):
|
||||
if exec_cmdline == "show users":
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n'
|
||||
'3B\r\nf.ce\tMaintainer\t01\t00'
|
||||
'\t00\t00\r\ntestuser\tSoftware'
|
||||
'\t01\t01\t00\t00\r\nCLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('set volume-qos'):
|
||||
ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
|
||||
elif exec_cmdline.startswith('show volumes'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n0560\r\n0000'
|
||||
'\tFJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
||||
'\tA001\t0B\t00\t0000\tabcd1234_TPP'
|
||||
'\t0000000000200000\t00\t00'
|
||||
'\t00000000\t0050\tFF\t00\tFF'
|
||||
'\tFF\t20\tFF\tFFFF\t00'
|
||||
'\t600000E00D2A0000002A011500140000'
|
||||
'\t00\t00\tFF\tFF\tFFFFFFFF\t00'
|
||||
'\t00\tFF\r\n0001\tFJosv_OgEZj1mSvKRvIKOExKktlg=='
|
||||
'\tA001\t0B\t00\t0000\tabcd1234_OSVD'
|
||||
'\t0000000000200000\t00\t00\t00000000'
|
||||
'\t0050\tFF\t00\tFF\tFF\t20\tFF\tFFFF'
|
||||
'\t00\t600000E00D2A0000002A0115001E0000'
|
||||
'\t00\t00\tFF\tFF\tFFFFFFFF\t00'
|
||||
'\t00\tFF' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('show enclosure-status'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n'
|
||||
'ETDX200S3_1\t01\tET203ACU\t4601417434\t280753\t20'
|
||||
'\t00\t00\t01\t02\t01001000\tV10L87-9000\t91\r\n02'
|
||||
'\r\n70000000\t30\r\nD0000100\t30\r\nCLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('show volume-qos'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n'
|
||||
'0001\r\n0000\tFJosv_0qJ4rpOHgFE8ipcJOMfBmg==\t01\t00\t00'
|
||||
'\r\nCLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('show qos-bandwidth-limit'):
|
||||
ret = ('\r\nCLI> %s\r\n00\r\n0001\t\r\n00\t0000ffff\t0000ffff'
|
||||
'\t0000ffff\t0000ffff\t0000ffff\t0000ffff\t0000ffff'
|
||||
'\t0000ffff\t0000ffff\t0000ffff\t0000ffff\t0000ffff\r\n'
|
||||
'CLI> ' % exec_cmdline)
|
||||
elif exec_cmdline.startswith('set qos-bandwidth-limit'):
|
||||
ret = '\r\nCLI> %s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
|
||||
else:
|
||||
ret = None
|
||||
return ret
|
||||
|
||||
def fake_safe_get(self, str=None):
|
||||
return str
|
||||
|
||||
def fake_eternus_connection(self):
|
||||
conn = FakeEternusConnection()
|
||||
return conn
|
||||
|
||||
def test_get_eternus_model(self):
|
||||
ETERNUS_MODEL = self.driver.common._get_eternus_model()
|
||||
self.assertEqual(3, ETERNUS_MODEL)
|
||||
|
||||
def test_get_matadata(self):
|
||||
TEST_METADATA = self.driver.common.get_metadata(TEST_VOLUME)
|
||||
self.assertEqual({}, TEST_METADATA)
|
||||
|
||||
def test_is_qos_or_format_support(self):
|
||||
QOS_SUPPORT = \
|
||||
self.driver.common._is_qos_or_format_support('QOS setting')
|
||||
self.assertTrue(QOS_SUPPORT)
|
||||
|
||||
def test_get_qos_category_by_value(self):
|
||||
FAKE_QOS_KEY = 'maxBWS'
|
||||
FAKE_QOS_VALUE = 700
|
||||
FAKE_QOS_DICT = {'bandwidth-limit': 2}
|
||||
QOS_Category_Dict = self.driver.common._get_qos_category_by_value(
|
||||
FAKE_QOS_KEY, FAKE_QOS_VALUE)
|
||||
self.assertEqual(FAKE_QOS_DICT, QOS_Category_Dict)
|
||||
|
||||
def test_get_param(self):
|
||||
FAKE_QOS_SPEC_DICT = {'total_bytes_sec': 2137152,
|
||||
'read_bytes_sec': 1068576,
|
||||
'unspport_key': 1234}
|
||||
EXPECTED_KEY_DICT = {'read_bytes_sec': int(FAKE_QOS_SPEC_DICT
|
||||
['read_bytes_sec'] /
|
||||
units.Mi),
|
||||
'read_iops_sec': MAX_IOPS,
|
||||
'total_bytes_sec': int(FAKE_QOS_SPEC_DICT
|
||||
['total_bytes_sec'] /
|
||||
units.Mi),
|
||||
'total_iops_sec': MAX_IOPS}
|
||||
KEY_DICT = self.driver.common._get_param(FAKE_QOS_SPEC_DICT)
|
||||
self.assertEqual(EXPECTED_KEY_DICT, KEY_DICT)
|
||||
|
||||
def test_check_iops(self):
|
||||
FAKE_QOS_KEY = 'total_iops_sec'
|
||||
FAKE_QOS_VALUE = 2137152
|
||||
QOS_VALUE = self.driver.common._check_iops(FAKE_QOS_KEY,
|
||||
FAKE_QOS_VALUE)
|
||||
self.assertEqual(FAKE_QOS_VALUE, QOS_VALUE)
|
||||
|
||||
def test_check_throughput(self):
|
||||
FAKE_QOS_KEY = 'total_bytes_sec'
|
||||
FAKE_QOS_VALUE = 2137152
|
||||
QOS_VALUE = self.driver.common._check_throughput(FAKE_QOS_KEY,
|
||||
FAKE_QOS_VALUE)
|
||||
self.assertEqual(int(FAKE_QOS_VALUE / units.Mi),
|
||||
QOS_VALUE)
|
||||
|
||||
def test_get_qos_category(self):
|
||||
FAKE_QOS_SPEC_DICT = {'total_bytes_sec': 2137152,
|
||||
'read_bytes_sec': 1068576}
|
||||
FAKE_KEY_DICT = {'read_bytes_sec': int(FAKE_QOS_SPEC_DICT
|
||||
['read_bytes_sec'] /
|
||||
units.Mi),
|
||||
'read_iops_sec': MAX_IOPS,
|
||||
'total_bytes_sec': int(FAKE_QOS_SPEC_DICT
|
||||
['total_bytes_sec'] /
|
||||
units.Mi),
|
||||
'total_iops_sec': MAX_IOPS}
|
||||
FAKE_RET_DICT = {'bandwidth-limit': FAKE_KEY_DICT['total_bytes_sec'],
|
||||
'read-bandwidth-limit':
|
||||
FAKE_KEY_DICT['read_bytes_sec'],
|
||||
'write-bandwidth-limit': 0}
|
||||
RET_DICT = self.driver.common._get_qos_category(FAKE_KEY_DICT)
|
||||
self.assertEqual(FAKE_RET_DICT, RET_DICT)
|
||||
|
||||
@mock.patch.object(eternus_dx_cli.FJDXCLI, '_exec_cli_with_eternus')
|
||||
def test_set_limit(self, mock_exec_cli_with_eternus):
|
||||
exec_cmdline = 'set qos-bandwidth-limit -mode volume-qos ' \
|
||||
'-bandwidth-limit 5 -iops 10000 -throughput 450'
|
||||
mock_exec_cli_with_eternus.return_value = \
|
||||
'\r\nCLI> %s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
|
||||
FAKE_MODE = 'volume-qos'
|
||||
FAKE_LIMIT = 5
|
||||
FAKE_IOPS = 10000
|
||||
FAKE_THROUGHOUTPUT = 450
|
||||
self.driver.common._set_limit(FAKE_MODE, FAKE_LIMIT,
|
||||
FAKE_IOPS, FAKE_THROUGHOUTPUT)
|
||||
mock_exec_cli_with_eternus.assert_called_with(exec_cmdline)
|
||||
|
@ -22,6 +22,8 @@ RETURN_TO_RESOURCEPOOL = 19
|
||||
DETACH = 8
|
||||
BROKEN = 5
|
||||
|
||||
DX_S2 = 2
|
||||
DX_S3 = 3
|
||||
JOB_RETRIES = 60
|
||||
JOB_INTERVAL_SEC = 10
|
||||
TIMES_MIN = 3
|
||||
@ -40,6 +42,15 @@ STOR_CONF = "FUJITSU_StorageConfigurationService"
|
||||
CTRL_CONF = "FUJITSU_ControllerConfigurationService"
|
||||
UNDEF_MSG = 'Undefined Error!!'
|
||||
|
||||
MAX_IOPS = 4294967295
|
||||
MAX_THROUGHPUT = 2097151
|
||||
MIN_IOPS = 1
|
||||
MIN_THROUGHPUT = 1
|
||||
|
||||
QOS_VERSION = 'V11L30-0000'
|
||||
# Here is a misspelling, and the right value should be "Thinprovisioning_POOL".
|
||||
# It would not be compatible with the metadata of the legacy volumes,
|
||||
# so this spelling mistake needs to be retained.
|
||||
POOL_TYPE_dic = {
|
||||
RAIDGROUP: 'RAID_GROUP',
|
||||
TPPOOL: 'Thinporvisioning_POOL',
|
||||
@ -53,6 +64,19 @@ OPERATION_dic = {
|
||||
OPC: DETACH,
|
||||
EC_REC: DETACH,
|
||||
}
|
||||
FJ_QOS_KEY_list = [
|
||||
'maxBWS'
|
||||
]
|
||||
FJ_QOS_KEY_BYTES_list = [
|
||||
'read_bytes_sec',
|
||||
'write_bytes_sec',
|
||||
'total_bytes_sec'
|
||||
]
|
||||
FJ_QOS_KEY_IOPS_list = [
|
||||
'read_iops_sec',
|
||||
'write_iops_sec',
|
||||
'total_iops_sec'
|
||||
]
|
||||
|
||||
RETCODE_dic = {
|
||||
'0': 'Success',
|
||||
|
@ -15,6 +15,7 @@
|
||||
#
|
||||
|
||||
"""Cinder Volume driver for Fujitsu ETERNUS DX S3 series."""
|
||||
|
||||
import six
|
||||
|
||||
from cinder.i18n import _
|
||||
@ -47,6 +48,12 @@ class FJDXCLI(object):
|
||||
self.CMD_dic = {
|
||||
'check_user_role': self._check_user_role,
|
||||
'show_pool_provision': self._show_pool_provision,
|
||||
'show_qos_bandwidth_limit': self._show_qos_bandwidth_limit,
|
||||
'set_qos_bandwidth_limit': self._set_qos_bandwidth_limit,
|
||||
'set_volume_qos': self._set_volume_qos,
|
||||
'show_volume_qos': self._show_volume_qos,
|
||||
'show_enclosure_status': self._show_enclosure_status,
|
||||
'delete_volume': self._delete_volume
|
||||
}
|
||||
|
||||
self.SMIS_dic = {
|
||||
@ -129,7 +136,7 @@ class FJDXCLI(object):
|
||||
stdoutdata = ''
|
||||
while True:
|
||||
temp = chan.recv(65535)
|
||||
if isinstance(temp, six.binary_type):
|
||||
if isinstance(temp, bytes):
|
||||
temp = temp.decode('utf-8')
|
||||
else:
|
||||
temp = str(temp)
|
||||
@ -143,7 +150,7 @@ class FJDXCLI(object):
|
||||
break
|
||||
except Exception as e:
|
||||
raise Exception(_("Execute CLI "
|
||||
"command error. Error: %s") % six.text_type(e))
|
||||
"command error. Error: %s") % e)
|
||||
finally:
|
||||
if ssh:
|
||||
self.ssh_pool.put(ssh)
|
||||
@ -188,7 +195,7 @@ class FJDXCLI(object):
|
||||
def _get_option(**option):
|
||||
"""Create option strings from dictionary."""
|
||||
ret = ""
|
||||
for key, value in six.iteritems(option):
|
||||
for key, value in option.items():
|
||||
ret += " -%(key)s %(value)s" % {'key': key, 'value': value}
|
||||
return ret
|
||||
|
||||
@ -232,6 +239,10 @@ class FJDXCLI(object):
|
||||
}
|
||||
return output
|
||||
|
||||
def _set_volume_qos(self, **option):
|
||||
"""Exec set volume-qos."""
|
||||
return self._exec_cli("set volume-qos", **option)
|
||||
|
||||
def _show_pool_provision(self, **option):
|
||||
"""Get TPP provision capacity information."""
|
||||
try:
|
||||
@ -257,8 +268,129 @@ class FJDXCLI(object):
|
||||
output = {
|
||||
'result': 0,
|
||||
'rc': '4',
|
||||
'message': "show pool provision capacity error: %s"
|
||||
% six.text_type(ex)
|
||||
'message': "show pool provision capacity error: %s" % ex
|
||||
}
|
||||
|
||||
return output
|
||||
|
||||
def _show_qos_bandwidth_limit(self, **option):
|
||||
"""Get qos bandwidth limit."""
|
||||
clidata = None
|
||||
try:
|
||||
output = self._exec_cli("show qos-bandwidth-limit", **option)
|
||||
|
||||
# return error
|
||||
rc = output['rc']
|
||||
|
||||
if rc != "0":
|
||||
return output
|
||||
|
||||
qoslist = []
|
||||
clidatalist = output.get('message')
|
||||
|
||||
for clidataline in clidatalist[1:]:
|
||||
clidata = clidataline.split('\t')
|
||||
qoslist.append({'total_limit': int(clidata[0], 16),
|
||||
'total_iops_sec': int(clidata[1], 16),
|
||||
'total_bytes_sec': int(clidata[2], 16),
|
||||
'read_limit': int(clidata[0], 16),
|
||||
'read_iops_sec': int(clidata[3], 16),
|
||||
'read_bytes_sec': int(clidata[4], 16),
|
||||
'write_limit': int(clidata[0], 16),
|
||||
'write_iops_sec': int(clidata[5], 16),
|
||||
'write_bytes_sec': int(clidata[6], 16)})
|
||||
|
||||
output['message'] = qoslist
|
||||
|
||||
except IndexError as ex:
|
||||
msg = ('The results returned by cli are not as expected. '
|
||||
'Exception string: %s' % clidata)
|
||||
output = {'result': 0,
|
||||
'rc': '4',
|
||||
'message': "Show qos bandwidth limit error: %s. %s"
|
||||
% (ex, msg)}
|
||||
|
||||
except Exception as ex:
|
||||
output = {'result': 0,
|
||||
'rc': '4',
|
||||
'message': "Show qos bandwidth limit error: %s" % ex}
|
||||
|
||||
return output
|
||||
|
||||
def _set_qos_bandwidth_limit(self, **option):
|
||||
"""Set qos bandwidth limit"""
|
||||
return self._exec_cli("set qos-bandwidth-limit", **option)
|
||||
|
||||
def _show_volume_qos(self, **option):
|
||||
"""Get volumes with qos."""
|
||||
clidata = None
|
||||
try:
|
||||
output = self._exec_cli("show volume-qos", **option)
|
||||
|
||||
# return error
|
||||
rc = output['rc']
|
||||
|
||||
if rc != "0":
|
||||
return output
|
||||
|
||||
vqosdatalist = []
|
||||
clidatalist = output.get('message')
|
||||
|
||||
for clidataline in clidatalist[1:]:
|
||||
clidata = clidataline.split('\t')
|
||||
vqosdatalist.append({'total_limit': int(clidata[2], 16),
|
||||
'read_limit': int(clidata[3], 16),
|
||||
'write_limit': int(clidata[4], 16)})
|
||||
|
||||
output['message'] = vqosdatalist
|
||||
|
||||
except IndexError as ex:
|
||||
msg = ('The results returned by cli are not as expected. '
|
||||
'Exception string: %s' % clidata)
|
||||
output = {'result': 0,
|
||||
'rc': '4',
|
||||
'message': "Show volume qos error: %s. %s" % (ex, msg)}
|
||||
|
||||
except Exception as ex:
|
||||
output = {'result': 0,
|
||||
'rc': '4',
|
||||
'message': "Show volume qos error: %s" % ex}
|
||||
|
||||
return output
|
||||
|
||||
def _show_enclosure_status(self, **option):
|
||||
"""Get the version of machine."""
|
||||
clidata = None
|
||||
try:
|
||||
output = self._exec_cli("show enclosure-status", **option)
|
||||
|
||||
# return error
|
||||
rc = output['rc']
|
||||
|
||||
if rc != "0":
|
||||
return output
|
||||
|
||||
clidatalist = output.get('message')
|
||||
clidata = clidatalist[0].split('\t')
|
||||
versioninfo = {'version': clidata[11]}
|
||||
|
||||
output['message'] = versioninfo
|
||||
|
||||
except IndexError as ex:
|
||||
msg = ('The results returned by cli are not as expected. '
|
||||
'Exception string: %s' % clidata)
|
||||
output = {'result': 0,
|
||||
'rc': '4',
|
||||
'message': "Show enclosure status error: %s. %s"
|
||||
% (ex, msg)}
|
||||
|
||||
except Exception as ex:
|
||||
output = {'result': 0,
|
||||
'rc': '4',
|
||||
'message': "Show enclosure status error: %s" % ex}
|
||||
|
||||
return output
|
||||
|
||||
def _delete_volume(self, **option):
|
||||
"""Exec delete volume."""
|
||||
return self._exec_cli('delete volume', **option)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -20,9 +20,10 @@
|
||||
FibreChannel Cinder Volume driver for Fujitsu ETERNUS DX S3 series.
|
||||
"""
|
||||
from oslo_log import log as logging
|
||||
import six
|
||||
|
||||
from cinder.common import constants
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder import interface
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.fujitsu.eternus_dx import eternus_dx_common
|
||||
@ -53,89 +54,50 @@ class FJDXFCDriver(driver.FibreChannelDriver):
|
||||
|
||||
def check_for_setup_error(self):
|
||||
if not self.common.pywbemAvailable:
|
||||
LOG.error('pywbem could not be imported! '
|
||||
'pywbem is necessary for this volume driver.')
|
||||
msg = _('pywbem could not be imported! '
|
||||
'pywbem is necessary for this volume driver.')
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Create volume."""
|
||||
LOG.debug('create_volume, '
|
||||
'volume id: %s, enter method.', volume['id'])
|
||||
model_update = self.common.create_volume(volume)
|
||||
|
||||
location, metadata = self.common.create_volume(volume)
|
||||
|
||||
v_metadata = self._get_metadata(volume)
|
||||
metadata.update(v_metadata)
|
||||
|
||||
LOG.debug('create_volume, info: %s, exit method.', metadata)
|
||||
return {'provider_location': six.text_type(location),
|
||||
'metadata': metadata}
|
||||
return model_update
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Creates a volume from a snapshot."""
|
||||
LOG.debug('create_volume_from_snapshot, '
|
||||
'volume id: %(vid)s, snap id: %(sid)s, enter method.',
|
||||
{'vid': volume['id'], 'sid': snapshot['id']})
|
||||
|
||||
location, metadata = (
|
||||
self.common.create_volume_from_snapshot(volume, snapshot))
|
||||
|
||||
v_metadata = self._get_metadata(volume)
|
||||
metadata.update(v_metadata)
|
||||
|
||||
LOG.debug('create_volume_from_snapshot, '
|
||||
'info: %s, exit method.', metadata)
|
||||
return {'provider_location': six.text_type(location),
|
||||
'metadata': metadata}
|
||||
return {'provider_location': str(location), 'metadata': metadata}
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Create cloned volume."""
|
||||
LOG.debug('create_cloned_volume, '
|
||||
'target volume id: %(tid)s, '
|
||||
'source volume id: %(sid)s, enter method.',
|
||||
{'tid': volume['id'], 'sid': src_vref['id']})
|
||||
|
||||
location, metadata = (
|
||||
self.common.create_cloned_volume(volume, src_vref))
|
||||
|
||||
v_metadata = self._get_metadata(volume)
|
||||
metadata.update(v_metadata)
|
||||
|
||||
LOG.debug('create_cloned_volume, '
|
||||
'info: %s, exit method.', metadata)
|
||||
return {'provider_location': six.text_type(location),
|
||||
'metadata': metadata}
|
||||
return {'provider_location': str(location), 'metadata': metadata}
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Delete volume on ETERNUS."""
|
||||
LOG.debug('delete_volume, '
|
||||
'volume id: %s, enter method.', volume['id'])
|
||||
|
||||
vol_exist = self.common.delete_volume(volume)
|
||||
|
||||
LOG.debug('delete_volume, '
|
||||
'delete: %s, exit method.', vol_exist)
|
||||
self.common.delete_volume(volume)
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Creates a snapshot."""
|
||||
LOG.debug('create_snapshot, '
|
||||
'snap id: %(sid)s, volume id: %(vid)s, enter method.',
|
||||
{'sid': snapshot['id'], 'vid': snapshot['volume_id']})
|
||||
|
||||
location, metadata = self.common.create_snapshot(snapshot)
|
||||
|
||||
LOG.debug('create_snapshot, info: %s, exit method.', metadata)
|
||||
return {'provider_location': six.text_type(location)}
|
||||
return {'provider_location': str(location)}
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Deletes a snapshot."""
|
||||
LOG.debug('delete_snapshot, '
|
||||
'snap id: %(sid)s, volume id: %(vid)s, enter method.',
|
||||
{'sid': snapshot['id'], 'vid': snapshot['volume_id']})
|
||||
|
||||
vol_exist = self.common.delete_snapshot(snapshot)
|
||||
|
||||
LOG.debug('delete_snapshot, '
|
||||
'delete: %s, exit method.', vol_exist)
|
||||
self.common.delete_snapshot(snapshot)
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
"""Driver entry point to get the export info for an existing volume."""
|
||||
@ -151,10 +113,6 @@ class FJDXFCDriver(driver.FibreChannelDriver):
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Allow connection to connector and return connection info."""
|
||||
LOG.debug('initialize_connection, volume id: %(vid)s, '
|
||||
'wwpns: %(wwpns)s, enter method.',
|
||||
{'vid': volume['id'], 'wwpns': connector['wwpns']})
|
||||
|
||||
info = self.common.initialize_connection(volume, connector)
|
||||
|
||||
data = info['data']
|
||||
@ -163,20 +121,12 @@ class FJDXFCDriver(driver.FibreChannelDriver):
|
||||
data['initiator_target_map'] = init_tgt_map
|
||||
|
||||
info['data'] = data
|
||||
LOG.debug('initialize_connection, '
|
||||
'info: %s, exit method.', info)
|
||||
fczm_utils.add_fc_zone(info)
|
||||
return info
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
"""Disallow connection from connector."""
|
||||
wwpns = connector.get('wwpns') if connector else None
|
||||
|
||||
LOG.debug('terminate_connection, volume id: %(vid)s, '
|
||||
'wwpns: %(wwpns)s, enter method.',
|
||||
{'vid': volume['id'], 'wwpns': wwpns})
|
||||
|
||||
map_exist = self.common.terminate_connection(volume, connector)
|
||||
self.common.terminate_connection(volume, connector)
|
||||
|
||||
info = {'driver_volume_type': 'fibre_channel',
|
||||
'data': {}}
|
||||
@ -189,15 +139,10 @@ class FJDXFCDriver(driver.FibreChannelDriver):
|
||||
info['data'] = {'initiator_target_map': init_tgt_map}
|
||||
fczm_utils.remove_fc_zone(info)
|
||||
|
||||
LOG.debug('terminate_connection, unmap: %(unmap)s, '
|
||||
'connection info: %(info)s, exit method',
|
||||
{'unmap': map_exist, 'info': info})
|
||||
return info
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get volume stats."""
|
||||
LOG.debug('get_volume_stats, refresh: %s, enter method.', refresh)
|
||||
|
||||
pool_name = None
|
||||
if refresh is True:
|
||||
data, pool_name = self.common.update_volume_stats()
|
||||
@ -207,18 +152,12 @@ class FJDXFCDriver(driver.FibreChannelDriver):
|
||||
self._stats = data
|
||||
|
||||
LOG.debug('get_volume_stats, '
|
||||
'pool name: %s, exit method.', pool_name)
|
||||
'pool name: %s.', pool_name)
|
||||
return self._stats
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend volume."""
|
||||
LOG.debug('extend_volume, '
|
||||
'volume id: %s, enter method.', volume['id'])
|
||||
|
||||
used_pool_name = self.common.extend_volume(volume, new_size)
|
||||
|
||||
LOG.debug('extend_volume, '
|
||||
'used pool name: %s, exit method.', used_pool_name)
|
||||
self.common.extend_volume(volume, new_size)
|
||||
|
||||
def _get_metadata(self, volume):
|
||||
v_metadata = volume.get('volume_metadata')
|
||||
|
@ -18,9 +18,10 @@
|
||||
|
||||
"""iSCSI Cinder Volume driver for Fujitsu ETERNUS DX S3 series."""
|
||||
from oslo_log import log as logging
|
||||
import six
|
||||
|
||||
from cinder.common import constants
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder import interface
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.fujitsu.eternus_dx import eternus_dx_common
|
||||
@ -50,35 +51,19 @@ class FJDXISCSIDriver(driver.ISCSIDriver):
|
||||
|
||||
def check_for_setup_error(self):
|
||||
if not self.common.pywbemAvailable:
|
||||
LOG.error('pywbem could not be imported! '
|
||||
'pywbem is necessary for this volume driver.')
|
||||
|
||||
return
|
||||
msg = _('pywbem could not be imported! '
|
||||
'pywbem is necessary for this volume driver.')
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Create volume."""
|
||||
LOG.info('create_volume, volume id: %s, Enter method.', volume['id'])
|
||||
model_update = self.common.create_volume(volume)
|
||||
|
||||
element_path, metadata = self.common.create_volume(volume)
|
||||
|
||||
v_metadata = volume.get('volume_metadata')
|
||||
if v_metadata:
|
||||
for data in v_metadata:
|
||||
metadata[data['key']] = data['value']
|
||||
else:
|
||||
v_metadata = volume.get('metadata', {})
|
||||
metadata.update(v_metadata)
|
||||
|
||||
LOG.info('create_volume, info: %s, Exit method.', metadata)
|
||||
return {'provider_location': six.text_type(element_path),
|
||||
'metadata': metadata}
|
||||
return model_update
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Creates a volume from a snapshot."""
|
||||
LOG.info('create_volume_from_snapshot, '
|
||||
'volume id: %(vid)s, snap id: %(sid)s, Enter method.',
|
||||
{'vid': volume['id'], 'sid': snapshot['id']})
|
||||
|
||||
element_path, metadata = (
|
||||
self.common.create_volume_from_snapshot(volume, snapshot))
|
||||
|
||||
@ -90,18 +75,10 @@ class FJDXISCSIDriver(driver.ISCSIDriver):
|
||||
v_metadata = volume.get('metadata', {})
|
||||
metadata.update(v_metadata)
|
||||
|
||||
LOG.info('create_volume_from_snapshot, '
|
||||
'info: %s, Exit method.', metadata)
|
||||
return {'provider_location': six.text_type(element_path),
|
||||
'metadata': metadata}
|
||||
return {'provider_location': str(element_path), 'metadata': metadata}
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Create cloned volume."""
|
||||
LOG.info('create_cloned_volume, '
|
||||
'target volume id: %(tid)s, '
|
||||
'source volume id: %(sid)s, Enter method.',
|
||||
{'tid': volume['id'], 'sid': src_vref['id']})
|
||||
|
||||
element_path, metadata = (
|
||||
self.common.create_cloned_volume(volume, src_vref))
|
||||
|
||||
@ -113,40 +90,21 @@ class FJDXISCSIDriver(driver.ISCSIDriver):
|
||||
v_metadata = volume.get('metadata', {})
|
||||
metadata.update(v_metadata)
|
||||
|
||||
LOG.info('create_cloned_volume, info: %s, Exit method.', metadata)
|
||||
return {'provider_location': six.text_type(element_path),
|
||||
'metadata': metadata}
|
||||
return {'provider_location': str(element_path), 'metadata': metadata}
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Delete volume on ETERNUS."""
|
||||
LOG.info('delete_volume, volume id: %s, Enter method.', volume['id'])
|
||||
|
||||
vol_exist = self.common.delete_volume(volume)
|
||||
|
||||
LOG.info('delete_volume, delete: %s, Exit method.', vol_exist)
|
||||
return
|
||||
self.common.delete_volume(volume)
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Creates a snapshot."""
|
||||
LOG.info('create_snapshot, snap id: %(sid)s, volume id: %(vid)s, '
|
||||
'Enter method.',
|
||||
{'sid': snapshot['id'], 'vid': snapshot['volume_id']})
|
||||
|
||||
element_path, metadata = self.common.create_snapshot(snapshot)
|
||||
|
||||
LOG.info('create_snapshot, info: %s, Exit method.', metadata)
|
||||
return {'provider_location': six.text_type(element_path)}
|
||||
return {'provider_location': str(element_path)}
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Deletes a snapshot."""
|
||||
LOG.info('delete_snapshot, snap id: %(sid)s, volume id: %(vid)s, '
|
||||
'Enter method.',
|
||||
{'sid': snapshot['id'], 'vid': snapshot['volume_id']})
|
||||
|
||||
vol_exist = self.common.delete_snapshot(snapshot)
|
||||
|
||||
LOG.info('delete_snapshot, delete: %s, Exit method.', vol_exist)
|
||||
return
|
||||
self.common.delete_snapshot(snapshot)
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
"""Driver entry point to get the export info for an existing volume."""
|
||||
@ -162,32 +120,16 @@ class FJDXISCSIDriver(driver.ISCSIDriver):
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Allow connection to connector and return connection info."""
|
||||
LOG.info('initialize_connection, volume id: %(vid)s, '
|
||||
'initiator: %(initiator)s, Enter method.',
|
||||
{'vid': volume['id'], 'initiator': connector['initiator']})
|
||||
|
||||
info = self.common.initialize_connection(volume, connector)
|
||||
|
||||
LOG.info('initialize_connection, info: %s, Exit method.', info)
|
||||
return info
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
"""Disallow connection from connector."""
|
||||
initiator = connector.get('initiator') if connector else None
|
||||
|
||||
LOG.info('terminate_connection, volume id: %(vid)s, '
|
||||
'initiator: %(initiator)s, Enter method.',
|
||||
{'vid': volume['id'], 'initiator': initiator})
|
||||
|
||||
map_exist = self.common.terminate_connection(volume, connector)
|
||||
|
||||
LOG.info('terminate_connection, unmap: %s, Exit method.', map_exist)
|
||||
return
|
||||
self.common.terminate_connection(volume, connector)
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get volume stats."""
|
||||
LOG.debug('get_volume_stats, refresh: %s, Enter method.', refresh)
|
||||
|
||||
pool_name = None
|
||||
if refresh is True:
|
||||
data, pool_name = self.common.update_volume_stats()
|
||||
@ -197,14 +139,9 @@ class FJDXISCSIDriver(driver.ISCSIDriver):
|
||||
self._stats = data
|
||||
|
||||
LOG.debug('get_volume_stats, '
|
||||
'pool name: %s, Exit method.', pool_name)
|
||||
'pool name: %s.', pool_name)
|
||||
return self._stats
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend volume."""
|
||||
LOG.info('extend_volume, volume id: %s, Enter method.', volume['id'])
|
||||
|
||||
used_pool_name = self.common.extend_volume(volume, new_size)
|
||||
|
||||
LOG.info('extend_volume, used pool name: %s, Exit method.',
|
||||
used_pool_name)
|
||||
self.common.extend_volume(volume, new_size)
|
||||
|
@ -3,7 +3,7 @@ Fujitsu ETERNUS DX driver
|
||||
=========================
|
||||
|
||||
Fujitsu ETERNUS DX driver provides FC and iSCSI support for
|
||||
ETERNUS DX S3 series.
|
||||
ETERNUS DX series.
|
||||
|
||||
The driver performs volume operations by communicating with
|
||||
ETERNUS DX. It uses a CIM client in Python called PyWBEM
|
||||
@ -17,18 +17,23 @@ System requirements
|
||||
|
||||
Supported storages:
|
||||
|
||||
* ETERNUS DX60 S3
|
||||
* ETERNUS DX100 S3/DX200 S3
|
||||
* ETERNUS DX500 S3/DX600 S3
|
||||
* ETERNUS DX8700 S3/DX8900 S3
|
||||
* ETERNUS AF150 S3
|
||||
* ETERNUS AF250 S3/AF250 S2/AF250
|
||||
* ETERNUS AF650 S3/AF650 S2/AF650
|
||||
* ETERNUS DX200F
|
||||
* ETERNUS DX60 S5/S4/S3
|
||||
* ETERNUS DX100 S5/S4/S3
|
||||
* ETERNUS DX200 S5/S4/S3
|
||||
* ETERNUS DX500 S5/S4/S3
|
||||
* ETERNUS DX600 S5/S4/S3
|
||||
* ETERNUS DX8700 S3/DX8900 S4/S3
|
||||
|
||||
Requirements:
|
||||
|
||||
* Firmware version V10L30 or later is required.
|
||||
* The multipath environment with ETERNUS Multipath Driver is unsupported.
|
||||
* An Advanced Copy Feature license is required
|
||||
to create a snapshot and a clone.
|
||||
to create snapshots, create volume from snapshots, or clone volumes.
|
||||
|
||||
Supported operations
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
@ -60,9 +65,10 @@ Perform the following steps using ETERNUS Web GUI or ETERNUS CLI.
|
||||
.. note::
|
||||
* These following operations require an account that has the ``Admin`` role.
|
||||
* For detailed operations, refer to ETERNUS Web GUI User's Guide or
|
||||
ETERNUS CLI User's Guide for ETERNUS DX S3 series.
|
||||
ETERNUS CLI User's Guide for ETERNUS DX series.
|
||||
|
||||
#. Create an account for communication with cinder controller.
|
||||
#. Create an account with software role for communication
|
||||
with cinder controller.
|
||||
|
||||
#. Enable the SMI-S of ETERNUS DX.
|
||||
|
||||
@ -77,17 +83,21 @@ Perform the following steps using ETERNUS Web GUI or ETERNUS CLI.
|
||||
#. Create Snap Data Pool Volume (SDPV) to enable Snap Data Pool (SDP) for
|
||||
``create a snapshot``.
|
||||
|
||||
#. Configure storage ports used for OpenStack.
|
||||
#. Configure storage ports to be used by the Block Storage service.
|
||||
|
||||
- Set those storage ports to CA mode.
|
||||
- Enable the host-affinity settings of those storage ports.
|
||||
* Set those storage ports to CA mode.
|
||||
* Enable the host-affinity settings of those storage ports.
|
||||
|
||||
(ETERNUS CLI command for enabling host-affinity settings):
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
CLI> set fc-parameters -host-affinity enable -port <CM#><CA#><Port#>
|
||||
CLI> set iscsi-parameters -host-affinity enable -port <CM#><CA#><Port#>
|
||||
CLI> set fc-parameters -host-affinity enable -port <CM#><CA#><Port>
|
||||
CLI> set iscsi-parameters -host-affinity enable -port <CM#><CA#><Port>
|
||||
|
||||
.. note::
|
||||
* Replace <CM#> and <CA#> with the name of the controller enclosure where the port is located.
|
||||
* Replace <Port> with the port number.
|
||||
|
||||
#. Ensure LAN connection between cinder controller and MNT port of ETERNUS DX
|
||||
and SAN connection between Compute nodes and CA ports of ETERNUS DX.
|
||||
@ -160,28 +170,30 @@ Configuration
|
||||
Where:
|
||||
|
||||
``EternusIP``
|
||||
IP address for the SMI-S connection of the ETRENUS DX.
|
||||
IP address of the SMI-S connection of the ETRENUS device.
|
||||
|
||||
Enter the IP address of MNT port of the ETERNUS DX.
|
||||
Use the IP address of the MNT port of device.
|
||||
|
||||
``EternusPort``
|
||||
Port number for the SMI-S connection port of the ETERNUS DX.
|
||||
Port number for the SMI-S connection port of the ETERNUS device.
|
||||
|
||||
``EternusUser``
|
||||
User name for the SMI-S connection of the ETERNUS DX.
|
||||
User name of ``sofware`` role for the connection ``EternusIP``.
|
||||
|
||||
``EternusPassword``
|
||||
Password for the SMI-S connection of the ETERNUS DX.
|
||||
Corresponding password of ``EternusUser`` on ``EternusIP``.
|
||||
|
||||
``EternusPool`` (Multiple setting allowed)
|
||||
Storage pool name for volumes.
|
||||
Name of the storage pool for the volumes from ``ETERNUS DX setup``.
|
||||
|
||||
Enter RAID Group name or TPP name in the ETERNUS DX.
|
||||
Use the pool RAID Group name or TPP name in the ETERNUS device.
|
||||
|
||||
``EternusSnapPool``
|
||||
Storage pool name for snapshots.
|
||||
Name of the storage pool for the snapshots from ``ETERNUS DX setup``.
|
||||
|
||||
Enter RAID Group name in the ETERNUS DX.
|
||||
Use the pool RAID Group name in the ETERNUS device.
|
||||
|
||||
If you did not create a different pool for snapshots, use the same value as ``ETternusPool``.
|
||||
|
||||
``EternusISCSIIP`` (Multiple setting allowed)
|
||||
iSCSI connection IP address of the ETERNUS DX.
|
||||
@ -221,11 +233,166 @@ Configuration example
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack volume type create DX_FC
|
||||
$ openstack volume type set --property volume_backend_name=FC DX_FX
|
||||
$ openstack volume type create DX_ISCSI
|
||||
$ openstack volume type set --property volume_backend_name=ISCSI DX_ISCSI
|
||||
$ cinder type-create DX_FC
|
||||
$ cinder type-key DX_FX set volume_backend_name=FC
|
||||
$ cinder type-create DX_ISCSI
|
||||
$ cinder type-key DX_ISCSI set volume_backend_name=ISCSI
|
||||
|
||||
By issuing these commands,
|
||||
the volume type ``DX_FC`` is associated with the ``FC``,
|
||||
and the type ``DX_ISCSI`` is associated with the ``ISCSI``.
|
||||
|
||||
|
||||
Supplementary Information for the Supported Functions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
QoS Settings
|
||||
------------
|
||||
|
||||
The QoS settings that are linked with the volume QoS function of the
|
||||
ETERNUS AF/DX are available.
|
||||
|
||||
An upper limit value of the bandwidth(BWS) can be set for each volume.
|
||||
A lower limit value can not be set.
|
||||
|
||||
The upper limit is set if the firmware version of the ETERNUS AF/DX is
|
||||
earlier than V11L30, and the IOPS/Throughput of
|
||||
Total/Read/Write for the volume is set separately for V11L30 and later.
|
||||
|
||||
The following procedure shows how to set the QoS.
|
||||
|
||||
#. Create a QoS definition.
|
||||
|
||||
* The firmware version of the ETERNUS AF/DX is earlier than V11L30
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
$ cinder qos-create <qos_name> maxBWS=xx
|
||||
|
||||
For <qos_name>, specify the name of the definition that is to be created.
|
||||
|
||||
For maxBWS, specify a value in MB.
|
||||
|
||||
* The firmware version of the ETERNUS AF/DX is V11L30 or later
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cinder qos-create <qos_name> read_iops_sec=15000 write_iops_sec=12600 total_iops_sec=15000 read_bytes_sec=800 write_bytes_sec=700 total_bytes_sec=800
|
||||
|
||||
#. When not using the existing volume type, create a new volume type.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cinder type-create <volume_type_name>
|
||||
|
||||
For <volume_type_name>, specify the name of the volume type that is to be created.
|
||||
|
||||
#. Associate the QoS definition with the volume type.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cinder qos-associate <qos_specs> <volume_type_id>
|
||||
|
||||
For <qos_specs>, specify the ID of the QoS definition that was created.
|
||||
|
||||
For <volume_type_id>, specify the ID of the volume type that was created.
|
||||
|
||||
**Cautions**
|
||||
|
||||
#. For the procedure to cancel the QoS settings,
|
||||
refer to "OpenStack Command-Line Interface Reference".
|
||||
|
||||
#. The QoS mode of the ETERNUS AF/DX must be enabled in advance.
|
||||
For details, refer to the ETERNUS Web GUI manuals.
|
||||
|
||||
#. When the firmware version of the ETERNUS AF/DX is earlier than V11L30,
|
||||
for the volume QoS settings of the ETERNUS AF/DX, upper limits are set
|
||||
using the predefined options.
|
||||
|
||||
Therefore, set the upper limit of the ETERNUS AF/DX side to a maximum value
|
||||
that does not exceed the specified maxBWS.
|
||||
|
||||
The following table shows the upper limits that can be set on the
|
||||
ETERNUS AF/DX side and example settings.
|
||||
For details about the volume QoS settings of the ETERNUS AF/DX,
|
||||
refer to the ETERNUS Web GUI manuals.
|
||||
|
||||
+--------------------------------+
|
||||
| Settings for the ETERNUS AF/DX |
|
||||
+================================+
|
||||
| Unlimited |
|
||||
+--------------------------------+
|
||||
| 15000 IOPS (800MB/s) |
|
||||
+--------------------------------+
|
||||
| 12600 IOPS (700MB/s) |
|
||||
+--------------------------------+
|
||||
| 10020 IOPS (600MB/s) |
|
||||
+--------------------------------+
|
||||
| 7500 IOPS (500MB/s) |
|
||||
+--------------------------------+
|
||||
| 5040 IOPS (400MB/s) |
|
||||
+--------------------------------+
|
||||
| 3000 IOPS (300MB/s) |
|
||||
+--------------------------------+
|
||||
| 1020 IOPS (200MB/s) |
|
||||
+--------------------------------+
|
||||
| 780 IOPS (100MB/s) |
|
||||
+--------------------------------+
|
||||
| 600 IOPS (70MB/s) |
|
||||
+--------------------------------+
|
||||
| 420 IOPS (40MB/s) |
|
||||
+--------------------------------+
|
||||
| 300 IOPS (25MB/s) |
|
||||
+--------------------------------+
|
||||
| 240 IOPS (20MB/s) |
|
||||
+--------------------------------+
|
||||
| 180 IOPS (15MB/s) |
|
||||
+--------------------------------+
|
||||
| 120 IOPS (10MB/s) |
|
||||
+--------------------------------+
|
||||
| 60 IOPS (5MB/s) |
|
||||
+--------------------------------+
|
||||
|
||||
* When specified maxBWS=750
|
||||
|
||||
"12600 IOPS (700MB/s)" is set on the ETERNUS AF/DX side.
|
||||
|
||||
* When specified maxBWS=900
|
||||
|
||||
"15000 IOPS (800MB/s)" is set on the ETERNUS AF/DX side.
|
||||
|
||||
#. While a QoS definition is being created, if an option other than
|
||||
maxBWS/read_iops_sec/write_iops_sec/total_iops_sec/read_bytes_sec
|
||||
/write_bytes_sec/total_bytes_sec is specified,
|
||||
a warning log is output and the QoS information setting is continued.
|
||||
|
||||
#. For an ETERNUS AF/DX wth a firmware version of before V11L30,
|
||||
if a QoS definition volume type that is set with read_iops_sec/
|
||||
write_iops_sec/total_iops_sec/read_bytes_sec/write_bytes_sec/total_bytes_sec
|
||||
is specified for Create Volume, a warning log is output
|
||||
and the process is terminated.
|
||||
|
||||
#. For an ETERNUS AF/DX with a firmware version of V11L30 or later,
|
||||
if a QoS definition volume type that is set with maxBWS is specified
|
||||
for Create Volume, a warning log is output and the process is terminated.
|
||||
|
||||
#. After the firmware of the ETERNUS AF/DX is upgraded from V11L10/V11L2x to
|
||||
a newer version, the volume types related to the QoS definition created
|
||||
before the firmware upgrade can no longer be used.
|
||||
Set a QoS definition and create a new volume type.
|
||||
|
||||
#. When the firmware of the ETERNUS AF/DX is downgraded to V11L10/V11L2x,
|
||||
do not use a volume type linked to a pre-firmware downgrade
|
||||
QoS definition, because the QoS definition may work differently from
|
||||
ones post-firmware downgrade.
|
||||
For the volume, create and link a volume type not associated with
|
||||
any QoS definition and after the downgrade, create and link a volume type
|
||||
associated with a QoS definition.
|
||||
|
||||
#. If Create Volume terminates with an error, Cinder may not invoke
|
||||
Delete Volume.
|
||||
|
||||
If volumes are created but the QoS settings fail, the
|
||||
ETERNUS OpenStack VolumeDriver ends the process to prevent the
|
||||
created volumes from being left in the ETERNUS AF/DX.
|
||||
If volumes fail to be created, the process terminates with an error.
|
||||
|
33
releasenotes/notes/fujitsu-qos-support-1c1528da06d0b38a.yaml
Normal file
33
releasenotes/notes/fujitsu-qos-support-1c1528da06d0b38a.yaml
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Fujitsu ETERNUS DX driver: Added support for QoS
|
||||
|
||||
What QoS settings are available depends upon the storage firmware version
|
||||
of the ETERNUS AF/DX.
|
||||
|
||||
* When the storage firmware version is less than V11L30-0000,
|
||||
only the upper limit of bandwidth(BWS) can be set using:
|
||||
|
||||
- ``maxBWS``
|
||||
|
||||
Note that when the firmware version of the ETERNUS AF/DX is earlier
|
||||
than V11L30, upper limits for the volume QoS settings of
|
||||
the ETERNUS AF/DX are set using predefined options. This means that
|
||||
you should set the upper limit *of the ETERNUS AF/DX side* to a maximum
|
||||
value that does not exceed the specified ``maxBWS``.
|
||||
|
||||
* When the storage firmware version is greater than V11L30-0000,
|
||||
the IOPS/Throughput of Total/Read/Write for the volume can be set
|
||||
separately using:
|
||||
|
||||
- ``read_bytes_sec``
|
||||
- ``write_bytes_sec``
|
||||
- ``total_bytes_sec``
|
||||
- ``read_iops_sec``
|
||||
- ``write_iops_sec``
|
||||
- ``total_iops_sec``
|
||||
|
||||
See the `Fujitsu ETERNUS DX driver documentation
|
||||
<https://docs.openstack.org/cinder/latest/configuration/block-storage/drivers/fujitsu-eternus-dx-driver.html>`_
|
||||
for details.
|
Loading…
Reference in New Issue
Block a user