Merge "Fujitsu driver: Improve volume deletion"

This commit is contained in:
Zuul 2024-04-19 17:54:31 +00:00 committed by Gerrit Code Review
commit e28867ba8d
6 changed files with 324 additions and 31 deletions

View File

@ -175,7 +175,7 @@ FAKE_POOLS = [{
}]
FAKE_STATS = {
'driver_version': '1.4.1',
'driver_version': '1.4.2',
'storage_protocol': 'iSCSI',
'vendor_name': 'FUJITSU',
'QoS_support': True,
@ -185,7 +185,7 @@ FAKE_STATS = {
'pools': FAKE_POOLS,
}
FAKE_STATS2 = {
'driver_version': '1.4.1',
'driver_version': '1.4.2',
'storage_protocol': 'FC',
'vendor_name': 'FUJITSU',
'QoS_support': True,
@ -375,8 +375,11 @@ class FakeCIMInstanceName(dict):
snaps = FakeCIMInstanceName()
snaps['ElementName'] = 'FJosv_OgEZj1mSvKRvIKOExKktlg=='
snaps['Name'] = None
snaps['DeviceID'] = FAKE_LUN_ID2
snaps['SystemName'] = STORAGE_SYSTEM
ret.append(snaps)
snaps.path = ''
snaps.classname = 'FUJITSU_StorageVolume'
map = FakeCIMInstanceName()
map['ElementName'] = 'FJosv_hhJsV9lcMBvAPADrGqucwg=='
@ -1037,6 +1040,16 @@ class FJFCDriverTestCase(test.TestCase):
'0002\t\r\n0000\tFJosv_0qJ4rpOHgFE8ipcJOMfBmg==\t0F'
'\t\r\n0001\tFJosv_OgEZj1mSvKRvIKOExKktlg==\t0D'
'\t\r\nCLI> ' % exec_cmdline)
elif exec_cmdline.startswith('show copy-sessions'):
ret = ('\r\nCLI> %s\r\n00\r\n0001\t\r\n'
'0001\tFFFF\t01\t08\tFF\tFF\t03\t02\tFF\tFF\t05ABD7D2\t'
'########################################\t'
'########################################\t'
'00000281\t00000286\t0001\t00\tFF\t0000000000000800\t'
'0000000000000000\t0000000000000100\t0000000000000800\t'
'04\t00\t00000000\t2020101009341400\t01\t10\tFFFF\tFFFF\t'
'0000000000000000\tFFFFFFFFFFFFFFFF\tFFFFFFFFFFFFFFFF\tFF\t'
'FF\t64\t00\t07\t00\t00\t00\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'
@ -1080,6 +1093,8 @@ class FJFCDriverTestCase(test.TestCase):
'\r\nCLI> ' % exec_cmdline)
elif exec_cmdline.startswith('set qos-bandwidth-limit'):
ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
elif exec_cmdline.startswith('stop copy-session'):
ret = '%s\r\n00\r\nCLI> ' % exec_cmdline
else:
ret = None
return ret
@ -1284,6 +1299,16 @@ class FJISCSIDriverTestCase(test.TestCase):
'0002\t\r\n0000\tFJosv_0qJ4rpOHgFE8ipcJOMfBmg==\t0F'
'\t\r\n0001\tFJosv_OgEZj1mSvKRvIKOExKktlg==\t0D'
'\t\r\nCLI> ' % exec_cmdline)
elif exec_cmdline.startswith('show copy-sessions'):
ret = ('\r\nCLI> %s\r\n00\r\n0001\t\r\n'
'0001\tFFFF\t01\t08\tFF\tFF\t03\t02\tFF\tFF\t05ABD7D2\t'
'########################################\t'
'########################################\t'
'00000281\t00000286\t0001\t00\tFF\t0000000000000800\t'
'0000000000000000\t0000000000000100\t0000000000000800\t'
'04\t00\t00000000\t2020101009341400\t01\t10\tFFFF\tFFFF\t'
'0000000000000000\tFFFFFFFFFFFFFFFF\tFFFFFFFFFFFFFFFF\tFF\t'
'FF\t64\t00\t07\t00\t00\t00\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'
@ -1515,6 +1540,16 @@ class FJCLITestCase(test.TestCase):
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 copy-sessions'):
ret = ('\r\nCLI> %s\r\n00\r\n0001\t\r\n'
'0001\tFFFF\t01\t08\tFF\tFF\t03\t02\tFF\tFF\t05ABD7D2\t'
'########################################\t'
'########################################\t'
'00000281\t00000286\t0001\t00\tFF\t0000000000000800\t'
'0000000000000000\t0000000000000100\t0000000000000800\t'
'04\t00\t00000000\t2020101009341400\t01\t10\tFFFF\tFFFF\t'
'0000000000000000\tFFFFFFFFFFFFFFFF\tFFFFFFFFFFFFFFFF\tFF\t'
'FF\t64\t00\t07\t00\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'
@ -1522,6 +1557,8 @@ class FJCLITestCase(test.TestCase):
'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('stop copy-session'):
ret = '%s\r\n00\r\nCLI> ' % exec_cmdline
elif exec_cmdline.startswith('delete volume'):
ret = '%s\r\n00\r\nCLI> ' % exec_cmdline
else:
@ -1608,6 +1645,21 @@ class FJCLITestCase(test.TestCase):
volume_number = self.cli._set_volume_qos(**FAKE_QOS_OPTION)
self.assertEqual(FAKE_QOS_OUTPUT, volume_number)
def test_show_copy_sessions(self):
FAKE_COPY_SESSION = [{
'Source Num': 641,
'Dest Num': 646,
'Type': 'Snap',
'Status': 'Active',
'Phase': 'Tracking',
'Session ID': 1,
}]
FAKE_COPY_SESSION_OUTPUT = {**FAKE_CLI_OUTPUT,
'message': FAKE_COPY_SESSION}
cpdatalist = self.cli._show_copy_sessions()
self.assertEqual(FAKE_COPY_SESSION_OUTPUT, cpdatalist)
def test_show_pool_provision(self):
FAKE_POOL_PROVIOSN_OPTION = self.create_fake_options(
pool_name='abcd1234_TPP')
@ -1666,6 +1718,15 @@ class FJCLITestCase(test.TestCase):
versioninfo = self.cli._show_enclosure_status()
self.assertEqual(FAKE_VERSION_INFO, versioninfo)
def test_stop_copy_session(self):
FAKE_SESSION_ID = '0001'
FAKE_STOP_OUTPUT = {**FAKE_CLI_OUTPUT, 'message': []}
FAKE_STOP_COPY_SESSION_OPTION = self.create_fake_options(
session_id=FAKE_SESSION_ID)
stop_output = self.cli._stop_copy_session(
**FAKE_STOP_COPY_SESSION_OPTION)
self.assertEqual(FAKE_STOP_OUTPUT, stop_output)
def test_delete_volume(self):
FAKE_VOLUME_NAME = 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
FAKE_DELETE_OUTPUT = {**FAKE_CLI_OUTPUT, 'message': []}
@ -1746,13 +1807,25 @@ class FJCommonTestCase(test.TestCase):
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 copy-sessions'):
ret = ('\r\nCLI> %s\r\n00\r\n0001\t\r\n'
'0001\tFFFF\t01\t08\tFF\tFF\t03\t02\tFF\tFF\t05ABD7D2\t'
'########################################\t'
'########################################\t'
'00000281\t00000286\t0001\t00\tFF\t0000000000000800\t'
'0000000000000000\t0000000000000100\t0000000000000800\t'
'04\t00\t00000000\t2020101009341400\t01\t10\tFFFF\tFFFF\t'
'0000000000000000\tFFFFFFFFFFFFFFFF\tFFFFFFFFFFFFFFFF\tFF\t'
'FF\t64\t00\t07\t00\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
ret = '%s\r\n00\r\n0001\r\nCLI> ' % exec_cmdline
elif exec_cmdline.startswith('stop copy-session'):
ret = '%s\r\n00\r\nCLI> ' % exec_cmdline
else:
ret = None
return ret
@ -1764,6 +1837,19 @@ class FJCommonTestCase(test.TestCase):
conn = FakeEternusConnection()
return conn
def test_get_volume_number(self):
vol_instance = FakeCIMInstanceName()
vol_instance['ElementName'] = 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
vol_instance['Purpose'] = '00228+0x06'
vol_instance['Name'] = None
vol_instance['DeviceID'] = FAKE_LUN_ID1
vol_instance['SystemName'] = STORAGE_SYSTEM
vol_instance.path = ''
vol_instance.classname = 'FUJITSU_StorageVolume'
volume_no = self.driver.common._get_volume_number(vol_instance)
self.assertEqual(FAKE_LUN_NO1, volume_no)
def test_get_eternus_model(self):
ETERNUS_MODEL = self.driver.common._get_eternus_model()
self.assertEqual(3, ETERNUS_MODEL)
@ -1846,3 +1932,15 @@ class FJCommonTestCase(test.TestCase):
self.driver.common._set_limit(FAKE_MODE, FAKE_LIMIT,
FAKE_IOPS, FAKE_THROUGHOUTPUT)
mock_exec_cli_with_eternus.assert_called_with(exec_cmdline)
def test_get_copy_sessions_list(self):
FAKE_COPY_SESSION = [{
'Source Num': 641,
'Dest Num': 646,
'Type': 'Snap',
'Status': 'Active',
'Phase': 'Tracking',
'Session ID': 1,
}]
copy_session_list = self.driver.common._get_copy_sessions_list()
self.assertEqual(FAKE_COPY_SESSION, copy_session_list)

View File

@ -50,8 +50,10 @@ class FJDXCLI(object):
'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_copy_sessions': self._show_copy_sessions,
'show_volume_qos': self._show_volume_qos,
'show_enclosure_status': self._show_enclosure_status,
'stop_copy_session': self._stop_copy_session,
'delete_volume': self._delete_volume
}
@ -276,6 +278,92 @@ class FJDXCLI(object):
return output
def _show_copy_sessions(self, **option):
"""Get copy sessions."""
try:
output = self._exec_cli("show copy-sessions", **option)
# return error
rc = output['rc']
if rc != "0":
return output
cpsdatalist = []
clidatalist = output.get('message')
for clidataline in clidatalist[1:]:
clidata = clidataline.split('\t')
# Get CopyType
if clidata[2] == '01':
# CopyKind: OPC
if bin(int(clidata[3], 16) & 16) != 0:
# eg. 0b10010000
temp_type = 'Snap'
elif bin(int(clidata[3], 16) & 64) != 0:
# eg. 0b11000000
temp_type = 'Snap+'
else:
temp_type = 'Other'
elif clidata[2] == '02':
# CopyKind: EC
if clidata[5] == 'FF':
temp_type = 'EC'
elif clidata[5] == '10':
temp_type = 'Sync_REC'
else:
temp_type = 'Other'
else:
temp_type = 'Other'
# Get Phases
if clidata[6] == '00':
temp_phase = 'No_Pair'
elif clidata[6] == '01':
temp_phase = 'Copying'
elif clidata[6] == '02':
temp_phase = 'Equivalent'
elif clidata[6] == '03':
temp_phase = 'Tracking'
elif clidata[6] == '04':
temp_phase = 'Tracking_Copying'
elif clidata[6] == '06':
temp_phase = 'Readying'
else:
temp_phase = 'Other'
# Get CopyStatus
if clidata[7] == '00':
temp_status = 'Idle'
elif clidata[7] == '01':
temp_status = 'Reserve'
elif clidata[7] == '02':
temp_status = 'Active'
elif clidata[7] == '03':
temp_status = 'Error_Suspend'
elif clidata[7] == '04':
temp_status = 'Suspend'
elif clidata[7] == '05':
temp_status = 'Halt'
else:
temp_status = 'Other'
cpsdatalist.append({'Source Num': int(clidata[13], 16),
'Dest Num': int(clidata[14], 16),
'Type': temp_type,
'Status': temp_status,
'Phase': temp_phase,
'Session ID': int(clidata[0], 16)})
output['message'] = cpsdatalist
except Exception as ex:
output = {'result': 0,
'rc': '4',
'message': "Show copy sessions error: %s"
% str(ex)}
return output
def _show_qos_bandwidth_limit(self, **option):
"""Get qos bandwidth limit."""
clidata = None
@ -394,6 +482,10 @@ class FJDXCLI(object):
return output
def _stop_copy_session(self, **option):
"""Exec stop copy-session."""
return self._exec_cli("stop copy-session", **option)
def _delete_volume(self, **option):
"""Exec delete volume."""
return self._exec_cli('delete volume', **option)

View File

@ -67,10 +67,11 @@ class FJDXCommon(object):
1.3.0 - Community base version
1.4.0 - Add support for QoS.
1.4.1 - Add the method for expanding RAID volumes by CLI.
1.4.2 - Add the secondary check for copy-sessions when deleting volumes.
"""
VERSION = "1.4.1"
VERSION = "1.4.2"
stats = {
'driver_version': VERSION,
'storage_protocol': None,
@ -261,7 +262,7 @@ class FJDXCommon(object):
'vol_name': volumename,
}
volume_no = "0x" + element['DeviceID'][24:28]
volume_no = self._get_volume_number(element)
metadata = {
'FJ_Backend': systemnamelist[0]['IdentifyingNumber'],
'FJ_Volume_Name': volumename,
@ -445,36 +446,37 @@ class FJDXCommon(object):
def delete_volume(self, volume):
"""Delete volume on ETERNUS."""
LOG.debug('delete_volume, volume id: %s.', volume['id'])
LOG.debug('delete_volume, volume id: %(vid)s.',
{'vid': volume['id']})
self.conn = self._get_eternus_connection()
vol_exist = self._delete_volume_setting(volume)
if not vol_exist:
LOG.debug('delete_volume, volume not found in 1st check.')
return False
return
# Check volume existence on ETERNUS again
# because volume is deleted when SnapOPC copysession is deleted.
vol_instance = self._find_lun(volume)
if vol_instance is None:
LOG.debug('delete_volume, volume not found in 2nd check, '
'but no problem.')
return True
self._delete_volume(vol_instance)
return True
try:
self._delete_volume(volume)
except Exception as ex:
msg = (_('delete_volume, '
'delete volume failed, '
'Error information: %s.')
% ex)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
@lockutils.synchronized('ETERNUS-vol', 'cinder-', True)
def _delete_volume_setting(self, volume):
"""Delete volume setting (HostAffinity, CopySession) on ETERNUS."""
LOG.debug('_delete_volume_setting, volume id: %s.', volume['id'])
LOG.debug('_delete_volume_setting, '
'volume id: %(vid)s.',
{'vid': volume['id']})
# Check the existence of volume.
volumename = self._get_volume_name(volume)
vol_instance = self._find_lun(volume)
if vol_instance is None:
if not vol_instance:
LOG.info('_delete_volume_setting, volumename:%(volumename)s, '
'volume not found on ETERNUS.',
{'volumename': volumename})
@ -514,6 +516,33 @@ class FJDXCommon(object):
for cpsession in delete_copysession_list:
self._delete_copysession(cpsession)
volume_no = self._get_volume_number(vol_instance)
cp_session_list = self._get_copy_sessions_list()
for cp in cp_session_list:
if cp['Dest Num'] != int(volume_no, 16):
continue
if cp['Type'] == 'Snap':
session_id = cp['Session ID']
param_dict = ({'session-id': session_id})
rc, emsg, clidata = self._exec_eternus_cli(
'stop_copy_session',
**param_dict)
if rc != 0:
msg = (_('_delete_volume_setting, '
'stop_copy_session failed. '
'Return code: %(rc)lu, '
'Error: %(errormsg)s, '
'Message: %(clidata)s.')
% {'rc': rc,
'errormsg': emsg,
'clidata': clidata})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
break
LOG.debug('_delete_volume_setting, '
'wait_cpsession: %(wait_cpsession)s, '
'delete_cpsession: %(delete_cpsession)s, complete.',
@ -522,15 +551,22 @@ class FJDXCommon(object):
return True
@lockutils.synchronized('ETERNUS-vol', 'cinder-', True)
def _delete_volume(self, vol_instance):
def _delete_volume(self, volume):
"""Delete volume on ETERNUS."""
LOG.debug('_delete_volume, volume name: %s.',
vol_instance['ElementName'])
LOG.debug('_delete_volume, volume id: %(vid)s.',
{'vid': volume['id']})
vol_instance = self._find_lun(volume)
if not vol_instance:
LOG.debug('_delete_volume, volume not found in 2nd check, '
'but no problem.')
return
volumename = vol_instance['ElementName']
configservice = self._find_eternus_service(CONSTANTS.STOR_CONF)
if configservice is None:
if not configservice:
msg = (_('_delete_volume, volumename: %(volumename)s, '
'Storage Configuration Service not found.')
% {'volumename': volumename})
@ -698,7 +734,7 @@ class FJDXCommon(object):
'vol_name': d_volumename,
}
sdv_no = "0x" + element['DeviceID'][24:28]
sdv_no = self._get_volume_number(element)
metadata = {'FJ_SDV_Name': d_volumename,
'FJ_SDV_No': sdv_no,
'FJ_Pool_Name': eternus_pool}
@ -710,9 +746,7 @@ class FJDXCommon(object):
'snapshot id: %(sid)s, volume id: %(vid)s.',
{'sid': snapshot['id'], 'vid': snapshot['volume_id']})
vol_exist = self.delete_volume(snapshot)
LOG.debug('delete_snapshot, vol_exist: %s.', vol_exist)
return vol_exist
self.delete_volume(snapshot)
def initialize_connection(self, volume, connector):
"""Allow connection to connector and return connection info."""
@ -1421,8 +1455,7 @@ class FJDXCommon(object):
'classname: %s.', classname)
try:
services = self._enum_eternus_instance_names(
str(classname))
services = self._enum_eternus_instance_names(str(classname))
except Exception:
msg = (_('_find_eternus_service, '
'classname: %(classname)s, '
@ -2426,7 +2459,7 @@ class FJDXCommon(object):
for vol_instance in vollist:
if src_id:
volume_no = "0x" + vol_instance['DeviceID'][24:28]
volume_no = self._get_volume_number(vol_instance)
try:
# Skip hidden tppv volumes.
if int(src_id) == int(volume_no, 16):
@ -2536,6 +2569,22 @@ class FJDXCommon(object):
return ret
def _get_volume_number(self, vol):
"""Get volume no(return a hex string)."""
if self.model_name == CONSTANTS.DX_S2:
volume_number = "0x%04X" % int(vol['DeviceID'][-5:])
else:
volume_number = "0x" + vol['DeviceID'][24:28]
LOG.debug('_get_volume_number: %s.', volume_number)
return volume_number
def _exec_eternus_smis_ReferenceNames(self, classname,
conn=None,
**param_dict):
ret = conn.ReferenceNames(classname, **param_dict)
return ret
def _check_user(self):
"""Check whether user's role is accessible to ETERNUS and Software."""
ret = True
@ -3131,3 +3180,30 @@ class FJDXCommon(object):
'clidata': clidata})
LOG.warning(msg)
raise exception.VolumeBackendAPIException(data=msg)
def _get_copy_sessions_list(self, **param):
"""Get copy sessions list."""
LOG.debug('_get_copy_sessions_list, Enter method.')
rc, emsg, clidata = self._exec_eternus_cli(
'show_copy_sessions',
**param
)
if rc != 0:
msg = (_('_get_copy_sessions_list, '
'get copy sessions failed. '
'Return code: %(rc)lu, '
'Error: %(emsg)s, '
'Message: %(clidata)s.')
% {'rc': rc,
'emsg': emsg,
'clidata': clidata})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('_get_copy_sessions_list, Exit method, '
'copy sessions list: %(clidata)s. ',
{'clidata': clidata})
return clidata

