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:
parent
a50b897459
commit
68590efa06
@ -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': []}
|
||||
|
@ -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)
|
||||
|
@ -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.')
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -47,6 +47,7 @@ Supported operations
|
||||
* Extend a volume.
|
||||
* Get volume statistics.
|
||||
* Migrate Volume.
|
||||
* Revert a volume to snapshot.
|
||||
|
||||
Preparation
|
||||
~~~~~~~~~~~
|
||||
|
@ -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
|
||||
|
@ -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.
|
Loading…
x
Reference in New Issue
Block a user