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'
|
'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_INITIATOR = 'iqn.1993-08.org.debian:01:8261afe17e4c'
|
||||||
ISCSI_TARGET_IP = '10.0.0.3'
|
ISCSI_TARGET_IP = '10.0.0.3'
|
||||||
ISCSI_TARGET_IQN = 'iqn.2000-09.com.fujitsu:storage-system.eternus-dxl:0'
|
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_WWPN = ['0123456789111111', '0123456789222222']
|
||||||
TEST_CONNECTOR = {'initiator': ISCSI_INITIATOR, 'wwpns': TEST_WWPN}
|
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'
|
STOR_CONF_SVC = 'FUJITSU_StorageConfigurationService'
|
||||||
CTRL_CONF_SVC = 'FUJITSU_ControllerConfigurationService'
|
CTRL_CONF_SVC = 'FUJITSU_ControllerConfigurationService'
|
||||||
@ -128,6 +141,9 @@ FAKE_LUN_NO2 = '0x001E'
|
|||||||
# Volume2 in pool abcd1234_RG
|
# Volume2 in pool abcd1234_RG
|
||||||
FAKE_LUN_ID3 = '600000E00D2800000028075301140000'
|
FAKE_LUN_ID3 = '600000E00D2800000028075301140000'
|
||||||
FAKE_LUN_NO3 = '0x0114'
|
FAKE_LUN_NO3 = '0x0114'
|
||||||
|
# VolumeQoS in pool abcd1234_TPP
|
||||||
|
FAKE_LUN_ID_QOS = '600000E00D2A0000002A011500140000'
|
||||||
|
FAKE_LUN_NO_QOS = '0x0014'
|
||||||
FAKE_SYSTEM_NAME = 'ET603SA4621302115'
|
FAKE_SYSTEM_NAME = 'ET603SA4621302115'
|
||||||
# abcd1234_TPP pool
|
# abcd1234_TPP pool
|
||||||
FAKE_USEGB = 2.0
|
FAKE_USEGB = 2.0
|
||||||
@ -160,20 +176,20 @@ FAKE_POOLS = [{
|
|||||||
}]
|
}]
|
||||||
|
|
||||||
FAKE_STATS = {
|
FAKE_STATS = {
|
||||||
'driver_version': '1.3.0',
|
'driver_version': '1.4.0',
|
||||||
'storage_protocol': 'iSCSI',
|
'storage_protocol': 'iSCSI',
|
||||||
'vendor_name': 'FUJITSU',
|
'vendor_name': 'FUJITSU',
|
||||||
'QoS_support': False,
|
'QoS_support': True,
|
||||||
'volume_backend_name': 'volume_backend_name',
|
'volume_backend_name': 'volume_backend_name',
|
||||||
'shared_targets': True,
|
'shared_targets': True,
|
||||||
'backend_state': 'up',
|
'backend_state': 'up',
|
||||||
'pools': FAKE_POOLS,
|
'pools': FAKE_POOLS,
|
||||||
}
|
}
|
||||||
FAKE_STATS2 = {
|
FAKE_STATS2 = {
|
||||||
'driver_version': '1.3.0',
|
'driver_version': '1.4.0',
|
||||||
'storage_protocol': 'FC',
|
'storage_protocol': 'FC',
|
||||||
'vendor_name': 'FUJITSU',
|
'vendor_name': 'FUJITSU',
|
||||||
'QoS_support': False,
|
'QoS_support': True,
|
||||||
'volume_backend_name': 'volume_backend_name',
|
'volume_backend_name': 'volume_backend_name',
|
||||||
'shared_targets': True,
|
'shared_targets': True,
|
||||||
'backend_state': 'up',
|
'backend_state': 'up',
|
||||||
@ -183,37 +199,58 @@ FAKE_STATS2 = {
|
|||||||
|
|
||||||
# Volume1 in pool abcd1234_TPP
|
# Volume1 in pool abcd1234_TPP
|
||||||
FAKE_KEYBIND1 = {
|
FAKE_KEYBIND1 = {
|
||||||
'CreationClassName': 'FUJITSU_StorageVolume',
|
|
||||||
'SystemName': STORAGE_SYSTEM,
|
'SystemName': STORAGE_SYSTEM,
|
||||||
'DeviceID': FAKE_LUN_ID1,
|
'DeviceID': FAKE_LUN_ID1,
|
||||||
'SystemCreationClassName': 'FUJITSU_StorageComputerSystem',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Volume2 in pool abcd1234_RG
|
# Volume2 in pool abcd1234_RG
|
||||||
FAKE_KEYBIND3 = {
|
FAKE_KEYBIND3 = {
|
||||||
'CreationClassName': 'FUJITSU_StorageVolume',
|
|
||||||
'SystemName': STORAGE_SYSTEM,
|
'SystemName': STORAGE_SYSTEM,
|
||||||
'DeviceID': FAKE_LUN_ID3,
|
'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
|
# Volume1
|
||||||
FAKE_LOCATION1 = {
|
FAKE_LOCATION1 = {
|
||||||
'classname': 'FUJITSU_StorageVolume',
|
'classname': 'FUJITSU_StorageVolume',
|
||||||
'keybindings': FAKE_KEYBIND1,
|
'keybindings': FAKE_KEYBIND1,
|
||||||
|
'vol_name': 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clone Volume
|
||||||
|
FAKE_CLONE_LOCATION = {
|
||||||
|
'classname': 'FUJITSU_StorageVolume',
|
||||||
|
'keybindings': FAKE_KEYBIND1,
|
||||||
|
'vol_name': 'FJosv_UkCZqMFZW3SU_JzxjHiKfg=='
|
||||||
}
|
}
|
||||||
|
|
||||||
# Volume2
|
# Volume2
|
||||||
FAKE_LOCATION3 = {
|
FAKE_LOCATION3 = {
|
||||||
'classname': 'FUJITSU_StorageVolume',
|
'classname': 'FUJITSU_StorageVolume',
|
||||||
'keybindings': FAKE_KEYBIND3,
|
'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.
|
# 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 = {
|
FAKE_LUN_META1 = {
|
||||||
'FJ_Pool_Type': 'Thinporvisioning_POOL',
|
'FJ_Pool_Type': 'Thinporvisioning_POOL',
|
||||||
'FJ_Volume_No': FAKE_LUN_NO1,
|
'FJ_Volume_No': FAKE_LUN_NO1,
|
||||||
'FJ_Volume_Name': u'FJosv_0qJ4rpOHgFE8ipcJOMfBmg==',
|
'FJ_Volume_Name': 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg==',
|
||||||
'FJ_Pool_Name': STORAGE_TYPE,
|
'FJ_Pool_Name': STORAGE_TYPE,
|
||||||
'FJ_Backend': FAKE_SYSTEM_NAME,
|
'FJ_Backend': FAKE_SYSTEM_NAME,
|
||||||
}
|
}
|
||||||
@ -222,10 +259,20 @@ FAKE_LUN_META1 = {
|
|||||||
FAKE_LUN_META3 = {
|
FAKE_LUN_META3 = {
|
||||||
'FJ_Pool_Type': 'RAID_GROUP',
|
'FJ_Pool_Type': 'RAID_GROUP',
|
||||||
'FJ_Volume_No': FAKE_LUN_NO3,
|
'FJ_Volume_No': FAKE_LUN_NO3,
|
||||||
'FJ_Volume_Name': u'FJosv_4whcadwDac7ANKHA2O719A==',
|
'FJ_Volume_Name': 'FJosv_4whcadwDac7ANKHA2O719A==',
|
||||||
'FJ_Pool_Name': STORAGE_TYPE2,
|
'FJ_Pool_Name': STORAGE_TYPE2,
|
||||||
'FJ_Backend': FAKE_SYSTEM_NAME,
|
'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
|
# Volume1
|
||||||
FAKE_MODEL_INFO1 = {
|
FAKE_MODEL_INFO1 = {
|
||||||
'provider_location': six.text_type(FAKE_LOCATION1),
|
'provider_location': six.text_type(FAKE_LOCATION1),
|
||||||
@ -236,17 +283,21 @@ FAKE_MODEL_INFO3 = {
|
|||||||
'provider_location': six.text_type(FAKE_LOCATION3),
|
'provider_location': six.text_type(FAKE_LOCATION3),
|
||||||
'metadata': FAKE_LUN_META3,
|
'metadata': FAKE_LUN_META3,
|
||||||
}
|
}
|
||||||
|
# VoluemQOS
|
||||||
|
FAKE_MODEL_INFO_QOS = {
|
||||||
|
'provider_location': six.text_type(FAKE_LOCATION_QOS),
|
||||||
|
'metadata': FAKE_LUN_META_QOS,
|
||||||
|
}
|
||||||
|
|
||||||
FAKE_KEYBIND2 = {
|
FAKE_KEYBIND2 = {
|
||||||
'CreationClassName': 'FUJITSU_StorageVolume',
|
|
||||||
'SystemName': STORAGE_SYSTEM,
|
'SystemName': STORAGE_SYSTEM,
|
||||||
'DeviceID': FAKE_LUN_ID2,
|
'DeviceID': FAKE_LUN_ID2,
|
||||||
'SystemCreationClassName': 'FUJITSU_StorageComputerSystem',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FAKE_LOCATION2 = {
|
FAKE_LOCATION2 = {
|
||||||
'classname': 'FUJITSU_StorageVolume',
|
'classname': 'FUJITSU_StorageVolume',
|
||||||
'keybindings': FAKE_KEYBIND2,
|
'keybindings': FAKE_KEYBIND2,
|
||||||
|
'vol_name': 'FJosv_OgEZj1mSvKRvIKOExKktlg=='
|
||||||
}
|
}
|
||||||
|
|
||||||
FAKE_SNAP_INFO = {
|
FAKE_SNAP_INFO = {
|
||||||
@ -256,16 +307,36 @@ FAKE_SNAP_INFO = {
|
|||||||
FAKE_LUN_META2 = {
|
FAKE_LUN_META2 = {
|
||||||
'FJ_Pool_Type': 'Thinporvisioning_POOL',
|
'FJ_Pool_Type': 'Thinporvisioning_POOL',
|
||||||
'FJ_Volume_No': FAKE_LUN_NO1,
|
'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_Pool_Name': STORAGE_TYPE,
|
||||||
'FJ_Backend': FAKE_SYSTEM_NAME,
|
'FJ_Backend': FAKE_SYSTEM_NAME,
|
||||||
}
|
}
|
||||||
|
|
||||||
FAKE_MODEL_INFO2 = {
|
FAKE_MODEL_INFO2 = {
|
||||||
'provider_location': six.text_type(FAKE_LOCATION1),
|
'provider_location': six.text_type(FAKE_CLONE_LOCATION),
|
||||||
'metadata': FAKE_LUN_META2,
|
'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):
|
class FJ_StorageVolume(dict):
|
||||||
pass
|
pass
|
||||||
@ -289,6 +360,32 @@ class FakeCIMInstanceName(dict):
|
|||||||
instancename.namespace = 'root/eternus'
|
instancename.namespace = 'root/eternus'
|
||||||
return instancename
|
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):
|
class FakeEternusConnection(object):
|
||||||
def InvokeMethod(self, MethodName, Service, ElementName=None, InPool=None,
|
def InvokeMethod(self, MethodName, Service, ElementName=None, InPool=None,
|
||||||
@ -383,6 +480,9 @@ class FakeEternusConnection(object):
|
|||||||
result = self._enum_scsiport_endpoint()
|
result = self._enum_scsiport_endpoint()
|
||||||
elif name == 'FUJITSU_StorageHardwareID':
|
elif name == 'FUJITSU_StorageHardwareID':
|
||||||
result = None
|
result = None
|
||||||
|
elif name == 'FUJITSU_StorageVolume':
|
||||||
|
instancename_1 = FakeCIMInstanceName()
|
||||||
|
result = instancename_1.fake_enumerateinstances()
|
||||||
else:
|
else:
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
@ -892,6 +992,10 @@ class FJFCDriverTestCase(test.TestCase):
|
|||||||
instancename.fake_create_eternus_instance_name)
|
instancename.fake_create_eternus_instance_name)
|
||||||
|
|
||||||
self.mock_object(ssh_utils, 'SSHPool', mock.Mock())
|
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.mock_object(eternus_dx_cli.FJDXCLI, '_exec_cli_with_eternus',
|
||||||
self.fake_exec_cli_with_eternus)
|
self.fake_exec_cli_with_eternus)
|
||||||
# Set fc driver to self.driver.
|
# Set fc driver to self.driver.
|
||||||
@ -900,11 +1004,12 @@ class FJFCDriverTestCase(test.TestCase):
|
|||||||
|
|
||||||
def fake_exec_cli_with_eternus(self, exec_cmdline):
|
def fake_exec_cli_with_eternus(self, exec_cmdline):
|
||||||
if exec_cmdline == "show users":
|
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'
|
'3B\r\nf.ce\tMaintainer\t01\t00'
|
||||||
'\t00\t00\r\ntestuser\tSoftware'
|
'\t00\t00\r\ntestuser\tSoftware'
|
||||||
'\t01\t01\t00\t00\r\nCLI> ')
|
'\t01\t01\t00\t00\r\nCLI> ' % exec_cmdline)
|
||||||
return ret
|
elif exec_cmdline.startswith('set volume-qos'):
|
||||||
|
ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
|
||||||
elif exec_cmdline.startswith('show volumes'):
|
elif exec_cmdline.startswith('show volumes'):
|
||||||
ret = ('\r\nCLI> %s\r\n00\r\n0560\r\n0000'
|
ret = ('\r\nCLI> %s\r\n00\r\n0560\r\n0000'
|
||||||
'\tFJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
'\tFJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
||||||
@ -914,14 +1019,69 @@ class FJFCDriverTestCase(test.TestCase):
|
|||||||
'\tFF\t20\tFF\tFFFF\t00'
|
'\tFF\t20\tFF\tFFFF\t00'
|
||||||
'\t600000E00D2A0000002A011500140000'
|
'\t600000E00D2A0000002A011500140000'
|
||||||
'\t00\t00\tFF\tFF\tFFFFFFFF\t00'
|
'\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'
|
'\tA001\t0B\t00\t0000\tabcd1234_OSVD'
|
||||||
'\t0000000000200000\t00\t00\t00000000'
|
'\t0000000000200000\t00\t00\t00000000'
|
||||||
'\t0050\tFF\t00\tFF\tFF\t20\tFF\tFFFF'
|
'\t0050\tFF\t00\tFF\tFF\t20\tFF\tFFFF'
|
||||||
'\t00\t600000E00D2A0000002A0115001E0000'
|
'\t00\t600000E00D2A0000002A0115001E0000'
|
||||||
'\t00\t00\tFF\tFF\tFFFFFFFF\t00'
|
'\t00\t00\tFF\tFF\tFFFFFFFF\t00'
|
||||||
'\t00\tFF' % exec_cmdline)
|
'\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):
|
def fake_safe_get(self, str=None):
|
||||||
return str
|
return str
|
||||||
@ -1030,6 +1190,14 @@ class FJFCDriverTestCase(test.TestCase):
|
|||||||
|
|
||||||
self.driver.extend_volume(volume_info, 10)
|
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):
|
class FJISCSIDriverTestCase(test.TestCase):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -1061,6 +1229,10 @@ class FJISCSIDriverTestCase(test.TestCase):
|
|||||||
self.fake_get_mapdata)
|
self.fake_get_mapdata)
|
||||||
|
|
||||||
self.mock_object(ssh_utils, 'SSHPool', mock.Mock())
|
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.mock_object(eternus_dx_cli.FJDXCLI, '_exec_cli_with_eternus',
|
||||||
self.fake_exec_cli_with_eternus)
|
self.fake_exec_cli_with_eternus)
|
||||||
# Set iscsi driver to self.driver.
|
# Set iscsi driver to self.driver.
|
||||||
@ -1069,11 +1241,12 @@ class FJISCSIDriverTestCase(test.TestCase):
|
|||||||
|
|
||||||
def fake_exec_cli_with_eternus(self, exec_cmdline):
|
def fake_exec_cli_with_eternus(self, exec_cmdline):
|
||||||
if exec_cmdline == "show users":
|
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'
|
'3B\r\nf.ce\tMaintainer\t01\t00'
|
||||||
'\t00\t00\r\ntestuser\tSoftware'
|
'\t00\t00\r\ntestuser\tSoftware'
|
||||||
'\t01\t01\t00\t00\r\nCLI> ')
|
'\t01\t01\t00\t00\r\nCLI> ' % exec_cmdline)
|
||||||
return ret
|
elif exec_cmdline.startswith('set volume-qos'):
|
||||||
|
ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
|
||||||
elif exec_cmdline.startswith('show volumes'):
|
elif exec_cmdline.startswith('show volumes'):
|
||||||
ret = ('\r\nCLI> %s\r\n00\r\n0560\r\n0000'
|
ret = ('\r\nCLI> %s\r\n00\r\n0560\r\n0000'
|
||||||
'\tFJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
'\tFJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
|
||||||
@ -1083,14 +1256,69 @@ class FJISCSIDriverTestCase(test.TestCase):
|
|||||||
'\tFF\t20\tFF\tFFFF\t00'
|
'\tFF\t20\tFF\tFFFF\t00'
|
||||||
'\t600000E00D2A0000002A011500140000'
|
'\t600000E00D2A0000002A011500140000'
|
||||||
'\t00\t00\tFF\tFF\tFFFFFFFF\t00'
|
'\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'
|
'\tA001\t0B\t00\t0000\tabcd1234_OSVD'
|
||||||
'\t0000000000200000\t00\t00\t00000000'
|
'\t0000000000200000\t00\t00\t00000000'
|
||||||
'\t0050\tFF\t00\tFF\tFF\t20\tFF\tFFFF'
|
'\t0050\tFF\t00\tFF\tFF\t20\tFF\tFFFF'
|
||||||
'\t00\t600000E00D2A0000002A0115001E0000'
|
'\t00\t600000E00D2A0000002A0115001E0000'
|
||||||
'\t00\t00\tFF\tFF\tFFFFFFFF\t00'
|
'\t00\t00\tFF\tFF\tFFFFFFFF\t00'
|
||||||
'\t00\tFF' % exec_cmdline)
|
'\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):
|
def fake_safe_get(self, str=None):
|
||||||
return str
|
return str
|
||||||
@ -1199,3 +1427,388 @@ class FJISCSIDriverTestCase(test.TestCase):
|
|||||||
volume_info[key] = TEST_VOLUME[key]
|
volume_info[key] = TEST_VOLUME[key]
|
||||||
|
|
||||||
self.driver.extend_volume(volume_info, 10)
|
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
|
DETACH = 8
|
||||||
BROKEN = 5
|
BROKEN = 5
|
||||||
|
|
||||||
|
DX_S2 = 2
|
||||||
|
DX_S3 = 3
|
||||||
JOB_RETRIES = 60
|
JOB_RETRIES = 60
|
||||||
JOB_INTERVAL_SEC = 10
|
JOB_INTERVAL_SEC = 10
|
||||||
TIMES_MIN = 3
|
TIMES_MIN = 3
|
||||||
@ -40,6 +42,15 @@ STOR_CONF = "FUJITSU_StorageConfigurationService"
|
|||||||
CTRL_CONF = "FUJITSU_ControllerConfigurationService"
|
CTRL_CONF = "FUJITSU_ControllerConfigurationService"
|
||||||
UNDEF_MSG = 'Undefined Error!!'
|
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 = {
|
POOL_TYPE_dic = {
|
||||||
RAIDGROUP: 'RAID_GROUP',
|
RAIDGROUP: 'RAID_GROUP',
|
||||||
TPPOOL: 'Thinporvisioning_POOL',
|
TPPOOL: 'Thinporvisioning_POOL',
|
||||||
@ -53,6 +64,19 @@ OPERATION_dic = {
|
|||||||
OPC: DETACH,
|
OPC: DETACH,
|
||||||
EC_REC: 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 = {
|
RETCODE_dic = {
|
||||||
'0': 'Success',
|
'0': 'Success',
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
"""Cinder Volume driver for Fujitsu ETERNUS DX S3 series."""
|
"""Cinder Volume driver for Fujitsu ETERNUS DX S3 series."""
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
@ -47,6 +48,12 @@ class FJDXCLI(object):
|
|||||||
self.CMD_dic = {
|
self.CMD_dic = {
|
||||||
'check_user_role': self._check_user_role,
|
'check_user_role': self._check_user_role,
|
||||||
'show_pool_provision': self._show_pool_provision,
|
'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 = {
|
self.SMIS_dic = {
|
||||||
@ -129,7 +136,7 @@ class FJDXCLI(object):
|
|||||||
stdoutdata = ''
|
stdoutdata = ''
|
||||||
while True:
|
while True:
|
||||||
temp = chan.recv(65535)
|
temp = chan.recv(65535)
|
||||||
if isinstance(temp, six.binary_type):
|
if isinstance(temp, bytes):
|
||||||
temp = temp.decode('utf-8')
|
temp = temp.decode('utf-8')
|
||||||
else:
|
else:
|
||||||
temp = str(temp)
|
temp = str(temp)
|
||||||
@ -143,7 +150,7 @@ class FJDXCLI(object):
|
|||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(_("Execute CLI "
|
raise Exception(_("Execute CLI "
|
||||||
"command error. Error: %s") % six.text_type(e))
|
"command error. Error: %s") % e)
|
||||||
finally:
|
finally:
|
||||||
if ssh:
|
if ssh:
|
||||||
self.ssh_pool.put(ssh)
|
self.ssh_pool.put(ssh)
|
||||||
@ -188,7 +195,7 @@ class FJDXCLI(object):
|
|||||||
def _get_option(**option):
|
def _get_option(**option):
|
||||||
"""Create option strings from dictionary."""
|
"""Create option strings from dictionary."""
|
||||||
ret = ""
|
ret = ""
|
||||||
for key, value in six.iteritems(option):
|
for key, value in option.items():
|
||||||
ret += " -%(key)s %(value)s" % {'key': key, 'value': value}
|
ret += " -%(key)s %(value)s" % {'key': key, 'value': value}
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@ -232,6 +239,10 @@ class FJDXCLI(object):
|
|||||||
}
|
}
|
||||||
return output
|
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):
|
def _show_pool_provision(self, **option):
|
||||||
"""Get TPP provision capacity information."""
|
"""Get TPP provision capacity information."""
|
||||||
try:
|
try:
|
||||||
@ -257,8 +268,129 @@ class FJDXCLI(object):
|
|||||||
output = {
|
output = {
|
||||||
'result': 0,
|
'result': 0,
|
||||||
'rc': '4',
|
'rc': '4',
|
||||||
'message': "show pool provision capacity error: %s"
|
'message': "show pool provision capacity error: %s" % ex
|
||||||
% six.text_type(ex)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
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.
|
FibreChannel Cinder Volume driver for Fujitsu ETERNUS DX S3 series.
|
||||||
"""
|
"""
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import six
|
|
||||||
|
|
||||||
from cinder.common import constants
|
from cinder.common import constants
|
||||||
|
from cinder import exception
|
||||||
|
from cinder.i18n import _
|
||||||
from cinder import interface
|
from cinder import interface
|
||||||
from cinder.volume import driver
|
from cinder.volume import driver
|
||||||
from cinder.volume.drivers.fujitsu.eternus_dx import eternus_dx_common
|
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):
|
def check_for_setup_error(self):
|
||||||
if not self.common.pywbemAvailable:
|
if not self.common.pywbemAvailable:
|
||||||
LOG.error('pywbem could not be imported! '
|
msg = _('pywbem could not be imported! '
|
||||||
'pywbem is necessary for this volume driver.')
|
'pywbem is necessary for this volume driver.')
|
||||||
|
LOG.error(msg)
|
||||||
|
raise exception.VolumeBackendAPIException(data=msg)
|
||||||
|
|
||||||
def create_volume(self, volume):
|
def create_volume(self, volume):
|
||||||
"""Create volume."""
|
"""Create volume."""
|
||||||
LOG.debug('create_volume, '
|
model_update = self.common.create_volume(volume)
|
||||||
'volume id: %s, enter method.', volume['id'])
|
|
||||||
|
|
||||||
location, metadata = self.common.create_volume(volume)
|
return model_update
|
||||||
|
|
||||||
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}
|
|
||||||
|
|
||||||
def create_volume_from_snapshot(self, volume, snapshot):
|
def create_volume_from_snapshot(self, volume, snapshot):
|
||||||
"""Creates a volume from a snapshot."""
|
"""Creates a volume from a snapshot."""
|
||||||
LOG.debug('create_volume_from_snapshot, '
|
|
||||||
'volume id: %(vid)s, snap id: %(sid)s, enter method.',
|
|
||||||
{'vid': volume['id'], 'sid': snapshot['id']})
|
|
||||||
|
|
||||||
location, metadata = (
|
location, metadata = (
|
||||||
self.common.create_volume_from_snapshot(volume, snapshot))
|
self.common.create_volume_from_snapshot(volume, snapshot))
|
||||||
|
|
||||||
v_metadata = self._get_metadata(volume)
|
v_metadata = self._get_metadata(volume)
|
||||||
metadata.update(v_metadata)
|
metadata.update(v_metadata)
|
||||||
|
|
||||||
LOG.debug('create_volume_from_snapshot, '
|
return {'provider_location': str(location), 'metadata': metadata}
|
||||||
'info: %s, exit method.', metadata)
|
|
||||||
return {'provider_location': six.text_type(location),
|
|
||||||
'metadata': metadata}
|
|
||||||
|
|
||||||
def create_cloned_volume(self, volume, src_vref):
|
def create_cloned_volume(self, volume, src_vref):
|
||||||
"""Create cloned volume."""
|
"""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 = (
|
location, metadata = (
|
||||||
self.common.create_cloned_volume(volume, src_vref))
|
self.common.create_cloned_volume(volume, src_vref))
|
||||||
|
|
||||||
v_metadata = self._get_metadata(volume)
|
v_metadata = self._get_metadata(volume)
|
||||||
metadata.update(v_metadata)
|
metadata.update(v_metadata)
|
||||||
|
|
||||||
LOG.debug('create_cloned_volume, '
|
return {'provider_location': str(location), 'metadata': metadata}
|
||||||
'info: %s, exit method.', metadata)
|
|
||||||
return {'provider_location': six.text_type(location),
|
|
||||||
'metadata': metadata}
|
|
||||||
|
|
||||||
def delete_volume(self, volume):
|
def delete_volume(self, volume):
|
||||||
"""Delete volume on ETERNUS."""
|
"""Delete volume on ETERNUS."""
|
||||||
LOG.debug('delete_volume, '
|
self.common.delete_volume(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)
|
|
||||||
|
|
||||||
def create_snapshot(self, snapshot):
|
def create_snapshot(self, snapshot):
|
||||||
"""Creates a 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)
|
location, metadata = self.common.create_snapshot(snapshot)
|
||||||
|
|
||||||
LOG.debug('create_snapshot, info: %s, exit method.', metadata)
|
return {'provider_location': str(location)}
|
||||||
return {'provider_location': six.text_type(location)}
|
|
||||||
|
|
||||||
def delete_snapshot(self, snapshot):
|
def delete_snapshot(self, snapshot):
|
||||||
"""Deletes a snapshot."""
|
"""Deletes a snapshot."""
|
||||||
LOG.debug('delete_snapshot, '
|
self.common.delete_snapshot(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)
|
|
||||||
|
|
||||||
def ensure_export(self, context, volume):
|
def ensure_export(self, context, volume):
|
||||||
"""Driver entry point to get the export info for an existing 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):
|
def initialize_connection(self, volume, connector):
|
||||||
"""Allow connection to connector and return connection info."""
|
"""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)
|
info = self.common.initialize_connection(volume, connector)
|
||||||
|
|
||||||
data = info['data']
|
data = info['data']
|
||||||
@ -163,20 +121,12 @@ class FJDXFCDriver(driver.FibreChannelDriver):
|
|||||||
data['initiator_target_map'] = init_tgt_map
|
data['initiator_target_map'] = init_tgt_map
|
||||||
|
|
||||||
info['data'] = data
|
info['data'] = data
|
||||||
LOG.debug('initialize_connection, '
|
|
||||||
'info: %s, exit method.', info)
|
|
||||||
fczm_utils.add_fc_zone(info)
|
fczm_utils.add_fc_zone(info)
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def terminate_connection(self, volume, connector, **kwargs):
|
def terminate_connection(self, volume, connector, **kwargs):
|
||||||
"""Disallow connection from connector."""
|
"""Disallow connection from connector."""
|
||||||
wwpns = connector.get('wwpns') if connector else None
|
self.common.terminate_connection(volume, connector)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
info = {'driver_volume_type': 'fibre_channel',
|
info = {'driver_volume_type': 'fibre_channel',
|
||||||
'data': {}}
|
'data': {}}
|
||||||
@ -189,15 +139,10 @@ class FJDXFCDriver(driver.FibreChannelDriver):
|
|||||||
info['data'] = {'initiator_target_map': init_tgt_map}
|
info['data'] = {'initiator_target_map': init_tgt_map}
|
||||||
fczm_utils.remove_fc_zone(info)
|
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
|
return info
|
||||||
|
|
||||||
def get_volume_stats(self, refresh=False):
|
def get_volume_stats(self, refresh=False):
|
||||||
"""Get volume stats."""
|
"""Get volume stats."""
|
||||||
LOG.debug('get_volume_stats, refresh: %s, enter method.', refresh)
|
|
||||||
|
|
||||||
pool_name = None
|
pool_name = None
|
||||||
if refresh is True:
|
if refresh is True:
|
||||||
data, pool_name = self.common.update_volume_stats()
|
data, pool_name = self.common.update_volume_stats()
|
||||||
@ -207,18 +152,12 @@ class FJDXFCDriver(driver.FibreChannelDriver):
|
|||||||
self._stats = data
|
self._stats = data
|
||||||
|
|
||||||
LOG.debug('get_volume_stats, '
|
LOG.debug('get_volume_stats, '
|
||||||
'pool name: %s, exit method.', pool_name)
|
'pool name: %s.', pool_name)
|
||||||
return self._stats
|
return self._stats
|
||||||
|
|
||||||
def extend_volume(self, volume, new_size):
|
def extend_volume(self, volume, new_size):
|
||||||
"""Extend volume."""
|
"""Extend volume."""
|
||||||
LOG.debug('extend_volume, '
|
self.common.extend_volume(volume, new_size)
|
||||||
'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)
|
|
||||||
|
|
||||||
def _get_metadata(self, volume):
|
def _get_metadata(self, volume):
|
||||||
v_metadata = volume.get('volume_metadata')
|
v_metadata = volume.get('volume_metadata')
|
||||||
|
@ -18,9 +18,10 @@
|
|||||||
|
|
||||||
"""iSCSI Cinder Volume driver for Fujitsu ETERNUS DX S3 series."""
|
"""iSCSI Cinder Volume driver for Fujitsu ETERNUS DX S3 series."""
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import six
|
|
||||||
|
|
||||||
from cinder.common import constants
|
from cinder.common import constants
|
||||||
|
from cinder import exception
|
||||||
|
from cinder.i18n import _
|
||||||
from cinder import interface
|
from cinder import interface
|
||||||
from cinder.volume import driver
|
from cinder.volume import driver
|
||||||
from cinder.volume.drivers.fujitsu.eternus_dx import eternus_dx_common
|
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):
|
def check_for_setup_error(self):
|
||||||
if not self.common.pywbemAvailable:
|
if not self.common.pywbemAvailable:
|
||||||
LOG.error('pywbem could not be imported! '
|
msg = _('pywbem could not be imported! '
|
||||||
'pywbem is necessary for this volume driver.')
|
'pywbem is necessary for this volume driver.')
|
||||||
|
LOG.error(msg)
|
||||||
return
|
raise exception.VolumeBackendAPIException(data=msg)
|
||||||
|
|
||||||
def create_volume(self, volume):
|
def create_volume(self, volume):
|
||||||
"""Create 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)
|
return model_update
|
||||||
|
|
||||||
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}
|
|
||||||
|
|
||||||
def create_volume_from_snapshot(self, volume, snapshot):
|
def create_volume_from_snapshot(self, volume, snapshot):
|
||||||
"""Creates a volume from a snapshot."""
|
"""Creates a volume from a snapshot."""
|
||||||
LOG.info('create_volume_from_snapshot, '
|
|
||||||
'volume id: %(vid)s, snap id: %(sid)s, Enter method.',
|
|
||||||
{'vid': volume['id'], 'sid': snapshot['id']})
|
|
||||||
|
|
||||||
element_path, metadata = (
|
element_path, metadata = (
|
||||||
self.common.create_volume_from_snapshot(volume, snapshot))
|
self.common.create_volume_from_snapshot(volume, snapshot))
|
||||||
|
|
||||||
@ -90,18 +75,10 @@ class FJDXISCSIDriver(driver.ISCSIDriver):
|
|||||||
v_metadata = volume.get('metadata', {})
|
v_metadata = volume.get('metadata', {})
|
||||||
metadata.update(v_metadata)
|
metadata.update(v_metadata)
|
||||||
|
|
||||||
LOG.info('create_volume_from_snapshot, '
|
return {'provider_location': str(element_path), 'metadata': metadata}
|
||||||
'info: %s, Exit method.', metadata)
|
|
||||||
return {'provider_location': six.text_type(element_path),
|
|
||||||
'metadata': metadata}
|
|
||||||
|
|
||||||
def create_cloned_volume(self, volume, src_vref):
|
def create_cloned_volume(self, volume, src_vref):
|
||||||
"""Create cloned volume."""
|
"""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 = (
|
element_path, metadata = (
|
||||||
self.common.create_cloned_volume(volume, src_vref))
|
self.common.create_cloned_volume(volume, src_vref))
|
||||||
|
|
||||||
@ -113,40 +90,21 @@ class FJDXISCSIDriver(driver.ISCSIDriver):
|
|||||||
v_metadata = volume.get('metadata', {})
|
v_metadata = volume.get('metadata', {})
|
||||||
metadata.update(v_metadata)
|
metadata.update(v_metadata)
|
||||||
|
|
||||||
LOG.info('create_cloned_volume, info: %s, Exit method.', metadata)
|
return {'provider_location': str(element_path), 'metadata': metadata}
|
||||||
return {'provider_location': six.text_type(element_path),
|
|
||||||
'metadata': metadata}
|
|
||||||
|
|
||||||
def delete_volume(self, volume):
|
def delete_volume(self, volume):
|
||||||
"""Delete volume on ETERNUS."""
|
"""Delete volume on ETERNUS."""
|
||||||
LOG.info('delete_volume, volume id: %s, Enter method.', volume['id'])
|
self.common.delete_volume(volume)
|
||||||
|
|
||||||
vol_exist = self.common.delete_volume(volume)
|
|
||||||
|
|
||||||
LOG.info('delete_volume, delete: %s, Exit method.', vol_exist)
|
|
||||||
return
|
|
||||||
|
|
||||||
def create_snapshot(self, snapshot):
|
def create_snapshot(self, snapshot):
|
||||||
"""Creates a 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)
|
element_path, metadata = self.common.create_snapshot(snapshot)
|
||||||
|
|
||||||
LOG.info('create_snapshot, info: %s, Exit method.', metadata)
|
return {'provider_location': str(element_path)}
|
||||||
return {'provider_location': six.text_type(element_path)}
|
|
||||||
|
|
||||||
def delete_snapshot(self, snapshot):
|
def delete_snapshot(self, snapshot):
|
||||||
"""Deletes a snapshot."""
|
"""Deletes a snapshot."""
|
||||||
LOG.info('delete_snapshot, snap id: %(sid)s, volume id: %(vid)s, '
|
self.common.delete_snapshot(snapshot)
|
||||||
'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
|
|
||||||
|
|
||||||
def ensure_export(self, context, volume):
|
def ensure_export(self, context, volume):
|
||||||
"""Driver entry point to get the export info for an existing 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):
|
def initialize_connection(self, volume, connector):
|
||||||
"""Allow connection to connector and return connection info."""
|
"""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)
|
info = self.common.initialize_connection(volume, connector)
|
||||||
|
|
||||||
LOG.info('initialize_connection, info: %s, Exit method.', info)
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def terminate_connection(self, volume, connector, **kwargs):
|
def terminate_connection(self, volume, connector, **kwargs):
|
||||||
"""Disallow connection from connector."""
|
"""Disallow connection from connector."""
|
||||||
initiator = connector.get('initiator') if connector else None
|
self.common.terminate_connection(volume, connector)
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def get_volume_stats(self, refresh=False):
|
def get_volume_stats(self, refresh=False):
|
||||||
"""Get volume stats."""
|
"""Get volume stats."""
|
||||||
LOG.debug('get_volume_stats, refresh: %s, Enter method.', refresh)
|
|
||||||
|
|
||||||
pool_name = None
|
pool_name = None
|
||||||
if refresh is True:
|
if refresh is True:
|
||||||
data, pool_name = self.common.update_volume_stats()
|
data, pool_name = self.common.update_volume_stats()
|
||||||
@ -197,14 +139,9 @@ class FJDXISCSIDriver(driver.ISCSIDriver):
|
|||||||
self._stats = data
|
self._stats = data
|
||||||
|
|
||||||
LOG.debug('get_volume_stats, '
|
LOG.debug('get_volume_stats, '
|
||||||
'pool name: %s, Exit method.', pool_name)
|
'pool name: %s.', pool_name)
|
||||||
return self._stats
|
return self._stats
|
||||||
|
|
||||||
def extend_volume(self, volume, new_size):
|
def extend_volume(self, volume, new_size):
|
||||||
"""Extend volume."""
|
"""Extend volume."""
|
||||||
LOG.info('extend_volume, volume id: %s, Enter method.', volume['id'])
|
self.common.extend_volume(volume, new_size)
|
||||||
|
|
||||||
used_pool_name = self.common.extend_volume(volume, new_size)
|
|
||||||
|
|
||||||
LOG.info('extend_volume, used pool name: %s, Exit method.',
|
|
||||||
used_pool_name)
|
|
||||||
|
@ -3,7 +3,7 @@ Fujitsu ETERNUS DX driver
|
|||||||
=========================
|
=========================
|
||||||
|
|
||||||
Fujitsu ETERNUS DX driver provides FC and iSCSI support for
|
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
|
The driver performs volume operations by communicating with
|
||||||
ETERNUS DX. It uses a CIM client in Python called PyWBEM
|
ETERNUS DX. It uses a CIM client in Python called PyWBEM
|
||||||
@ -17,18 +17,23 @@ System requirements
|
|||||||
|
|
||||||
Supported storages:
|
Supported storages:
|
||||||
|
|
||||||
* ETERNUS DX60 S3
|
* ETERNUS AF150 S3
|
||||||
* ETERNUS DX100 S3/DX200 S3
|
* ETERNUS AF250 S3/AF250 S2/AF250
|
||||||
* ETERNUS DX500 S3/DX600 S3
|
* ETERNUS AF650 S3/AF650 S2/AF650
|
||||||
* ETERNUS DX8700 S3/DX8900 S3
|
|
||||||
* ETERNUS DX200F
|
* 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:
|
Requirements:
|
||||||
|
|
||||||
* Firmware version V10L30 or later is required.
|
* Firmware version V10L30 or later is required.
|
||||||
* The multipath environment with ETERNUS Multipath Driver is unsupported.
|
* The multipath environment with ETERNUS Multipath Driver is unsupported.
|
||||||
* An Advanced Copy Feature license is required
|
* 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
|
Supported operations
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
@ -60,9 +65,10 @@ Perform the following steps using ETERNUS Web GUI or ETERNUS CLI.
|
|||||||
.. note::
|
.. note::
|
||||||
* These following operations require an account that has the ``Admin`` role.
|
* These following operations require an account that has the ``Admin`` role.
|
||||||
* For detailed operations, refer to ETERNUS Web GUI User's Guide or
|
* 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.
|
#. 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 Snap Data Pool Volume (SDPV) to enable Snap Data Pool (SDP) for
|
||||||
``create a snapshot``.
|
``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.
|
* Set those storage ports to CA mode.
|
||||||
- Enable the host-affinity settings of those storage ports.
|
* Enable the host-affinity settings of those storage ports.
|
||||||
|
|
||||||
(ETERNUS CLI command for enabling host-affinity settings):
|
(ETERNUS CLI command for enabling host-affinity settings):
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
CLI> set fc-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#>
|
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
|
#. Ensure LAN connection between cinder controller and MNT port of ETERNUS DX
|
||||||
and SAN connection between Compute nodes and CA ports of ETERNUS DX.
|
and SAN connection between Compute nodes and CA ports of ETERNUS DX.
|
||||||
@ -160,28 +170,30 @@ Configuration
|
|||||||
Where:
|
Where:
|
||||||
|
|
||||||
``EternusIP``
|
``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``
|
``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``
|
``EternusUser``
|
||||||
User name for the SMI-S connection of the ETERNUS DX.
|
User name of ``sofware`` role for the connection ``EternusIP``.
|
||||||
|
|
||||||
``EternusPassword``
|
``EternusPassword``
|
||||||
Password for the SMI-S connection of the ETERNUS DX.
|
Corresponding password of ``EternusUser`` on ``EternusIP``.
|
||||||
|
|
||||||
``EternusPool`` (Multiple setting allowed)
|
``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``
|
``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)
|
``EternusISCSIIP`` (Multiple setting allowed)
|
||||||
iSCSI connection IP address of the ETERNUS DX.
|
iSCSI connection IP address of the ETERNUS DX.
|
||||||
@ -221,11 +233,166 @@ Configuration example
|
|||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ openstack volume type create DX_FC
|
$ cinder type-create DX_FC
|
||||||
$ openstack volume type set --property volume_backend_name=FC DX_FX
|
$ cinder type-key DX_FX set volume_backend_name=FC
|
||||||
$ openstack volume type create DX_ISCSI
|
$ cinder type-create DX_ISCSI
|
||||||
$ openstack volume type set --property volume_backend_name=ISCSI DX_ISCSI
|
$ cinder type-key DX_ISCSI set volume_backend_name=ISCSI
|
||||||
|
|
||||||
By issuing these commands,
|
By issuing these commands,
|
||||||
the volume type ``DX_FC`` is associated with the ``FC``,
|
the volume type ``DX_FC`` is associated with the ``FC``,
|
||||||
and the type ``DX_ISCSI`` is associated with the ``ISCSI``.
|
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…
x
Reference in New Issue
Block a user