Fujitsu Driver: Add support for revert to snapshot

Adding support to revert a volume to a snapshot in Fujitsu driver.
This patch implements:
- revert_to_snapshot()

Implements: blueprint fujitsu-revert-to-snapshot-support
Change-Id: I6d74c718784c6ca46c51ce0a122521081236ac0b
This commit is contained in:
inori 2024-01-10 05:11:21 -05:00 committed by xuq.fnstxz
parent a50b897459
commit 68590efa06
8 changed files with 246 additions and 5 deletions

View File

@ -181,7 +181,7 @@ FAKE_POOLS = [{
}]
FAKE_STATS = {
'driver_version': '1.4.6',
'driver_version': '1.4.7',
'storage_protocol': 'iSCSI',
'vendor_name': 'FUJITSU',
'QoS_support': True,
@ -191,7 +191,7 @@ FAKE_STATS = {
'pools': FAKE_POOLS,
}
FAKE_STATS2 = {
'driver_version': '1.4.6',
'driver_version': '1.4.7',
'storage_protocol': 'FC',
'vendor_name': 'FUJITSU',
'QoS_support': True,
@ -425,7 +425,7 @@ class FakeEternusConnection(object):
SourceElement=None, TargetElement=None,
Operation=None, CopyType=None,
Synchronization=None, ProtocolControllers=None,
TargetPool=None):
TargetPool=None, WaitForCopyState=None):
global MAP_STAT, VOL_STAT
if MethodName == 'CreateOrModifyElementFromStoragePool':
VOL_STAT = '1'
@ -630,6 +630,35 @@ class FakeEternusConnection(object):
def _ref_storage_sync(self):
syncnames = []
cpsessions = {}
synced = FakeCIMInstanceName()
synced_keybindings = {}
synced_keybindings['CreationClassName'] = STOR_VOL
synced_keybindings['DeviceID'] = FAKE_LUN_ID2
synced_keybindings['SystemCreationClassName'] = \
'FUJITSU_StorageComputerSystem'
synced_keybindings['SystemName'] = STORAGE_SYSTEM
synced['ClassName'] = STOR_VOL
synced.keybindings = synced_keybindings
cpsessions['SyncedElement'] = synced
system = FakeCIMInstanceName()
system_keybindings = {}
system_keybindings['CreationClassName'] = STOR_VOL
system_keybindings['DeviceID'] = FAKE_LUN_ID1
system_keybindings['SystemCreationClassName'] = \
'FUJITSU_StorageComputerSystem'
system_keybindings['SystemName'] = STORAGE_SYSTEM
system['ClassName'] = STOR_VOL
system.keybindings = system_keybindings
cpsessions['SystemElement'] = system
cpsessions['classname'] = STOR_SYNC
syncnames.append(cpsessions)
return syncnames
def _default_ref(self, objectpath):
@ -842,6 +871,7 @@ class FakeEternusConnection(object):
},
}
volume['provider_location'] = str(name)
volume.path.keybindings = name['keybindings']
volumes.append(volume)
volume3 = FJ_StorageVolume()
@ -867,6 +897,7 @@ class FakeEternusConnection(object):
},
}
volume3['provider_location'] = str(name3)
volume3.path.keybindings = name3['keybindings']
volumes.append(volume3)
snap_vol = FJ_StorageVolume()
@ -891,6 +922,7 @@ class FakeEternusConnection(object):
},
}
snap_vol['provider_location'] = str(name2)
snap_vol.path.keybindings = name2['keybindings']
volumes.append(snap_vol)
snap_vol2 = FJ_StorageVolume()
@ -927,6 +959,18 @@ class FakeEternusConnection(object):
clone_vol['SystemCreationClassName'] = 'FUJITSU_StorageComputerSystem'
clone_vol.path = clone_vol
clone_vol.path.classname = clone_vol['CreationClassName']
name_clone = {
'classname': 'FUJITSU_StorageVolume',
'keybindings': {
'CreationClassName': 'FUJITSU_StorageVolume',
'SystemName': STORAGE_SYSTEM,
'DeviceID': clone_vol['DeviceID'],
'SystemCreationClassName': 'FUJITSU_StorageComputerSystem',
},
}
clone_vol['provider_location'] = str(name_clone)
clone_vol.path.keybindings = name_clone['keybindings']
volumes.append(clone_vol)
return volumes
@ -1318,6 +1362,23 @@ class FJFCDriverTestCase(test.TestCase):
}
self.assertEqual(FAKE_MIGRATED_MODEL_UPDATE, model_update)
def test_revert_to_snapshot(self):
self.driver.common.revert_to_snapshot = mock.Mock()
model_info = self.driver.create_volume(TEST_VOLUME)
self.volume_update(TEST_VOLUME, model_info)
self.assertEqual(FAKE_MODEL_INFO1, model_info)
snap_info = self.driver.create_snapshot(TEST_SNAP)
self.volume_update(TEST_SNAP, snap_info)
self.assertEqual(FAKE_SNAP_INFO, snap_info)
self.driver.revert_to_snapshot(self.context,
TEST_VOLUME,
TEST_SNAP)
self.driver.common.revert_to_snapshot.assert_called_with(TEST_VOLUME,
TEST_SNAP)
class FJISCSIDriverTestCase(test.TestCase):
def __init__(self, *args, **kwargs):
@ -1618,6 +1679,23 @@ class FJISCSIDriverTestCase(test.TestCase):
}
self.assertEqual(FAKE_MIGRATED_MODEL_UPDATE, model_update)
def test_revert_to_snapshot(self):
self.driver.common.revert_to_snapshot = mock.Mock()
model_info = self.driver.create_volume(TEST_VOLUME)
self.volume_update(TEST_VOLUME, model_info)
self.assertEqual(FAKE_MODEL_INFO1, model_info)
snap_info = self.driver.create_snapshot(TEST_SNAP)
self.volume_update(TEST_SNAP, snap_info)
self.assertEqual(FAKE_SNAP_INFO, snap_info)
self.driver.revert_to_snapshot(self.context,
TEST_VOLUME,
TEST_SNAP)
self.driver.common.revert_to_snapshot.assert_called_with(TEST_VOLUME,
TEST_SNAP)
class FJCLITestCase(test.TestCase):
def __init__(self, *args, **kwargs):
@ -1701,6 +1779,8 @@ class FJCLITestCase(test.TestCase):
ret = '%s\r\n00\r\nCLI> ' % exec_cmdline
elif exec_cmdline.startswith('start copy-snap-opc'):
ret = '%s\r\n00\r\n0019\r\nCLI> ' % exec_cmdline
elif exec_cmdline.startswith('start copy-opc'):
ret = '%s\r\n00\r\n0019\r\nCLI> ' % exec_cmdline
else:
ret = None
return ret
@ -1884,6 +1964,19 @@ class FJCLITestCase(test.TestCase):
**FAKE_STOP_COPY_SESSION_OPTION)
self.assertEqual(FAKE_STOP_OUTPUT, stop_output)
def test_start_copy_opc(self):
FAKE_SNAP_OPC_OPTION = self.create_fake_options(
source_volume_number=31,
destination_volume_number=39,
)
FAKE_OPC_ID = '0019'
FAKE_OPC_INFO = {**FAKE_CLI_OUTPUT,
'message': [FAKE_OPC_ID]}
opc_id = self.cli._start_copy_opc(**FAKE_SNAP_OPC_OPTION)
self.assertEqual(FAKE_OPC_INFO, opc_id)
def test_delete_volume(self):
FAKE_VOLUME_NAME = 'FJosv_0qJ4rpOHgFE8ipcJOMfBmg=='
FAKE_DELETE_OUTPUT = {**FAKE_CLI_OUTPUT, 'message': []}