View File

@ -87,8 +87,14 @@ class FJDXFCDriver(driver.FibreChannelDriver):
def delete_volume(self, volume):
"""Delete volume on ETERNUS."""
LOG.debug('delete_volume, '
'volume id: %s, Enter method.', volume['id'])
self.common.delete_volume(volume)
LOG.debug('delete_volume, '
'volume id: %s, delete succeed.', volume['id'])
def create_snapshot(self, snapshot):
"""Creates a snapshot."""
location, metadata = self.common.create_snapshot(snapshot)

View File

@ -94,8 +94,14 @@ class FJDXISCSIDriver(driver.ISCSIDriver):
def delete_volume(self, volume):
"""Delete volume on ETERNUS."""
LOG.debug('delete_volume, '
'volume id: %s, Enter method.', volume['id'])
self.common.delete_volume(volume)
LOG.debug('delete_volume, '
'volume id: %s, delete succeed.', volume['id'])
def create_snapshot(self, snapshot):
"""Creates a snapshot."""
element_path, metadata = self.common.create_snapshot(snapshot)

View File

@ -0,0 +1,15 @@
---
features:
- |
Fujitsu ETERNUS DX driver: Improve volume deletion
To improve the volume deletion process, add a step to check associated copy
sessions. Additionally, it also improves the process of retrieving
storage-managed volume numbers.
There was a problem where the volume could not be deleted because the copy
session information acquired by SMI-S IF from ETERNUS DX Storage, which was
cached and did not reflect the information that had just been executed.
This problem has been addressed through improvements in information
retrieval.