NetApp: Support share revert to snapshot
This commit adds support for the revert-to-snapshot feature to the NetApp cDOT drivers for both normal and replicated shares. Implements: blueprint netapp-cdot-share-revert-to-snapshot Change-Id: Ia939eba03b3db9cbba0cc6c16184578e8c8893d1
This commit is contained in:
parent
f4aed13f86
commit
794e9a74fc
@ -39,7 +39,7 @@ Mapping of share drivers and share features support
|
|||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| Generic (Cinder as back-end) | J | K | L | L | J | J | M | \- |
|
| Generic (Cinder as back-end) | J | K | L | L | J | J | M | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| NetApp Clustered Data ONTAP | J | L | L | L | J | J | N | \- |
|
| NetApp Clustered Data ONTAP | J | L | L | L | J | J | N | O |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| EMC VNX | J | \- | \- | \- | J | J | \- | \- |
|
| EMC VNX | J | \- | \- | \- | J | J | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
@ -214,7 +214,7 @@ More information: :ref:`capabilities_and_extra_specs`
|
|||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| Generic (Cinder as back-end) | J | K | \- | \- | \- | L | \- | J | \- |
|
| Generic (Cinder as back-end) | J | K | \- | \- | \- | L | \- | J | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| NetApp Clustered Data ONTAP | J | K | M | M | M | L | \- | J | \- |
|
| NetApp Clustered Data ONTAP | J | K | M | M | M | L | \- | J | O |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| EMC VNX | J | \- | \- | \- | \- | L | \- | J | \- |
|
| EMC VNX | J | \- | \- | \- | \- | L | \- | J | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
|
@ -2114,6 +2114,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
},
|
},
|
||||||
'desired-attributes': {
|
'desired-attributes': {
|
||||||
'snapshot-info': {
|
'snapshot-info': {
|
||||||
|
'access-time': None,
|
||||||
'name': None,
|
'name': None,
|
||||||
'volume': None,
|
'volume': None,
|
||||||
'busy': None,
|
'busy': None,
|
||||||
@ -2159,6 +2160,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
|
|
||||||
snapshot_info = snapshot_info_list[0]
|
snapshot_info = snapshot_info_list[0]
|
||||||
snapshot = {
|
snapshot = {
|
||||||
|
'access-time': snapshot_info.get_child_content('access-time'),
|
||||||
'name': snapshot_info.get_child_content('name'),
|
'name': snapshot_info.get_child_content('name'),
|
||||||
'volume': snapshot_info.get_child_content('volume'),
|
'volume': snapshot_info.get_child_content('volume'),
|
||||||
'busy': strutils.bool_from_string(
|
'busy': strutils.bool_from_string(
|
||||||
@ -2184,9 +2186,26 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
self.send_request('snapshot-rename', api_args)
|
self.send_request('snapshot-rename', api_args)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def delete_snapshot(self, volume_name, snapshot_name):
|
def restore_snapshot(self, volume_name, snapshot_name):
|
||||||
|
"""Reverts a volume to the specified snapshot."""
|
||||||
|
api_args = {
|
||||||
|
'volume': volume_name,
|
||||||
|
'snapshot': snapshot_name,
|
||||||
|
}
|
||||||
|
self.send_request('snapshot-restore-volume', api_args)
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def delete_snapshot(self, volume_name, snapshot_name, ignore_owners=False):
|
||||||
"""Deletes a volume snapshot."""
|
"""Deletes a volume snapshot."""
|
||||||
api_args = {'volume': volume_name, 'snapshot': snapshot_name}
|
|
||||||
|
ignore_owners = ('true' if strutils.bool_from_string(ignore_owners)
|
||||||
|
else 'false')
|
||||||
|
|
||||||
|
api_args = {
|
||||||
|
'volume': volume_name,
|
||||||
|
'snapshot': snapshot_name,
|
||||||
|
'ignore-owners': ignore_owners,
|
||||||
|
}
|
||||||
self.send_request('snapshot-delete', api_args)
|
self.send_request('snapshot-delete', api_args)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
@ -3264,6 +3283,28 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
|
|
||||||
return has_snapmirrors
|
return has_snapmirrors
|
||||||
|
|
||||||
|
def list_snapmirror_snapshots(self, volume_name, newer_than=None):
|
||||||
|
"""Gets SnapMirror snapshots on a volume."""
|
||||||
|
api_args = {
|
||||||
|
'query': {
|
||||||
|
'snapshot-info': {
|
||||||
|
'dependency': 'snapmirror',
|
||||||
|
'volume': volume_name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if newer_than:
|
||||||
|
api_args['query']['snapshot-info'][
|
||||||
|
'access-time'] = '>' + newer_than
|
||||||
|
|
||||||
|
result = self.send_iter_request('snapshot-get-iter', api_args)
|
||||||
|
|
||||||
|
attributes_list = result.get_child_by_name(
|
||||||
|
'attributes-list') or netapp_api.NaElement('none')
|
||||||
|
|
||||||
|
return [snapshot_info.get_child_content('name')
|
||||||
|
for snapshot_info in attributes_list.get_children()]
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def start_volume_move(self, volume_name, vserver, destination_aggregate,
|
def start_volume_move(self, volume_name, vserver, destination_aggregate,
|
||||||
cutover_action='wait'):
|
cutover_action='wait'):
|
||||||
|
@ -278,7 +278,7 @@ class DataMotionSession(object):
|
|||||||
dest_volume_name,
|
dest_volume_name,
|
||||||
clear_checkpoint=False)
|
clear_checkpoint=False)
|
||||||
|
|
||||||
def break_snapmirror(self, source_share_obj, dest_share_obj):
|
def break_snapmirror(self, source_share_obj, dest_share_obj, mount=True):
|
||||||
"""Breaks SnapMirror relationship.
|
"""Breaks SnapMirror relationship.
|
||||||
|
|
||||||
1. Quiesce any ongoing snapmirror transfers
|
1. Quiesce any ongoing snapmirror transfers
|
||||||
@ -304,7 +304,8 @@ class DataMotionSession(object):
|
|||||||
dest_volume_name)
|
dest_volume_name)
|
||||||
|
|
||||||
# 3. Mount the destination volume and create a junction path
|
# 3. Mount the destination volume and create a junction path
|
||||||
dest_client.mount_volume(dest_volume_name)
|
if mount:
|
||||||
|
dest_client.mount_volume(dest_volume_name)
|
||||||
|
|
||||||
def resync_snapmirror(self, source_share_obj, dest_share_obj):
|
def resync_snapmirror(self, source_share_obj, dest_share_obj):
|
||||||
"""Resync SnapMirror relationship. """
|
"""Resync SnapMirror relationship. """
|
||||||
|
@ -55,6 +55,9 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
|
|||||||
def create_snapshot(self, context, snapshot, **kwargs):
|
def create_snapshot(self, context, snapshot, **kwargs):
|
||||||
return self.library.create_snapshot(context, snapshot, **kwargs)
|
return self.library.create_snapshot(context, snapshot, **kwargs)
|
||||||
|
|
||||||
|
def revert_to_snapshot(self, context, snapshot, **kwargs):
|
||||||
|
return self.library.revert_to_snapshot(context, snapshot, **kwargs)
|
||||||
|
|
||||||
def delete_share(self, context, share, **kwargs):
|
def delete_share(self, context, share, **kwargs):
|
||||||
self.library.delete_share(context, share, **kwargs)
|
self.library.delete_share(context, share, **kwargs)
|
||||||
|
|
||||||
@ -165,6 +168,11 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
|
|||||||
replica_snapshot, share_server=None):
|
replica_snapshot, share_server=None):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def revert_to_replicated_snapshot(self, context, active_replica,
|
||||||
|
replica_list, active_replica_snapshot,
|
||||||
|
replica_snapshots, share_server=None):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def migration_check_compatibility(self, context, source_share,
|
def migration_check_compatibility(self, context, source_share,
|
||||||
destination_share, share_server=None,
|
destination_share, share_server=None,
|
||||||
destination_share_server=None):
|
destination_share_server=None):
|
||||||
|
@ -55,6 +55,9 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
|
|||||||
def create_snapshot(self, context, snapshot, **kwargs):
|
def create_snapshot(self, context, snapshot, **kwargs):
|
||||||
return self.library.create_snapshot(context, snapshot, **kwargs)
|
return self.library.create_snapshot(context, snapshot, **kwargs)
|
||||||
|
|
||||||
|
def revert_to_snapshot(self, context, snapshot, **kwargs):
|
||||||
|
return self.library.revert_to_snapshot(context, snapshot, **kwargs)
|
||||||
|
|
||||||
def delete_share(self, context, share, **kwargs):
|
def delete_share(self, context, share, **kwargs):
|
||||||
self.library.delete_share(context, share, **kwargs)
|
self.library.delete_share(context, share, **kwargs)
|
||||||
|
|
||||||
@ -180,6 +183,13 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
|
|||||||
replica_list, share_replica, replica_snapshots, replica_snapshot,
|
replica_list, share_replica, replica_snapshots, replica_snapshot,
|
||||||
share_server=share_server)
|
share_server=share_server)
|
||||||
|
|
||||||
|
def revert_to_replicated_snapshot(self, context, active_replica,
|
||||||
|
replica_list, active_replica_snapshot,
|
||||||
|
replica_snapshots, **kwargs):
|
||||||
|
return self.library.revert_to_replicated_snapshot(
|
||||||
|
context, active_replica, replica_list, active_replica_snapshot,
|
||||||
|
replica_snapshots, **kwargs)
|
||||||
|
|
||||||
def migration_check_compatibility(self, context, source_share,
|
def migration_check_compatibility(self, context, source_share,
|
||||||
destination_share, share_server=None,
|
destination_share, share_server=None,
|
||||||
destination_share_server=None):
|
destination_share_server=None):
|
||||||
|
@ -304,6 +304,9 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||||||
'dedupe': [True, False],
|
'dedupe': [True, False],
|
||||||
'compression': [True, False],
|
'compression': [True, False],
|
||||||
'thin_provisioning': [True, False],
|
'thin_provisioning': [True, False],
|
||||||
|
'snapshot_support': True,
|
||||||
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add storage service catalog data.
|
# Add storage service catalog data.
|
||||||
@ -792,6 +795,14 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||||||
vserver_client.create_snapshot(share_name, snapshot_name)
|
vserver_client.create_snapshot(share_name, snapshot_name)
|
||||||
return {'provider_location': snapshot_name}
|
return {'provider_location': snapshot_name}
|
||||||
|
|
||||||
|
def revert_to_snapshot(self, context, snapshot, share_server=None):
|
||||||
|
"""Reverts a share (in place) to the specified snapshot."""
|
||||||
|
vserver, vserver_client = self._get_vserver(share_server=share_server)
|
||||||
|
share_name = self._get_backend_share_name(snapshot['share_id'])
|
||||||
|
snapshot_name = self._get_backend_snapshot_name(snapshot['id'])
|
||||||
|
LOG.debug('Restoring snapshot %s', snapshot_name)
|
||||||
|
vserver_client.restore_snapshot(share_name, snapshot_name)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def delete_snapshot(self, context, snapshot, share_server=None,
|
def delete_snapshot(self, context, snapshot, share_server=None,
|
||||||
snapshot_name=None):
|
snapshot_name=None):
|
||||||
@ -1257,6 +1268,11 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||||||
if r['replica_state'] == constants.REPLICA_STATE_ACTIVE:
|
if r['replica_state'] == constants.REPLICA_STATE_ACTIVE:
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
def _find_nonactive_replicas(self, replica_list):
|
||||||
|
"""Returns a list of all except the active replica."""
|
||||||
|
return [replica for replica in replica_list
|
||||||
|
if replica['replica_state'] != constants.REPLICA_STATE_ACTIVE]
|
||||||
|
|
||||||
def create_replica(self, context, replica_list, new_replica,
|
def create_replica(self, context, replica_list, new_replica,
|
||||||
access_rules, share_snapshots, share_server=None):
|
access_rules, share_snapshots, share_server=None):
|
||||||
"""Creates the new replica on this backend and sets up SnapMirror."""
|
"""Creates the new replica on this backend and sets up SnapMirror."""
|
||||||
@ -1324,6 +1340,12 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||||||
msg_args = {'share_name': share_name, 'vserver': vserver}
|
msg_args = {'share_name': share_name, 'vserver': vserver}
|
||||||
raise exception.ShareResourceNotFound(msg % msg_args)
|
raise exception.ShareResourceNotFound(msg % msg_args)
|
||||||
|
|
||||||
|
# NOTE(cknight): The SnapMirror may have been intentionally broken by
|
||||||
|
# a revert-to-snapshot operation, in which case this method should not
|
||||||
|
# attempt to change anything.
|
||||||
|
if active_replica['status'] == constants.STATUS_REVERTING:
|
||||||
|
return None
|
||||||
|
|
||||||
dm_session = data_motion.DataMotionSession()
|
dm_session = data_motion.DataMotionSession()
|
||||||
try:
|
try:
|
||||||
snapmirrors = dm_session.get_snapmirrors(active_replica, replica)
|
snapmirrors = dm_session.get_snapmirrors(active_replica, replica)
|
||||||
@ -1623,6 +1645,50 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||||||
if e.code != netapp_api.EOBJECTNOTFOUND:
|
if e.code != netapp_api.EOBJECTNOTFOUND:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def revert_to_replicated_snapshot(self, context, active_replica,
|
||||||
|
replica_list, active_replica_snapshot,
|
||||||
|
replica_snapshots, share_server=None):
|
||||||
|
"""Reverts a replicated share (in place) to the specified snapshot."""
|
||||||
|
vserver, vserver_client = self._get_vserver(share_server=share_server)
|
||||||
|
share_name = self._get_backend_share_name(
|
||||||
|
active_replica_snapshot['share_id'])
|
||||||
|
snapshot_name = self._get_backend_snapshot_name(
|
||||||
|
active_replica_snapshot['id'])
|
||||||
|
LOG.debug('Restoring snapshot %s', snapshot_name)
|
||||||
|
|
||||||
|
dm_session = data_motion.DataMotionSession()
|
||||||
|
non_active_replica_list = self._find_nonactive_replicas(replica_list)
|
||||||
|
|
||||||
|
# Ensure source snapshot exists
|
||||||
|
vserver_client.get_snapshot(share_name, snapshot_name)
|
||||||
|
|
||||||
|
# Break all mirrors
|
||||||
|
for replica in non_active_replica_list:
|
||||||
|
try:
|
||||||
|
dm_session.break_snapmirror(
|
||||||
|
active_replica, replica, mount=False)
|
||||||
|
except netapp_api.NaApiError as e:
|
||||||
|
if e.code != netapp_api.EOBJECTNOTFOUND:
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Delete source SnapMirror snapshots that will prevent a snap restore
|
||||||
|
snapmirror_snapshot_names = vserver_client.list_snapmirror_snapshots(
|
||||||
|
share_name)
|
||||||
|
for snapmirror_snapshot_name in snapmirror_snapshot_names:
|
||||||
|
vserver_client.delete_snapshot(
|
||||||
|
share_name, snapmirror_snapshot_name, ignore_owners=True)
|
||||||
|
|
||||||
|
# Restore source snapshot of interest
|
||||||
|
vserver_client.restore_snapshot(share_name, snapshot_name)
|
||||||
|
|
||||||
|
# Reestablish mirrors
|
||||||
|
for replica in non_active_replica_list:
|
||||||
|
try:
|
||||||
|
dm_session.resync_snapmirror(active_replica, replica)
|
||||||
|
except netapp_api.NaApiError as e:
|
||||||
|
if e.code != netapp_api.EOBJECTNOTFOUND:
|
||||||
|
raise
|
||||||
|
|
||||||
def _check_destination_vserver_for_vol_move(self, source_share,
|
def _check_destination_vserver_for_vol_move(self, source_share,
|
||||||
source_vserver,
|
source_vserver,
|
||||||
dest_share_server):
|
dest_share_server):
|
||||||
|
@ -1395,10 +1395,12 @@ VOLUME_MODIFY_ITER_ERROR_RESPONSE = etree.XML("""
|
|||||||
</results>
|
</results>
|
||||||
""" % {'volume': SHARE_NAME, 'vserver': VSERVER_NAME})
|
""" % {'volume': SHARE_NAME, 'vserver': VSERVER_NAME})
|
||||||
|
|
||||||
|
SNAPSHOT_ACCESS_TIME = '1466640058'
|
||||||
SNAPSHOT_GET_ITER_NOT_BUSY_RESPONSE = etree.XML("""
|
SNAPSHOT_GET_ITER_NOT_BUSY_RESPONSE = etree.XML("""
|
||||||
<results status="passed">
|
<results status="passed">
|
||||||
<attributes-list>
|
<attributes-list>
|
||||||
<snapshot-info>
|
<snapshot-info>
|
||||||
|
<access-time>%(access_time)s</access-time>
|
||||||
<busy>false</busy>
|
<busy>false</busy>
|
||||||
<name>%(snap)s</name>
|
<name>%(snap)s</name>
|
||||||
<volume>%(volume)s</volume>
|
<volume>%(volume)s</volume>
|
||||||
@ -1407,12 +1409,18 @@ SNAPSHOT_GET_ITER_NOT_BUSY_RESPONSE = etree.XML("""
|
|||||||
</attributes-list>
|
</attributes-list>
|
||||||
<num-records>1</num-records>
|
<num-records>1</num-records>
|
||||||
</results>
|
</results>
|
||||||
""" % {'snap': SNAPSHOT_NAME, 'volume': SHARE_NAME, 'vserver': VSERVER_NAME})
|
""" % {
|
||||||
|
'access_time': SNAPSHOT_ACCESS_TIME,
|
||||||
|
'snap': SNAPSHOT_NAME,
|
||||||
|
'volume': SHARE_NAME,
|
||||||
|
'vserver': VSERVER_NAME,
|
||||||
|
})
|
||||||
|
|
||||||
SNAPSHOT_GET_ITER_BUSY_RESPONSE = etree.XML("""
|
SNAPSHOT_GET_ITER_BUSY_RESPONSE = etree.XML("""
|
||||||
<results status="passed">
|
<results status="passed">
|
||||||
<attributes-list>
|
<attributes-list>
|
||||||
<snapshot-info>
|
<snapshot-info>
|
||||||
|
<access-time>%(access_time)s</access-time>
|
||||||
<busy>true</busy>
|
<busy>true</busy>
|
||||||
<name>%(snap)s</name>
|
<name>%(snap)s</name>
|
||||||
<volume>%(volume)s</volume>
|
<volume>%(volume)s</volume>
|
||||||
@ -1426,7 +1434,12 @@ SNAPSHOT_GET_ITER_BUSY_RESPONSE = etree.XML("""
|
|||||||
</attributes-list>
|
</attributes-list>
|
||||||
<num-records>1</num-records>
|
<num-records>1</num-records>
|
||||||
</results>
|
</results>
|
||||||
""" % {'snap': SNAPSHOT_NAME, 'volume': SHARE_NAME, 'vserver': VSERVER_NAME})
|
""" % {
|
||||||
|
'access_time': SNAPSHOT_ACCESS_TIME,
|
||||||
|
'snap': SNAPSHOT_NAME,
|
||||||
|
'volume': SHARE_NAME,
|
||||||
|
'vserver': VSERVER_NAME,
|
||||||
|
})
|
||||||
|
|
||||||
SNAPSHOT_GET_ITER_NOT_UNIQUE_RESPONSE = etree.XML("""
|
SNAPSHOT_GET_ITER_NOT_UNIQUE_RESPONSE = etree.XML("""
|
||||||
<results status="passed">
|
<results status="passed">
|
||||||
@ -1513,6 +1526,24 @@ SNAPSHOT_GET_ITER_DELETED_RESPONSE = etree.XML("""
|
|||||||
'vserver': VSERVER_NAME,
|
'vserver': VSERVER_NAME,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
SNAPSHOT_GET_ITER_SNAPMIRROR_RESPONSE = etree.XML("""
|
||||||
|
<results status="passed">
|
||||||
|
<attributes-list>
|
||||||
|
<snapshot-info>
|
||||||
|
<name>%(snap)s</name>
|
||||||
|
<volume>%(volume)s</volume>
|
||||||
|
<vserver>%(vserver)s</vserver>
|
||||||
|
</snapshot-info>
|
||||||
|
</attributes-list>
|
||||||
|
<num-records>1</num-records>
|
||||||
|
</results>
|
||||||
|
|
||||||
|
""" % {
|
||||||
|
'snap': SNAPSHOT_NAME,
|
||||||
|
'volume': SHARE_NAME,
|
||||||
|
'vserver': VSERVER_NAME,
|
||||||
|
})
|
||||||
|
|
||||||
CIFS_SHARE_ACCESS_CONTROL_GET_ITER = etree.XML("""
|
CIFS_SHARE_ACCESS_CONTROL_GET_ITER = etree.XML("""
|
||||||
<results status="passed">
|
<results status="passed">
|
||||||
<attributes-list>
|
<attributes-list>
|
||||||
|
@ -3570,6 +3570,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||||||
@ddt.data({
|
@ddt.data({
|
||||||
'mock_return': fake.SNAPSHOT_GET_ITER_NOT_BUSY_RESPONSE,
|
'mock_return': fake.SNAPSHOT_GET_ITER_NOT_BUSY_RESPONSE,
|
||||||
'expected': {
|
'expected': {
|
||||||
|
'access-time': fake.SNAPSHOT_ACCESS_TIME,
|
||||||
'name': fake.SNAPSHOT_NAME,
|
'name': fake.SNAPSHOT_NAME,
|
||||||
'volume': fake.SHARE_NAME,
|
'volume': fake.SHARE_NAME,
|
||||||
'busy': False,
|
'busy': False,
|
||||||
@ -3578,6 +3579,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||||||
}, {
|
}, {
|
||||||
'mock_return': fake.SNAPSHOT_GET_ITER_BUSY_RESPONSE,
|
'mock_return': fake.SNAPSHOT_GET_ITER_BUSY_RESPONSE,
|
||||||
'expected': {
|
'expected': {
|
||||||
|
'access-time': fake.SNAPSHOT_ACCESS_TIME,
|
||||||
'name': fake.SNAPSHOT_NAME,
|
'name': fake.SNAPSHOT_NAME,
|
||||||
'volume': fake.SHARE_NAME,
|
'volume': fake.SHARE_NAME,
|
||||||
'busy': True,
|
'busy': True,
|
||||||
@ -3603,6 +3605,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||||||
},
|
},
|
||||||
'desired-attributes': {
|
'desired-attributes': {
|
||||||
'snapshot-info': {
|
'snapshot-info': {
|
||||||
|
'access-time': None,
|
||||||
'name': None,
|
'name': None,
|
||||||
'volume': None,
|
'volume': None,
|
||||||
'busy': None,
|
'busy': None,
|
||||||
@ -3658,15 +3661,32 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||||||
self.client.send_request.assert_has_calls([
|
self.client.send_request.assert_has_calls([
|
||||||
mock.call('snapshot-rename', snapshot_rename_args)])
|
mock.call('snapshot-rename', snapshot_rename_args)])
|
||||||
|
|
||||||
def test_delete_snapshot(self):
|
def test_restore_snapshot(self):
|
||||||
|
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
self.client.delete_snapshot(fake.SHARE_NAME, fake.SNAPSHOT_NAME)
|
self.client.restore_snapshot(fake.SHARE_NAME,
|
||||||
|
fake.SNAPSHOT_NAME)
|
||||||
|
|
||||||
|
snapshot_restore_args = {
|
||||||
|
'volume': fake.SHARE_NAME,
|
||||||
|
'snapshot': fake.SNAPSHOT_NAME,
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('snapshot-restore-volume', snapshot_restore_args)])
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_delete_snapshot(self, ignore_owners):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.delete_snapshot(
|
||||||
|
fake.SHARE_NAME, fake.SNAPSHOT_NAME, ignore_owners=ignore_owners)
|
||||||
|
|
||||||
snapshot_delete_args = {
|
snapshot_delete_args = {
|
||||||
'volume': fake.SHARE_NAME,
|
'volume': fake.SHARE_NAME,
|
||||||
'snapshot': fake.SNAPSHOT_NAME
|
'snapshot': fake.SNAPSHOT_NAME,
|
||||||
|
'ignore-owners': 'true' if ignore_owners else 'false',
|
||||||
}
|
}
|
||||||
|
|
||||||
self.client.send_request.assert_has_calls([
|
self.client.send_request.assert_has_calls([
|
||||||
@ -5539,6 +5559,35 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||||||
expected_get_snapmirrors_calls)
|
expected_get_snapmirrors_calls)
|
||||||
self.assertTrue(mock_exc_log.called)
|
self.assertTrue(mock_exc_log.called)
|
||||||
|
|
||||||
|
@ddt.data(None, '12345')
|
||||||
|
def test_list_snapmirror_snapshots(self, newer_than):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(
|
||||||
|
fake.SNAPSHOT_GET_ITER_SNAPMIRROR_RESPONSE)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_iter_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
result = self.client.list_snapmirror_snapshots(fake.SHARE_NAME,
|
||||||
|
newer_than=newer_than)
|
||||||
|
|
||||||
|
snapshot_get_iter_args = {
|
||||||
|
'query': {
|
||||||
|
'snapshot-info': {
|
||||||
|
'dependency': 'snapmirror',
|
||||||
|
'volume': fake.SHARE_NAME,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if newer_than:
|
||||||
|
snapshot_get_iter_args['query']['snapshot-info']['access-time'] = (
|
||||||
|
'>' + newer_than)
|
||||||
|
self.client.send_iter_request.assert_has_calls([
|
||||||
|
mock.call('snapshot-get-iter', snapshot_get_iter_args)])
|
||||||
|
|
||||||
|
expected = [fake.SNAPSHOT_NAME]
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
@ddt.data('start_volume_move', 'check_volume_move')
|
@ddt.data('start_volume_move', 'check_volume_move')
|
||||||
def test_volume_move_method(self, method_name):
|
def test_volume_move_method(self, method_name):
|
||||||
|
|
||||||
|
@ -350,6 +350,22 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
|||||||
self.mock_dest_client.mount_volume.assert_called_once_with(
|
self.mock_dest_client.mount_volume.assert_called_once_with(
|
||||||
self.fake_dest_vol_name)
|
self.fake_dest_vol_name)
|
||||||
|
|
||||||
|
def test_break_snapmirror_no_mount(self):
|
||||||
|
self.mock_object(self.dm_session, 'quiesce_then_abort')
|
||||||
|
|
||||||
|
self.dm_session.break_snapmirror(self.fake_src_share,
|
||||||
|
self.fake_dest_share,
|
||||||
|
mount=False)
|
||||||
|
|
||||||
|
self.mock_dest_client.break_snapmirror.assert_called_once_with(
|
||||||
|
self.source_vserver, self.fake_src_vol_name,
|
||||||
|
self.dest_vserver, self.fake_dest_vol_name)
|
||||||
|
|
||||||
|
self.dm_session.quiesce_then_abort.assert_called_once_with(
|
||||||
|
self.fake_src_share, self.fake_dest_share)
|
||||||
|
|
||||||
|
self.assertFalse(self.mock_dest_client.mount_volume.called)
|
||||||
|
|
||||||
def test_break_snapmirror_wait_for_quiesced(self):
|
def test_break_snapmirror_wait_for_quiesced(self):
|
||||||
self.mock_object(self.dm_session, 'quiesce_then_abort')
|
self.mock_object(self.dm_session, 'quiesce_then_abort')
|
||||||
|
|
||||||
|
@ -1205,6 +1205,25 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
snapshot_name)
|
snapshot_name)
|
||||||
self.assertEqual(snapshot_name, model_update['provider_location'])
|
self.assertEqual(snapshot_name, model_update['provider_location'])
|
||||||
|
|
||||||
|
def test_revert_to_snapshot(self):
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
self.mock_object(self.library,
|
||||||
|
'_get_vserver',
|
||||||
|
mock.Mock(return_value=(fake.VSERVER1,
|
||||||
|
vserver_client)))
|
||||||
|
|
||||||
|
result = self.library.revert_to_snapshot(
|
||||||
|
self.context, fake.SNAPSHOT, share_server=fake.SHARE_SERVER)
|
||||||
|
|
||||||
|
self.assertIsNone(result)
|
||||||
|
share_name = self.library._get_backend_share_name(
|
||||||
|
fake.SNAPSHOT['share_id'])
|
||||||
|
snapshot_name = self.library._get_backend_snapshot_name(
|
||||||
|
fake.SNAPSHOT['id'])
|
||||||
|
vserver_client.restore_snapshot.assert_called_once_with(share_name,
|
||||||
|
snapshot_name)
|
||||||
|
|
||||||
def test_delete_snapshot(self):
|
def test_delete_snapshot(self):
|
||||||
|
|
||||||
vserver_client = mock.Mock()
|
vserver_client = mock.Mock()
|
||||||
@ -2480,6 +2499,26 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
self.assertFalse(self.mock_dm_session.create_snapmirror.called)
|
self.assertFalse(self.mock_dm_session.create_snapmirror.called)
|
||||||
self.assertEqual(constants.STATUS_OUT_OF_SYNC, result)
|
self.assertEqual(constants.STATUS_OUT_OF_SYNC, result)
|
||||||
|
|
||||||
|
def test_update_replica_state_share_reverting_to_snapshot(self):
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
self.mock_object(vserver_client, 'volume_exists',
|
||||||
|
mock.Mock(return_value=True))
|
||||||
|
self.mock_object(self.library,
|
||||||
|
'_get_vserver',
|
||||||
|
mock.Mock(return_value=(fake.VSERVER1,
|
||||||
|
vserver_client)))
|
||||||
|
self.mock_dm_session.get_snapmirrors = mock.Mock(return_value=[])
|
||||||
|
|
||||||
|
replica = copy.deepcopy(fake.SHARE)
|
||||||
|
replica['status'] = constants.STATUS_REVERTING
|
||||||
|
|
||||||
|
result = self.library.update_replica_state(
|
||||||
|
None, [replica], replica, None, [], share_server=None)
|
||||||
|
|
||||||
|
self.assertFalse(self.mock_dm_session.get_snapmirrors.called)
|
||||||
|
self.assertFalse(self.mock_dm_session.create_snapmirror.called)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
def test_update_replica_state_no_snapmirror_create_failed(self):
|
def test_update_replica_state_no_snapmirror_create_failed(self):
|
||||||
vserver_client = mock.Mock()
|
vserver_client = mock.Mock()
|
||||||
self.mock_object(vserver_client, 'volume_exists',
|
self.mock_object(vserver_client, 'volume_exists',
|
||||||
@ -3621,6 +3660,221 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
|
|
||||||
self.assertIsNone(model_update)
|
self.assertIsNone(model_update)
|
||||||
|
|
||||||
|
def _get_fake_replicas_and_snapshots(self):
|
||||||
|
|
||||||
|
fake_replica_3 = copy.deepcopy(self.fake_replica_2)
|
||||||
|
fake_replica_3['id'] = fake.SHARE_ID3
|
||||||
|
fake_snapshot = copy.deepcopy(fake.SNAPSHOT)
|
||||||
|
fake_snapshot['share_id'] = self.fake_replica['id']
|
||||||
|
snapshot_name = self.library._get_backend_snapshot_name(
|
||||||
|
fake_snapshot['id'])
|
||||||
|
fake_snapshot['provider_location'] = snapshot_name
|
||||||
|
fake_snapshot_2 = copy.deepcopy(fake.SNAPSHOT)
|
||||||
|
fake_snapshot_2['id'] = uuidutils.generate_uuid()
|
||||||
|
fake_snapshot_2['share_id'] = self.fake_replica_2['id']
|
||||||
|
fake_snapshot_2['provider_location'] = snapshot_name
|
||||||
|
fake_snapshot_3 = copy.deepcopy(fake.SNAPSHOT)
|
||||||
|
fake_snapshot_3['id'] = uuidutils.generate_uuid()
|
||||||
|
fake_snapshot_3['share_id'] = fake_replica_3['id']
|
||||||
|
fake_snapshot_3['provider_location'] = snapshot_name
|
||||||
|
replica_list = [self.fake_replica, self.fake_replica_2, fake_replica_3]
|
||||||
|
snapshot_list = [fake_snapshot, fake_snapshot_2, fake_snapshot_3]
|
||||||
|
return replica_list, snapshot_list
|
||||||
|
|
||||||
|
def test_revert_to_replicated_snapshot(self):
|
||||||
|
|
||||||
|
replica_list, snapshot_list = self._get_fake_replicas_and_snapshots()
|
||||||
|
fake_replica, fake_replica_2, fake_replica_3 = replica_list
|
||||||
|
fake_snapshot, fake_snapshot_2, fake_snapshot_3 = snapshot_list
|
||||||
|
share_name = self.library._get_backend_share_name(
|
||||||
|
fake_snapshot['share_id'])
|
||||||
|
snapshot_name = self.library._get_backend_snapshot_name(
|
||||||
|
fake_snapshot['id'])
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
vserver_client.get_snapshot.return_value = fake.CDOT_SNAPSHOT
|
||||||
|
vserver_client.list_snapmirror_snapshots.return_value = ['sm_snap']
|
||||||
|
self.mock_object(self.library,
|
||||||
|
'_get_vserver',
|
||||||
|
mock.Mock(return_value=(fake.VSERVER1,
|
||||||
|
vserver_client)))
|
||||||
|
|
||||||
|
self.library.revert_to_replicated_snapshot(
|
||||||
|
self.context, self.fake_replica, replica_list, fake_snapshot,
|
||||||
|
snapshot_list, share_server=fake.SHARE_SERVER)
|
||||||
|
|
||||||
|
vserver_client.get_snapshot.assert_called_once_with(
|
||||||
|
share_name, snapshot_name)
|
||||||
|
vserver_client.list_snapmirror_snapshots.assert_called_once_with(
|
||||||
|
share_name)
|
||||||
|
vserver_client.delete_snapshot.assert_called_once_with(
|
||||||
|
share_name, 'sm_snap', ignore_owners=True)
|
||||||
|
vserver_client.restore_snapshot.assert_called_once_with(
|
||||||
|
share_name, snapshot_name)
|
||||||
|
|
||||||
|
self.mock_dm_session.break_snapmirror.assert_has_calls(
|
||||||
|
[mock.call(self.fake_replica, self.fake_replica_2, mount=False),
|
||||||
|
mock.call(self.fake_replica, fake_replica_3, mount=False)],
|
||||||
|
any_order=True)
|
||||||
|
self.mock_dm_session.resync_snapmirror.assert_has_calls(
|
||||||
|
[mock.call(self.fake_replica, self.fake_replica_2),
|
||||||
|
mock.call(self.fake_replica, fake_replica_3)],
|
||||||
|
any_order=True)
|
||||||
|
|
||||||
|
def test_revert_to_replicated_snapshot_not_found(self):
|
||||||
|
|
||||||
|
replica_list, snapshot_list = self._get_fake_replicas_and_snapshots()
|
||||||
|
fake_snapshot, fake_snapshot_2, fake_snapshot_3 = snapshot_list
|
||||||
|
share_name = self.library._get_backend_share_name(
|
||||||
|
fake_snapshot['share_id'])
|
||||||
|
snapshot_name = self.library._get_backend_snapshot_name(
|
||||||
|
fake_snapshot['id'])
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
vserver_client.get_snapshot.side_effect = netapp_api.NaApiError
|
||||||
|
vserver_client.list_snapmirror_snapshots.return_value = ['sm_snap']
|
||||||
|
self.mock_object(self.library,
|
||||||
|
'_get_vserver',
|
||||||
|
mock.Mock(return_value=(fake.VSERVER1,
|
||||||
|
vserver_client)))
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
netapp_api.NaApiError, self.library.revert_to_replicated_snapshot,
|
||||||
|
self.context, self.fake_replica, replica_list, fake_snapshot,
|
||||||
|
snapshot_list, share_server=fake.SHARE_SERVER)
|
||||||
|
|
||||||
|
vserver_client.get_snapshot.assert_called_once_with(
|
||||||
|
share_name, snapshot_name)
|
||||||
|
self.assertFalse(vserver_client.list_snapmirror_snapshots.called)
|
||||||
|
self.assertFalse(vserver_client.delete_snapshot.called)
|
||||||
|
self.assertFalse(vserver_client.restore_snapshot.called)
|
||||||
|
self.assertFalse(self.mock_dm_session.break_snapmirror.called)
|
||||||
|
self.assertFalse(self.mock_dm_session.resync_snapmirror.called)
|
||||||
|
|
||||||
|
def test_revert_to_replicated_snapshot_snapmirror_break_error(self):
|
||||||
|
|
||||||
|
replica_list, snapshot_list = self._get_fake_replicas_and_snapshots()
|
||||||
|
fake_snapshot, fake_snapshot_2, fake_snapshot_3 = snapshot_list
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
vserver_client.get_snapshot.return_value = fake.CDOT_SNAPSHOT
|
||||||
|
vserver_client.list_snapmirror_snapshots.return_value = ['sm_snap']
|
||||||
|
self.mock_object(self.library,
|
||||||
|
'_get_vserver',
|
||||||
|
mock.Mock(return_value=(fake.VSERVER1,
|
||||||
|
vserver_client)))
|
||||||
|
self.mock_dm_session.break_snapmirror.side_effect = (
|
||||||
|
netapp_api.NaApiError)
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
netapp_api.NaApiError, self.library.revert_to_replicated_snapshot,
|
||||||
|
self.context, self.fake_replica, replica_list, fake_snapshot,
|
||||||
|
snapshot_list, share_server=fake.SHARE_SERVER)
|
||||||
|
|
||||||
|
def test_revert_to_replicated_snapshot_snapmirror_break_not_found(self):
|
||||||
|
|
||||||
|
replica_list, snapshot_list = self._get_fake_replicas_and_snapshots()
|
||||||
|
fake_replica, fake_replica_2, fake_replica_3 = replica_list
|
||||||
|
fake_snapshot, fake_snapshot_2, fake_snapshot_3 = snapshot_list
|
||||||
|
share_name = self.library._get_backend_share_name(
|
||||||
|
fake_snapshot['share_id'])
|
||||||
|
snapshot_name = self.library._get_backend_snapshot_name(
|
||||||
|
fake_snapshot['id'])
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
vserver_client.get_snapshot.return_value = fake.CDOT_SNAPSHOT
|
||||||
|
vserver_client.list_snapmirror_snapshots.return_value = ['sm_snap']
|
||||||
|
self.mock_object(self.library,
|
||||||
|
'_get_vserver',
|
||||||
|
mock.Mock(return_value=(fake.VSERVER1,
|
||||||
|
vserver_client)))
|
||||||
|
self.mock_dm_session.break_snapmirror.side_effect = (
|
||||||
|
netapp_api.NaApiError(code=netapp_api.EOBJECTNOTFOUND))
|
||||||
|
|
||||||
|
self.library.revert_to_replicated_snapshot(
|
||||||
|
self.context, self.fake_replica, replica_list, fake_snapshot,
|
||||||
|
snapshot_list, share_server=fake.SHARE_SERVER)
|
||||||
|
|
||||||
|
vserver_client.get_snapshot.assert_called_once_with(
|
||||||
|
share_name, snapshot_name)
|
||||||
|
vserver_client.list_snapmirror_snapshots.assert_called_once_with(
|
||||||
|
share_name)
|
||||||
|
vserver_client.delete_snapshot.assert_called_once_with(
|
||||||
|
share_name, 'sm_snap', ignore_owners=True)
|
||||||
|
vserver_client.restore_snapshot.assert_called_once_with(
|
||||||
|
share_name, snapshot_name)
|
||||||
|
|
||||||
|
self.mock_dm_session.break_snapmirror.assert_has_calls(
|
||||||
|
[mock.call(self.fake_replica, self.fake_replica_2, mount=False),
|
||||||
|
mock.call(self.fake_replica, fake_replica_3, mount=False)],
|
||||||
|
any_order=True)
|
||||||
|
self.mock_dm_session.resync_snapmirror.assert_has_calls(
|
||||||
|
[mock.call(self.fake_replica, self.fake_replica_2),
|
||||||
|
mock.call(self.fake_replica, fake_replica_3)],
|
||||||
|
any_order=True)
|
||||||
|
|
||||||
|
def test_revert_to_replicated_snapshot_snapmirror_resync_error(self):
|
||||||
|
|
||||||
|
replica_list, snapshot_list = self._get_fake_replicas_and_snapshots()
|
||||||
|
fake_snapshot, fake_snapshot_2, fake_snapshot_3 = snapshot_list
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
vserver_client.get_snapshot.return_value = fake.CDOT_SNAPSHOT
|
||||||
|
vserver_client.list_snapmirror_snapshots.return_value = ['sm_snap']
|
||||||
|
self.mock_object(self.library,
|
||||||
|
'_get_vserver',
|
||||||
|
mock.Mock(return_value=(fake.VSERVER1,
|
||||||
|
vserver_client)))
|
||||||
|
self.mock_dm_session.resync_snapmirror.side_effect = (
|
||||||
|
netapp_api.NaApiError)
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
netapp_api.NaApiError, self.library.revert_to_replicated_snapshot,
|
||||||
|
self.context, self.fake_replica, replica_list, fake_snapshot,
|
||||||
|
snapshot_list, share_server=fake.SHARE_SERVER)
|
||||||
|
|
||||||
|
def test_revert_to_replicated_snapshot_snapmirror_resync_not_found(self):
|
||||||
|
|
||||||
|
replica_list, snapshot_list = self._get_fake_replicas_and_snapshots()
|
||||||
|
fake_replica, fake_replica_2, fake_replica_3 = replica_list
|
||||||
|
fake_snapshot, fake_snapshot_2, fake_snapshot_3 = snapshot_list
|
||||||
|
share_name = self.library._get_backend_share_name(
|
||||||
|
fake_snapshot['share_id'])
|
||||||
|
snapshot_name = self.library._get_backend_snapshot_name(
|
||||||
|
fake_snapshot['id'])
|
||||||
|
|
||||||
|
vserver_client = mock.Mock()
|
||||||
|
vserver_client.get_snapshot.return_value = fake.CDOT_SNAPSHOT
|
||||||
|
vserver_client.list_snapmirror_snapshots.return_value = ['sm_snap']
|
||||||
|
self.mock_object(self.library,
|
||||||
|
'_get_vserver',
|
||||||
|
mock.Mock(return_value=(fake.VSERVER1,
|
||||||
|
vserver_client)))
|
||||||
|
self.mock_dm_session.resync_snapmirror.side_effect = (
|
||||||
|
netapp_api.NaApiError(code=netapp_api.EOBJECTNOTFOUND))
|
||||||
|
|
||||||
|
self.library.revert_to_replicated_snapshot(
|
||||||
|
self.context, self.fake_replica, replica_list, fake_snapshot,
|
||||||
|
snapshot_list, share_server=fake.SHARE_SERVER)
|
||||||
|
|
||||||
|
vserver_client.get_snapshot.assert_called_once_with(
|
||||||
|
share_name, snapshot_name)
|
||||||
|
vserver_client.list_snapmirror_snapshots.assert_called_once_with(
|
||||||
|
share_name)
|
||||||
|
vserver_client.delete_snapshot.assert_called_once_with(
|
||||||
|
share_name, 'sm_snap', ignore_owners=True)
|
||||||
|
vserver_client.restore_snapshot.assert_called_once_with(
|
||||||
|
share_name, snapshot_name)
|
||||||
|
|
||||||
|
self.mock_dm_session.break_snapmirror.assert_has_calls(
|
||||||
|
[mock.call(self.fake_replica, self.fake_replica_2, mount=False),
|
||||||
|
mock.call(self.fake_replica, fake_replica_3, mount=False)],
|
||||||
|
any_order=True)
|
||||||
|
self.mock_dm_session.resync_snapmirror.assert_has_calls(
|
||||||
|
[mock.call(self.fake_replica, self.fake_replica_2),
|
||||||
|
mock.call(self.fake_replica, fake_replica_3)],
|
||||||
|
any_order=True)
|
||||||
|
|
||||||
def test_migration_check_compatibility_no_cluster_credentials(self):
|
def test_migration_check_compatibility_no_cluster_credentials(self):
|
||||||
self.library._have_cluster_creds = False
|
self.library._have_cluster_creds = False
|
||||||
self.mock_object(data_motion, 'get_backend_configuration')
|
self.mock_object(data_motion, 'get_backend_configuration')
|
||||||
|
@ -36,6 +36,7 @@ FLEXVOL_NAME = 'fake_volume'
|
|||||||
JUNCTION_PATH = '/%s' % FLEXVOL_NAME
|
JUNCTION_PATH = '/%s' % FLEXVOL_NAME
|
||||||
EXPORT_LOCATION = '%s:%s' % (HOST_NAME, JUNCTION_PATH)
|
EXPORT_LOCATION = '%s:%s' % (HOST_NAME, JUNCTION_PATH)
|
||||||
SNAPSHOT_NAME = 'fake_snapshot'
|
SNAPSHOT_NAME = 'fake_snapshot'
|
||||||
|
SNAPSHOT_ACCESS_TIME = '1466455782'
|
||||||
CONSISTENCY_GROUP_NAME = 'fake_consistency_group'
|
CONSISTENCY_GROUP_NAME = 'fake_consistency_group'
|
||||||
SHARE_SIZE = 10
|
SHARE_SIZE = 10
|
||||||
TENANT_ID = '24cb2448-13d8-4f41-afd9-eff5c4fd2a57'
|
TENANT_ID = '24cb2448-13d8-4f41-afd9-eff5c4fd2a57'
|
||||||
@ -95,6 +96,7 @@ SHARE = {
|
|||||||
'network_allocations': [{'ip_address': 'ip'}]
|
'network_allocations': [{'ip_address': 'ip'}]
|
||||||
},
|
},
|
||||||
'replica_state': constants.REPLICA_STATE_ACTIVE,
|
'replica_state': constants.REPLICA_STATE_ACTIVE,
|
||||||
|
'status': constants.STATUS_AVAILABLE,
|
||||||
}
|
}
|
||||||
|
|
||||||
FLEXVOL_TO_MANAGE = {
|
FLEXVOL_TO_MANAGE = {
|
||||||
@ -292,6 +294,7 @@ CDOT_SNAPSHOT = {
|
|||||||
'volume': SHARE_NAME,
|
'volume': SHARE_NAME,
|
||||||
'busy': False,
|
'busy': False,
|
||||||
'owners': set(),
|
'owners': set(),
|
||||||
|
'access-time': SNAPSHOT_ACCESS_TIME,
|
||||||
}
|
}
|
||||||
|
|
||||||
CDOT_SNAPSHOT_BUSY_VOLUME_CLONE = {
|
CDOT_SNAPSHOT_BUSY_VOLUME_CLONE = {
|
||||||
@ -299,6 +302,7 @@ CDOT_SNAPSHOT_BUSY_VOLUME_CLONE = {
|
|||||||
'volume': SHARE_NAME,
|
'volume': SHARE_NAME,
|
||||||
'busy': True,
|
'busy': True,
|
||||||
'owners': {'volume clone'},
|
'owners': {'volume clone'},
|
||||||
|
'access-time': SNAPSHOT_ACCESS_TIME,
|
||||||
}
|
}
|
||||||
|
|
||||||
CDOT_SNAPSHOT_BUSY_SNAPMIRROR = {
|
CDOT_SNAPSHOT_BUSY_SNAPMIRROR = {
|
||||||
@ -306,6 +310,7 @@ CDOT_SNAPSHOT_BUSY_SNAPMIRROR = {
|
|||||||
'volume': SHARE_NAME,
|
'volume': SHARE_NAME,
|
||||||
'busy': True,
|
'busy': True,
|
||||||
'owners': {'snapmirror'},
|
'owners': {'snapmirror'},
|
||||||
|
'access-time': SNAPSHOT_ACCESS_TIME,
|
||||||
}
|
}
|
||||||
|
|
||||||
CDOT_CLONE_CHILD_1 = 'fake_child_1'
|
CDOT_CLONE_CHILD_1 = 'fake_child_1'
|
||||||
@ -545,71 +550,87 @@ SSC_INFO_VSERVER_CREDS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
POOLS = [
|
POOLS = [
|
||||||
{'pool_name': AGGREGATES[0],
|
{
|
||||||
'netapp_aggregate': AGGREGATES[0],
|
'pool_name': AGGREGATES[0],
|
||||||
'total_capacity_gb': 3.3,
|
'netapp_aggregate': AGGREGATES[0],
|
||||||
'free_capacity_gb': 1.1,
|
'total_capacity_gb': 3.3,
|
||||||
'allocated_capacity_gb': 2.2,
|
'free_capacity_gb': 1.1,
|
||||||
'qos': 'False',
|
'allocated_capacity_gb': 2.2,
|
||||||
'reserved_percentage': 5,
|
'qos': 'False',
|
||||||
'dedupe': [True, False],
|
'reserved_percentage': 5,
|
||||||
'compression': [True, False],
|
'dedupe': [True, False],
|
||||||
'thin_provisioning': [True, False],
|
'compression': [True, False],
|
||||||
'netapp_raid_type': 'raid4',
|
'thin_provisioning': [True, False],
|
||||||
'netapp_disk_type': 'FCAL',
|
'netapp_raid_type': 'raid4',
|
||||||
'netapp_hybrid_aggregate': 'false',
|
'netapp_disk_type': 'FCAL',
|
||||||
'utilization': 30.0,
|
'netapp_hybrid_aggregate': 'false',
|
||||||
'filter_function': 'filter',
|
'utilization': 30.0,
|
||||||
'goodness_function': 'goodness',
|
'filter_function': 'filter',
|
||||||
},
|
'goodness_function': 'goodness',
|
||||||
{'pool_name': AGGREGATES[1],
|
'snapshot_support': True,
|
||||||
'netapp_aggregate': AGGREGATES[1],
|
'create_share_from_snapshot_support': True,
|
||||||
'total_capacity_gb': 6.0,
|
'revert_to_snapshot_support': True,
|
||||||
'free_capacity_gb': 2.0,
|
},
|
||||||
'allocated_capacity_gb': 4.0,
|
{
|
||||||
'qos': 'False',
|
'pool_name': AGGREGATES[1],
|
||||||
'reserved_percentage': 5,
|
'netapp_aggregate': AGGREGATES[1],
|
||||||
'dedupe': [True, False],
|
'total_capacity_gb': 6.0,
|
||||||
'compression': [True, False],
|
'free_capacity_gb': 2.0,
|
||||||
'thin_provisioning': [True, False],
|
'allocated_capacity_gb': 4.0,
|
||||||
'netapp_raid_type': 'raid_dp',
|
'qos': 'False',
|
||||||
'netapp_disk_type': ['SATA', 'SSD'],
|
'reserved_percentage': 5,
|
||||||
'netapp_hybrid_aggregate': 'true',
|
'dedupe': [True, False],
|
||||||
'utilization': 42.0,
|
'compression': [True, False],
|
||||||
'filter_function': 'filter',
|
'thin_provisioning': [True, False],
|
||||||
'goodness_function': 'goodness',
|
'netapp_raid_type': 'raid_dp',
|
||||||
},
|
'netapp_disk_type': ['SATA', 'SSD'],
|
||||||
|
'netapp_hybrid_aggregate': 'true',
|
||||||
|
'utilization': 42.0,
|
||||||
|
'filter_function': 'filter',
|
||||||
|
'goodness_function': 'goodness',
|
||||||
|
'snapshot_support': True,
|
||||||
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': True,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
POOLS_VSERVER_CREDS = [
|
POOLS_VSERVER_CREDS = [
|
||||||
{'pool_name': AGGREGATES[0],
|
{
|
||||||
'netapp_aggregate': AGGREGATES[0],
|
'pool_name': AGGREGATES[0],
|
||||||
'total_capacity_gb': 'unknown',
|
'netapp_aggregate': AGGREGATES[0],
|
||||||
'free_capacity_gb': 1.1,
|
'total_capacity_gb': 'unknown',
|
||||||
'allocated_capacity_gb': 0.0,
|
'free_capacity_gb': 1.1,
|
||||||
'qos': 'False',
|
'allocated_capacity_gb': 0.0,
|
||||||
'reserved_percentage': 5,
|
'qos': 'False',
|
||||||
'dedupe': [True, False],
|
'reserved_percentage': 5,
|
||||||
'compression': [True, False],
|
'dedupe': [True, False],
|
||||||
'thin_provisioning': [True, False],
|
'compression': [True, False],
|
||||||
'utilization': 50.0,
|
'thin_provisioning': [True, False],
|
||||||
'filter_function': None,
|
'utilization': 50.0,
|
||||||
'goodness_function': None,
|
'filter_function': None,
|
||||||
},
|
'goodness_function': None,
|
||||||
{'pool_name': AGGREGATES[1],
|
'snapshot_support': True,
|
||||||
'netapp_aggregate': AGGREGATES[1],
|
'create_share_from_snapshot_support': True,
|
||||||
'total_capacity_gb': 'unknown',
|
'revert_to_snapshot_support': True,
|
||||||
'free_capacity_gb': 2.0,
|
},
|
||||||
'allocated_capacity_gb': 0.0,
|
{
|
||||||
'qos': 'False',
|
'pool_name': AGGREGATES[1],
|
||||||
'reserved_percentage': 5,
|
'netapp_aggregate': AGGREGATES[1],
|
||||||
'dedupe': [True, False],
|
'total_capacity_gb': 'unknown',
|
||||||
'compression': [True, False],
|
'free_capacity_gb': 2.0,
|
||||||
'thin_provisioning': [True, False],
|
'allocated_capacity_gb': 0.0,
|
||||||
'utilization': 50.0,
|
'qos': 'False',
|
||||||
'filter_function': None,
|
'reserved_percentage': 5,
|
||||||
'goodness_function': None,
|
'dedupe': [True, False],
|
||||||
},
|
'compression': [True, False],
|
||||||
|
'thin_provisioning': [True, False],
|
||||||
|
'utilization': 50.0,
|
||||||
|
'filter_function': None,
|
||||||
|
'goodness_function': None,
|
||||||
|
'snapshot_support': True,
|
||||||
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': True,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
SSC_AGGREGATES = [
|
SSC_AGGREGATES = [
|
||||||
|
@ -20,6 +20,7 @@ from tempest.lib.common.utils import data_utils
|
|||||||
from testtools import testcase as tc
|
from testtools import testcase as tc
|
||||||
|
|
||||||
from manila_tempest_tests.common import constants
|
from manila_tempest_tests.common import constants
|
||||||
|
from manila_tempest_tests import share_exceptions
|
||||||
from manila_tempest_tests.tests.api import base
|
from manila_tempest_tests.tests.api import base
|
||||||
|
|
||||||
CONF = config.CONF
|
CONF = config.CONF
|
||||||
@ -72,6 +73,26 @@ class RevertToSnapshotTest(base.BaseSharesMixedTest):
|
|||||||
|
|
||||||
cls.share = cls.create_share(share_type_id=cls.st_id)
|
cls.share = cls.create_share(share_type_id=cls.st_id)
|
||||||
|
|
||||||
|
if CONF.share.run_replication_tests:
|
||||||
|
# Create replicated share type
|
||||||
|
cls.replicated_share_type_name = data_utils.rand_name("share-type")
|
||||||
|
cls.replication_type = CONF.share.backend_replication_type
|
||||||
|
if cls.replication_type not in constants.REPLICATION_TYPE_CHOICES:
|
||||||
|
raise share_exceptions.ShareReplicationTypeException(
|
||||||
|
replication_type=cls.replication_type
|
||||||
|
)
|
||||||
|
cls.zones = cls.get_availability_zones(client=cls.admin_client)
|
||||||
|
cls.share_zone = cls.zones[0]
|
||||||
|
cls.replica_zone = cls.zones[-1]
|
||||||
|
|
||||||
|
extra_specs = cls.add_extra_specs_to_dict(
|
||||||
|
{"replication_type": cls.replication_type})
|
||||||
|
share_type = cls.create_share_type(
|
||||||
|
cls.replicated_share_type_name,
|
||||||
|
extra_specs=extra_specs,
|
||||||
|
client=cls.admin_client)
|
||||||
|
cls.replicated_share_type = share_type["share_type"]
|
||||||
|
|
||||||
@tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
|
@tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
|
||||||
@ddt.data(
|
@ddt.data(
|
||||||
*{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
|
*{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
|
||||||
@ -107,3 +128,35 @@ class RevertToSnapshotTest(base.BaseSharesMixedTest):
|
|||||||
version=version)
|
version=version)
|
||||||
self.shares_v2_client.wait_for_share_status(self.share['id'],
|
self.shares_v2_client.wait_for_share_status(self.share['id'],
|
||||||
constants.STATUS_AVAILABLE)
|
constants.STATUS_AVAILABLE)
|
||||||
|
|
||||||
|
@tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
|
||||||
|
@tc.skipUnless(CONF.share.run_replication_tests,
|
||||||
|
'Replication tests are disabled.')
|
||||||
|
@ddt.data(
|
||||||
|
*{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
|
||||||
|
CONF.share.max_api_microversion}
|
||||||
|
)
|
||||||
|
def test_revert_to_replicated_snapshot(self, version):
|
||||||
|
"""Test reverting to a replicated snapshot."""
|
||||||
|
share = self.create_share(
|
||||||
|
share_type_id=self.replicated_share_type['id'],
|
||||||
|
availability_zone=self.share_zone
|
||||||
|
)
|
||||||
|
|
||||||
|
share_replica = self.create_share_replica(share["id"],
|
||||||
|
self.replica_zone)
|
||||||
|
self.shares_v2_client.wait_for_share_replica_status(
|
||||||
|
share_replica['id'], constants.REPLICATION_STATE_IN_SYNC,
|
||||||
|
status_attr='replica_state')
|
||||||
|
|
||||||
|
snapshot = self.create_snapshot_wait_for_active(share["id"])
|
||||||
|
|
||||||
|
self.shares_v2_client.revert_to_snapshot(
|
||||||
|
share['id'],
|
||||||
|
snapshot['id'],
|
||||||
|
version=version)
|
||||||
|
self.shares_v2_client.wait_for_share_status(share['id'],
|
||||||
|
constants.STATUS_AVAILABLE)
|
||||||
|
self.shares_v2_client.wait_for_share_replica_status(
|
||||||
|
share_replica['id'], constants.REPLICATION_STATE_IN_SYNC,
|
||||||
|
status_attr='replica_state')
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added support for share revert-to-snapshot to NetApp Data ONTAP drivers.
|
||||||
|
upgrades:
|
||||||
|
- If using existing share types with Data ONTAP, set the
|
||||||
|
'revert_to_snapshot_support' extra spec to allow creating shares that
|
||||||
|
support in-place revert-to-snapshot functionality. This modification will
|
||||||
|
not affect existing shares of such types.
|
Loading…
Reference in New Issue
Block a user