View File

@ -55,6 +55,7 @@ class FJDXCLI(object):
'show_enclosure_status': self._show_enclosure_status,
'start_copy_snap_opc': self._start_copy_snap_opc,
'stop_copy_session': self._stop_copy_session,
'start_copy_opc': self._start_copy_opc,
'delete_volume': self._delete_volume
}
@ -491,6 +492,10 @@ class FJDXCLI(object):
"""Exec stop copy-session."""
return self._exec_cli("stop copy-session", **option)
def _start_copy_opc(self, **option):
"""Exec start copy-opc."""
return self._exec_cli("start copy-opc", **option)
def _delete_volume(self, **option):
"""Exec delete volume."""
return self._exec_cli('delete volume', **option)

View File

@ -75,10 +75,11 @@ class FJDXCommon(object):
1.4.4 - Add support for update migrated volume.
1.4.5 - Add metadata for snapshot.
1.4.6 - Add parameter fujitsu_use_cli_copy.
1.4.7 - Add support for revert-to-snapshot.
"""
VERSION = "1.4.6"
VERSION = "1.4.7"
stats = {
'driver_version': VERSION,
'storage_protocol': None,
@ -3438,6 +3439,129 @@ class FJDXCommon(object):
LOG.warning(msg)
raise exception.VolumeBackendAPIException(data=msg)
def revert_to_snapshot(self, volume, snapshot):
"""Revert volume to snapshot."""
LOG.debug('revert_to_snapshot, Enter method, '
'volume id: %(vid)s, '
'snapshot id: %(sid)s. ',
{'vid': volume['id'], 'sid': snapshot['id']})
vol_instance = self._find_lun(volume)
sdv_instance = self._find_lun(snapshot)
volume_no = self._get_volume_number(vol_instance)
snapshot_no = self._get_volume_number(sdv_instance)
# Check the existence of volume.
if not vol_instance:
msg = (_('revert_to_snapshot, '
'source volume not found on ETERNUS, '
'volume: %(volume)s. ')
% {'volume': volume})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Check the existence of sdv.
if not sdv_instance:
msg = (_('revert_to_snapshot, '
'snapshot volume not found on ETERNUS. '
'snapshot: %(snapshot)s. ')
% {'snapshot': snapshot})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
sdvsession = None
cpsessionlist = self._find_copysession(vol_instance)
LOG.debug('revert_to_snapshot, '
'cpsessionlist: %(cpsessionlist)s. ',
{'cpsessionlist': cpsessionlist})
for cpsession in cpsessionlist:
if (cpsession['SystemElement'].keybindings.get('DeviceID') ==
vol_instance.path.keybindings.get('DeviceID')):
if (cpsession['SyncedElement'].keybindings.get('DeviceID') ==
sdv_instance.path.keybindings.get('DeviceID')):
sdvsession = cpsession
break
if sdvsession:
LOG.debug('revert_to_snapshot, '
'sdvsession: %(sdvsession)s. ',
{'sdvsession': sdvsession})
repservice = self._find_eternus_service(
"FUJITSU_ReplicationService")
if repservice is None:
msg = _('revert_to_snapshot, '
'Replication Service not found. ')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Invoke method for revert to snapshot
rc, errordesc, job = self._exec_eternus_service(
'ModifyReplicaSynchronization',
repservice,
Operation=self._pywbem_uint(15, '16'),
WaitForCopyState=self._pywbem_uint(8, '16'),
Synchronization=sdvsession)
if rc != 0:
msg = (_('revert_to_snapshot, '
'_exec_eternus_service error, '
'volume: %(volume)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s, '
'Message: %(job)s.')
% {'volume': volume['id'],
'rc': rc,
'errordesc': errordesc,
'job': job})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
else:
LOG.debug('revert_to_snapshot, '
'successfully. ')
else:
is_find = False
cp_session_list = self._get_copy_sessions_list()
for cp in cp_session_list:
if (cp['Source Num'] == int(volume_no, 16) and
cp['Dest Num'] == int(snapshot_no, 16) and
cp['Type'] == 'Snap'):
is_find = True
break
if is_find is True:
param_dict = (
{'source-volume-number': int(snapshot_no, 16),
'destination-volume-number': int(volume_no, 16)})
rc, emsg, clidata = self._exec_eternus_cli(
'start_copy_opc',
**param_dict)
if rc != 0:
msg = (_('revert_to_snapshot, '
'start_copy_opc 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)
else:
msg = (_('revert_to_snapshot, '
'snapshot volume not found on ETERNUS. '
'snapshot: %(snapshot)s. ')
% {'snapshot': snapshot})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('revert_to_snapshot, Exit method. ')
def _get_copy_sessions_list(self, **param):
"""Get copy sessions list."""
LOG.debug('_get_copy_sessions_list, Enter method.')

View File

@ -195,6 +195,10 @@ class FJDXFCDriver(driver.FibreChannelDriver):
return model_update
def revert_to_snapshot(self, context, volume, snapshot):
"""Revert volume to snapshot."""
return self.common.revert_to_snapshot(volume, snapshot)
def _get_metadata(self, volume):
v_metadata = volume.get('volume_metadata')
if v_metadata:

View File

@ -181,3 +181,7 @@ class FJDXISCSIDriver(driver.ISCSIDriver):
'target volume meta: %s, Exit method.', model_update)
return model_update
def revert_to_snapshot(self, context, volume, snapshot):
"""Revert volume to snapshot."""
return self.common.revert_to_snapshot(volume, snapshot)

View File

@ -47,6 +47,7 @@ Supported operations
* Extend a volume.
* Get volume statistics.
* Migrate Volume.
* Revert a volume to snapshot.
Preparation
~~~~~~~~~~~

View File

@ -905,7 +905,7 @@ driver.dell_emc_vmax_3=complete
driver.dell_emc_vnx=complete
driver.dell_emc_powerflex=complete
driver.dell_emc_xtremio=missing
driver.fujitsu_eternus=missing
driver.fujitsu_eternus=complete
driver.fungible=missing
driver.hitachi_vsp=complete
driver.hpe_3par=complete

View File

@ -0,0 +1,10 @@
---
features:
- |
Fujitsu Eternus DX driver: Added support for revert to snapshot operation.
Added support of revert to snapshot functionality.
If a volume with snapshots has been extended, causing a mismatch in size
between the origin volume and the snapshot, reverting will be guarded by
cinder-api.