[NetApp] Add support for share server migration
This patch adds support for share server migration between NetApp ONTAP drivers. This operation is now supported for migrating a share server and all its resources between two different clusters. Share server migration relies on ONTAP features available only in versions equal and greater than ``9.4``. Also, in order to have share server migration working across ONTAP clusters, they must be peered in advance. At this moment, share server migration doesn't support migrate a share server without disrupting the access to shares, since the export locations are updated at the migration complete phase. The driver doesn't support changing security services while changing the destination share network. This functionality can be added in the future. Co-Authored-By: Andre Beltrami <debeltrami@gmail.com> Implements: bp netapp-share-server-migration Depends-On: Ic0751027d2c3f1ef7ab0f7836baff3070a230cfd Change-Id: Idfac890c034cf8cbb65abf685ab6cab5ef13a4b1 Signed-off-by: Douglas Viroel <viroel@gmail.com>
This commit is contained in:
parent
1886bf6fe5
commit
4bcf21eaf1
|
@ -779,6 +779,10 @@ class VserverNotSpecified(NetAppException):
|
||||||
message = _("Vserver not specified.")
|
message = _("Vserver not specified.")
|
||||||
|
|
||||||
|
|
||||||
|
class VserverNotReady(NetAppException):
|
||||||
|
message = _("Vserver %(vserver)s is not ready yet.")
|
||||||
|
|
||||||
|
|
||||||
class EMCPowerMaxXMLAPIError(Invalid):
|
class EMCPowerMaxXMLAPIError(Invalid):
|
||||||
message = _("%(err)s")
|
message = _("%(err)s")
|
||||||
|
|
||||||
|
|
|
@ -45,8 +45,10 @@ EVOLNOTCLONE = '13170'
|
||||||
EVOLMOVE_CANNOT_MOVE_TO_CFO = '13633'
|
EVOLMOVE_CANNOT_MOVE_TO_CFO = '13633'
|
||||||
EAGGRDOESNOTEXIST = '14420'
|
EAGGRDOESNOTEXIST = '14420'
|
||||||
EVOL_NOT_MOUNTED = '14716'
|
EVOL_NOT_MOUNTED = '14716'
|
||||||
|
EVSERVERALREADYSTARTED = '14923'
|
||||||
ESIS_CLONE_NOT_LICENSED = '14956'
|
ESIS_CLONE_NOT_LICENSED = '14956'
|
||||||
EOBJECTNOTFOUND = '15661'
|
EOBJECTNOTFOUND = '15661'
|
||||||
|
EVSERVERNOTFOUND = '15698'
|
||||||
E_VIFMGR_PORT_ALREADY_ASSIGNED_TO_BROADCAST_DOMAIN = '18605'
|
E_VIFMGR_PORT_ALREADY_ASSIGNED_TO_BROADCAST_DOMAIN = '18605'
|
||||||
ERELATION_EXISTS = '17122'
|
ERELATION_EXISTS = '17122'
|
||||||
ENOTRANSFER_IN_PROGRESS = '17130'
|
ENOTRANSFER_IN_PROGRESS = '17130'
|
||||||
|
@ -55,6 +57,7 @@ EANOTHER_OP_ACTIVE = '17131'
|
||||||
ERELATION_NOT_QUIESCED = '17127'
|
ERELATION_NOT_QUIESCED = '17127'
|
||||||
ESOURCE_IS_DIFFERENT = '17105'
|
ESOURCE_IS_DIFFERENT = '17105'
|
||||||
EVOL_CLONE_BEING_SPLIT = '17151'
|
EVOL_CLONE_BEING_SPLIT = '17151'
|
||||||
|
ESVMDR_CANNOT_PERFORM_OP_FOR_STATUS = '18815'
|
||||||
|
|
||||||
|
|
||||||
class NaServer(object):
|
class NaServer(object):
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -94,32 +94,48 @@ class DataMotionSession(object):
|
||||||
|
|
||||||
def _get_backend_qos_policy_group_name(self, share):
|
def _get_backend_qos_policy_group_name(self, share):
|
||||||
"""Get QoS policy name according to QoS policy group name template."""
|
"""Get QoS policy name according to QoS policy group name template."""
|
||||||
__, config = self._get_backend_config_obj(share)
|
__, config = self.get_backend_name_and_config_obj(share['host'])
|
||||||
return config.netapp_qos_policy_group_name_template % {
|
return config.netapp_qos_policy_group_name_template % {
|
||||||
'share_id': share['id'].replace('-', '_')}
|
'share_id': share['id'].replace('-', '_')}
|
||||||
|
|
||||||
|
def _get_backend_snapmirror_policy_name_svm(self, share_server_id,
|
||||||
|
backend_name):
|
||||||
|
config = get_backend_configuration(backend_name)
|
||||||
|
return (config.netapp_snapmirror_policy_name_svm_template
|
||||||
|
% {'share_server_id': share_server_id.replace('-', '_')})
|
||||||
|
|
||||||
|
def get_vserver_from_share_server(self, share_server):
|
||||||
|
backend_details = share_server.get('backend_details')
|
||||||
|
if backend_details:
|
||||||
|
return backend_details.get('vserver_name')
|
||||||
|
|
||||||
def get_vserver_from_share(self, share_obj):
|
def get_vserver_from_share(self, share_obj):
|
||||||
share_server = share_obj.get('share_server')
|
share_server = share_obj.get('share_server')
|
||||||
if share_server:
|
if share_server:
|
||||||
backend_details = share_server.get('backend_details')
|
return self.get_vserver_from_share_server(share_server)
|
||||||
if backend_details:
|
|
||||||
return backend_details.get('vserver_name')
|
|
||||||
|
|
||||||
def _get_backend_config_obj(self, share_obj):
|
def get_backend_name_and_config_obj(self, host):
|
||||||
backend_name = share_utils.extract_host(
|
backend_name = share_utils.extract_host(host, level='backend_name')
|
||||||
share_obj['host'], level='backend_name')
|
|
||||||
config = get_backend_configuration(backend_name)
|
config = get_backend_configuration(backend_name)
|
||||||
return backend_name, config
|
return backend_name, config
|
||||||
|
|
||||||
def get_backend_info_for_share(self, share_obj):
|
def get_backend_info_for_share(self, share_obj):
|
||||||
backend_name, config = self._get_backend_config_obj(share_obj)
|
backend_name, config = self.get_backend_name_and_config_obj(
|
||||||
|
share_obj['host'])
|
||||||
vserver = (self.get_vserver_from_share(share_obj) or
|
vserver = (self.get_vserver_from_share(share_obj) or
|
||||||
config.netapp_vserver)
|
config.netapp_vserver)
|
||||||
volume_name = self._get_backend_volume_name(
|
volume_name = self._get_backend_volume_name(config, share_obj)
|
||||||
config, share_obj)
|
|
||||||
|
|
||||||
return volume_name, vserver, backend_name
|
return volume_name, vserver, backend_name
|
||||||
|
|
||||||
|
def get_client_and_vserver_name(self, share_server):
|
||||||
|
destination_host = share_server.get('host')
|
||||||
|
vserver = self.get_vserver_from_share_server(share_server)
|
||||||
|
backend, __ = self.get_backend_name_and_config_obj(destination_host)
|
||||||
|
client = get_client_for_backend(backend, vserver_name=vserver)
|
||||||
|
|
||||||
|
return client, vserver
|
||||||
|
|
||||||
def get_snapmirrors(self, source_share_obj, dest_share_obj):
|
def get_snapmirrors(self, source_share_obj, dest_share_obj):
|
||||||
dest_volume_name, dest_vserver, dest_backend = (
|
dest_volume_name, dest_vserver, dest_backend = (
|
||||||
self.get_backend_info_for_share(dest_share_obj))
|
self.get_backend_info_for_share(dest_share_obj))
|
||||||
|
@ -130,8 +146,8 @@ class DataMotionSession(object):
|
||||||
source_share_obj)
|
source_share_obj)
|
||||||
|
|
||||||
snapmirrors = dest_client.get_snapmirrors(
|
snapmirrors = dest_client.get_snapmirrors(
|
||||||
src_vserver, src_volume_name,
|
source_vserver=src_vserver, dest_vserver=dest_vserver,
|
||||||
dest_vserver, dest_volume_name,
|
source_volume=src_volume_name, dest_volume=dest_volume_name,
|
||||||
desired_attributes=['relationship-status',
|
desired_attributes=['relationship-status',
|
||||||
'mirror-state',
|
'mirror-state',
|
||||||
'source-vserver',
|
'source-vserver',
|
||||||
|
@ -155,17 +171,17 @@ class DataMotionSession(object):
|
||||||
|
|
||||||
# 1. Create SnapMirror relationship
|
# 1. Create SnapMirror relationship
|
||||||
# TODO(ameade): Change the schedule from hourly to a config value
|
# TODO(ameade): Change the schedule from hourly to a config value
|
||||||
dest_client.create_snapmirror(src_vserver,
|
dest_client.create_snapmirror_vol(src_vserver,
|
||||||
src_volume_name,
|
|
||||||
dest_vserver,
|
|
||||||
dest_volume_name,
|
|
||||||
schedule='hourly')
|
|
||||||
|
|
||||||
# 2. Initialize async transfer of the initial data
|
|
||||||
dest_client.initialize_snapmirror(src_vserver,
|
|
||||||
src_volume_name,
|
src_volume_name,
|
||||||
dest_vserver,
|
dest_vserver,
|
||||||
dest_volume_name)
|
dest_volume_name,
|
||||||
|
schedule='hourly')
|
||||||
|
|
||||||
|
# 2. Initialize async transfer of the initial data
|
||||||
|
dest_client.initialize_snapmirror_vol(src_vserver,
|
||||||
|
src_volume_name,
|
||||||
|
dest_vserver,
|
||||||
|
dest_volume_name)
|
||||||
|
|
||||||
def delete_snapmirror(self, source_share_obj, dest_share_obj,
|
def delete_snapmirror(self, source_share_obj, dest_share_obj,
|
||||||
release=True):
|
release=True):
|
||||||
|
@ -185,21 +201,21 @@ class DataMotionSession(object):
|
||||||
|
|
||||||
# 1. Abort any ongoing transfers
|
# 1. Abort any ongoing transfers
|
||||||
try:
|
try:
|
||||||
dest_client.abort_snapmirror(src_vserver,
|
dest_client.abort_snapmirror_vol(src_vserver,
|
||||||
src_volume_name,
|
src_volume_name,
|
||||||
dest_vserver,
|
dest_vserver,
|
||||||
dest_volume_name,
|
dest_volume_name,
|
||||||
clear_checkpoint=False)
|
clear_checkpoint=False)
|
||||||
except netapp_api.NaApiError:
|
except netapp_api.NaApiError:
|
||||||
# Snapmirror is already deleted
|
# Snapmirror is already deleted
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 2. Delete SnapMirror Relationship and cleanup destination snapshots
|
# 2. Delete SnapMirror Relationship and cleanup destination snapshots
|
||||||
try:
|
try:
|
||||||
dest_client.delete_snapmirror(src_vserver,
|
dest_client.delete_snapmirror_vol(src_vserver,
|
||||||
src_volume_name,
|
src_volume_name,
|
||||||
dest_vserver,
|
dest_vserver,
|
||||||
dest_volume_name)
|
dest_volume_name)
|
||||||
except netapp_api.NaApiError as e:
|
except netapp_api.NaApiError as e:
|
||||||
with excutils.save_and_reraise_exception() as exc_context:
|
with excutils.save_and_reraise_exception() as exc_context:
|
||||||
if (e.code == netapp_api.EOBJECTNOTFOUND or
|
if (e.code == netapp_api.EOBJECTNOTFOUND or
|
||||||
|
@ -218,10 +234,10 @@ class DataMotionSession(object):
|
||||||
# 3. Cleanup SnapMirror relationship on source
|
# 3. Cleanup SnapMirror relationship on source
|
||||||
try:
|
try:
|
||||||
if src_client:
|
if src_client:
|
||||||
src_client.release_snapmirror(src_vserver,
|
src_client.release_snapmirror_vol(src_vserver,
|
||||||
src_volume_name,
|
src_volume_name,
|
||||||
dest_vserver,
|
dest_vserver,
|
||||||
dest_volume_name)
|
dest_volume_name)
|
||||||
except netapp_api.NaApiError as e:
|
except netapp_api.NaApiError as e:
|
||||||
with excutils.save_and_reraise_exception() as exc_context:
|
with excutils.save_and_reraise_exception() as exc_context:
|
||||||
if (e.code == netapp_api.EOBJECTNOTFOUND or
|
if (e.code == netapp_api.EOBJECTNOTFOUND or
|
||||||
|
@ -242,50 +258,81 @@ class DataMotionSession(object):
|
||||||
source_share_obj)
|
source_share_obj)
|
||||||
|
|
||||||
# Update SnapMirror
|
# Update SnapMirror
|
||||||
dest_client.update_snapmirror(src_vserver,
|
dest_client.update_snapmirror_vol(src_vserver,
|
||||||
src_volume_name,
|
src_volume_name,
|
||||||
dest_vserver,
|
dest_vserver,
|
||||||
dest_volume_name)
|
dest_volume_name)
|
||||||
|
|
||||||
|
def quiesce_then_abort_svm(self, source_share_server, dest_share_server):
|
||||||
|
source_client, source_vserver = self.get_client_and_vserver_name(
|
||||||
|
source_share_server)
|
||||||
|
dest_client, dest_vserver = self.get_client_and_vserver_name(
|
||||||
|
dest_share_server)
|
||||||
|
|
||||||
|
# 1. Attempt to quiesce, then abort
|
||||||
|
dest_client.quiesce_snapmirror_svm(source_vserver, dest_vserver)
|
||||||
|
|
||||||
|
dest_backend = share_utils.extract_host(dest_share_server['host'],
|
||||||
|
level='backend_name')
|
||||||
|
config = get_backend_configuration(dest_backend)
|
||||||
|
retries = config.netapp_snapmirror_quiesce_timeout / 5
|
||||||
|
|
||||||
|
@utils.retry(exception.ReplicationException, interval=5,
|
||||||
|
retries=retries, backoff_rate=1)
|
||||||
|
def wait_for_quiesced():
|
||||||
|
snapmirror = dest_client.get_snapmirrors_svm(
|
||||||
|
source_vserver=source_vserver, dest_vserver=dest_vserver,
|
||||||
|
desired_attributes=['relationship-status', 'mirror-state']
|
||||||
|
)[0]
|
||||||
|
if snapmirror.get('relationship-status') != 'quiesced':
|
||||||
|
raise exception.ReplicationException(
|
||||||
|
reason="Snapmirror relationship is not quiesced.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
wait_for_quiesced()
|
||||||
|
except exception.ReplicationException:
|
||||||
|
dest_client.abort_snapmirror_svm(source_vserver,
|
||||||
|
dest_vserver,
|
||||||
|
clear_checkpoint=False)
|
||||||
|
|
||||||
def quiesce_then_abort(self, source_share_obj, dest_share_obj):
|
def quiesce_then_abort(self, source_share_obj, dest_share_obj):
|
||||||
dest_volume_name, dest_vserver, dest_backend = (
|
dest_volume, dest_vserver, dest_backend = (
|
||||||
self.get_backend_info_for_share(dest_share_obj))
|
self.get_backend_info_for_share(dest_share_obj))
|
||||||
dest_client = get_client_for_backend(dest_backend,
|
dest_client = get_client_for_backend(dest_backend,
|
||||||
vserver_name=dest_vserver)
|
vserver_name=dest_vserver)
|
||||||
|
|
||||||
src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
|
src_volume, src_vserver, __ = self.get_backend_info_for_share(
|
||||||
source_share_obj)
|
source_share_obj)
|
||||||
|
|
||||||
# 1. Attempt to quiesce, then abort
|
# 1. Attempt to quiesce, then abort
|
||||||
dest_client.quiesce_snapmirror(src_vserver,
|
dest_client.quiesce_snapmirror_vol(src_vserver,
|
||||||
src_volume_name,
|
src_volume,
|
||||||
dest_vserver,
|
dest_vserver,
|
||||||
dest_volume_name)
|
dest_volume)
|
||||||
|
|
||||||
config = get_backend_configuration(share_utils.extract_host(
|
config = get_backend_configuration(dest_backend)
|
||||||
source_share_obj['host'], level='backend_name'))
|
|
||||||
retries = config.netapp_snapmirror_quiesce_timeout / 5
|
retries = config.netapp_snapmirror_quiesce_timeout / 5
|
||||||
|
|
||||||
@utils.retry(exception.ReplicationException, interval=5,
|
@utils.retry(exception.ReplicationException, interval=5,
|
||||||
retries=retries, backoff_rate=1)
|
retries=retries, backoff_rate=1)
|
||||||
def wait_for_quiesced():
|
def wait_for_quiesced():
|
||||||
snapmirror = dest_client.get_snapmirrors(
|
snapmirror = dest_client.get_snapmirrors(
|
||||||
src_vserver, src_volume_name, dest_vserver,
|
source_vserver=src_vserver, dest_vserver=dest_vserver,
|
||||||
dest_volume_name, desired_attributes=['relationship-status',
|
source_volume=src_volume, dest_volume=dest_volume,
|
||||||
'mirror-state']
|
desired_attributes=['relationship-status', 'mirror-state']
|
||||||
)[0]
|
)[0]
|
||||||
if snapmirror.get('relationship-status') != 'quiesced':
|
if snapmirror.get('relationship-status') != 'quiesced':
|
||||||
raise exception.ReplicationException(
|
raise exception.ReplicationException(
|
||||||
reason=("Snapmirror relationship is not quiesced."))
|
reason="Snapmirror relationship is not quiesced.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wait_for_quiesced()
|
wait_for_quiesced()
|
||||||
except exception.ReplicationException:
|
except exception.ReplicationException:
|
||||||
dest_client.abort_snapmirror(src_vserver,
|
dest_client.abort_snapmirror_vol(src_vserver,
|
||||||
src_volume_name,
|
src_volume,
|
||||||
dest_vserver,
|
dest_vserver,
|
||||||
dest_volume_name,
|
dest_volume,
|
||||||
clear_checkpoint=False)
|
clear_checkpoint=False)
|
||||||
|
|
||||||
def break_snapmirror(self, source_share_obj, dest_share_obj, mount=True):
|
def break_snapmirror(self, source_share_obj, dest_share_obj, mount=True):
|
||||||
"""Breaks SnapMirror relationship.
|
"""Breaks SnapMirror relationship.
|
||||||
|
@ -307,10 +354,10 @@ class DataMotionSession(object):
|
||||||
self.quiesce_then_abort(source_share_obj, dest_share_obj)
|
self.quiesce_then_abort(source_share_obj, dest_share_obj)
|
||||||
|
|
||||||
# 2. Break SnapMirror
|
# 2. Break SnapMirror
|
||||||
dest_client.break_snapmirror(src_vserver,
|
dest_client.break_snapmirror_vol(src_vserver,
|
||||||
src_volume_name,
|
src_volume_name,
|
||||||
dest_vserver,
|
dest_vserver,
|
||||||
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
|
||||||
if mount:
|
if mount:
|
||||||
|
@ -326,10 +373,10 @@ class DataMotionSession(object):
|
||||||
src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
|
src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
|
||||||
source_share_obj)
|
source_share_obj)
|
||||||
|
|
||||||
dest_client.resync_snapmirror(src_vserver,
|
dest_client.resync_snapmirror_vol(src_vserver,
|
||||||
src_volume_name,
|
src_volume_name,
|
||||||
dest_vserver,
|
dest_vserver,
|
||||||
dest_volume_name)
|
dest_volume_name)
|
||||||
|
|
||||||
def resume_snapmirror(self, source_share_obj, dest_share_obj):
|
def resume_snapmirror(self, source_share_obj, dest_share_obj):
|
||||||
"""Resume SnapMirror relationship from a quiesced state."""
|
"""Resume SnapMirror relationship from a quiesced state."""
|
||||||
|
@ -341,10 +388,10 @@ class DataMotionSession(object):
|
||||||
src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
|
src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
|
||||||
source_share_obj)
|
source_share_obj)
|
||||||
|
|
||||||
dest_client.resume_snapmirror(src_vserver,
|
dest_client.resume_snapmirror_vol(src_vserver,
|
||||||
src_volume_name,
|
src_volume_name,
|
||||||
dest_vserver,
|
dest_vserver,
|
||||||
dest_volume_name)
|
dest_volume_name)
|
||||||
|
|
||||||
def change_snapmirror_source(self, replica,
|
def change_snapmirror_source(self, replica,
|
||||||
orig_source_replica,
|
orig_source_replica,
|
||||||
|
@ -400,16 +447,16 @@ class DataMotionSession(object):
|
||||||
|
|
||||||
# 3. create
|
# 3. create
|
||||||
# TODO(ameade): Update the schedule if needed.
|
# TODO(ameade): Update the schedule if needed.
|
||||||
replica_client.create_snapmirror(new_src_vserver,
|
replica_client.create_snapmirror_vol(new_src_vserver,
|
||||||
new_src_volume_name,
|
new_src_volume_name,
|
||||||
replica_vserver,
|
replica_vserver,
|
||||||
replica_volume_name,
|
replica_volume_name,
|
||||||
schedule='hourly')
|
schedule='hourly')
|
||||||
# 4. resync
|
# 4. resync
|
||||||
replica_client.resync_snapmirror(new_src_vserver,
|
replica_client.resync_snapmirror_vol(new_src_vserver,
|
||||||
new_src_volume_name,
|
new_src_volume_name,
|
||||||
replica_vserver,
|
replica_vserver,
|
||||||
replica_volume_name)
|
replica_volume_name)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def remove_qos_on_old_active_replica(self, orig_active_replica):
|
def remove_qos_on_old_active_replica(self, orig_active_replica):
|
||||||
|
@ -430,3 +477,254 @@ class DataMotionSession(object):
|
||||||
"for replica %s to unset QoS policy and mark "
|
"for replica %s to unset QoS policy and mark "
|
||||||
"the QoS policy group for deletion.",
|
"the QoS policy group for deletion.",
|
||||||
orig_active_replica['id'])
|
orig_active_replica['id'])
|
||||||
|
|
||||||
|
def create_snapmirror_svm(self, source_share_server,
|
||||||
|
dest_share_server):
|
||||||
|
"""Sets up a SnapMirror relationship between two vServers.
|
||||||
|
|
||||||
|
1. Create a SnapMirror policy for SVM DR
|
||||||
|
2. Create SnapMirror relationship
|
||||||
|
3. Initialize data transfer asynchronously
|
||||||
|
"""
|
||||||
|
dest_client, dest_vserver = self.get_client_and_vserver_name(
|
||||||
|
dest_share_server)
|
||||||
|
src_vserver = self.get_vserver_from_share_server(source_share_server)
|
||||||
|
|
||||||
|
# 1: Create SnapMirror policy for SVM DR
|
||||||
|
dest_backend_name = share_utils.extract_host(dest_share_server['host'],
|
||||||
|
level='backend_name')
|
||||||
|
policy_name = self._get_backend_snapmirror_policy_name_svm(
|
||||||
|
dest_share_server['id'],
|
||||||
|
dest_backend_name,
|
||||||
|
)
|
||||||
|
dest_client.create_snapmirror_policy(policy_name)
|
||||||
|
|
||||||
|
# 2. Create SnapMirror relationship
|
||||||
|
dest_client.create_snapmirror_svm(src_vserver,
|
||||||
|
dest_vserver,
|
||||||
|
policy=policy_name,
|
||||||
|
schedule='hourly')
|
||||||
|
|
||||||
|
# 2. Initialize async transfer of the initial data
|
||||||
|
dest_client.initialize_snapmirror_svm(src_vserver,
|
||||||
|
dest_vserver)
|
||||||
|
|
||||||
|
def get_snapmirrors_svm(self, source_share_server, dest_share_server):
|
||||||
|
"""Get SnapMirrors between two vServers."""
|
||||||
|
|
||||||
|
dest_client, dest_vserver = self.get_client_and_vserver_name(
|
||||||
|
dest_share_server)
|
||||||
|
src_vserver = self.get_vserver_from_share_server(source_share_server)
|
||||||
|
|
||||||
|
snapmirrors = dest_client.get_snapmirrors_svm(
|
||||||
|
source_vserver=src_vserver, dest_vserver=dest_vserver,
|
||||||
|
desired_attributes=['relationship-status',
|
||||||
|
'mirror-state',
|
||||||
|
'last-transfer-end-timestamp'])
|
||||||
|
return snapmirrors
|
||||||
|
|
||||||
|
def get_snapmirror_destinations_svm(self, source_share_server,
|
||||||
|
dest_share_server):
|
||||||
|
"""Get SnapMirrors between two vServers."""
|
||||||
|
|
||||||
|
dest_client, dest_vserver = self.get_client_and_vserver_name(
|
||||||
|
dest_share_server)
|
||||||
|
src_vserver = self.get_vserver_from_share_server(source_share_server)
|
||||||
|
|
||||||
|
snapmirrors = dest_client.get_snapmirror_destinations_svm(
|
||||||
|
source_vserver=src_vserver, dest_vserver=dest_vserver)
|
||||||
|
return snapmirrors
|
||||||
|
|
||||||
|
def update_snapmirror_svm(self, source_share_server, dest_share_server):
|
||||||
|
"""Schedule a SnapMirror update to happen on the backend."""
|
||||||
|
|
||||||
|
dest_client, dest_vserver = self.get_client_and_vserver_name(
|
||||||
|
dest_share_server)
|
||||||
|
src_vserver = self.get_vserver_from_share_server(source_share_server)
|
||||||
|
|
||||||
|
# Update SnapMirror
|
||||||
|
dest_client.update_snapmirror_svm(src_vserver, dest_vserver)
|
||||||
|
|
||||||
|
def quiesce_and_break_snapmirror_svm(self, source_share_server,
|
||||||
|
dest_share_server):
|
||||||
|
"""Abort and break a SnapMirror relationship between vServers.
|
||||||
|
|
||||||
|
1. Quiesce SnapMirror
|
||||||
|
2. Break SnapMirror
|
||||||
|
"""
|
||||||
|
dest_client, dest_vserver = self.get_client_and_vserver_name(
|
||||||
|
dest_share_server)
|
||||||
|
src_vserver = self.get_vserver_from_share_server(source_share_server)
|
||||||
|
|
||||||
|
# 1. Attempt to quiesce, then abort
|
||||||
|
self.quiesce_then_abort_svm(source_share_server, dest_share_server)
|
||||||
|
|
||||||
|
# 2. Break SnapMirror
|
||||||
|
dest_client.break_snapmirror_svm(src_vserver, dest_vserver)
|
||||||
|
|
||||||
|
def cancel_snapmirror_svm(self, source_share_server, dest_share_server):
|
||||||
|
"""Cancels SnapMirror relationship between vServers."""
|
||||||
|
|
||||||
|
dest_backend = share_utils.extract_host(dest_share_server['host'],
|
||||||
|
level='backend_name')
|
||||||
|
dest_config = get_backend_configuration(dest_backend)
|
||||||
|
server_timeout = (
|
||||||
|
dest_config.netapp_server_migration_state_change_timeout)
|
||||||
|
dest_client, dest_vserver = self.get_client_and_vserver_name(
|
||||||
|
dest_share_server)
|
||||||
|
|
||||||
|
snapmirrors = self.get_snapmirrors_svm(source_share_server,
|
||||||
|
dest_share_server)
|
||||||
|
if snapmirrors:
|
||||||
|
# 1. Attempt to quiesce and break snapmirror
|
||||||
|
self.quiesce_and_break_snapmirror_svm(source_share_server,
|
||||||
|
dest_share_server)
|
||||||
|
|
||||||
|
# NOTE(dviroel): Lets wait until the destination vserver be
|
||||||
|
# promoted to 'default' and state 'running', before starting
|
||||||
|
# shutting down the source
|
||||||
|
self.wait_for_vserver_state(dest_vserver, dest_client,
|
||||||
|
subtype='default', state='running',
|
||||||
|
operational_state='stopped',
|
||||||
|
timeout=server_timeout)
|
||||||
|
# 2. Delete SnapMirror
|
||||||
|
self.delete_snapmirror_svm(source_share_server, dest_share_server)
|
||||||
|
else:
|
||||||
|
dest_info = dest_client.get_vserver_info(dest_vserver)
|
||||||
|
if dest_info is None:
|
||||||
|
# NOTE(dviroel): Nothing to cancel since the destination does
|
||||||
|
# not exist.
|
||||||
|
return
|
||||||
|
if dest_info.get('subtype') == 'dp_destination':
|
||||||
|
# NOTE(dviroel): Can be a corner case where no snapmirror
|
||||||
|
# relationship was found but the destination vserver is stuck
|
||||||
|
# in DP mode. We need to convert it to 'default' to release
|
||||||
|
# its resources later.
|
||||||
|
self.convert_svm_to_default_subtype(dest_vserver, dest_client,
|
||||||
|
timeout=server_timeout)
|
||||||
|
|
||||||
|
def convert_svm_to_default_subtype(self, vserver_name, client,
|
||||||
|
is_dest_path=True, timeout=300):
|
||||||
|
interval = 10
|
||||||
|
retries = (timeout / interval or 1)
|
||||||
|
|
||||||
|
@utils.retry(exception.VserverNotReady, interval=interval,
|
||||||
|
retries=retries, backoff_rate=1)
|
||||||
|
def wait_for_state():
|
||||||
|
vserver_info = client.get_vserver_info(vserver_name)
|
||||||
|
if vserver_info.get('subtype') != 'default':
|
||||||
|
if is_dest_path:
|
||||||
|
client.break_snapmirror_svm(dest_vserver=vserver_name)
|
||||||
|
else:
|
||||||
|
client.break_snapmirror_svm(source_vserver=vserver_name)
|
||||||
|
raise exception.VserverNotReady(vserver=vserver_name)
|
||||||
|
try:
|
||||||
|
wait_for_state()
|
||||||
|
except exception.VserverNotReady:
|
||||||
|
msg = _("Vserver %s did not reach the expected state. Retries "
|
||||||
|
"exhausted. Aborting.") % vserver_name
|
||||||
|
raise exception.NetAppException(message=msg)
|
||||||
|
|
||||||
|
def delete_snapmirror_svm(self, src_share_server, dest_share_server,
|
||||||
|
release=True):
|
||||||
|
"""Ensures all information about a SnapMirror relationship is removed.
|
||||||
|
|
||||||
|
1. Abort SnapMirror
|
||||||
|
2. Delete the SnapMirror
|
||||||
|
3. Release SnapMirror to cleanup SnapMirror metadata and snapshots
|
||||||
|
"""
|
||||||
|
src_client, src_vserver = self.get_client_and_vserver_name(
|
||||||
|
src_share_server)
|
||||||
|
dest_client, dest_vserver = self.get_client_and_vserver_name(
|
||||||
|
dest_share_server)
|
||||||
|
# 1. Abort any ongoing transfers
|
||||||
|
try:
|
||||||
|
dest_client.abort_snapmirror_svm(src_vserver, dest_vserver)
|
||||||
|
except netapp_api.NaApiError:
|
||||||
|
# SnapMirror is already deleted
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 2. Delete SnapMirror Relationship and cleanup destination snapshots
|
||||||
|
try:
|
||||||
|
dest_client.delete_snapmirror_svm(src_vserver, dest_vserver)
|
||||||
|
except netapp_api.NaApiError as e:
|
||||||
|
with excutils.save_and_reraise_exception() as exc_context:
|
||||||
|
if (e.code == netapp_api.EOBJECTNOTFOUND or
|
||||||
|
e.code == netapp_api.ESOURCE_IS_DIFFERENT or
|
||||||
|
"(entry doesn't exist)" in e.message):
|
||||||
|
LOG.info('No snapmirror relationship to delete')
|
||||||
|
exc_context.reraise = False
|
||||||
|
|
||||||
|
# 3. Release SnapMirror
|
||||||
|
if release:
|
||||||
|
src_backend = share_utils.extract_host(src_share_server['host'],
|
||||||
|
level='backend_name')
|
||||||
|
src_config = get_backend_configuration(src_backend)
|
||||||
|
release_timeout = (
|
||||||
|
src_config.netapp_snapmirror_release_timeout)
|
||||||
|
self.wait_for_snapmirror_release_svm(src_vserver,
|
||||||
|
dest_vserver,
|
||||||
|
src_client,
|
||||||
|
timeout=release_timeout)
|
||||||
|
|
||||||
|
def wait_for_vserver_state(self, vserver_name, client, state=None,
|
||||||
|
operational_state=None, subtype=None,
|
||||||
|
timeout=300):
|
||||||
|
interval = 10
|
||||||
|
retries = (timeout / interval or 1)
|
||||||
|
|
||||||
|
expected = {}
|
||||||
|
if state:
|
||||||
|
expected['state'] = state
|
||||||
|
if operational_state:
|
||||||
|
expected['operational_state'] = operational_state
|
||||||
|
if subtype:
|
||||||
|
expected['subtype'] = subtype
|
||||||
|
|
||||||
|
@utils.retry(exception.VserverNotReady, interval=interval,
|
||||||
|
retries=retries, backoff_rate=1)
|
||||||
|
def wait_for_state():
|
||||||
|
vserver_info = client.get_vserver_info(vserver_name)
|
||||||
|
if not all(item in vserver_info.items() for
|
||||||
|
item in expected.items()):
|
||||||
|
raise exception.VserverNotReady(vserver=vserver_name)
|
||||||
|
try:
|
||||||
|
wait_for_state()
|
||||||
|
except exception.VserverNotReady:
|
||||||
|
msg = _("Vserver %s did not reach the expected state. Retries "
|
||||||
|
"exhausted. Aborting.") % vserver_name
|
||||||
|
raise exception.NetAppException(message=msg)
|
||||||
|
|
||||||
|
def wait_for_snapmirror_release_svm(self, source_vserver, dest_vserver,
|
||||||
|
src_client, timeout=300):
|
||||||
|
interval = 10
|
||||||
|
retries = (timeout / interval or 1)
|
||||||
|
|
||||||
|
@utils.retry(exception.NetAppException, interval=interval,
|
||||||
|
retries=retries, backoff_rate=1)
|
||||||
|
def release_snapmirror():
|
||||||
|
snapmirrors = src_client.get_snapmirror_destinations_svm(
|
||||||
|
source_vserver=source_vserver, dest_vserver=dest_vserver)
|
||||||
|
if not snapmirrors:
|
||||||
|
LOG.debug("No snapmirrors to be released in source location.")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
src_client.release_snapmirror_svm(source_vserver,
|
||||||
|
dest_vserver)
|
||||||
|
except netapp_api.NaApiError as e:
|
||||||
|
if (e.code == netapp_api.EOBJECTNOTFOUND or
|
||||||
|
e.code == netapp_api.ESOURCE_IS_DIFFERENT or
|
||||||
|
"(entry doesn't exist)" in e.message):
|
||||||
|
LOG.debug('Snapmirror relationship does not exists '
|
||||||
|
'anymore.')
|
||||||
|
|
||||||
|
msg = _('Snapmirror release sent to source vserver. We will '
|
||||||
|
'wait for it to be released.')
|
||||||
|
raise exception.NetAppException(vserver=msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
release_snapmirror()
|
||||||
|
except exception.NetAppException:
|
||||||
|
msg = _("Unable to release the snapmirror from source vserver %s. "
|
||||||
|
"Retries exhausted. Aborting") % source_vserver
|
||||||
|
raise exception.NetAppException(message=msg)
|
||||||
|
|
|
@ -287,9 +287,38 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
|
||||||
def get_share_status(self, share_instance, share_server=None):
|
def get_share_status(self, share_instance, share_server=None):
|
||||||
return self.library.get_share_status(share_instance, share_server)
|
return self.library.get_share_status(share_instance, share_server)
|
||||||
|
|
||||||
def choose_share_server_compatible_with_share(self, context,
|
def share_server_migration_check_compatibility(
|
||||||
share_servers, share,
|
self, context, share_server, dest_host, old_share_network,
|
||||||
snapshot=None,
|
new_share_network, shares_request_spec):
|
||||||
|
|
||||||
|
return self.library.share_server_migration_check_compatibility(
|
||||||
|
context, share_server, dest_host, old_share_network,
|
||||||
|
new_share_network, shares_request_spec)
|
||||||
|
|
||||||
|
def share_server_migration_start(self, context, src_share_server,
|
||||||
|
dest_share_server, shares, snapshots):
|
||||||
|
self.library.share_server_migration_start(
|
||||||
|
context, src_share_server, dest_share_server, shares, snapshots)
|
||||||
|
|
||||||
|
def share_server_migration_continue(self, context, src_share_server,
|
||||||
|
dest_share_server, shares, snapshots):
|
||||||
|
return self.library.share_server_migration_continue(
|
||||||
|
context, src_share_server, dest_share_server, shares, snapshots)
|
||||||
|
|
||||||
|
def share_server_migration_complete(self, context, src_share_server,
|
||||||
|
dest_share_server, shares, snapshots,
|
||||||
|
new_network_info):
|
||||||
|
return self.library.share_server_migration_complete(
|
||||||
|
context, src_share_server, dest_share_server, shares, snapshots,
|
||||||
|
new_network_info)
|
||||||
|
|
||||||
|
def share_server_migration_cancel(self, context, src_share_server,
|
||||||
|
dest_share_server, shares, snapshots):
|
||||||
|
self.library.share_server_migration_cancel(
|
||||||
|
context, src_share_server, dest_share_server, shares, snapshots)
|
||||||
|
|
||||||
|
def choose_share_server_compatible_with_share(self, context, share_servers,
|
||||||
|
share, snapshot=None,
|
||||||
share_group=None):
|
share_group=None):
|
||||||
return self.library.choose_share_server_compatible_with_share(
|
return self.library.choose_share_server_compatible_with_share(
|
||||||
context, share_servers, share, snapshot=snapshot,
|
context, share_servers, share, snapshot=snapshot,
|
||||||
|
@ -301,3 +330,9 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
|
||||||
return self.library.choose_share_server_compatible_with_share_group(
|
return self.library.choose_share_server_compatible_with_share_group(
|
||||||
context, share_servers, share_group_ref,
|
context, share_servers, share_group_ref,
|
||||||
share_group_snapshot=share_group_snapshot)
|
share_group_snapshot=share_group_snapshot)
|
||||||
|
|
||||||
|
def share_server_migration_get_progress(self, context, src_share_server,
|
||||||
|
dest_share_server, shares,
|
||||||
|
snapshots):
|
||||||
|
return self.library.share_server_migration_get_progress(
|
||||||
|
context, src_share_server, dest_share_server, shares, snapshots)
|
||||||
|
|
|
@ -284,6 +284,32 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
|
||||||
def get_share_status(self, share_instance, share_server=None):
|
def get_share_status(self, share_instance, share_server=None):
|
||||||
return self.library.get_share_status(share_instance, share_server)
|
return self.library.get_share_status(share_instance, share_server)
|
||||||
|
|
||||||
|
def share_server_migration_start(self, context, src_share_server,
|
||||||
|
dest_share_server, shares, snapshots):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def share_server_migration_continue(self, context, src_share_server,
|
||||||
|
dest_share_server, shares, snapshots):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def share_server_migration_complete(self, context, src_share_server,
|
||||||
|
dest_share_server, shares, snapshots,
|
||||||
|
new_network_info):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def share_server_migration_cancel(self, context, src_share_server,
|
||||||
|
dest_share_server, shares, snapshots):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def share_server_migration_check_compatibility(
|
||||||
|
self, context, share_server, dest_host, old_share_network,
|
||||||
|
new_share_network, shares_request_spec):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def share_server_migration_get_progress(self, context, src_share_server,
|
||||||
|
dest_share_server):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def choose_share_server_compatible_with_share(self, context, share_servers,
|
def choose_share_server_compatible_with_share(self, context, share_servers,
|
||||||
share, snapshot=None,
|
share, snapshot=None,
|
||||||
share_group=None):
|
share_group=None):
|
||||||
|
|
|
@ -273,6 +273,10 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||||
return self.configuration.netapp_qos_policy_group_name_template % {
|
return self.configuration.netapp_qos_policy_group_name_template % {
|
||||||
'share_id': share_id.replace('-', '_')}
|
'share_id': share_id.replace('-', '_')}
|
||||||
|
|
||||||
|
def _get_backend_snapmirror_policy_name_svm(self, share_server_id):
|
||||||
|
return (self.configuration.netapp_snapmirror_policy_name_svm_template
|
||||||
|
% {'share_server_id': share_server_id.replace('-', '_')})
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def _get_aggregate_space(self):
|
def _get_aggregate_space(self):
|
||||||
aggregates = self._find_matching_aggregates()
|
aggregates = self._find_matching_aggregates()
|
||||||
|
@ -1155,7 +1159,8 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def _create_export(self, share, share_server, vserver, vserver_client,
|
def _create_export(self, share, share_server, vserver, vserver_client,
|
||||||
clear_current_export_policy=True):
|
clear_current_export_policy=True,
|
||||||
|
ensure_share_already_exists=False):
|
||||||
"""Creates NAS storage."""
|
"""Creates NAS storage."""
|
||||||
helper = self._get_helper(share)
|
helper = self._get_helper(share)
|
||||||
helper.set_client(vserver_client)
|
helper.set_client(vserver_client)
|
||||||
|
@ -1177,7 +1182,8 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||||
# Create the share and get a callback for generating export locations
|
# Create the share and get a callback for generating export locations
|
||||||
callback = helper.create_share(
|
callback = helper.create_share(
|
||||||
share, share_name,
|
share, share_name,
|
||||||
clear_current_export_policy=clear_current_export_policy)
|
clear_current_export_policy=clear_current_export_policy,
|
||||||
|
ensure_share_already_exists=ensure_share_already_exists)
|
||||||
|
|
||||||
# Generate export locations using addresses, metadata and callback
|
# Generate export locations using addresses, metadata and callback
|
||||||
export_locations = [
|
export_locations = [
|
||||||
|
@ -1919,14 +1925,16 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||||
|
|
||||||
if snapmirror.get('mirror-state') != 'snapmirrored':
|
if snapmirror.get('mirror-state') != 'snapmirrored':
|
||||||
try:
|
try:
|
||||||
vserver_client.resume_snapmirror(snapmirror['source-vserver'],
|
vserver_client.resume_snapmirror_vol(
|
||||||
snapmirror['source-volume'],
|
snapmirror['source-vserver'],
|
||||||
vserver,
|
snapmirror['source-volume'],
|
||||||
share_name)
|
vserver,
|
||||||
vserver_client.resync_snapmirror(snapmirror['source-vserver'],
|
share_name)
|
||||||
snapmirror['source-volume'],
|
vserver_client.resync_snapmirror_vol(
|
||||||
vserver,
|
snapmirror['source-vserver'],
|
||||||
share_name)
|
snapmirror['source-volume'],
|
||||||
|
vserver,
|
||||||
|
share_name)
|
||||||
return constants.REPLICA_STATE_OUT_OF_SYNC
|
return constants.REPLICA_STATE_OUT_OF_SYNC
|
||||||
except netapp_api.NaApiError:
|
except netapp_api.NaApiError:
|
||||||
LOG.exception("Could not resync snapmirror.")
|
LOG.exception("Could not resync snapmirror.")
|
||||||
|
@ -2592,7 +2600,7 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||||
msg_args = {
|
msg_args = {
|
||||||
'share_move_state': move_status['state']
|
'share_move_state': move_status['state']
|
||||||
}
|
}
|
||||||
msg = _("Migration cancelation was not successful. The share "
|
msg = _("Migration cancellation was not successful. The share "
|
||||||
"migration state failed while transitioning from "
|
"migration state failed while transitioning from "
|
||||||
"%(share_move_state)s state to 'failed'. Retries "
|
"%(share_move_state)s state to 'failed'. Retries "
|
||||||
"exhausted.") % msg_args
|
"exhausted.") % msg_args
|
||||||
|
@ -2842,3 +2850,32 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||||
self.volume_rehost(share, src_vserver, dest_vserver)
|
self.volume_rehost(share, src_vserver, dest_vserver)
|
||||||
# Mount the volume on the destination vserver
|
# Mount the volume on the destination vserver
|
||||||
dest_vserver_client.mount_volume(volume_name)
|
dest_vserver_client.mount_volume(volume_name)
|
||||||
|
|
||||||
|
def _check_capacity_compatibility(self, pools, thin_provision, size):
|
||||||
|
"""Check if the size requested is suitable for the available pools"""
|
||||||
|
|
||||||
|
backend_free_capacity = 0.0
|
||||||
|
|
||||||
|
for pool in pools:
|
||||||
|
if "unknown" in (pool['free_capacity_gb'],
|
||||||
|
pool['total_capacity_gb']):
|
||||||
|
return False
|
||||||
|
reserved = float(pool['reserved_percentage']) / 100
|
||||||
|
|
||||||
|
total_pool_free = math.floor(
|
||||||
|
pool['free_capacity_gb'] -
|
||||||
|
pool['total_capacity_gb'] * reserved)
|
||||||
|
|
||||||
|
if thin_provision:
|
||||||
|
# If thin provision is enabled it's necessary recalculate the
|
||||||
|
# total_pool_free considering the max over subscription ratio
|
||||||
|
# for each pool. After summing the free space for each pool we
|
||||||
|
# have the total backend free capacity to compare with the
|
||||||
|
# requested size.
|
||||||
|
if pool['max_over_subscription_ratio'] >= 1:
|
||||||
|
total_pool_free = math.floor(
|
||||||
|
total_pool_free * pool['max_over_subscription_ratio'])
|
||||||
|
|
||||||
|
backend_free_capacity += total_pool_free
|
||||||
|
|
||||||
|
return size <= backend_free_capacity
|
||||||
|
|
|
@ -29,6 +29,7 @@ from oslo_utils import excutils
|
||||||
|
|
||||||
from manila import exception
|
from manila import exception
|
||||||
from manila.i18n import _
|
from manila.i18n import _
|
||||||
|
from manila.share.drivers.netapp.dataontap.client import api as netapp_api
|
||||||
from manila.share.drivers.netapp.dataontap.client import client_cmode
|
from manila.share.drivers.netapp.dataontap.client import client_cmode
|
||||||
from manila.share.drivers.netapp.dataontap.cluster_mode import data_motion
|
from manila.share.drivers.netapp.dataontap.cluster_mode import data_motion
|
||||||
from manila.share.drivers.netapp.dataontap.cluster_mode import lib_base
|
from manila.share.drivers.netapp.dataontap.cluster_mode import lib_base
|
||||||
|
@ -72,8 +73,8 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||||
check_for_setup_error())
|
check_for_setup_error())
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def _get_vserver(self, share_server=None, vserver_name=None):
|
def _get_vserver(self, share_server=None, vserver_name=None,
|
||||||
|
backend_name=None):
|
||||||
if share_server:
|
if share_server:
|
||||||
backend_details = share_server.get('backend_details')
|
backend_details = share_server.get('backend_details')
|
||||||
vserver = backend_details.get(
|
vserver = backend_details.get(
|
||||||
|
@ -86,13 +87,19 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||||
elif vserver_name:
|
elif vserver_name:
|
||||||
vserver = vserver_name
|
vserver = vserver_name
|
||||||
else:
|
else:
|
||||||
msg = _('Share server not provided')
|
msg = _('Share server or vserver name not provided')
|
||||||
raise exception.InvalidInput(reason=msg)
|
raise exception.InvalidInput(reason=msg)
|
||||||
|
|
||||||
if not self._client.vserver_exists(vserver):
|
if backend_name:
|
||||||
|
vserver_client = data_motion.get_client_for_backend(
|
||||||
|
backend_name, vserver
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
vserver_client = self._get_api_client(vserver)
|
||||||
|
|
||||||
|
if not vserver_client.vserver_exists(vserver):
|
||||||
raise exception.VserverNotFound(vserver=vserver)
|
raise exception.VserverNotFound(vserver=vserver)
|
||||||
|
|
||||||
vserver_client = self._get_api_client(vserver)
|
|
||||||
return vserver, vserver_client
|
return vserver, vserver_client
|
||||||
|
|
||||||
def _get_ems_pool_info(self):
|
def _get_ems_pool_info(self):
|
||||||
|
@ -152,7 +159,7 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||||
server_details['nfs_config'] = jsonutils.dumps(nfs_config)
|
server_details['nfs_config'] = jsonutils.dumps(nfs_config)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._create_vserver(vserver_name, network_info,
|
self._create_vserver(vserver_name, network_info, metadata,
|
||||||
nfs_config=nfs_config)
|
nfs_config=nfs_config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
e.detail_data = {'server_details': server_details}
|
e.detail_data = {'server_details': server_details}
|
||||||
|
@ -208,12 +215,20 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||||
return self.configuration.netapp_vserver_name_template % server_id
|
return self.configuration.netapp_vserver_name_template % server_id
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def _create_vserver(self, vserver_name, network_info, nfs_config=None):
|
def _create_vserver(self, vserver_name, network_info, metadata=None,
|
||||||
|
nfs_config=None):
|
||||||
"""Creates Vserver with given parameters if it doesn't exist."""
|
"""Creates Vserver with given parameters if it doesn't exist."""
|
||||||
|
|
||||||
if self._client.vserver_exists(vserver_name):
|
if self._client.vserver_exists(vserver_name):
|
||||||
msg = _('Vserver %s already exists.')
|
msg = _('Vserver %s already exists.')
|
||||||
raise exception.NetAppException(msg % vserver_name)
|
raise exception.NetAppException(msg % vserver_name)
|
||||||
|
# NOTE(dviroel): check if this vserver will be a data protection server
|
||||||
|
is_dp_destination = False
|
||||||
|
if metadata and metadata.get('migration_destination') is True:
|
||||||
|
is_dp_destination = True
|
||||||
|
msg = _("Starting creation of a vserver with 'dp_destination' "
|
||||||
|
"subtype.")
|
||||||
|
LOG.debug(msg)
|
||||||
|
|
||||||
# NOTE(lseki): If there's already an ipspace created for the same VLAN
|
# NOTE(lseki): If there's already an ipspace created for the same VLAN
|
||||||
# port, reuse it. It will be named after the previously created share
|
# port, reuse it. It will be named after the previously created share
|
||||||
|
@ -224,47 +239,66 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||||
ipspace_name = self._client.get_ipspace_name_for_vlan_port(
|
ipspace_name = self._client.get_ipspace_name_for_vlan_port(
|
||||||
node_name, port, vlan) or self._create_ipspace(network_info)
|
node_name, port, vlan) or self._create_ipspace(network_info)
|
||||||
|
|
||||||
LOG.debug('Vserver %s does not exist, creating.', vserver_name)
|
if is_dp_destination:
|
||||||
self._client.create_vserver(
|
# Get Data ONTAP aggregate name as pool name.
|
||||||
vserver_name,
|
LOG.debug('Creating a new Vserver (%s) for data protection.',
|
||||||
self.configuration.netapp_root_volume_aggregate,
|
vserver_name)
|
||||||
self.configuration.netapp_root_volume,
|
self._client.create_vserver_dp_destination(
|
||||||
self._find_matching_aggregates(),
|
vserver_name,
|
||||||
ipspace_name)
|
self._find_matching_aggregates(),
|
||||||
|
ipspace_name)
|
||||||
|
# Set up port and broadcast domain for the current ipspace
|
||||||
|
self._create_port_and_broadcast_domain(ipspace_name, network_info)
|
||||||
|
else:
|
||||||
|
LOG.debug('Vserver %s does not exist, creating.', vserver_name)
|
||||||
|
self._client.create_vserver(
|
||||||
|
vserver_name,
|
||||||
|
self.configuration.netapp_root_volume_aggregate,
|
||||||
|
self.configuration.netapp_root_volume,
|
||||||
|
self._find_matching_aggregates(),
|
||||||
|
ipspace_name)
|
||||||
|
|
||||||
vserver_client = self._get_api_client(vserver=vserver_name)
|
vserver_client = self._get_api_client(vserver=vserver_name)
|
||||||
security_services = None
|
|
||||||
try:
|
|
||||||
self._create_vserver_lifs(vserver_name,
|
|
||||||
vserver_client,
|
|
||||||
network_info,
|
|
||||||
ipspace_name)
|
|
||||||
|
|
||||||
self._create_vserver_admin_lif(vserver_name,
|
security_services = network_info.get('security_services')
|
||||||
vserver_client,
|
try:
|
||||||
network_info,
|
self._setup_network_for_vserver(
|
||||||
ipspace_name)
|
vserver_name, vserver_client, network_info, ipspace_name,
|
||||||
|
security_services=security_services, nfs_config=nfs_config)
|
||||||
|
except Exception:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
LOG.error("Failed to configure Vserver.")
|
||||||
|
# NOTE(dviroel): At this point, the lock was already
|
||||||
|
# acquired by the caller of _create_vserver.
|
||||||
|
self._delete_vserver(vserver_name,
|
||||||
|
security_services=security_services,
|
||||||
|
needs_lock=False)
|
||||||
|
|
||||||
self._create_vserver_routes(vserver_client,
|
def _setup_network_for_vserver(self, vserver_name, vserver_client,
|
||||||
network_info)
|
network_info, ipspace_name,
|
||||||
|
enable_nfs=True, security_services=None,
|
||||||
|
nfs_config=None):
|
||||||
|
self._create_vserver_lifs(vserver_name,
|
||||||
|
vserver_client,
|
||||||
|
network_info,
|
||||||
|
ipspace_name)
|
||||||
|
|
||||||
|
self._create_vserver_admin_lif(vserver_name,
|
||||||
|
vserver_client,
|
||||||
|
network_info,
|
||||||
|
ipspace_name)
|
||||||
|
|
||||||
|
self._create_vserver_routes(vserver_client,
|
||||||
|
network_info)
|
||||||
|
if enable_nfs:
|
||||||
vserver_client.enable_nfs(
|
vserver_client.enable_nfs(
|
||||||
self.configuration.netapp_enabled_share_protocols,
|
self.configuration.netapp_enabled_share_protocols,
|
||||||
nfs_config=nfs_config)
|
nfs_config=nfs_config)
|
||||||
|
|
||||||
security_services = network_info.get('security_services')
|
if security_services:
|
||||||
if security_services:
|
self._client.setup_security_services(security_services,
|
||||||
self._client.setup_security_services(security_services,
|
vserver_client,
|
||||||
vserver_client,
|
vserver_name)
|
||||||
vserver_name)
|
|
||||||
except Exception:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
LOG.error("Failed to configure Vserver.")
|
|
||||||
# NOTE(dviroel): At this point, the lock was already acquired
|
|
||||||
# by the caller of _create_vserver.
|
|
||||||
self._delete_vserver(vserver_name,
|
|
||||||
security_services=security_services,
|
|
||||||
needs_lock=False)
|
|
||||||
|
|
||||||
def _get_valid_ipspace_name(self, network_id):
|
def _get_valid_ipspace_name(self, network_id):
|
||||||
"""Get IPspace name according to network id."""
|
"""Get IPspace name according to network id."""
|
||||||
|
@ -376,6 +410,21 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||||
ip_address, netmask, vlan, node_name, port, vserver_name,
|
ip_address, netmask, vlan, node_name, port, vserver_name,
|
||||||
lif_name, ipspace_name, mtu)
|
lif_name, ipspace_name, mtu)
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def _create_port_and_broadcast_domain(self, ipspace_name, network_info):
|
||||||
|
nodes = self._client.list_cluster_nodes()
|
||||||
|
node_network_info = zip(nodes, network_info['network_allocations'])
|
||||||
|
|
||||||
|
for node_name, network_allocation in node_network_info:
|
||||||
|
|
||||||
|
port = self._get_node_data_port(node_name)
|
||||||
|
vlan = network_allocation['segmentation_id']
|
||||||
|
network_mtu = network_allocation.get('mtu')
|
||||||
|
mtu = network_mtu or DEFAULT_MTU
|
||||||
|
|
||||||
|
self._client.create_port_and_broadcast_domain(
|
||||||
|
node_name, port, vlan, mtu, ipspace_name)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def get_network_allocations_number(self):
|
def get_network_allocations_number(self):
|
||||||
"""Get number of network interfaces to be created."""
|
"""Get number of network interfaces to be created."""
|
||||||
|
@ -415,6 +464,7 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||||
|
|
||||||
vserver_client = self._get_api_client(vserver=vserver)
|
vserver_client = self._get_api_client(vserver=vserver)
|
||||||
network_interfaces = vserver_client.get_network_interfaces()
|
network_interfaces = vserver_client.get_network_interfaces()
|
||||||
|
snapmirror_policies = self._client.get_snapmirror_policies(vserver)
|
||||||
|
|
||||||
interfaces_on_vlans = []
|
interfaces_on_vlans = []
|
||||||
vlans = []
|
vlans = []
|
||||||
|
@ -430,6 +480,11 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||||
vlan_id = None
|
vlan_id = None
|
||||||
|
|
||||||
def _delete_vserver_without_lock():
|
def _delete_vserver_without_lock():
|
||||||
|
# NOTE(dviroel): always delete all policies before deleting the
|
||||||
|
# vserver
|
||||||
|
for policy in snapmirror_policies:
|
||||||
|
vserver_client.delete_snapmirror_policy(policy)
|
||||||
|
|
||||||
# NOTE(dviroel): Attempt to delete all vserver peering
|
# NOTE(dviroel): Attempt to delete all vserver peering
|
||||||
# created by replication
|
# created by replication
|
||||||
self._delete_vserver_peers(vserver)
|
self._delete_vserver_peers(vserver)
|
||||||
|
@ -437,13 +492,17 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||||
self._client.delete_vserver(vserver,
|
self._client.delete_vserver(vserver,
|
||||||
vserver_client,
|
vserver_client,
|
||||||
security_services=security_services)
|
security_services=security_services)
|
||||||
|
ipspace_deleted = False
|
||||||
if (ipspace_name and ipspace_name not in CLUSTER_IPSPACES
|
if (ipspace_name and ipspace_name not in CLUSTER_IPSPACES
|
||||||
and not self._client.ipspace_has_data_vservers(
|
and not self._client.ipspace_has_data_vservers(
|
||||||
ipspace_name)):
|
ipspace_name)):
|
||||||
self._client.delete_ipspace(ipspace_name)
|
self._client.delete_ipspace(ipspace_name)
|
||||||
|
ipspace_deleted = True
|
||||||
|
|
||||||
self._delete_vserver_vlans(interfaces_on_vlans)
|
if not ipspace_name or ipspace_deleted:
|
||||||
|
# NOTE(dviroel): only delete vlans if they are not being used
|
||||||
|
# by any ipspaces and data vservers.
|
||||||
|
self._delete_vserver_vlans(interfaces_on_vlans)
|
||||||
|
|
||||||
@utils.synchronized('netapp-VLAN-%s' % vlan_id, external=True)
|
@utils.synchronized('netapp-VLAN-%s' % vlan_id, external=True)
|
||||||
def _delete_vserver_with_lock():
|
def _delete_vserver_with_lock():
|
||||||
|
@ -592,8 +651,7 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||||
|
|
||||||
def _get_snapmirrors(self, vserver, peer_vserver):
|
def _get_snapmirrors(self, vserver, peer_vserver):
|
||||||
return self._client.get_snapmirrors(
|
return self._client.get_snapmirrors(
|
||||||
source_vserver=vserver, source_volume=None,
|
source_vserver=vserver, dest_vserver=peer_vserver)
|
||||||
destination_vserver=peer_vserver, destination_volume=None)
|
|
||||||
|
|
||||||
def _get_vservers_from_replicas(self, context, replica_list, new_replica):
|
def _get_vservers_from_replicas(self, context, replica_list, new_replica):
|
||||||
active_replica = self.find_active_replica(replica_list)
|
active_replica = self.find_active_replica(replica_list)
|
||||||
|
@ -706,10 +764,13 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||||
extra_specs = share_types.get_extra_specs_from_share(share)
|
extra_specs = share_types.get_extra_specs_from_share(share)
|
||||||
nfs_config = self._get_nfs_config_provisioning_options(extra_specs)
|
nfs_config = self._get_nfs_config_provisioning_options(extra_specs)
|
||||||
|
|
||||||
|
# Avoid the reuse of 'dp_protection' vservers:
|
||||||
for share_server in share_servers:
|
for share_server in share_servers:
|
||||||
if self._check_reuse_share_server(share_server, nfs_config,
|
if self._check_reuse_share_server(share_server, nfs_config,
|
||||||
share_group=share_group):
|
share_group=share_group):
|
||||||
return share_server
|
return share_server
|
||||||
|
|
||||||
|
# There is no compatible share server to be reused
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
|
@ -720,6 +781,16 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||||
share_server['id']):
|
share_server['id']):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
backend_name = share_utils.extract_host(share_server['host'],
|
||||||
|
level='backend_name')
|
||||||
|
vserver_name, client = self._get_vserver(share_server,
|
||||||
|
backend_name=backend_name)
|
||||||
|
vserver_info = client.get_vserver_info(vserver_name)
|
||||||
|
if (vserver_info.get('operational_state') != 'running'
|
||||||
|
or vserver_info.get('state') != 'running'
|
||||||
|
or vserver_info.get('subtype') != 'default'):
|
||||||
|
return False
|
||||||
|
|
||||||
if self.is_nfs_config_supported:
|
if self.is_nfs_config_supported:
|
||||||
# NOTE(felipe_rodrigues): Do not check that the share nfs_config
|
# NOTE(felipe_rodrigues): Do not check that the share nfs_config
|
||||||
# matches with the group nfs_config, because the API guarantees
|
# matches with the group nfs_config, because the API guarantees
|
||||||
|
@ -799,3 +870,417 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||||
return (super(NetAppCmodeMultiSVMFileStorageLibrary, self).
|
return (super(NetAppCmodeMultiSVMFileStorageLibrary, self).
|
||||||
manage_existing(share, driver_options,
|
manage_existing(share, driver_options,
|
||||||
share_server=share_server))
|
share_server=share_server))
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def share_server_migration_check_compatibility(
|
||||||
|
self, context, source_share_server, dest_host, old_share_network,
|
||||||
|
new_share_network, shares_request_spec):
|
||||||
|
|
||||||
|
not_compatible = {
|
||||||
|
'compatible': False,
|
||||||
|
'writable': None,
|
||||||
|
'nondisruptive': None,
|
||||||
|
'preserve_snapshots': None,
|
||||||
|
'migration_cancel': None,
|
||||||
|
'migration_get_progress': None,
|
||||||
|
'share_network_id': None
|
||||||
|
}
|
||||||
|
|
||||||
|
# We need cluster creds, of course
|
||||||
|
if not self._have_cluster_creds:
|
||||||
|
msg = _("Cluster credentials have not been configured with this "
|
||||||
|
"share driver. Cannot perform server migration operation.")
|
||||||
|
LOG.error(msg)
|
||||||
|
return not_compatible
|
||||||
|
|
||||||
|
# Vserver will spread across aggregates in this implementation
|
||||||
|
if share_utils.extract_host(dest_host, level='pool') is not None:
|
||||||
|
msg = _("Cannot perform server migration to a specific pool. "
|
||||||
|
"Please choose a destination host 'host@backend' as "
|
||||||
|
"destination.")
|
||||||
|
LOG.error(msg)
|
||||||
|
return not_compatible
|
||||||
|
|
||||||
|
src_backend_name = share_utils.extract_host(
|
||||||
|
source_share_server['host'], level='backend_name')
|
||||||
|
src_vserver, src_client = self._get_vserver(
|
||||||
|
source_share_server, backend_name=src_backend_name)
|
||||||
|
dest_backend_name = share_utils.extract_host(dest_host,
|
||||||
|
level='backend_name')
|
||||||
|
# Block migration within the same backend.
|
||||||
|
if src_backend_name == dest_backend_name:
|
||||||
|
msg = _("Cannot perform server migration within the same backend. "
|
||||||
|
"Please choose a destination host different from the "
|
||||||
|
"source.")
|
||||||
|
LOG.error(msg)
|
||||||
|
return not_compatible
|
||||||
|
|
||||||
|
src_cluster_name = src_client.get_cluster_name()
|
||||||
|
# NOTE(dviroel): This call is supposed to made in the destination host
|
||||||
|
dest_cluster_name = self._client.get_cluster_name()
|
||||||
|
# Must be in different clusters too, SVM-DR restriction
|
||||||
|
if src_cluster_name == dest_cluster_name:
|
||||||
|
msg = _("Cannot perform server migration within the same cluster. "
|
||||||
|
"Please choose a destination host that's in a different "
|
||||||
|
"cluster.")
|
||||||
|
LOG.error(msg)
|
||||||
|
return not_compatible
|
||||||
|
|
||||||
|
# Check for SVM DR support
|
||||||
|
# NOTE(dviroel): These clients can only be used for non-tunneling
|
||||||
|
# requests.
|
||||||
|
dst_client = data_motion.get_client_for_backend(dest_backend_name,
|
||||||
|
vserver_name=None)
|
||||||
|
if (not src_client.is_svm_dr_supported()
|
||||||
|
or not dst_client.is_svm_dr_supported()):
|
||||||
|
msg = _("Cannot perform server migration because at leat one of "
|
||||||
|
"the backends doesn't support SVM DR.")
|
||||||
|
LOG.error(msg)
|
||||||
|
return not_compatible
|
||||||
|
|
||||||
|
# Blocking different security services for now
|
||||||
|
if old_share_network['id'] != new_share_network['id']:
|
||||||
|
new_sec_services = new_share_network.get('security_services', [])
|
||||||
|
old_sec_services = old_share_network.get('security_services', [])
|
||||||
|
if new_sec_services or old_sec_services:
|
||||||
|
new_sec_serv_ids = [ss['id'] for ss in new_sec_services]
|
||||||
|
old_sec_serv_ids = [ss['id'] for ss in old_sec_services]
|
||||||
|
if not set(new_sec_serv_ids) == set(old_sec_serv_ids):
|
||||||
|
msg = _("Cannot perform server migration for different "
|
||||||
|
"security services. Please choose a suitable "
|
||||||
|
"share network that matches the source security "
|
||||||
|
"service.")
|
||||||
|
LOG.error(msg)
|
||||||
|
return not_compatible
|
||||||
|
|
||||||
|
pools = self._get_pools()
|
||||||
|
# Check 'netapp_flexvol_encryption' and 'revert_to_snapshot_support'
|
||||||
|
specs_to_validate = ('netapp_flexvol_encryption',
|
||||||
|
'revert_to_snapshot_support')
|
||||||
|
for req_spec in shares_request_spec.get('shares_req_spec', []):
|
||||||
|
extra_specs = req_spec.get('share_type', {}).get('extra_specs', {})
|
||||||
|
for spec in specs_to_validate:
|
||||||
|
if extra_specs.get(spec) and not pools[0][spec]:
|
||||||
|
msg = _("Cannot perform server migration since the "
|
||||||
|
"destination host doesn't support the required "
|
||||||
|
"extra-spec %s.") % spec
|
||||||
|
LOG.error(msg)
|
||||||
|
return not_compatible
|
||||||
|
# TODO(dviroel): disk_type extra-spec
|
||||||
|
|
||||||
|
# Check capacity
|
||||||
|
server_total_size = (shares_request_spec.get('shares_size', 0) +
|
||||||
|
shares_request_spec.get('snapshots_size', 0))
|
||||||
|
# NOTE(dviroel): If the backend has a 'max_over_subscription_ratio'
|
||||||
|
# configured and greater than 1, we'll consider thin provisioning
|
||||||
|
# enable for all shares.
|
||||||
|
thin_provisioning = self.configuration.max_over_subscription_ratio > 1
|
||||||
|
if self.configuration.netapp_server_migration_check_capacity is True:
|
||||||
|
if not self._check_capacity_compatibility(pools, thin_provisioning,
|
||||||
|
server_total_size):
|
||||||
|
msg = _("Cannot perform server migration because destination "
|
||||||
|
"host doesn't have enough free space.")
|
||||||
|
LOG.error(msg)
|
||||||
|
return not_compatible
|
||||||
|
|
||||||
|
compatibility = {
|
||||||
|
'compatible': True,
|
||||||
|
'writable': True,
|
||||||
|
'nondisruptive': False,
|
||||||
|
'preserve_snapshots': True,
|
||||||
|
'share_network_id': new_share_network['id'],
|
||||||
|
'migration_cancel': True,
|
||||||
|
'migration_get_progress': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
return compatibility
|
||||||
|
|
||||||
|
def share_server_migration_start(self, context, source_share_server,
|
||||||
|
dest_share_server, share_intances,
|
||||||
|
snapshot_instances):
|
||||||
|
"""Start share server migration using SVM DR.
|
||||||
|
|
||||||
|
1. Create vserver peering between source and destination
|
||||||
|
2. Create SnapMirror
|
||||||
|
"""
|
||||||
|
src_backend_name = share_utils.extract_host(
|
||||||
|
source_share_server['host'], level='backend_name')
|
||||||
|
src_vserver, src_client = self._get_vserver(
|
||||||
|
share_server=source_share_server, backend_name=src_backend_name)
|
||||||
|
src_cluster = src_client.get_cluster_name()
|
||||||
|
|
||||||
|
dest_backend_name = share_utils.extract_host(
|
||||||
|
dest_share_server['host'], level='backend_name')
|
||||||
|
dest_vserver, dest_client = self._get_vserver(
|
||||||
|
share_server=dest_share_server, backend_name=dest_backend_name)
|
||||||
|
dest_cluster = dest_client.get_cluster_name()
|
||||||
|
|
||||||
|
# 1. Check and create vserver peer if needed
|
||||||
|
if not self._get_vserver_peers(dest_vserver, src_vserver):
|
||||||
|
# Request vserver peer creation from destination to source
|
||||||
|
# NOTE(dviroel): vserver peering rollback is handled by
|
||||||
|
# '_delete_vserver' function.
|
||||||
|
dest_client.create_vserver_peer(
|
||||||
|
dest_vserver, src_vserver,
|
||||||
|
peer_cluster_name=src_cluster)
|
||||||
|
|
||||||
|
# Accepts the vserver peering using active replica host's
|
||||||
|
# client (inter-cluster only)
|
||||||
|
if dest_cluster != src_cluster:
|
||||||
|
src_client.accept_vserver_peer(src_vserver, dest_vserver)
|
||||||
|
|
||||||
|
# 2. Create SnapMirror
|
||||||
|
dm_session = data_motion.DataMotionSession()
|
||||||
|
try:
|
||||||
|
dm_session.create_snapmirror_svm(source_share_server,
|
||||||
|
dest_share_server)
|
||||||
|
except Exception:
|
||||||
|
# NOTE(dviroel): vserver peer delete will be handled on vserver
|
||||||
|
# teardown
|
||||||
|
dm_session.cancel_snapmirror_svm(source_share_server,
|
||||||
|
dest_share_server)
|
||||||
|
msg_args = {
|
||||||
|
'src': source_share_server['id'],
|
||||||
|
'dest': dest_share_server['id'],
|
||||||
|
}
|
||||||
|
msg = _('Could not initialize SnapMirror between %(src)s and '
|
||||||
|
'%(dest)s vservers.') % msg_args
|
||||||
|
raise exception.NetAppException(message=msg)
|
||||||
|
|
||||||
|
msg_args = {
|
||||||
|
'src': source_share_server['id'],
|
||||||
|
'dest': dest_share_server['id'],
|
||||||
|
}
|
||||||
|
msg = _('Starting share server migration from %(src)s to %(dest)s.')
|
||||||
|
LOG.info(msg, msg_args)
|
||||||
|
|
||||||
|
def _get_snapmirror_svm(self, source_share_server, dest_share_server):
|
||||||
|
dm_session = data_motion.DataMotionSession()
|
||||||
|
try:
|
||||||
|
snapmirrors = dm_session.get_snapmirrors_svm(
|
||||||
|
source_share_server, dest_share_server)
|
||||||
|
except netapp_api.NaApiError:
|
||||||
|
msg_args = {
|
||||||
|
'src': source_share_server['id'],
|
||||||
|
'dest': dest_share_server['id']
|
||||||
|
}
|
||||||
|
msg = _("Could not retrieve snapmirrors between source "
|
||||||
|
"%(src)s and destination %(dest)s vServers.") % msg_args
|
||||||
|
LOG.exception(msg)
|
||||||
|
raise exception.NetAppException(message=msg)
|
||||||
|
|
||||||
|
return snapmirrors
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def share_server_migration_continue(self, context, source_share_server,
|
||||||
|
dest_share_server, share_instances,
|
||||||
|
snapshot_instances):
|
||||||
|
"""Continues a share server migration using SVM DR."""
|
||||||
|
snapmirrors = self._get_snapmirror_svm(source_share_server,
|
||||||
|
dest_share_server)
|
||||||
|
if not snapmirrors:
|
||||||
|
msg_args = {
|
||||||
|
'src': source_share_server['id'],
|
||||||
|
'dest': dest_share_server['id']
|
||||||
|
}
|
||||||
|
msg = _("No snapmirror relationship was found between source "
|
||||||
|
"%(src)s and destination %(dest)s vServers.") % msg_args
|
||||||
|
LOG.exception(msg)
|
||||||
|
raise exception.NetAppException(message=msg)
|
||||||
|
|
||||||
|
snapmirror = snapmirrors[0]
|
||||||
|
in_progress_status = ['preparing', 'transferring', 'finalizing']
|
||||||
|
mirror_state = snapmirror.get('mirror-state')
|
||||||
|
status = snapmirror.get('relationship-status')
|
||||||
|
if mirror_state != 'snapmirrored' and status in in_progress_status:
|
||||||
|
LOG.debug("Data transfer still in progress.")
|
||||||
|
return False
|
||||||
|
elif mirror_state == 'snapmirrored' and status == 'idle':
|
||||||
|
LOG.info("Source and destination vServers are now snapmirrored.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
msg = _("Snapmirror is not ready yet. The current mirror state is "
|
||||||
|
"'%(mirror_state)s' and relationship status is '%(status)s'.")
|
||||||
|
msg_args = {
|
||||||
|
'mirror_state': mirror_state,
|
||||||
|
'status': status,
|
||||||
|
}
|
||||||
|
LOG.debug(msg, msg_args)
|
||||||
|
return False
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def share_server_migration_complete(self, context, source_share_server,
|
||||||
|
dest_share_server, share_instances,
|
||||||
|
snapshot_instances, new_network_alloc):
|
||||||
|
"""Completes share server migration using SVM DR.
|
||||||
|
|
||||||
|
1. Do a last SnapMirror update.
|
||||||
|
2. Quiesce, abort and then break the relationship.
|
||||||
|
3. Stop the source vserver
|
||||||
|
4. Configure network interfaces in the destination vserver
|
||||||
|
5. Start the destinarion vserver
|
||||||
|
6. Delete and release the snapmirror
|
||||||
|
7. Build the list of export_locations for each share
|
||||||
|
8. Release all resources from the source share server
|
||||||
|
"""
|
||||||
|
dm_session = data_motion.DataMotionSession()
|
||||||
|
try:
|
||||||
|
# 1. Start an update to try to get a last minute transfer before we
|
||||||
|
# quiesce and break
|
||||||
|
dm_session.update_snapmirror_svm(source_share_server,
|
||||||
|
dest_share_server)
|
||||||
|
except exception.StorageCommunicationException:
|
||||||
|
# Ignore any errors since the current source may be unreachable
|
||||||
|
pass
|
||||||
|
|
||||||
|
src_backend_name = share_utils.extract_host(
|
||||||
|
source_share_server['host'], level='backend_name')
|
||||||
|
src_vserver, src_client = self._get_vserver(
|
||||||
|
share_server=source_share_server, backend_name=src_backend_name)
|
||||||
|
|
||||||
|
dest_backend_name = share_utils.extract_host(
|
||||||
|
dest_share_server['host'], level='backend_name')
|
||||||
|
dest_vserver, dest_client = self._get_vserver(
|
||||||
|
share_server=dest_share_server, backend_name=dest_backend_name)
|
||||||
|
try:
|
||||||
|
# 2. Attempt to quiesce, abort and then break SnapMirror
|
||||||
|
dm_session.quiesce_and_break_snapmirror_svm(source_share_server,
|
||||||
|
dest_share_server)
|
||||||
|
# NOTE(dviroel): Lets wait until the destination vserver be
|
||||||
|
# promoted to 'default' and state 'running', before starting
|
||||||
|
# shutting down the source
|
||||||
|
dm_session.wait_for_vserver_state(
|
||||||
|
dest_vserver, dest_client, subtype='default',
|
||||||
|
state='running', operational_state='stopped',
|
||||||
|
timeout=(self.configuration.
|
||||||
|
netapp_server_migration_state_change_timeout))
|
||||||
|
|
||||||
|
# 3. Stop source vserver
|
||||||
|
src_client.stop_vserver(src_vserver)
|
||||||
|
|
||||||
|
# 4. Setup network configuration
|
||||||
|
ipspace_name = dest_client.get_vserver_ipspace(dest_vserver)
|
||||||
|
|
||||||
|
# NOTE(dviroel): Security service and NFS configuration should be
|
||||||
|
# handled by SVM DR, so no changes will be made here.
|
||||||
|
vlan = new_network_alloc['segmentation_id']
|
||||||
|
|
||||||
|
@utils.synchronized('netapp-VLAN-%s' % vlan, external=True)
|
||||||
|
def setup_network_for_destination_vserver():
|
||||||
|
self._setup_network_for_vserver(
|
||||||
|
dest_vserver, dest_client, new_network_alloc, ipspace_name,
|
||||||
|
enable_nfs=False,
|
||||||
|
security_services=None)
|
||||||
|
|
||||||
|
setup_network_for_destination_vserver()
|
||||||
|
|
||||||
|
# 5. Start the destination.
|
||||||
|
dest_client.start_vserver(dest_vserver)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# Try to recover source vserver
|
||||||
|
try:
|
||||||
|
src_client.start_vserver(src_vserver)
|
||||||
|
except Exception:
|
||||||
|
LOG.warning("Unable to recover source share server after a "
|
||||||
|
"migration failure.")
|
||||||
|
# Destroy any snapmirror and make destination vserver to have its
|
||||||
|
# subtype set to 'default'
|
||||||
|
dm_session.cancel_snapmirror_svm(source_share_server,
|
||||||
|
dest_share_server)
|
||||||
|
# Rollback resources transferred to the destination
|
||||||
|
for instance in share_instances:
|
||||||
|
self._delete_share(instance, dest_client, remove_export=False)
|
||||||
|
|
||||||
|
msg_args = {
|
||||||
|
'src': source_share_server['id'],
|
||||||
|
'dest': dest_share_server['id'],
|
||||||
|
}
|
||||||
|
msg = _('Could not complete the migration between %(src)s and '
|
||||||
|
'%(dest)s vservers.') % msg_args
|
||||||
|
raise exception.NetAppException(message=msg)
|
||||||
|
|
||||||
|
# 6. Delete/release snapmirror
|
||||||
|
dm_session.delete_snapmirror_svm(source_share_server,
|
||||||
|
dest_share_server)
|
||||||
|
|
||||||
|
# 7. Build a dict with shares/snapshot location updates
|
||||||
|
# NOTE(dviroel): For SVM DR, the share names aren't modified, only the
|
||||||
|
# export_locations are updated due to network changes.
|
||||||
|
share_updates = {}
|
||||||
|
for instance in share_instances:
|
||||||
|
# Get the volume to find out the associated aggregate
|
||||||
|
try:
|
||||||
|
share_name = self._get_backend_share_name(instance['id'])
|
||||||
|
volume = dest_client.get_volume(share_name)
|
||||||
|
except Exception:
|
||||||
|
msg_args = {
|
||||||
|
'src': source_share_server['id'],
|
||||||
|
'dest': dest_share_server['id'],
|
||||||
|
}
|
||||||
|
msg = _('Could not complete the migration between %(src)s and '
|
||||||
|
'%(dest)s vservers. One of the shares was not found '
|
||||||
|
'in the destination vserver.') % msg_args
|
||||||
|
raise exception.NetAppException(message=msg)
|
||||||
|
|
||||||
|
export_locations = self._create_export(
|
||||||
|
instance, dest_share_server, dest_vserver, dest_client,
|
||||||
|
clear_current_export_policy=False,
|
||||||
|
ensure_share_already_exists=True)
|
||||||
|
|
||||||
|
share_updates.update({
|
||||||
|
instance['id']: {
|
||||||
|
'export_locations': export_locations,
|
||||||
|
'pool_name': volume.get('aggregate')
|
||||||
|
}})
|
||||||
|
|
||||||
|
# NOTE(dviroel): Nothing to update in snapshot instances since the
|
||||||
|
# provider location didn't change.
|
||||||
|
|
||||||
|
# 8. Release source share resources
|
||||||
|
for instance in share_instances:
|
||||||
|
self._delete_share(instance, src_client, remove_export=True)
|
||||||
|
|
||||||
|
# NOTE(dviroel): source share server deletion must be triggered by
|
||||||
|
# the manager after finishing the migration
|
||||||
|
LOG.info('Share server migration completed.')
|
||||||
|
return {
|
||||||
|
'share_updates': share_updates,
|
||||||
|
}
|
||||||
|
|
||||||
|
def share_server_migration_cancel(self, context, source_share_server,
|
||||||
|
dest_share_server, shares, snapshots):
|
||||||
|
"""Cancel a share server migration that is using SVM DR."""
|
||||||
|
|
||||||
|
dm_session = data_motion.DataMotionSession()
|
||||||
|
dest_backend_name = share_utils.extract_host(dest_share_server['host'],
|
||||||
|
level='backend_name')
|
||||||
|
dest_vserver, dest_client = self._get_vserver(
|
||||||
|
share_server=dest_share_server, backend_name=dest_backend_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
snapmirrors = self._get_snapmirror_svm(source_share_server,
|
||||||
|
dest_share_server)
|
||||||
|
if snapmirrors:
|
||||||
|
dm_session.cancel_snapmirror_svm(source_share_server,
|
||||||
|
dest_share_server)
|
||||||
|
# Do a simple volume cleanup in the destination vserver
|
||||||
|
for instance in shares:
|
||||||
|
self._delete_share(instance, dest_client, remove_export=False)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
msg_args = {
|
||||||
|
'src': source_share_server['id'],
|
||||||
|
'dest': dest_share_server['id'],
|
||||||
|
}
|
||||||
|
msg = _('Unable to cancel SnapMirror relationship between %(src)s '
|
||||||
|
'and %(dest)s vservers.') % msg_args
|
||||||
|
raise exception.NetAppException(message=msg)
|
||||||
|
|
||||||
|
LOG.info('Share server migration was cancelled.')
|
||||||
|
|
||||||
|
def share_server_migration_get_progress(self, context, src_share_server,
|
||||||
|
dest_share_server, shares,
|
||||||
|
snapshots):
|
||||||
|
# TODO(dviroel): get snapmirror info to infer the progress
|
||||||
|
return {'total_progress': 0}
|
||||||
|
|
|
@ -29,9 +29,15 @@ class NetAppCmodeCIFSHelper(base.NetAppBaseHelper):
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def create_share(self, share, share_name,
|
def create_share(self, share, share_name,
|
||||||
clear_current_export_policy=True):
|
clear_current_export_policy=True,
|
||||||
|
ensure_share_already_exists=False):
|
||||||
"""Creates CIFS share on Data ONTAP Vserver."""
|
"""Creates CIFS share on Data ONTAP Vserver."""
|
||||||
self._client.create_cifs_share(share_name)
|
if not ensure_share_already_exists:
|
||||||
|
self._client.create_cifs_share(share_name)
|
||||||
|
elif not self._client.cifs_share_exists(share_name):
|
||||||
|
msg = _("The expected CIFS share %(share_name)s was not found.")
|
||||||
|
msg_args = {'share_name': share_name}
|
||||||
|
raise exception.NetAppException(msg % msg_args)
|
||||||
if clear_current_export_policy:
|
if clear_current_export_policy:
|
||||||
self._client.remove_cifs_share_access(share_name, 'Everyone')
|
self._client.remove_cifs_share_access(share_name, 'Everyone')
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,12 @@ class NetAppCmodeNFSHelper(base.NetAppBaseHelper):
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def create_share(self, share, share_name,
|
def create_share(self, share, share_name,
|
||||||
clear_current_export_policy=True):
|
clear_current_export_policy=True,
|
||||||
|
ensure_share_already_exists=False):
|
||||||
"""Creates NFS share."""
|
"""Creates NFS share."""
|
||||||
|
# TODO(dviroel): Ensure that nfs share already exists if
|
||||||
|
# ensure_share_already_exists is True. Although, no conflicts are
|
||||||
|
# expected here since there is no create share operation being made.
|
||||||
if clear_current_export_policy:
|
if clear_current_export_policy:
|
||||||
self._client.clear_nfs_export_policy_for_volume(share_name)
|
self._client.clear_nfs_export_policy_for_volume(share_name)
|
||||||
self._ensure_export_policy(share, share_name)
|
self._ensure_export_policy(share, share_name)
|
||||||
|
|
|
@ -111,7 +111,11 @@ netapp_provisioning_opts = [
|
||||||
"nothing will be changed during startup. This will not "
|
"nothing will be changed during startup. This will not "
|
||||||
"affect new shares, which will have their snapshot "
|
"affect new shares, which will have their snapshot "
|
||||||
"directory always visible, unless toggled by the share "
|
"directory always visible, unless toggled by the share "
|
||||||
"type extra spec 'netapp:hide_snapdir'."), ]
|
"type extra spec 'netapp:hide_snapdir'."),
|
||||||
|
cfg.StrOpt('netapp_snapmirror_policy_name_svm_template',
|
||||||
|
help='NetApp SnapMirror policy name template for Storage '
|
||||||
|
'Virtual Machines (Vservers).',
|
||||||
|
default='snapmirror_policy_%(share_server_id)s'), ]
|
||||||
|
|
||||||
netapp_cluster_opts = [
|
netapp_cluster_opts = [
|
||||||
cfg.StrOpt('netapp_vserver',
|
cfg.StrOpt('netapp_vserver',
|
||||||
|
@ -145,6 +149,11 @@ netapp_data_motion_opts = [
|
||||||
help='The maximum time in seconds to wait for existing '
|
help='The maximum time in seconds to wait for existing '
|
||||||
'snapmirror transfers to complete before aborting when '
|
'snapmirror transfers to complete before aborting when '
|
||||||
'promoting a replica.'),
|
'promoting a replica.'),
|
||||||
|
cfg.IntOpt('netapp_snapmirror_release_timeout',
|
||||||
|
min=0,
|
||||||
|
default=3600, # One Hour
|
||||||
|
help='The maximum time in seconds to wait for a snapmirror '
|
||||||
|
'release when breaking snapmirror relationships.'),
|
||||||
cfg.IntOpt('netapp_volume_move_cutover_timeout',
|
cfg.IntOpt('netapp_volume_move_cutover_timeout',
|
||||||
min=0,
|
min=0,
|
||||||
default=3600, # One Hour,
|
default=3600, # One Hour,
|
||||||
|
@ -162,7 +171,27 @@ netapp_data_motion_opts = [
|
||||||
default=3600, # One Hour,
|
default=3600, # One Hour,
|
||||||
help='The maximum time in seconds that migration cancel '
|
help='The maximum time in seconds that migration cancel '
|
||||||
'waits for all migration operations be completely '
|
'waits for all migration operations be completely '
|
||||||
'aborted.'), ]
|
'aborted.'),
|
||||||
|
cfg.IntOpt('netapp_server_migration_state_change_timeout',
|
||||||
|
min=0,
|
||||||
|
default=3600, # One hour,
|
||||||
|
help='The maximum time in seconds that a share server '
|
||||||
|
'migration waits for a vserver to change its internal '
|
||||||
|
'states.'),
|
||||||
|
cfg.BoolOpt('netapp_server_migration_check_capacity',
|
||||||
|
default=True,
|
||||||
|
help='Specify if the capacity check must be made by the '
|
||||||
|
'driver while performing a share server migration. '
|
||||||
|
'If enabled, the driver will validate if the destination '
|
||||||
|
'backend can hold all shares and snapshots capacities '
|
||||||
|
'from the source share server.'),
|
||||||
|
cfg.IntOpt('netapp_server_migration_state_change_timeout',
|
||||||
|
min=0,
|
||||||
|
default=3600, # One hour,
|
||||||
|
help='The maximum time in seconds that a share server '
|
||||||
|
'migration waits for a vserver to change its internal '
|
||||||
|
'states.'),
|
||||||
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
CONF.register_opts(netapp_proxy_opts)
|
CONF.register_opts(netapp_proxy_opts)
|
||||||
|
|
|
@ -74,6 +74,17 @@ DELETED_EXPORT_POLICIES = {
|
||||||
}
|
}
|
||||||
QOS_POLICY_GROUP_NAME = 'fake_qos_policy_group_name'
|
QOS_POLICY_GROUP_NAME = 'fake_qos_policy_group_name'
|
||||||
QOS_MAX_THROUGHPUT = '5000B/s'
|
QOS_MAX_THROUGHPUT = '5000B/s'
|
||||||
|
VSERVER_TYPE_DEFAULT = 'default'
|
||||||
|
VSERVER_TYPE_DP_DEST = 'dp_destination'
|
||||||
|
VSERVER_OP_STATE_RUNNING = 'running'
|
||||||
|
VSERVER_STATE = 'running'
|
||||||
|
VSERVER_INFO = {
|
||||||
|
'name': VSERVER_NAME,
|
||||||
|
'subtype': VSERVER_TYPE_DEFAULT,
|
||||||
|
'operational_state': VSERVER_OP_STATE_RUNNING,
|
||||||
|
'state': VSERVER_STATE,
|
||||||
|
}
|
||||||
|
SNAPMIRROR_POLICY_NAME = 'fake_snapmirror_policy'
|
||||||
|
|
||||||
USER_NAME = 'fake_user'
|
USER_NAME = 'fake_user'
|
||||||
|
|
||||||
|
@ -198,6 +209,20 @@ VSERVER_GET_ITER_RESPONSE = etree.XML("""
|
||||||
</results>
|
</results>
|
||||||
""" % {'fake_vserver': VSERVER_NAME})
|
""" % {'fake_vserver': VSERVER_NAME})
|
||||||
|
|
||||||
|
VSERVER_GET_ITER_RESPONSE_INFO = etree.XML("""
|
||||||
|
<results status="passed">
|
||||||
|
<attributes-list>
|
||||||
|
<vserver-info>
|
||||||
|
<operational-state>%(operational_state)s</operational-state>
|
||||||
|
<state>%(state)s</state>
|
||||||
|
<vserver-name>%(name)s</vserver-name>
|
||||||
|
<vserver-subtype>%(subtype)s</vserver-subtype>
|
||||||
|
</vserver-info>
|
||||||
|
</attributes-list>
|
||||||
|
<num-records>1</num-records>
|
||||||
|
</results>
|
||||||
|
""" % VSERVER_INFO)
|
||||||
|
|
||||||
VSERVER_GET_ROOT_VOLUME_NAME_RESPONSE = etree.XML("""
|
VSERVER_GET_ROOT_VOLUME_NAME_RESPONSE = etree.XML("""
|
||||||
<results status="passed">
|
<results status="passed">
|
||||||
<attributes-list>
|
<attributes-list>
|
||||||
|
@ -1702,6 +1727,18 @@ CIFS_SHARE_ACCESS_CONTROL_GET_ITER = etree.XML("""
|
||||||
</results>
|
</results>
|
||||||
""" % {'volume': SHARE_NAME})
|
""" % {'volume': SHARE_NAME})
|
||||||
|
|
||||||
|
CIFS_SHARE_GET_ITER_RESPONSE = etree.XML("""
|
||||||
|
<results status="passed">
|
||||||
|
<attributes-list>
|
||||||
|
<cifs-share>
|
||||||
|
<share-name>%(share_name)s</share-name>
|
||||||
|
<vserver>fake_vserver</vserver>
|
||||||
|
</cifs-share>
|
||||||
|
</attributes-list>
|
||||||
|
<num-records>1</num-records>
|
||||||
|
</results>
|
||||||
|
""" % {'share_name': SHARE_NAME})
|
||||||
|
|
||||||
NFS_EXPORT_RULES = ('10.10.10.10', '10.10.10.20')
|
NFS_EXPORT_RULES = ('10.10.10.10', '10.10.10.20')
|
||||||
|
|
||||||
NFS_EXPORTFS_LIST_RULES_2_NO_RULES_RESPONSE = etree.XML("""
|
NFS_EXPORTFS_LIST_RULES_2_NO_RULES_RESPONSE = etree.XML("""
|
||||||
|
@ -2373,6 +2410,7 @@ SNAPMIRROR_GET_ITER_FILTERED_RESPONSE = etree.XML("""
|
||||||
<destination-volume>fake_destination_volume</destination-volume>
|
<destination-volume>fake_destination_volume</destination-volume>
|
||||||
<is-healthy>true</is-healthy>
|
<is-healthy>true</is-healthy>
|
||||||
<mirror-state>snapmirrored</mirror-state>
|
<mirror-state>snapmirrored</mirror-state>
|
||||||
|
<relationship-status>idle</relationship-status>
|
||||||
<schedule>daily</schedule>
|
<schedule>daily</schedule>
|
||||||
<source-vserver>fake_source_vserver</source-vserver>
|
<source-vserver>fake_source_vserver</source-vserver>
|
||||||
<source-volume>fake_source_volume</source-volume>
|
<source-volume>fake_source_volume</source-volume>
|
||||||
|
@ -2382,6 +2420,35 @@ SNAPMIRROR_GET_ITER_FILTERED_RESPONSE = etree.XML("""
|
||||||
</results>
|
</results>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
SNAPMIRROR_GET_ITER_FILTERED_RESPONSE_2 = etree.XML("""
|
||||||
|
<results status="passed">
|
||||||
|
<attributes-list>
|
||||||
|
<snapmirror-info>
|
||||||
|
<source-vserver>fake_source_vserver</source-vserver>
|
||||||
|
<destination-vserver>fake_destination_vserver</destination-vserver>
|
||||||
|
<mirror-state>snapmirrored</mirror-state>
|
||||||
|
<relationship-status>idle</relationship-status>
|
||||||
|
</snapmirror-info>
|
||||||
|
</attributes-list>
|
||||||
|
<num-records>1</num-records>
|
||||||
|
</results>
|
||||||
|
""")
|
||||||
|
|
||||||
|
SNAPMIRROR_GET_DESTINATIONS_ITER_FILTERED_RESPONSE = etree.XML("""
|
||||||
|
<results status="passed">
|
||||||
|
<attributes-list>
|
||||||
|
<snapmirror-destination-info>
|
||||||
|
<destination-location>fake_destination_vserver:</destination-location>
|
||||||
|
<destination-vserver>fake_destination_vserver</destination-vserver>
|
||||||
|
<relationship-id>fake_relationship_id</relationship-id>
|
||||||
|
<source-location>fake_source_vserver:</source-location>
|
||||||
|
<source-vserver>fake_source_vserver</source-vserver>
|
||||||
|
</snapmirror-destination-info>
|
||||||
|
</attributes-list>
|
||||||
|
<num-records>1</num-records>
|
||||||
|
</results>
|
||||||
|
""")
|
||||||
|
|
||||||
SNAPMIRROR_INITIALIZE_RESULT = etree.XML("""
|
SNAPMIRROR_INITIALIZE_RESULT = etree.XML("""
|
||||||
<results status="passed">
|
<results status="passed">
|
||||||
<result-status>succeeded</result-status>
|
<result-status>succeeded</result-status>
|
||||||
|
@ -2605,6 +2672,20 @@ QOS_POLICY_GROUP_GET_ITER_RESPONSE = etree.XML("""
|
||||||
'max_throughput': QOS_MAX_THROUGHPUT,
|
'max_throughput': QOS_MAX_THROUGHPUT,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
SNAPMIRROR_POLICY_GET_ITER_RESPONSE = etree.XML("""
|
||||||
|
<results status="passed">
|
||||||
|
<attributes-list>
|
||||||
|
<snapmirror-policy-info>
|
||||||
|
<policy-name>%(policy_name)s</policy-name>
|
||||||
|
<vserver-name>%(vserver_name)s</vserver-name>
|
||||||
|
</snapmirror-policy-info>
|
||||||
|
</attributes-list>
|
||||||
|
<num-records>1</num-records>
|
||||||
|
</results>""" % {
|
||||||
|
'policy_name': SNAPMIRROR_POLICY_NAME,
|
||||||
|
'vserver_name': VSERVER_NAME,
|
||||||
|
})
|
||||||
|
|
||||||
FAKE_VOL_XML = """<volume-info>
|
FAKE_VOL_XML = """<volume-info>
|
||||||
<name>open123</name>
|
<name>open123</name>
|
||||||
<state>online</state>
|
<state>online</state>
|
||||||
|
|
|
@ -472,6 +472,31 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
mock.call('vserver-create', vserver_create_args),
|
mock.call('vserver-create', vserver_create_args),
|
||||||
mock.call('vserver-modify', vserver_modify_args)])
|
mock.call('vserver-modify', vserver_modify_args)])
|
||||||
|
|
||||||
|
def test_create_vserver_dp_destination(self):
|
||||||
|
|
||||||
|
self.client.features.add_feature('IPSPACES')
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
vserver_create_args = {
|
||||||
|
'vserver-name': fake.VSERVER_NAME,
|
||||||
|
'ipspace': fake.IPSPACE_NAME,
|
||||||
|
'vserver-subtype': fake.VSERVER_TYPE_DP_DEST,
|
||||||
|
}
|
||||||
|
vserver_modify_args = {
|
||||||
|
'aggr-list': [{'aggr-name': aggr_name} for aggr_name
|
||||||
|
in fake.SHARE_AGGREGATE_NAMES],
|
||||||
|
'vserver-name': fake.VSERVER_NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.create_vserver_dp_destination(
|
||||||
|
fake.VSERVER_NAME,
|
||||||
|
fake.SHARE_AGGREGATE_NAMES,
|
||||||
|
fake.IPSPACE_NAME)
|
||||||
|
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('vserver-create', vserver_create_args),
|
||||||
|
mock.call('vserver-modify', vserver_modify_args)])
|
||||||
|
|
||||||
def test_create_vserver_ipspaces_not_supported(self):
|
def test_create_vserver_ipspaces_not_supported(self):
|
||||||
|
|
||||||
self.assertRaises(exception.NetAppException,
|
self.assertRaises(exception.NetAppException,
|
||||||
|
@ -680,8 +705,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
def test_delete_vserver_no_volumes(self):
|
def test_delete_vserver_no_volumes(self):
|
||||||
|
|
||||||
self.mock_object(self.client,
|
self.mock_object(self.client,
|
||||||
'vserver_exists',
|
'get_vserver_info',
|
||||||
mock.Mock(return_value=True))
|
mock.Mock(return_value=fake.VSERVER_INFO))
|
||||||
self.mock_object(self.client,
|
self.mock_object(self.client,
|
||||||
'get_vserver_root_volume_name',
|
'get_vserver_root_volume_name',
|
||||||
mock.Mock(return_value=fake.ROOT_VOLUME_NAME))
|
mock.Mock(return_value=fake.ROOT_VOLUME_NAME))
|
||||||
|
@ -707,8 +732,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
def test_delete_vserver_one_volume(self):
|
def test_delete_vserver_one_volume(self):
|
||||||
|
|
||||||
self.mock_object(self.client,
|
self.mock_object(self.client,
|
||||||
'vserver_exists',
|
'get_vserver_info',
|
||||||
mock.Mock(return_value=True))
|
mock.Mock(return_value=fake.VSERVER_INFO))
|
||||||
self.mock_object(self.client,
|
self.mock_object(self.client,
|
||||||
'get_vserver_root_volume_name',
|
'get_vserver_root_volume_name',
|
||||||
mock.Mock(return_value=fake.ROOT_VOLUME_NAME))
|
mock.Mock(return_value=fake.ROOT_VOLUME_NAME))
|
||||||
|
@ -734,8 +759,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
def test_delete_vserver_one_volume_already_offline(self):
|
def test_delete_vserver_one_volume_already_offline(self):
|
||||||
|
|
||||||
self.mock_object(self.client,
|
self.mock_object(self.client,
|
||||||
'vserver_exists',
|
'get_vserver_info',
|
||||||
mock.Mock(return_value=True))
|
mock.Mock(return_value=fake.VSERVER_INFO))
|
||||||
self.mock_object(self.client,
|
self.mock_object(self.client,
|
||||||
'get_vserver_root_volume_name',
|
'get_vserver_root_volume_name',
|
||||||
mock.Mock(return_value=fake.ROOT_VOLUME_NAME))
|
mock.Mock(return_value=fake.ROOT_VOLUME_NAME))
|
||||||
|
@ -765,8 +790,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
def test_delete_vserver_one_volume_api_error(self):
|
def test_delete_vserver_one_volume_api_error(self):
|
||||||
|
|
||||||
self.mock_object(self.client,
|
self.mock_object(self.client,
|
||||||
'vserver_exists',
|
'get_vserver_info',
|
||||||
mock.Mock(return_value=True))
|
mock.Mock(return_value=fake.VSERVER_INFO))
|
||||||
self.mock_object(self.client,
|
self.mock_object(self.client,
|
||||||
'get_vserver_root_volume_name',
|
'get_vserver_root_volume_name',
|
||||||
mock.Mock(return_value=fake.ROOT_VOLUME_NAME))
|
mock.Mock(return_value=fake.ROOT_VOLUME_NAME))
|
||||||
|
@ -787,8 +812,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
def test_delete_vserver_multiple_volumes(self):
|
def test_delete_vserver_multiple_volumes(self):
|
||||||
|
|
||||||
self.mock_object(self.client,
|
self.mock_object(self.client,
|
||||||
'vserver_exists',
|
'get_vserver_info',
|
||||||
mock.Mock(return_value=True))
|
mock.Mock(return_value=fake.VSERVER_INFO))
|
||||||
self.mock_object(self.client,
|
self.mock_object(self.client,
|
||||||
'get_vserver_root_volume_name',
|
'get_vserver_root_volume_name',
|
||||||
mock.Mock(return_value=fake.ROOT_VOLUME_NAME))
|
mock.Mock(return_value=fake.ROOT_VOLUME_NAME))
|
||||||
|
@ -804,8 +829,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
def test_delete_vserver_not_found(self):
|
def test_delete_vserver_not_found(self):
|
||||||
|
|
||||||
self.mock_object(self.client,
|
self.mock_object(self.client,
|
||||||
'vserver_exists',
|
'get_vserver_info',
|
||||||
mock.Mock(return_value=False))
|
mock.Mock(return_value=None))
|
||||||
|
|
||||||
self.client.delete_vserver(fake.VSERVER_NAME,
|
self.client.delete_vserver(fake.VSERVER_NAME,
|
||||||
self.vserver_client)
|
self.vserver_client)
|
||||||
|
@ -5771,7 +5796,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
def test_create_snapmirror(self, schedule, policy):
|
def test_create_snapmirror(self, schedule, policy):
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
self.client.create_snapmirror(
|
self.client.create_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
|
||||||
schedule=schedule, policy=policy)
|
schedule=schedule, policy=policy)
|
||||||
|
@ -5795,7 +5820,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
code=netapp_api.ERELATION_EXISTS))
|
code=netapp_api.ERELATION_EXISTS))
|
||||||
self.mock_object(self.client, 'send_request', mock_send_req)
|
self.mock_object(self.client, 'send_request', mock_send_req)
|
||||||
|
|
||||||
self.client.create_snapmirror(
|
self.client.create_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
|
|
||||||
|
@ -5814,11 +5839,29 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
code=0))
|
code=0))
|
||||||
self.mock_object(self.client, 'send_request', mock_send_req)
|
self.mock_object(self.client, 'send_request', mock_send_req)
|
||||||
|
|
||||||
self.assertRaises(netapp_api.NaApiError, self.client.create_snapmirror,
|
self.assertRaises(netapp_api.NaApiError,
|
||||||
|
self.client.create_snapmirror_vol,
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
self.assertTrue(self.client.send_request.called)
|
self.assertTrue(self.client.send_request.called)
|
||||||
|
|
||||||
|
def test_create_snapmirror_svm(self):
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.create_snapmirror_svm(fake.SM_SOURCE_VSERVER,
|
||||||
|
fake.SM_DEST_VSERVER,
|
||||||
|
max_transfer_rate='fake_xfer_rate')
|
||||||
|
|
||||||
|
snapmirror_create_args = {
|
||||||
|
'source-vserver': fake.SM_SOURCE_VSERVER,
|
||||||
|
'destination-vserver': fake.SM_DEST_VSERVER,
|
||||||
|
'relationship-type': 'data_protection',
|
||||||
|
'identity-preserve': 'true',
|
||||||
|
'max-transfer-rate': 'fake_xfer_rate'
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('snapmirror-create', snapmirror_create_args)])
|
||||||
|
|
||||||
@ddt.data(
|
@ddt.data(
|
||||||
{
|
{
|
||||||
'source_snapshot': 'fake_snapshot',
|
'source_snapshot': 'fake_snapshot',
|
||||||
|
@ -5837,7 +5880,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
'send_request',
|
'send_request',
|
||||||
mock.Mock(return_value=api_response))
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
result = self.client.initialize_snapmirror(
|
result = self.client.initialize_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
|
||||||
source_snapshot=source_snapshot,
|
source_snapshot=source_snapshot,
|
||||||
|
@ -5865,12 +5908,38 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
}
|
}
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_initialize_snapmirror_svm(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(fake.SNAPMIRROR_INITIALIZE_RESULT)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
result = self.client.initialize_snapmirror_svm(fake.SM_SOURCE_VSERVER,
|
||||||
|
fake.SM_DEST_VSERVER)
|
||||||
|
|
||||||
|
snapmirror_initialize_args = {
|
||||||
|
'source-location': fake.SM_SOURCE_VSERVER + ':',
|
||||||
|
'destination-location': fake.SM_DEST_VSERVER + ':',
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('snapmirror-initialize', snapmirror_initialize_args)])
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'operation-id': None,
|
||||||
|
'status': 'succeeded',
|
||||||
|
'jobid': None,
|
||||||
|
'error-code': None,
|
||||||
|
'error-message': None
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
@ddt.data(True, False)
|
@ddt.data(True, False)
|
||||||
def test_release_snapmirror(self, relationship_info_only):
|
def test_release_snapmirror(self, relationship_info_only):
|
||||||
|
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
self.client.release_snapmirror(
|
self.client.release_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
|
||||||
relationship_info_only=relationship_info_only)
|
relationship_info_only=relationship_info_only)
|
||||||
|
@ -5887,14 +5956,35 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.client.send_request.assert_has_calls([
|
self.client.send_request.assert_has_calls([
|
||||||
mock.call('snapmirror-release-iter', snapmirror_release_args)])
|
mock.call('snapmirror-release-iter', snapmirror_release_args,
|
||||||
|
enable_tunneling=True)])
|
||||||
|
|
||||||
|
def test_release_snapmirror_svm(self):
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.release_snapmirror_svm(
|
||||||
|
fake.SM_SOURCE_VSERVER, fake.SM_DEST_VSERVER)
|
||||||
|
|
||||||
|
snapmirror_release_args = {
|
||||||
|
'query': {
|
||||||
|
'snapmirror-destination-info': {
|
||||||
|
'source-location': fake.SM_SOURCE_VSERVER + ':',
|
||||||
|
'destination-location': fake.SM_DEST_VSERVER + ':',
|
||||||
|
'relationship-info-only': 'false'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('snapmirror-release-iter', snapmirror_release_args,
|
||||||
|
enable_tunneling=False)])
|
||||||
|
|
||||||
def test_quiesce_snapmirror(self):
|
def test_quiesce_snapmirror(self):
|
||||||
|
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
self.client.quiesce_snapmirror(
|
self.client.quiesce_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
|
|
||||||
|
@ -5907,12 +5997,26 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
self.client.send_request.assert_has_calls([
|
self.client.send_request.assert_has_calls([
|
||||||
mock.call('snapmirror-quiesce', snapmirror_quiesce_args)])
|
mock.call('snapmirror-quiesce', snapmirror_quiesce_args)])
|
||||||
|
|
||||||
|
def test_quiesce_snapmirror_svm(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.quiesce_snapmirror_svm(
|
||||||
|
fake.SM_SOURCE_VSERVER, fake.SM_DEST_VSERVER)
|
||||||
|
|
||||||
|
snapmirror_quiesce_args = {
|
||||||
|
'source-location': fake.SM_SOURCE_VSERVER + ':',
|
||||||
|
'destination-location': fake.SM_DEST_VSERVER + ':',
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('snapmirror-quiesce', snapmirror_quiesce_args)])
|
||||||
|
|
||||||
@ddt.data(True, False)
|
@ddt.data(True, False)
|
||||||
def test_abort_snapmirror(self, clear_checkpoint):
|
def test_abort_snapmirror(self, clear_checkpoint):
|
||||||
|
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
self.client.abort_snapmirror(
|
self.client.abort_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
|
||||||
clear_checkpoint=clear_checkpoint)
|
clear_checkpoint=clear_checkpoint)
|
||||||
|
@ -5927,12 +6031,27 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
self.client.send_request.assert_has_calls([
|
self.client.send_request.assert_has_calls([
|
||||||
mock.call('snapmirror-abort', snapmirror_abort_args)])
|
mock.call('snapmirror-abort', snapmirror_abort_args)])
|
||||||
|
|
||||||
|
def test_abort_snapmirror_svm(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.abort_snapmirror_svm(
|
||||||
|
fake.SM_SOURCE_VSERVER, fake.SM_DEST_VSERVER)
|
||||||
|
|
||||||
|
snapmirror_abort_args = {
|
||||||
|
'source-location': fake.SM_SOURCE_VSERVER + ':',
|
||||||
|
'destination-location': fake.SM_DEST_VSERVER + ':',
|
||||||
|
'clear-checkpoint': 'false'
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('snapmirror-abort', snapmirror_abort_args)])
|
||||||
|
|
||||||
def test_abort_snapmirror_no_transfer_in_progress(self):
|
def test_abort_snapmirror_no_transfer_in_progress(self):
|
||||||
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
|
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
|
||||||
code=netapp_api.ENOTRANSFER_IN_PROGRESS))
|
code=netapp_api.ENOTRANSFER_IN_PROGRESS))
|
||||||
self.mock_object(self.client, 'send_request', mock_send_req)
|
self.mock_object(self.client, 'send_request', mock_send_req)
|
||||||
|
|
||||||
self.client.abort_snapmirror(
|
self.client.abort_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
|
|
||||||
|
@ -5950,7 +6069,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(code=0))
|
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(code=0))
|
||||||
self.mock_object(self.client, 'send_request', mock_send_req)
|
self.mock_object(self.client, 'send_request', mock_send_req)
|
||||||
|
|
||||||
self.assertRaises(netapp_api.NaApiError, self.client.abort_snapmirror,
|
self.assertRaises(netapp_api.NaApiError,
|
||||||
|
self.client.abort_snapmirror_vol,
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
|
|
||||||
|
@ -5958,7 +6078,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
|
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
self.client.break_snapmirror(
|
self.client.break_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
|
|
||||||
|
@ -5971,6 +6091,20 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
self.client.send_request.assert_has_calls([
|
self.client.send_request.assert_has_calls([
|
||||||
mock.call('snapmirror-break', snapmirror_break_args)])
|
mock.call('snapmirror-break', snapmirror_break_args)])
|
||||||
|
|
||||||
|
def test_break_snapmirror_svm(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.break_snapmirror_svm(
|
||||||
|
fake.SM_SOURCE_VSERVER, fake.SM_DEST_VSERVER)
|
||||||
|
|
||||||
|
snapmirror_break_args = {
|
||||||
|
'source-location': fake.SM_SOURCE_VSERVER + ':',
|
||||||
|
'destination-location': fake.SM_DEST_VSERVER + ':',
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('snapmirror-break', snapmirror_break_args)])
|
||||||
|
|
||||||
@ddt.data(
|
@ddt.data(
|
||||||
{
|
{
|
||||||
'schedule': 'fake_schedule',
|
'schedule': 'fake_schedule',
|
||||||
|
@ -5991,7 +6125,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
|
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
self.client.modify_snapmirror(
|
self.client.modify_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
|
||||||
schedule=schedule, policy=policy, tries=tries,
|
schedule=schedule, policy=policy, tries=tries,
|
||||||
|
@ -6018,7 +6152,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
|
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
self.client.update_snapmirror(
|
self.client.update_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
|
|
||||||
|
@ -6031,12 +6165,26 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
self.client.send_request.assert_has_calls([
|
self.client.send_request.assert_has_calls([
|
||||||
mock.call('snapmirror-update', snapmirror_update_args)])
|
mock.call('snapmirror-update', snapmirror_update_args)])
|
||||||
|
|
||||||
|
def test_update_snapmirror_svm(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.update_snapmirror_svm(
|
||||||
|
fake.SM_SOURCE_VSERVER, fake.SM_DEST_VSERVER)
|
||||||
|
|
||||||
|
snapmirror_update_args = {
|
||||||
|
'source-location': fake.SM_SOURCE_VSERVER + ':',
|
||||||
|
'destination-location': fake.SM_DEST_VSERVER + ':',
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('snapmirror-update', snapmirror_update_args)])
|
||||||
|
|
||||||
def test_update_snapmirror_already_transferring(self):
|
def test_update_snapmirror_already_transferring(self):
|
||||||
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
|
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
|
||||||
code=netapp_api.ETRANSFER_IN_PROGRESS))
|
code=netapp_api.ETRANSFER_IN_PROGRESS))
|
||||||
self.mock_object(self.client, 'send_request', mock_send_req)
|
self.mock_object(self.client, 'send_request', mock_send_req)
|
||||||
|
|
||||||
self.client.update_snapmirror(
|
self.client.update_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
|
|
||||||
|
@ -6054,7 +6202,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
code=netapp_api.EANOTHER_OP_ACTIVE))
|
code=netapp_api.EANOTHER_OP_ACTIVE))
|
||||||
self.mock_object(self.client, 'send_request', mock_send_req)
|
self.mock_object(self.client, 'send_request', mock_send_req)
|
||||||
|
|
||||||
self.client.update_snapmirror(
|
self.client.update_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
|
|
||||||
|
@ -6071,7 +6219,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(code=0))
|
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(code=0))
|
||||||
self.mock_object(self.client, 'send_request', mock_send_req)
|
self.mock_object(self.client, 'send_request', mock_send_req)
|
||||||
|
|
||||||
self.assertRaises(netapp_api.NaApiError, self.client.update_snapmirror,
|
self.assertRaises(netapp_api.NaApiError,
|
||||||
|
self.client.update_snapmirror_vol,
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
|
|
||||||
|
@ -6079,7 +6228,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
|
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
self.client.delete_snapmirror(
|
self.client.delete_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
|
|
||||||
|
@ -6096,6 +6245,24 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
self.client.send_request.assert_has_calls([
|
self.client.send_request.assert_has_calls([
|
||||||
mock.call('snapmirror-destroy-iter', snapmirror_delete_args)])
|
mock.call('snapmirror-destroy-iter', snapmirror_delete_args)])
|
||||||
|
|
||||||
|
def test_delete_snapmirror_svm(self):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.delete_snapmirror_svm(
|
||||||
|
fake.SM_SOURCE_VSERVER, fake.SM_DEST_VSERVER)
|
||||||
|
|
||||||
|
snapmirror_delete_args = {
|
||||||
|
'query': {
|
||||||
|
'snapmirror-info': {
|
||||||
|
'source-location': fake.SM_SOURCE_VSERVER + ':',
|
||||||
|
'destination-location': fake.SM_DEST_VSERVER + ':',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('snapmirror-destroy-iter', snapmirror_delete_args)])
|
||||||
|
|
||||||
def test__get_snapmirrors(self):
|
def test__get_snapmirrors(self):
|
||||||
|
|
||||||
api_response = netapp_api.NaElement(fake.SNAPMIRROR_GET_ITER_RESPONSE)
|
api_response = netapp_api.NaElement(fake.SNAPMIRROR_GET_ITER_RESPONSE)
|
||||||
|
@ -6114,8 +6281,10 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
result = self.client._get_snapmirrors(
|
result = self.client._get_snapmirrors(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
source_vserver=fake.SM_SOURCE_VSERVER,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
|
source_volume=fake.SM_SOURCE_VOLUME,
|
||||||
|
dest_vserver=fake.SM_DEST_VSERVER,
|
||||||
|
dest_volume=fake.SM_DEST_VOLUME,
|
||||||
desired_attributes=desired_attributes)
|
desired_attributes=desired_attributes)
|
||||||
|
|
||||||
snapmirror_get_iter_args = {
|
snapmirror_get_iter_args = {
|
||||||
|
@ -6165,11 +6334,14 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
|
|
||||||
desired_attributes = ['source-vserver', 'source-volume',
|
desired_attributes = ['source-vserver', 'source-volume',
|
||||||
'destination-vserver', 'destination-volume',
|
'destination-vserver', 'destination-volume',
|
||||||
'is-healthy', 'mirror-state', 'schedule']
|
'is-healthy', 'mirror-state', 'schedule',
|
||||||
|
'relationship-status']
|
||||||
|
|
||||||
result = self.client.get_snapmirrors(
|
result = self.client.get_snapmirrors(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
source_vserver=fake.SM_SOURCE_VSERVER,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
|
dest_vserver=fake.SM_DEST_VSERVER,
|
||||||
|
source_volume=fake.SM_SOURCE_VOLUME,
|
||||||
|
dest_volume=fake.SM_DEST_VOLUME,
|
||||||
desired_attributes=desired_attributes)
|
desired_attributes=desired_attributes)
|
||||||
|
|
||||||
snapmirror_get_iter_args = {
|
snapmirror_get_iter_args = {
|
||||||
|
@ -6190,6 +6362,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
'is-healthy': None,
|
'is-healthy': None,
|
||||||
'mirror-state': None,
|
'mirror-state': None,
|
||||||
'schedule': None,
|
'schedule': None,
|
||||||
|
'relationship-status': None,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -6202,16 +6375,97 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
'is-healthy': 'true',
|
'is-healthy': 'true',
|
||||||
'mirror-state': 'snapmirrored',
|
'mirror-state': 'snapmirrored',
|
||||||
'schedule': 'daily',
|
'schedule': 'daily',
|
||||||
|
'relationship-status': 'idle'
|
||||||
}]
|
}]
|
||||||
|
|
||||||
self.client.send_iter_request.assert_has_calls([
|
self.client.send_iter_request.assert_has_calls([
|
||||||
mock.call('snapmirror-get-iter', snapmirror_get_iter_args)])
|
mock.call('snapmirror-get-iter', snapmirror_get_iter_args)])
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_get_snapmirrors_svm(self):
|
||||||
|
|
||||||
|
api_response = netapp_api.NaElement(
|
||||||
|
fake.SNAPMIRROR_GET_ITER_FILTERED_RESPONSE_2)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_iter_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
desired_attributes = ['source-vserver', 'destination-vserver',
|
||||||
|
'relationship-status', 'mirror-state']
|
||||||
|
|
||||||
|
result = self.client.get_snapmirrors_svm(
|
||||||
|
source_vserver=fake.SM_SOURCE_VSERVER,
|
||||||
|
dest_vserver=fake.SM_DEST_VSERVER,
|
||||||
|
desired_attributes=desired_attributes)
|
||||||
|
|
||||||
|
snapmirror_get_iter_args = {
|
||||||
|
'query': {
|
||||||
|
'snapmirror-info': {
|
||||||
|
'source-location': fake.SM_SOURCE_VSERVER + ':',
|
||||||
|
'destination-location': fake.SM_DEST_VSERVER + ':',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'snapmirror-info': {
|
||||||
|
'source-vserver': None,
|
||||||
|
'destination-vserver': None,
|
||||||
|
'relationship-status': None,
|
||||||
|
'mirror-state': None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = [{
|
||||||
|
'source-vserver': fake.SM_SOURCE_VSERVER,
|
||||||
|
'destination-vserver': fake.SM_DEST_VSERVER,
|
||||||
|
'relationship-status': 'idle',
|
||||||
|
'mirror-state': 'snapmirrored',
|
||||||
|
}]
|
||||||
|
|
||||||
|
self.client.send_iter_request.assert_has_calls([
|
||||||
|
mock.call('snapmirror-get-iter', snapmirror_get_iter_args)])
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
@ddt.data(fake.SNAPMIRROR_GET_DESTINATIONS_ITER_FILTERED_RESPONSE,
|
||||||
|
fake.NO_RECORDS_RESPONSE)
|
||||||
|
def test_get_snapmirror_destinations_svm(self, api_response):
|
||||||
|
self.mock_object(
|
||||||
|
self.client, 'send_iter_request',
|
||||||
|
mock.Mock(return_value=netapp_api.NaElement(api_response)))
|
||||||
|
|
||||||
|
result = self.client.get_snapmirror_destinations_svm(
|
||||||
|
source_vserver=fake.SM_SOURCE_VSERVER,
|
||||||
|
dest_vserver=fake.SM_DEST_VSERVER)
|
||||||
|
|
||||||
|
snapmirror_get_iter_args = {
|
||||||
|
'query': {
|
||||||
|
'snapmirror-destination-info': {
|
||||||
|
'source-location': fake.SM_SOURCE_VSERVER + ':',
|
||||||
|
'destination-location': fake.SM_DEST_VSERVER + ':',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if api_response == fake.NO_RECORDS_RESPONSE:
|
||||||
|
expected = []
|
||||||
|
else:
|
||||||
|
expected = [{
|
||||||
|
'source-vserver': fake.SM_SOURCE_VSERVER,
|
||||||
|
'destination-vserver': fake.SM_DEST_VSERVER,
|
||||||
|
'source-location': fake.SM_SOURCE_VSERVER + ':',
|
||||||
|
'destination-location': fake.SM_DEST_VSERVER + ':',
|
||||||
|
'relationship-id': 'fake_relationship_id',
|
||||||
|
}]
|
||||||
|
|
||||||
|
self.client.send_iter_request.assert_has_calls([
|
||||||
|
mock.call('snapmirror-get-destination-iter',
|
||||||
|
snapmirror_get_iter_args)])
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
def test_resume_snapmirror(self):
|
def test_resume_snapmirror(self):
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
self.client.resume_snapmirror(
|
self.client.resume_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
|
|
||||||
|
@ -6224,12 +6478,25 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
self.client.send_request.assert_has_calls([
|
self.client.send_request.assert_has_calls([
|
||||||
mock.call('snapmirror-resume', snapmirror_resume_args)])
|
mock.call('snapmirror-resume', snapmirror_resume_args)])
|
||||||
|
|
||||||
|
def test_resume_snapmirror_svm(self):
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.resume_snapmirror_svm(
|
||||||
|
fake.SM_SOURCE_VSERVER, fake.SM_DEST_VSERVER)
|
||||||
|
|
||||||
|
snapmirror_resume_args = {
|
||||||
|
'source-location': fake.SM_SOURCE_VSERVER + ':',
|
||||||
|
'destination-location': fake.SM_DEST_VSERVER + ':',
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('snapmirror-resume', snapmirror_resume_args)])
|
||||||
|
|
||||||
def test_resume_snapmirror_not_quiesed(self):
|
def test_resume_snapmirror_not_quiesed(self):
|
||||||
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
|
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
|
||||||
code=netapp_api.ERELATION_NOT_QUIESCED))
|
code=netapp_api.ERELATION_NOT_QUIESCED))
|
||||||
self.mock_object(self.client, 'send_request', mock_send_req)
|
self.mock_object(self.client, 'send_request', mock_send_req)
|
||||||
|
|
||||||
self.client.resume_snapmirror(
|
self.client.resume_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
|
|
||||||
|
@ -6246,14 +6513,15 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(code=0))
|
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(code=0))
|
||||||
self.mock_object(self.client, 'send_request', mock_send_req)
|
self.mock_object(self.client, 'send_request', mock_send_req)
|
||||||
|
|
||||||
self.assertRaises(netapp_api.NaApiError, self.client.resume_snapmirror,
|
self.assertRaises(netapp_api.NaApiError,
|
||||||
|
self.client.resume_snapmirror_vol,
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
|
|
||||||
def test_resync_snapmirror(self):
|
def test_resync_snapmirror(self):
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
self.client.resync_snapmirror(
|
self.client.resync_snapmirror_vol(
|
||||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||||
|
|
||||||
|
@ -6266,6 +6534,19 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
self.client.send_request.assert_has_calls([
|
self.client.send_request.assert_has_calls([
|
||||||
mock.call('snapmirror-resync', snapmirror_resync_args)])
|
mock.call('snapmirror-resync', snapmirror_resync_args)])
|
||||||
|
|
||||||
|
def test_resync_snapmirror_svm(self):
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
|
||||||
|
self.client.resync_snapmirror_svm(
|
||||||
|
fake.SM_SOURCE_VSERVER, fake.SM_DEST_VSERVER)
|
||||||
|
|
||||||
|
snapmirror_resync_args = {
|
||||||
|
'source-location': fake.SM_SOURCE_VSERVER + ':',
|
||||||
|
'destination-location': fake.SM_DEST_VSERVER + ':',
|
||||||
|
}
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('snapmirror-resync', snapmirror_resync_args)])
|
||||||
|
|
||||||
@ddt.data('source', 'destination', None)
|
@ddt.data('source', 'destination', None)
|
||||||
def test_volume_has_snapmirror_relationships(self, snapmirror_rel_type):
|
def test_volume_has_snapmirror_relationships(self, snapmirror_rel_type):
|
||||||
"""Snapmirror relationships can be both ways."""
|
"""Snapmirror relationships can be both ways."""
|
||||||
|
@ -6282,8 +6563,10 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
}
|
}
|
||||||
expected_get_snapmirrors_call_count = 2
|
expected_get_snapmirrors_call_count = 2
|
||||||
expected_get_snapmirrors_calls = [
|
expected_get_snapmirrors_calls = [
|
||||||
mock.call(vol['owning-vserver-name'], vol['name'], None, None),
|
mock.call(source_vserver=vol['owning-vserver-name'],
|
||||||
mock.call(None, None, vol['owning-vserver-name'], vol['name']),
|
source_volume=vol['name']),
|
||||||
|
mock.call(dest_vserver=vol['owning-vserver-name'],
|
||||||
|
dest_volume=vol['name']),
|
||||||
]
|
]
|
||||||
if snapmirror_rel_type is None:
|
if snapmirror_rel_type is None:
|
||||||
side_effect = ([], [])
|
side_effect = ([], [])
|
||||||
|
@ -6315,7 +6598,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
|
|
||||||
vol = fake.FAKE_MANAGE_VOLUME
|
vol = fake.FAKE_MANAGE_VOLUME
|
||||||
expected_get_snapmirrors_calls = [
|
expected_get_snapmirrors_calls = [
|
||||||
mock.call(vol['owning-vserver-name'], vol['name'], None, None),
|
mock.call(source_vserver=vol['owning-vserver-name'],
|
||||||
|
source_volume=vol['name']),
|
||||||
]
|
]
|
||||||
mock_get_snapmirrors_call = self.mock_object(
|
mock_get_snapmirrors_call = self.mock_object(
|
||||||
self.client, 'get_snapmirrors', mock.Mock(
|
self.client, 'get_snapmirrors', mock.Mock(
|
||||||
|
@ -6878,3 +7162,207 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||||
nfs_config = self.client.parse_nfs_config(parent_elem, desired_args)
|
nfs_config = self.client.parse_nfs_config(parent_elem, desired_args)
|
||||||
|
|
||||||
self.assertDictEqual(nfs_config, expected_nfs)
|
self.assertDictEqual(nfs_config, expected_nfs)
|
||||||
|
|
||||||
|
@ddt.data(fake.NO_RECORDS_RESPONSE,
|
||||||
|
fake.VSERVER_GET_ITER_RESPONSE_INFO)
|
||||||
|
def test_get_vserver_info(self, api_response):
|
||||||
|
self.mock_object(self.client, 'send_iter_request',
|
||||||
|
mock.Mock(
|
||||||
|
return_value=netapp_api.NaElement(
|
||||||
|
api_response)))
|
||||||
|
|
||||||
|
result = self.client.get_vserver_info(fake.VSERVER_NAME)
|
||||||
|
|
||||||
|
expected_api_args = {
|
||||||
|
'query': {
|
||||||
|
'vserver-info': {
|
||||||
|
'vserver-name': fake.VSERVER_NAME,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'vserver-info': {
|
||||||
|
'vserver-name': None,
|
||||||
|
'vserver-subtype': None,
|
||||||
|
'state': None,
|
||||||
|
'operational-state': None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.client.send_iter_request.assert_called_once_with(
|
||||||
|
'vserver-get-iter', expected_api_args)
|
||||||
|
if api_response == fake.NO_RECORDS_RESPONSE:
|
||||||
|
self.assertIsNone(result)
|
||||||
|
else:
|
||||||
|
self.assertDictMatch(fake.VSERVER_INFO, result)
|
||||||
|
|
||||||
|
@ddt.data({'discard_network': True, 'preserve_snapshots': False},
|
||||||
|
{'discard_network': False, 'preserve_snapshots': True})
|
||||||
|
@ddt.unpack
|
||||||
|
def test_create_snapmirror_policy(self, discard_network,
|
||||||
|
preserve_snapshots):
|
||||||
|
api_response = netapp_api.NaElement(fake.PASSED_RESPONSE)
|
||||||
|
self.mock_object(self.client, 'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
self.client.create_snapmirror_policy(
|
||||||
|
fake.SNAPMIRROR_POLICY_NAME, discard_network_info=discard_network,
|
||||||
|
preserve_snapshots=preserve_snapshots)
|
||||||
|
|
||||||
|
expected_create_api_args = {
|
||||||
|
'policy-name': fake.SNAPMIRROR_POLICY_NAME,
|
||||||
|
'type': 'async_mirror',
|
||||||
|
}
|
||||||
|
if discard_network:
|
||||||
|
expected_create_api_args['discard-configs'] = {
|
||||||
|
'svmdr-config-obj': 'network'
|
||||||
|
}
|
||||||
|
expected_calls = [
|
||||||
|
mock.call('snapmirror-policy-create', expected_create_api_args)
|
||||||
|
]
|
||||||
|
|
||||||
|
if preserve_snapshots:
|
||||||
|
expected_add_rules = {
|
||||||
|
'policy-name': fake.SNAPMIRROR_POLICY_NAME,
|
||||||
|
'snapmirror-label': 'all_source_snapshots',
|
||||||
|
'keep': '1',
|
||||||
|
'preserve': 'false'
|
||||||
|
}
|
||||||
|
expected_calls.append(mock.call('snapmirror-policy-add-rule',
|
||||||
|
expected_add_rules))
|
||||||
|
|
||||||
|
self.client.send_request.assert_has_calls(expected_calls)
|
||||||
|
|
||||||
|
def test_delete_snapmirror_policy(self):
|
||||||
|
api_response = netapp_api.NaElement(fake.PASSED_RESPONSE)
|
||||||
|
self.mock_object(self.client, 'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
self.client.delete_snapmirror_policy(fake.SNAPMIRROR_POLICY_NAME)
|
||||||
|
|
||||||
|
expected_api_args = {
|
||||||
|
'policy-name': fake.SNAPMIRROR_POLICY_NAME,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.send_request.assert_called_once_with(
|
||||||
|
'snapmirror-policy-delete', expected_api_args)
|
||||||
|
|
||||||
|
def test_delete_snapmirror_policy_not_found(self):
|
||||||
|
self.mock_object(self.client, 'send_request',
|
||||||
|
self._mock_api_error(code=netapp_api.EOBJECTNOTFOUND))
|
||||||
|
|
||||||
|
self.client.delete_snapmirror_policy(fake.SNAPMIRROR_POLICY_NAME)
|
||||||
|
|
||||||
|
expected_api_args = {
|
||||||
|
'policy-name': fake.SNAPMIRROR_POLICY_NAME,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.send_request.assert_called_once_with(
|
||||||
|
'snapmirror-policy-delete', expected_api_args)
|
||||||
|
|
||||||
|
def test_get_snapmirror_policies(self):
|
||||||
|
api_response = netapp_api.NaElement(
|
||||||
|
fake.SNAPMIRROR_POLICY_GET_ITER_RESPONSE)
|
||||||
|
self.mock_object(self.client, 'send_iter_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
result = self.client.get_snapmirror_policies(fake.VSERVER_NAME)
|
||||||
|
|
||||||
|
expected_api_args = {
|
||||||
|
'query': {
|
||||||
|
'snapmirror-policy-info': {
|
||||||
|
'vserver-name': fake.VSERVER_NAME,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'snapmirror-policy-info': {
|
||||||
|
'policy-name': None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.send_iter_request.assert_called_once_with(
|
||||||
|
'snapmirror-policy-get-iter', expected_api_args)
|
||||||
|
self.assertEqual([fake.SNAPMIRROR_POLICY_NAME], result)
|
||||||
|
|
||||||
|
@ddt.data(True, False, None)
|
||||||
|
def test_start_vserver(self, force):
|
||||||
|
api_response = netapp_api.NaElement(fake.PASSED_RESPONSE)
|
||||||
|
self.mock_object(self.client, 'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
self.client.start_vserver(fake.VSERVER_NAME, force=force)
|
||||||
|
|
||||||
|
expected_api_args = {
|
||||||
|
'vserver-name': fake.VSERVER_NAME,
|
||||||
|
}
|
||||||
|
if force is not None:
|
||||||
|
expected_api_args['force'] = 'true' if force is True else 'false'
|
||||||
|
|
||||||
|
self.client.send_request.assert_called_once_with(
|
||||||
|
'vserver-start', expected_api_args, enable_tunneling=False)
|
||||||
|
|
||||||
|
def test_start_vserver_already_started(self):
|
||||||
|
self.mock_object(self.client, 'send_request',
|
||||||
|
self._mock_api_error(
|
||||||
|
code=netapp_api.EVSERVERALREADYSTARTED))
|
||||||
|
|
||||||
|
self.client.start_vserver(fake.VSERVER_NAME)
|
||||||
|
|
||||||
|
expected_api_args = {
|
||||||
|
'vserver-name': fake.VSERVER_NAME,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.send_request.assert_called_once_with(
|
||||||
|
'vserver-start', expected_api_args, enable_tunneling=False)
|
||||||
|
|
||||||
|
def test_stop_vserver(self):
|
||||||
|
api_response = netapp_api.NaElement(fake.PASSED_RESPONSE)
|
||||||
|
self.mock_object(self.client, 'send_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
|
||||||
|
self.client.stop_vserver(fake.VSERVER_NAME)
|
||||||
|
|
||||||
|
expected_api_args = {
|
||||||
|
'vserver-name': fake.VSERVER_NAME,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.send_request.assert_called_once_with(
|
||||||
|
'vserver-stop', expected_api_args, enable_tunneling=False)
|
||||||
|
|
||||||
|
def test_is_svm_dr_supported(self):
|
||||||
|
self.client.features.add_feature('SVM_DR')
|
||||||
|
|
||||||
|
result = self.client.is_svm_dr_supported()
|
||||||
|
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
@ddt.data({'get_iter_response': fake.CIFS_SHARE_GET_ITER_RESPONSE,
|
||||||
|
'expected_result': True},
|
||||||
|
{'get_iter_response': fake.NO_RECORDS_RESPONSE,
|
||||||
|
'expected_result': False})
|
||||||
|
@ddt.unpack
|
||||||
|
def test_cifs_share_exists(self, get_iter_response, expected_result):
|
||||||
|
api_response = netapp_api.NaElement(get_iter_response)
|
||||||
|
self.mock_object(self.client,
|
||||||
|
'send_iter_request',
|
||||||
|
mock.Mock(return_value=api_response))
|
||||||
|
fake_share_path = '/%s' % fake.SHARE_NAME
|
||||||
|
|
||||||
|
result = self.client.cifs_share_exists(fake.SHARE_NAME)
|
||||||
|
|
||||||
|
cifs_share_get_iter_args = {
|
||||||
|
'query': {
|
||||||
|
'cifs-share': {
|
||||||
|
'share-name': fake.SHARE_NAME,
|
||||||
|
'path': fake_share_path,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'desired-attributes': {
|
||||||
|
'cifs-share': {
|
||||||
|
'share-name': None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.assertEqual(expected_result, result)
|
||||||
|
self.client.send_iter_request.assert_called_once_with(
|
||||||
|
'cifs-share-get-iter', cifs_share_get_iter_args)
|
||||||
|
|
|
@ -151,6 +151,8 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.fake_src_share = copy.deepcopy(fake.SHARE)
|
self.fake_src_share = copy.deepcopy(fake.SHARE)
|
||||||
self.fake_src_share_server = copy.deepcopy(fake.SHARE_SERVER)
|
self.fake_src_share_server = copy.deepcopy(fake.SHARE_SERVER)
|
||||||
self.source_vserver = 'source_vserver'
|
self.source_vserver = 'source_vserver'
|
||||||
|
self.source_backend_name = (
|
||||||
|
self.fake_src_share_server['host'].split('@')[1])
|
||||||
self.fake_src_share_server['backend_details']['vserver_name'] = (
|
self.fake_src_share_server['backend_details']['vserver_name'] = (
|
||||||
self.source_vserver
|
self.source_vserver
|
||||||
)
|
)
|
||||||
|
@ -158,8 +160,10 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.fake_src_share['id'] = 'c02d497a-236c-4852-812a-0d39373e312a'
|
self.fake_src_share['id'] = 'c02d497a-236c-4852-812a-0d39373e312a'
|
||||||
self.fake_src_vol_name = 'share_c02d497a_236c_4852_812a_0d39373e312a'
|
self.fake_src_vol_name = 'share_c02d497a_236c_4852_812a_0d39373e312a'
|
||||||
self.fake_dest_share = copy.deepcopy(fake.SHARE)
|
self.fake_dest_share = copy.deepcopy(fake.SHARE)
|
||||||
self.fake_dest_share_server = copy.deepcopy(fake.SHARE_SERVER)
|
self.fake_dest_share_server = copy.deepcopy(fake.SHARE_SERVER_2)
|
||||||
self.dest_vserver = 'dest_vserver'
|
self.dest_vserver = 'dest_vserver'
|
||||||
|
self.dest_backend_name = (
|
||||||
|
self.fake_dest_share_server['host'].split('@')[1])
|
||||||
self.fake_dest_share_server['backend_details']['vserver_name'] = (
|
self.fake_dest_share_server['backend_details']['vserver_name'] = (
|
||||||
self.dest_vserver
|
self.dest_vserver
|
||||||
)
|
)
|
||||||
|
@ -172,6 +176,25 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.mock_object(data_motion, 'get_client_for_backend',
|
self.mock_object(data_motion, 'get_client_for_backend',
|
||||||
mock.Mock(side_effect=[self.mock_dest_client,
|
mock.Mock(side_effect=[self.mock_dest_client,
|
||||||
self.mock_src_client]))
|
self.mock_src_client]))
|
||||||
|
self.mock_object(self.dm_session, 'get_client_and_vserver_name',
|
||||||
|
mock.Mock(side_effect=[
|
||||||
|
(self.mock_src_client, self.source_vserver),
|
||||||
|
(self.mock_dest_client, self.dest_vserver)]))
|
||||||
|
|
||||||
|
def test_get_client_and_vserver_name(self):
|
||||||
|
dm_session = data_motion.DataMotionSession()
|
||||||
|
client = mock.Mock()
|
||||||
|
self.mock_object(data_motion, 'get_client_for_backend',
|
||||||
|
mock.Mock(return_value=client))
|
||||||
|
|
||||||
|
result = dm_session.get_client_and_vserver_name(fake.SHARE_SERVER)
|
||||||
|
expected = (client,
|
||||||
|
fake.SHARE_SERVER['backend_details']['vserver_name'])
|
||||||
|
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
data_motion.get_client_for_backend.assert_called_once_with(
|
||||||
|
fake.BACKEND_NAME, vserver_name=fake.VSERVER1
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_snapmirror(self):
|
def test_create_snapmirror(self):
|
||||||
mock_dest_client = mock.Mock()
|
mock_dest_client = mock.Mock()
|
||||||
|
@ -181,15 +204,50 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.dm_session.create_snapmirror(self.fake_src_share,
|
self.dm_session.create_snapmirror(self.fake_src_share,
|
||||||
self.fake_dest_share)
|
self.fake_dest_share)
|
||||||
|
|
||||||
mock_dest_client.create_snapmirror.assert_called_once_with(
|
mock_dest_client.create_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name, schedule='hourly'
|
self.fake_dest_vol_name, schedule='hourly'
|
||||||
)
|
)
|
||||||
mock_dest_client.initialize_snapmirror.assert_called_once_with(
|
mock_dest_client.initialize_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name
|
self.fake_dest_vol_name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_create_snapmirror_svm(self):
|
||||||
|
mock_dest_client = mock.Mock()
|
||||||
|
self.mock_object(self.dm_session, 'get_client_and_vserver_name',
|
||||||
|
mock.Mock(return_value=(mock_dest_client,
|
||||||
|
self.dest_vserver)))
|
||||||
|
self.mock_object(self.dm_session, 'get_vserver_from_share_server',
|
||||||
|
mock.Mock(return_value=self.source_vserver))
|
||||||
|
policy_name = 'policy_' + self.dest_vserver
|
||||||
|
get_snapmirro_policy_name = self.mock_object(
|
||||||
|
self.dm_session, '_get_backend_snapmirror_policy_name_svm',
|
||||||
|
mock.Mock(return_value=policy_name))
|
||||||
|
|
||||||
|
self.dm_session.create_snapmirror_svm(self.fake_src_share_server,
|
||||||
|
self.fake_dest_share_server)
|
||||||
|
|
||||||
|
self.dm_session.get_client_and_vserver_name.assert_called_once_with(
|
||||||
|
self.fake_dest_share_server
|
||||||
|
)
|
||||||
|
self.dm_session.get_vserver_from_share_server.assert_called_once_with(
|
||||||
|
self.fake_src_share_server
|
||||||
|
)
|
||||||
|
get_snapmirro_policy_name.assert_called_once_with(
|
||||||
|
self.fake_dest_share_server['id'], self.dest_backend_name
|
||||||
|
)
|
||||||
|
mock_dest_client.create_snapmirror_policy.assert_called_once_with(
|
||||||
|
policy_name
|
||||||
|
)
|
||||||
|
mock_dest_client.create_snapmirror_svm.assert_called_once_with(
|
||||||
|
self.source_vserver, self.dest_vserver,
|
||||||
|
policy=policy_name, schedule='hourly'
|
||||||
|
)
|
||||||
|
mock_dest_client.initialize_snapmirror_svm.assert_called_once_with(
|
||||||
|
self.source_vserver, self.dest_vserver
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_snapmirror(self):
|
def test_delete_snapmirror(self):
|
||||||
mock_src_client = mock.Mock()
|
mock_src_client = mock.Mock()
|
||||||
mock_dest_client = mock.Mock()
|
mock_dest_client = mock.Mock()
|
||||||
|
@ -200,26 +258,50 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.dm_session.delete_snapmirror(self.fake_src_share,
|
self.dm_session.delete_snapmirror(self.fake_src_share,
|
||||||
self.fake_dest_share)
|
self.fake_dest_share)
|
||||||
|
|
||||||
mock_dest_client.abort_snapmirror.assert_called_once_with(
|
mock_dest_client.abort_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name, clear_checkpoint=False
|
self.fake_dest_vol_name, clear_checkpoint=False
|
||||||
)
|
)
|
||||||
mock_dest_client.delete_snapmirror.assert_called_once_with(
|
mock_dest_client.delete_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name
|
self.fake_dest_vol_name
|
||||||
)
|
)
|
||||||
mock_src_client.release_snapmirror.assert_called_once_with(
|
mock_src_client.release_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name
|
self.fake_dest_vol_name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_delete_snapmirror_svm(self, call_release):
|
||||||
|
self.mock_object(self.dm_session, 'wait_for_snapmirror_release_svm')
|
||||||
|
mock_backend_config = na_fakes.create_configuration()
|
||||||
|
mock_backend_config.netapp_snapmirror_release_timeout = 30
|
||||||
|
self.mock_object(data_motion, 'get_backend_configuration',
|
||||||
|
mock.Mock(return_value=mock_backend_config))
|
||||||
|
|
||||||
|
self.dm_session.delete_snapmirror_svm(self.fake_src_share_server,
|
||||||
|
self.fake_dest_share_server,
|
||||||
|
release=call_release)
|
||||||
|
|
||||||
|
self.mock_dest_client.abort_snapmirror_svm.assert_called_once_with(
|
||||||
|
self.source_vserver, self.dest_vserver
|
||||||
|
)
|
||||||
|
self.mock_dest_client.delete_snapmirror_svm.assert_called_once_with(
|
||||||
|
self.source_vserver, self.dest_vserver
|
||||||
|
)
|
||||||
|
if call_release:
|
||||||
|
release_mock = self.dm_session.wait_for_snapmirror_release_svm
|
||||||
|
release_mock.assert_called_once_with(
|
||||||
|
self.source_vserver, self.dest_vserver, self.mock_src_client,
|
||||||
|
timeout=mock_backend_config.netapp_snapmirror_release_timeout
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_snapmirror_does_not_exist(self):
|
def test_delete_snapmirror_does_not_exist(self):
|
||||||
"""Ensure delete succeeds when the snapmirror does not exist."""
|
"""Ensure delete succeeds when the snapmirror does not exist."""
|
||||||
mock_src_client = mock.Mock()
|
mock_src_client = mock.Mock()
|
||||||
mock_dest_client = mock.Mock()
|
mock_dest_client = mock.Mock()
|
||||||
mock_dest_client.abort_snapmirror.side_effect = netapp_api.NaApiError(
|
mock_dest_client.abort_snapmirror_vol.side_effect = (
|
||||||
code=netapp_api.EAPIERROR
|
netapp_api.NaApiError(code=netapp_api.EAPIERROR))
|
||||||
)
|
|
||||||
self.mock_object(data_motion, 'get_client_for_backend',
|
self.mock_object(data_motion, 'get_client_for_backend',
|
||||||
mock.Mock(side_effect=[mock_dest_client,
|
mock.Mock(side_effect=[mock_dest_client,
|
||||||
mock_src_client]))
|
mock_src_client]))
|
||||||
|
@ -227,26 +309,50 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.dm_session.delete_snapmirror(self.fake_src_share,
|
self.dm_session.delete_snapmirror(self.fake_src_share,
|
||||||
self.fake_dest_share)
|
self.fake_dest_share)
|
||||||
|
|
||||||
mock_dest_client.abort_snapmirror.assert_called_once_with(
|
mock_dest_client.abort_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name, clear_checkpoint=False
|
self.fake_dest_vol_name, clear_checkpoint=False
|
||||||
)
|
)
|
||||||
mock_dest_client.delete_snapmirror.assert_called_once_with(
|
mock_dest_client.delete_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name
|
self.fake_dest_vol_name
|
||||||
)
|
)
|
||||||
mock_src_client.release_snapmirror.assert_called_once_with(
|
mock_src_client.release_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name
|
self.fake_dest_vol_name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_delete_snapmirror_svm_does_not_exist(self):
|
||||||
|
"""Ensure delete succeeds when the snapmirror does not exist."""
|
||||||
|
self.mock_dest_client.abort_snapmirror_svm.side_effect = (
|
||||||
|
netapp_api.NaApiError(code=netapp_api.EAPIERROR))
|
||||||
|
self.mock_object(self.dm_session, 'wait_for_snapmirror_release_svm')
|
||||||
|
mock_backend_config = na_fakes.create_configuration()
|
||||||
|
mock_backend_config.netapp_snapmirror_release_timeout = 30
|
||||||
|
self.mock_object(data_motion, 'get_backend_configuration',
|
||||||
|
mock.Mock(return_value=mock_backend_config))
|
||||||
|
|
||||||
|
self.dm_session.delete_snapmirror_svm(self.fake_src_share_server,
|
||||||
|
self.fake_dest_share_server)
|
||||||
|
|
||||||
|
self.mock_dest_client.abort_snapmirror_svm.assert_called_once_with(
|
||||||
|
self.source_vserver, self.dest_vserver
|
||||||
|
)
|
||||||
|
self.mock_dest_client.delete_snapmirror_svm.assert_called_once_with(
|
||||||
|
self.source_vserver, self.dest_vserver
|
||||||
|
)
|
||||||
|
release_mock = self.dm_session.wait_for_snapmirror_release_svm
|
||||||
|
release_mock.assert_called_once_with(
|
||||||
|
self.source_vserver, self.dest_vserver, self.mock_src_client,
|
||||||
|
timeout=mock_backend_config.netapp_snapmirror_release_timeout
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_snapmirror_error_deleting(self):
|
def test_delete_snapmirror_error_deleting(self):
|
||||||
"""Ensure delete succeeds when the snapmirror does not exist."""
|
"""Ensure delete succeeds when the snapmirror does not exist."""
|
||||||
mock_src_client = mock.Mock()
|
mock_src_client = mock.Mock()
|
||||||
mock_dest_client = mock.Mock()
|
mock_dest_client = mock.Mock()
|
||||||
mock_dest_client.delete_snapmirror.side_effect = netapp_api.NaApiError(
|
mock_dest_client.delete_snapmirror_vol.side_effect = (
|
||||||
code=netapp_api.ESOURCE_IS_DIFFERENT
|
netapp_api.NaApiError(code=netapp_api.ESOURCE_IS_DIFFERENT))
|
||||||
)
|
|
||||||
self.mock_object(data_motion, 'get_client_for_backend',
|
self.mock_object(data_motion, 'get_client_for_backend',
|
||||||
mock.Mock(side_effect=[mock_dest_client,
|
mock.Mock(side_effect=[mock_dest_client,
|
||||||
mock_src_client]))
|
mock_src_client]))
|
||||||
|
@ -254,24 +360,49 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.dm_session.delete_snapmirror(self.fake_src_share,
|
self.dm_session.delete_snapmirror(self.fake_src_share,
|
||||||
self.fake_dest_share)
|
self.fake_dest_share)
|
||||||
|
|
||||||
mock_dest_client.abort_snapmirror.assert_called_once_with(
|
mock_dest_client.abort_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name, clear_checkpoint=False
|
self.fake_dest_vol_name, clear_checkpoint=False
|
||||||
)
|
)
|
||||||
mock_dest_client.delete_snapmirror.assert_called_once_with(
|
mock_dest_client.delete_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name
|
self.fake_dest_vol_name
|
||||||
)
|
)
|
||||||
mock_src_client.release_snapmirror.assert_called_once_with(
|
mock_src_client.release_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name
|
self.fake_dest_vol_name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_delete_snapmirror_svm_error_deleting(self):
|
||||||
|
"""Ensure delete succeeds when the snapmirror does not exist."""
|
||||||
|
self.mock_dest_client.delete_snapmirror_svm.side_effect = (
|
||||||
|
netapp_api.NaApiError(code=netapp_api.ESOURCE_IS_DIFFERENT))
|
||||||
|
self.mock_object(self.dm_session, 'wait_for_snapmirror_release_svm')
|
||||||
|
mock_backend_config = na_fakes.create_configuration()
|
||||||
|
mock_backend_config.netapp_snapmirror_release_timeout = 30
|
||||||
|
self.mock_object(data_motion, 'get_backend_configuration',
|
||||||
|
mock.Mock(return_value=mock_backend_config))
|
||||||
|
|
||||||
|
self.dm_session.delete_snapmirror_svm(self.fake_src_share_server,
|
||||||
|
self.fake_dest_share_server)
|
||||||
|
|
||||||
|
self.mock_dest_client.abort_snapmirror_svm.assert_called_once_with(
|
||||||
|
self.source_vserver, self.dest_vserver
|
||||||
|
)
|
||||||
|
self.mock_dest_client.delete_snapmirror_svm.assert_called_once_with(
|
||||||
|
self.source_vserver, self.dest_vserver
|
||||||
|
)
|
||||||
|
release_mock = self.dm_session.wait_for_snapmirror_release_svm
|
||||||
|
release_mock.assert_called_once_with(
|
||||||
|
self.source_vserver, self.dest_vserver, self.mock_src_client,
|
||||||
|
timeout=mock_backend_config.netapp_snapmirror_release_timeout
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_snapmirror_error_releasing(self):
|
def test_delete_snapmirror_error_releasing(self):
|
||||||
"""Ensure delete succeeds when the snapmirror does not exist."""
|
"""Ensure delete succeeds when the snapmirror does not exist."""
|
||||||
mock_src_client = mock.Mock()
|
mock_src_client = mock.Mock()
|
||||||
mock_dest_client = mock.Mock()
|
mock_dest_client = mock.Mock()
|
||||||
mock_src_client.release_snapmirror.side_effect = (
|
mock_src_client.release_snapmirror_vol.side_effect = (
|
||||||
netapp_api.NaApiError(code=netapp_api.EOBJECTNOTFOUND))
|
netapp_api.NaApiError(code=netapp_api.EOBJECTNOTFOUND))
|
||||||
self.mock_object(data_motion, 'get_client_for_backend',
|
self.mock_object(data_motion, 'get_client_for_backend',
|
||||||
mock.Mock(side_effect=[mock_dest_client,
|
mock.Mock(side_effect=[mock_dest_client,
|
||||||
|
@ -280,15 +411,15 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.dm_session.delete_snapmirror(self.fake_src_share,
|
self.dm_session.delete_snapmirror(self.fake_src_share,
|
||||||
self.fake_dest_share)
|
self.fake_dest_share)
|
||||||
|
|
||||||
mock_dest_client.abort_snapmirror.assert_called_once_with(
|
mock_dest_client.abort_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name, clear_checkpoint=False
|
self.fake_dest_vol_name, clear_checkpoint=False
|
||||||
)
|
)
|
||||||
mock_dest_client.delete_snapmirror.assert_called_once_with(
|
mock_dest_client.delete_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name
|
self.fake_dest_vol_name
|
||||||
)
|
)
|
||||||
mock_src_client.release_snapmirror.assert_called_once_with(
|
mock_src_client.release_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name
|
self.fake_dest_vol_name
|
||||||
)
|
)
|
||||||
|
@ -304,15 +435,15 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.fake_dest_share,
|
self.fake_dest_share,
|
||||||
release=False)
|
release=False)
|
||||||
|
|
||||||
mock_dest_client.abort_snapmirror.assert_called_once_with(
|
mock_dest_client.abort_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name, clear_checkpoint=False
|
self.fake_dest_vol_name, clear_checkpoint=False
|
||||||
)
|
)
|
||||||
mock_dest_client.delete_snapmirror.assert_called_once_with(
|
mock_dest_client.delete_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name
|
self.fake_dest_vol_name
|
||||||
)
|
)
|
||||||
self.assertFalse(mock_src_client.release_snapmirror.called)
|
self.assertFalse(mock_src_client.release_snapmirror_vol.called)
|
||||||
|
|
||||||
def test_delete_snapmirror_source_unreachable(self):
|
def test_delete_snapmirror_source_unreachable(self):
|
||||||
mock_src_client = mock.Mock()
|
mock_src_client = mock.Mock()
|
||||||
|
@ -324,16 +455,16 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.dm_session.delete_snapmirror(self.fake_src_share,
|
self.dm_session.delete_snapmirror(self.fake_src_share,
|
||||||
self.fake_dest_share)
|
self.fake_dest_share)
|
||||||
|
|
||||||
mock_dest_client.abort_snapmirror.assert_called_once_with(
|
mock_dest_client.abort_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name, clear_checkpoint=False
|
self.fake_dest_vol_name, clear_checkpoint=False
|
||||||
)
|
)
|
||||||
mock_dest_client.delete_snapmirror.assert_called_once_with(
|
mock_dest_client.delete_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||||
self.fake_dest_vol_name
|
self.fake_dest_vol_name
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertFalse(mock_src_client.release_snapmirror.called)
|
self.assertFalse(mock_src_client.release_snapmirror_vol.called)
|
||||||
|
|
||||||
def test_break_snapmirror(self):
|
def test_break_snapmirror(self):
|
||||||
self.mock_object(self.dm_session, 'quiesce_then_abort')
|
self.mock_object(self.dm_session, 'quiesce_then_abort')
|
||||||
|
@ -341,7 +472,7 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.dm_session.break_snapmirror(self.fake_src_share,
|
self.dm_session.break_snapmirror(self.fake_src_share,
|
||||||
self.fake_dest_share)
|
self.fake_dest_share)
|
||||||
|
|
||||||
self.mock_dest_client.break_snapmirror.assert_called_once_with(
|
self.mock_dest_client.break_snapmirror_vol.assert_called_once_with(
|
||||||
self.source_vserver, self.fake_src_vol_name,
|
self.source_vserver, self.fake_src_vol_name,
|
||||||
self.dest_vserver, self.fake_dest_vol_name)
|
self.dest_vserver, self.fake_dest_vol_name)
|
||||||
|
|
||||||
|
@ -358,7 +489,7 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.fake_dest_share,
|
self.fake_dest_share,
|
||||||
mount=False)
|
mount=False)
|
||||||
|
|
||||||
self.mock_dest_client.break_snapmirror.assert_called_once_with(
|
self.mock_dest_client.break_snapmirror_vol.assert_called_once_with(
|
||||||
self.source_vserver, self.fake_src_vol_name,
|
self.source_vserver, self.fake_src_vol_name,
|
||||||
self.dest_vserver, self.fake_dest_vol_name)
|
self.dest_vserver, self.fake_dest_vol_name)
|
||||||
|
|
||||||
|
@ -376,7 +507,7 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.dm_session.quiesce_then_abort.assert_called_once_with(
|
self.dm_session.quiesce_then_abort.assert_called_once_with(
|
||||||
self.fake_src_share, self.fake_dest_share)
|
self.fake_src_share, self.fake_dest_share)
|
||||||
|
|
||||||
self.mock_dest_client.break_snapmirror.assert_called_once_with(
|
self.mock_dest_client.break_snapmirror_vol.assert_called_once_with(
|
||||||
self.source_vserver, self.fake_src_vol_name,
|
self.source_vserver, self.fake_src_vol_name,
|
||||||
self.dest_vserver, self.fake_dest_vol_name)
|
self.dest_vserver, self.fake_dest_vol_name)
|
||||||
|
|
||||||
|
@ -398,22 +529,54 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.fake_dest_share)
|
self.fake_dest_share)
|
||||||
|
|
||||||
self.mock_dest_client.get_snapmirrors.assert_called_with(
|
self.mock_dest_client.get_snapmirrors.assert_called_with(
|
||||||
self.source_vserver, self.fake_src_vol_name,
|
source_vserver=self.source_vserver,
|
||||||
self.dest_vserver, self.fake_dest_vol_name,
|
dest_vserver=self.dest_vserver,
|
||||||
|
source_volume=self.fake_src_vol_name,
|
||||||
|
dest_volume=self.fake_dest_vol_name,
|
||||||
desired_attributes=['relationship-status', 'mirror-state']
|
desired_attributes=['relationship-status', 'mirror-state']
|
||||||
)
|
)
|
||||||
self.assertEqual(2, self.mock_dest_client.get_snapmirrors.call_count)
|
self.assertEqual(2, self.mock_dest_client.get_snapmirrors.call_count)
|
||||||
|
|
||||||
self.mock_dest_client.quiesce_snapmirror.assert_called_with(
|
self.mock_dest_client.quiesce_snapmirror_vol.assert_called_with(
|
||||||
self.source_vserver, self.fake_src_vol_name,
|
self.source_vserver, self.fake_src_vol_name,
|
||||||
self.dest_vserver, self.fake_dest_vol_name)
|
self.dest_vserver, self.fake_dest_vol_name)
|
||||||
|
|
||||||
self.mock_dest_client.abort_snapmirror.assert_called_once_with(
|
self.mock_dest_client.abort_snapmirror_vol.assert_called_once_with(
|
||||||
self.source_vserver, self.fake_src_vol_name,
|
self.source_vserver, self.fake_src_vol_name,
|
||||||
self.dest_vserver, self.fake_dest_vol_name,
|
self.dest_vserver, self.fake_dest_vol_name,
|
||||||
clear_checkpoint=False
|
clear_checkpoint=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_quiesce_then_abort_svm_timeout(self):
|
||||||
|
self.mock_object(time, 'sleep')
|
||||||
|
mock_get_snapmirrors = mock.Mock(
|
||||||
|
return_value=[{'relationship-status': "transferring"}])
|
||||||
|
self.mock_object(self.mock_dest_client, 'get_snapmirrors_svm',
|
||||||
|
mock_get_snapmirrors)
|
||||||
|
mock_backend_config = na_fakes.create_configuration()
|
||||||
|
mock_backend_config.netapp_snapmirror_quiesce_timeout = 10
|
||||||
|
self.mock_object(data_motion, 'get_backend_configuration',
|
||||||
|
mock.Mock(return_value=mock_backend_config))
|
||||||
|
|
||||||
|
self.dm_session.quiesce_then_abort_svm(self.fake_src_share_server,
|
||||||
|
self.fake_dest_share_server)
|
||||||
|
|
||||||
|
self.mock_dest_client.get_snapmirrors_svm.assert_called_with(
|
||||||
|
source_vserver=self.source_vserver,
|
||||||
|
dest_vserver=self.dest_vserver,
|
||||||
|
desired_attributes=['relationship-status', 'mirror-state']
|
||||||
|
)
|
||||||
|
self.assertEqual(2,
|
||||||
|
self.mock_dest_client.get_snapmirrors_svm.call_count)
|
||||||
|
|
||||||
|
self.mock_dest_client.quiesce_snapmirror_svm.assert_called_with(
|
||||||
|
self.source_vserver, self.dest_vserver)
|
||||||
|
|
||||||
|
self.mock_dest_client.abort_snapmirror_svm.assert_called_once_with(
|
||||||
|
self.source_vserver, self.dest_vserver,
|
||||||
|
clear_checkpoint=False
|
||||||
|
)
|
||||||
|
|
||||||
def test_quiesce_then_abort_wait_for_quiesced(self):
|
def test_quiesce_then_abort_wait_for_quiesced(self):
|
||||||
self.mock_object(time, 'sleep')
|
self.mock_object(time, 'sleep')
|
||||||
self.mock_object(self.mock_dest_client, 'get_snapmirrors',
|
self.mock_object(self.mock_dest_client, 'get_snapmirrors',
|
||||||
|
@ -425,21 +588,44 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.fake_dest_share)
|
self.fake_dest_share)
|
||||||
|
|
||||||
self.mock_dest_client.get_snapmirrors.assert_called_with(
|
self.mock_dest_client.get_snapmirrors.assert_called_with(
|
||||||
self.source_vserver, self.fake_src_vol_name,
|
source_vserver=self.source_vserver,
|
||||||
self.dest_vserver, self.fake_dest_vol_name,
|
dest_vserver=self.dest_vserver,
|
||||||
|
source_volume=self.fake_src_vol_name,
|
||||||
|
dest_volume=self.fake_dest_vol_name,
|
||||||
desired_attributes=['relationship-status', 'mirror-state']
|
desired_attributes=['relationship-status', 'mirror-state']
|
||||||
)
|
)
|
||||||
self.assertEqual(2, self.mock_dest_client.get_snapmirrors.call_count)
|
self.assertEqual(2, self.mock_dest_client.get_snapmirrors.call_count)
|
||||||
|
|
||||||
self.mock_dest_client.quiesce_snapmirror.assert_called_once_with(
|
self.mock_dest_client.quiesce_snapmirror_vol.assert_called_once_with(
|
||||||
self.source_vserver, self.fake_src_vol_name,
|
self.source_vserver, self.fake_src_vol_name,
|
||||||
self.dest_vserver, self.fake_dest_vol_name)
|
self.dest_vserver, self.fake_dest_vol_name)
|
||||||
|
|
||||||
|
def test_quiesce_then_abort_svm_wait_for_quiesced(self):
|
||||||
|
self.mock_object(time, 'sleep')
|
||||||
|
self.mock_object(self.mock_dest_client, 'get_snapmirrors_svm',
|
||||||
|
mock.Mock(side_effect=[
|
||||||
|
[{'relationship-status': "transferring"}],
|
||||||
|
[{'relationship-status': "quiesced"}]]))
|
||||||
|
|
||||||
|
self.dm_session.quiesce_then_abort_svm(self.fake_src_share_server,
|
||||||
|
self.fake_dest_share_server)
|
||||||
|
|
||||||
|
self.mock_dest_client.get_snapmirrors_svm.assert_called_with(
|
||||||
|
source_vserver=self.source_vserver,
|
||||||
|
dest_vserver=self.dest_vserver,
|
||||||
|
desired_attributes=['relationship-status', 'mirror-state']
|
||||||
|
)
|
||||||
|
self.assertEqual(2,
|
||||||
|
self.mock_dest_client.get_snapmirrors_svm.call_count)
|
||||||
|
|
||||||
|
self.mock_dest_client.quiesce_snapmirror_svm.assert_called_once_with(
|
||||||
|
self.source_vserver, self.dest_vserver)
|
||||||
|
|
||||||
def test_resync_snapmirror(self):
|
def test_resync_snapmirror(self):
|
||||||
self.dm_session.resync_snapmirror(self.fake_src_share,
|
self.dm_session.resync_snapmirror(self.fake_src_share,
|
||||||
self.fake_dest_share)
|
self.fake_dest_share)
|
||||||
|
|
||||||
self.mock_dest_client.resync_snapmirror.assert_called_once_with(
|
self.mock_dest_client.resync_snapmirror_vol.assert_called_once_with(
|
||||||
self.source_vserver, self.fake_src_vol_name,
|
self.source_vserver, self.fake_src_vol_name,
|
||||||
self.dest_vserver, self.fake_dest_vol_name)
|
self.dest_vserver, self.fake_dest_vol_name)
|
||||||
|
|
||||||
|
@ -459,19 +645,19 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.fake_dest_share, self.fake_src_share, fake_new_src_share,
|
self.fake_dest_share, self.fake_src_share, fake_new_src_share,
|
||||||
[self.fake_dest_share, self.fake_src_share, fake_new_src_share])
|
[self.fake_dest_share, self.fake_src_share, fake_new_src_share])
|
||||||
|
|
||||||
self.assertFalse(self.mock_src_client.release_snapmirror.called)
|
self.assertFalse(self.mock_src_client.release_snapmirror_vol.called)
|
||||||
|
|
||||||
self.assertEqual(4, self.dm_session.delete_snapmirror.call_count)
|
self.assertEqual(4, self.dm_session.delete_snapmirror.call_count)
|
||||||
self.dm_session.delete_snapmirror.assert_called_with(
|
self.dm_session.delete_snapmirror.assert_called_with(
|
||||||
mock.ANY, mock.ANY, release=False
|
mock.ANY, mock.ANY, release=False
|
||||||
)
|
)
|
||||||
|
|
||||||
self.mock_dest_client.create_snapmirror.assert_called_once_with(
|
self.mock_dest_client.create_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, fake_new_src_share_name, mock.ANY,
|
mock.ANY, fake_new_src_share_name, mock.ANY,
|
||||||
self.fake_dest_vol_name, schedule='hourly'
|
self.fake_dest_vol_name, schedule='hourly'
|
||||||
)
|
)
|
||||||
|
|
||||||
self.mock_dest_client.resync_snapmirror.assert_called_once_with(
|
self.mock_dest_client.resync_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, fake_new_src_share_name, mock.ANY,
|
mock.ANY, fake_new_src_share_name, mock.ANY,
|
||||||
self.fake_dest_vol_name
|
self.fake_dest_vol_name
|
||||||
)
|
)
|
||||||
|
@ -517,11 +703,11 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.dm_session.delete_snapmirror.assert_called_with(
|
self.dm_session.delete_snapmirror.assert_called_with(
|
||||||
mock.ANY, mock.ANY, release=False
|
mock.ANY, mock.ANY, release=False
|
||||||
)
|
)
|
||||||
self.mock_dest_client.create_snapmirror.assert_called_once_with(
|
self.mock_dest_client.create_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, fake_new_src_share_name, mock.ANY,
|
mock.ANY, fake_new_src_share_name, mock.ANY,
|
||||||
self.fake_dest_vol_name, schedule='hourly'
|
self.fake_dest_vol_name, schedule='hourly'
|
||||||
)
|
)
|
||||||
self.mock_dest_client.resync_snapmirror.assert_called_once_with(
|
self.mock_dest_client.resync_snapmirror_vol.assert_called_once_with(
|
||||||
mock.ANY, fake_new_src_share_name, mock.ANY,
|
mock.ANY, fake_new_src_share_name, mock.ANY,
|
||||||
self.fake_dest_vol_name
|
self.fake_dest_vol_name
|
||||||
)
|
)
|
||||||
|
@ -533,8 +719,10 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
self.fake_dest_share)
|
self.fake_dest_share)
|
||||||
|
|
||||||
self.mock_dest_client.get_snapmirrors.assert_called_with(
|
self.mock_dest_client.get_snapmirrors.assert_called_with(
|
||||||
self.source_vserver, self.fake_src_vol_name,
|
source_vserver=self.source_vserver,
|
||||||
self.dest_vserver, self.fake_dest_vol_name,
|
dest_vserver=self.dest_vserver,
|
||||||
|
source_volume=self.fake_src_vol_name,
|
||||||
|
dest_volume=self.fake_dest_vol_name,
|
||||||
desired_attributes=['relationship-status',
|
desired_attributes=['relationship-status',
|
||||||
'mirror-state',
|
'mirror-state',
|
||||||
'source-vserver',
|
'source-vserver',
|
||||||
|
@ -543,23 +731,158 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(1, self.mock_dest_client.get_snapmirrors.call_count)
|
self.assertEqual(1, self.mock_dest_client.get_snapmirrors.call_count)
|
||||||
|
|
||||||
|
def test_get_snapmirrors_svm(self):
|
||||||
|
mock_dest_client = mock.Mock()
|
||||||
|
self.mock_object(self.dm_session, 'get_client_and_vserver_name',
|
||||||
|
mock.Mock(return_value=(mock_dest_client,
|
||||||
|
self.dest_vserver)))
|
||||||
|
self.mock_object(mock_dest_client, 'get_snapmirrors_svm')
|
||||||
|
|
||||||
|
self.dm_session.get_snapmirrors_svm(self.fake_src_share_server,
|
||||||
|
self.fake_dest_share_server)
|
||||||
|
|
||||||
|
mock_dest_client.get_snapmirrors_svm.assert_called_with(
|
||||||
|
source_vserver=self.source_vserver,
|
||||||
|
dest_vserver=self.dest_vserver,
|
||||||
|
desired_attributes=['relationship-status',
|
||||||
|
'mirror-state',
|
||||||
|
'last-transfer-end-timestamp']
|
||||||
|
)
|
||||||
|
self.assertEqual(1, mock_dest_client.get_snapmirrors_svm.call_count)
|
||||||
|
|
||||||
|
def test_get_snapmirror_destinations_svm(self):
|
||||||
|
mock_dest_client = mock.Mock()
|
||||||
|
self.mock_object(self.dm_session, 'get_client_and_vserver_name',
|
||||||
|
mock.Mock(return_value=(mock_dest_client,
|
||||||
|
self.dest_vserver)))
|
||||||
|
self.mock_object(mock_dest_client, 'get_snapmirror_destinations_svm')
|
||||||
|
|
||||||
|
self.dm_session.get_snapmirror_destinations_svm(
|
||||||
|
self.fake_src_share_server, self.fake_dest_share_server)
|
||||||
|
|
||||||
|
mock_dest_client.get_snapmirror_destinations_svm.assert_called_with(
|
||||||
|
source_vserver=self.source_vserver,
|
||||||
|
dest_vserver=self.dest_vserver,
|
||||||
|
)
|
||||||
|
self.assertEqual(1, mock_dest_client.get_snapmirror_destinations_svm
|
||||||
|
.call_count)
|
||||||
|
|
||||||
def test_update_snapmirror(self):
|
def test_update_snapmirror(self):
|
||||||
self.mock_object(self.mock_dest_client, 'get_snapmirrors')
|
self.mock_object(self.mock_dest_client, 'get_snapmirrors')
|
||||||
|
|
||||||
self.dm_session.update_snapmirror(self.fake_src_share,
|
self.dm_session.update_snapmirror(self.fake_src_share,
|
||||||
self.fake_dest_share)
|
self.fake_dest_share)
|
||||||
|
|
||||||
self.mock_dest_client.update_snapmirror.assert_called_once_with(
|
self.mock_dest_client.update_snapmirror_vol.assert_called_once_with(
|
||||||
self.source_vserver, self.fake_src_vol_name,
|
self.source_vserver, self.fake_src_vol_name,
|
||||||
self.dest_vserver, self.fake_dest_vol_name)
|
self.dest_vserver, self.fake_dest_vol_name)
|
||||||
|
|
||||||
|
def test_update_snapmirror_svm(self):
|
||||||
|
mock_dest_client = mock.Mock()
|
||||||
|
self.mock_object(self.dm_session, 'get_client_and_vserver_name',
|
||||||
|
mock.Mock(return_value=(mock_dest_client,
|
||||||
|
self.dest_vserver)))
|
||||||
|
|
||||||
|
self.dm_session.update_snapmirror_svm(self.fake_src_share_server,
|
||||||
|
self.fake_dest_share_server)
|
||||||
|
|
||||||
|
mock_dest_client.update_snapmirror_svm.assert_called_once_with(
|
||||||
|
self.source_vserver, self.dest_vserver)
|
||||||
|
|
||||||
|
def test_abort_and_break_snapmirror_svm(self):
|
||||||
|
mock_dest_client = mock.Mock()
|
||||||
|
self.mock_object(self.dm_session, 'get_client_and_vserver_name',
|
||||||
|
mock.Mock(return_value=(mock_dest_client,
|
||||||
|
self.dest_vserver)))
|
||||||
|
self.mock_object(self.dm_session, 'quiesce_then_abort_svm')
|
||||||
|
|
||||||
|
self.dm_session.quiesce_and_break_snapmirror_svm(
|
||||||
|
self.fake_src_share_server, self.fake_dest_share_server
|
||||||
|
)
|
||||||
|
|
||||||
|
self.dm_session.get_client_and_vserver_name.assert_called_once_with(
|
||||||
|
self.fake_dest_share_server
|
||||||
|
)
|
||||||
|
self.dm_session.quiesce_then_abort_svm.assert_called_once_with(
|
||||||
|
self.fake_src_share_server, self.fake_dest_share_server
|
||||||
|
)
|
||||||
|
mock_dest_client.break_snapmirror_svm(self.source_vserver,
|
||||||
|
self.dest_vserver)
|
||||||
|
|
||||||
|
@ddt.data({'snapmirrors': ['fake_snapmirror'],
|
||||||
|
'vserver_subtype': 'default'},
|
||||||
|
{'snapmirrors': [],
|
||||||
|
'vserver_subtype': 'default'},
|
||||||
|
{'snapmirrors': [],
|
||||||
|
'vserver_subtype': 'dp_destination'})
|
||||||
|
@ddt.unpack
|
||||||
|
def test_cancel_snapmirror_svm(self, snapmirrors, vserver_subtype):
|
||||||
|
mock_dest_client = mock.Mock()
|
||||||
|
self.mock_object(self.dm_session, 'get_client_and_vserver_name',
|
||||||
|
mock.Mock(return_value=(mock_dest_client,
|
||||||
|
self.dest_vserver)))
|
||||||
|
mock_backend_config = na_fakes.create_configuration()
|
||||||
|
mock_backend_config.netapp_server_migration_state_change_timeout = 30
|
||||||
|
self.mock_object(data_motion, 'get_backend_configuration',
|
||||||
|
mock.Mock(return_value=mock_backend_config))
|
||||||
|
self.mock_object(self.dm_session, 'get_snapmirrors_svm',
|
||||||
|
mock.Mock(return_value=snapmirrors))
|
||||||
|
self.mock_object(self.dm_session, 'quiesce_and_break_snapmirror_svm')
|
||||||
|
self.mock_object(self.dm_session, 'wait_for_vserver_state')
|
||||||
|
self.mock_object(self.dm_session, 'delete_snapmirror_svm')
|
||||||
|
vserver_info = copy.deepcopy(fake.VSERVER_INFO)
|
||||||
|
vserver_info['subtype'] = vserver_subtype
|
||||||
|
self.mock_object(mock_dest_client, 'get_vserver_info',
|
||||||
|
mock.Mock(return_value=vserver_info))
|
||||||
|
self.mock_object(self.dm_session, 'convert_svm_to_default_subtype')
|
||||||
|
|
||||||
|
self.dm_session.cancel_snapmirror_svm(self.fake_src_share_server,
|
||||||
|
self.fake_dest_share_server)
|
||||||
|
|
||||||
|
data_motion.get_backend_configuration.assert_called_once_with(
|
||||||
|
self.dest_backend_name
|
||||||
|
)
|
||||||
|
self.dm_session.get_client_and_vserver_name.assert_called_once_with(
|
||||||
|
self.fake_dest_share_server
|
||||||
|
)
|
||||||
|
self.dm_session.get_snapmirrors_svm.assert_called_once_with(
|
||||||
|
self.fake_src_share_server, self.fake_dest_share_server
|
||||||
|
)
|
||||||
|
if snapmirrors:
|
||||||
|
quiesce_mock = self.dm_session.quiesce_and_break_snapmirror_svm
|
||||||
|
quiesce_mock.assert_called_once_with(
|
||||||
|
self.fake_src_share_server, self.fake_dest_share_server
|
||||||
|
)
|
||||||
|
self.dm_session.wait_for_vserver_state.assert_called_once_with(
|
||||||
|
self.dest_vserver, mock_dest_client, subtype='default',
|
||||||
|
state='running', operational_state='stopped',
|
||||||
|
timeout=(mock_backend_config
|
||||||
|
.netapp_server_migration_state_change_timeout)
|
||||||
|
)
|
||||||
|
self.dm_session.delete_snapmirror_svm.assert_called_once_with(
|
||||||
|
self.fake_src_share_server, self.fake_dest_share_server
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
mock_dest_client.get_vserver_info.assert_called_once_with(
|
||||||
|
self.dest_vserver
|
||||||
|
)
|
||||||
|
convert_svm = self.dm_session.convert_svm_to_default_subtype
|
||||||
|
if vserver_subtype == 'dp_destination':
|
||||||
|
convert_svm.assert_called_once_with(
|
||||||
|
self.dest_vserver, mock_dest_client,
|
||||||
|
timeout=(mock_backend_config
|
||||||
|
.netapp_server_migration_state_change_timeout)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.assertFalse(convert_svm.called)
|
||||||
|
|
||||||
def test_resume_snapmirror(self):
|
def test_resume_snapmirror(self):
|
||||||
self.mock_object(self.mock_dest_client, 'get_snapmirrors')
|
self.mock_object(self.mock_dest_client, 'get_snapmirrors')
|
||||||
|
|
||||||
self.dm_session.resume_snapmirror(self.fake_src_share,
|
self.dm_session.resume_snapmirror(self.fake_src_share,
|
||||||
self.fake_dest_share)
|
self.fake_dest_share)
|
||||||
|
|
||||||
self.mock_dest_client.resume_snapmirror.assert_called_once_with(
|
self.mock_dest_client.resume_snapmirror_vol.assert_called_once_with(
|
||||||
self.source_vserver, self.fake_src_vol_name,
|
self.source_vserver, self.fake_src_vol_name,
|
||||||
self.dest_vserver, self.fake_dest_vol_name)
|
self.dest_vserver, self.fake_dest_vol_name)
|
||||||
|
|
||||||
|
@ -601,3 +924,127 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||||
(mock_source_client.set_qos_policy_group_for_volume
|
(mock_source_client.set_qos_policy_group_for_volume
|
||||||
.assert_called_once_with(self.fake_src_vol_name, 'none'))
|
.assert_called_once_with(self.fake_src_vol_name, 'none'))
|
||||||
data_motion.LOG.exception.assert_not_called()
|
data_motion.LOG.exception.assert_not_called()
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_convert_svm_to_default_subtype(self, is_dest):
|
||||||
|
mock_client = mock.Mock()
|
||||||
|
vserver_info_default = copy.deepcopy(fake.VSERVER_INFO)
|
||||||
|
vserver_info_default['subtype'] = 'default'
|
||||||
|
vserver_info_dp = copy.deepcopy(fake.VSERVER_INFO)
|
||||||
|
vserver_info_dp['subtype'] = 'dp_destination'
|
||||||
|
self.mock_object(mock_client, 'get_vserver_info',
|
||||||
|
mock.Mock(side_effect=[vserver_info_dp,
|
||||||
|
vserver_info_default]))
|
||||||
|
self.mock_object(mock_client, 'break_snapmirror_svm')
|
||||||
|
|
||||||
|
self.dm_session.convert_svm_to_default_subtype(fake.VSERVER1,
|
||||||
|
mock_client,
|
||||||
|
is_dest_path=is_dest,
|
||||||
|
timeout=20)
|
||||||
|
|
||||||
|
mock_client.get_vserver_info.assert_has_calls([
|
||||||
|
mock.call(fake.VSERVER1), mock.call(fake.VSERVER1)])
|
||||||
|
if is_dest:
|
||||||
|
mock_client.break_snapmirror_svm.assert_called_once_with(
|
||||||
|
dest_vserver=fake.VSERVER1
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
mock_client.break_snapmirror_svm.assert_called_once_with(
|
||||||
|
source_vserver=fake.VSERVER1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_convert_svm_to_default_subtype_timeout(self):
|
||||||
|
mock_client = mock.Mock()
|
||||||
|
vserver_info_dp = copy.deepcopy(fake.VSERVER_INFO)
|
||||||
|
vserver_info_dp['subtype'] = 'dp_destination'
|
||||||
|
self.mock_object(mock_client, 'get_vserver_info',
|
||||||
|
mock.Mock(side_effect=[vserver_info_dp]))
|
||||||
|
self.mock_object(mock_client, 'break_snapmirror_svm')
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exception.NetAppException,
|
||||||
|
self.dm_session.convert_svm_to_default_subtype,
|
||||||
|
fake.VSERVER1, mock_client, is_dest_path=True, timeout=10)
|
||||||
|
|
||||||
|
mock_client.get_vserver_info.assert_called_once_with(fake.VSERVER1)
|
||||||
|
mock_client.break_snapmirror_svm.assert_called_once_with(
|
||||||
|
dest_vserver=fake.VSERVER1)
|
||||||
|
|
||||||
|
def test_wait_for_vserver_state(self,):
|
||||||
|
mock_client = mock.Mock()
|
||||||
|
vserver_info_default = copy.deepcopy(fake.VSERVER_INFO)
|
||||||
|
vserver_info_default['subtype'] = 'default'
|
||||||
|
vserver_info_dp = copy.deepcopy(fake.VSERVER_INFO)
|
||||||
|
vserver_info_dp['subtype'] = 'dp_destination'
|
||||||
|
self.mock_object(mock_client, 'get_vserver_info',
|
||||||
|
mock.Mock(side_effect=[vserver_info_dp,
|
||||||
|
vserver_info_default]))
|
||||||
|
|
||||||
|
self.dm_session.wait_for_vserver_state(fake.VSERVER1, mock_client,
|
||||||
|
state='running',
|
||||||
|
operational_state='running',
|
||||||
|
subtype='default', timeout=20)
|
||||||
|
|
||||||
|
mock_client.get_vserver_info.assert_has_calls([
|
||||||
|
mock.call(fake.VSERVER1), mock.call(fake.VSERVER1)])
|
||||||
|
|
||||||
|
def test_wait_for_vserver_state_timeout(self):
|
||||||
|
mock_client = mock.Mock()
|
||||||
|
vserver_info_dp = copy.deepcopy(fake.VSERVER_INFO)
|
||||||
|
vserver_info_dp['subtype'] = 'dp_destination'
|
||||||
|
self.mock_object(mock_client, 'get_vserver_info',
|
||||||
|
mock.Mock(side_effect=[vserver_info_dp]))
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exception.NetAppException,
|
||||||
|
self.dm_session.wait_for_vserver_state,
|
||||||
|
fake.VSERVER1, mock_client, state='running',
|
||||||
|
operational_state='running', subtype='default', timeout=10)
|
||||||
|
|
||||||
|
mock_client.get_vserver_info.assert_called_once_with(fake.VSERVER1)
|
||||||
|
|
||||||
|
@ddt.data(mock.Mock(),
|
||||||
|
mock.Mock(side_effect=netapp_api.NaApiError(
|
||||||
|
code=netapp_api.EOBJECTNOTFOUND)))
|
||||||
|
def test_wait_for_snapmirror_release_svm(self, release_snapmirror_ret):
|
||||||
|
src_mock_client = mock.Mock()
|
||||||
|
get_snapmirrors_mock = self.mock_object(
|
||||||
|
src_mock_client, 'get_snapmirror_destinations_svm',
|
||||||
|
mock.Mock(side_effect=[['fake_snapmirror'], []]))
|
||||||
|
self.mock_object(src_mock_client, 'release_snapmirror_svm',
|
||||||
|
release_snapmirror_ret)
|
||||||
|
|
||||||
|
self.dm_session.wait_for_snapmirror_release_svm(fake.VSERVER1,
|
||||||
|
fake.VSERVER2,
|
||||||
|
src_mock_client,
|
||||||
|
timeout=20)
|
||||||
|
get_snapmirrors_mock.assert_has_calls([
|
||||||
|
mock.call(source_vserver=fake.VSERVER1,
|
||||||
|
dest_vserver=fake.VSERVER2),
|
||||||
|
mock.call(source_vserver=fake.VSERVER1,
|
||||||
|
dest_vserver=fake.VSERVER2)])
|
||||||
|
if release_snapmirror_ret.side_effect is None:
|
||||||
|
src_mock_client.release_snapmirror_svm.assert_called_once_with(
|
||||||
|
fake.VSERVER1, fake.VSERVER2)
|
||||||
|
else:
|
||||||
|
src_mock_client.release_snapmirror_svm.assert_called_once_with(
|
||||||
|
fake.VSERVER1, fake.VSERVER2
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_wait_for_snapmirror_release_svm_timeout(self):
|
||||||
|
src_mock_client = mock.Mock()
|
||||||
|
get_snapmirrors_mock = self.mock_object(
|
||||||
|
src_mock_client, 'get_snapmirror_destinations_svm',
|
||||||
|
mock.Mock(side_effect=[['fake_snapmirror']]))
|
||||||
|
self.mock_object(src_mock_client, 'release_snapmirror_svm')
|
||||||
|
|
||||||
|
self.assertRaises(exception.NetAppException,
|
||||||
|
self.dm_session.wait_for_snapmirror_release_svm,
|
||||||
|
fake.VSERVER1, fake.VSERVER2,
|
||||||
|
src_mock_client, timeout=10)
|
||||||
|
|
||||||
|
get_snapmirrors_mock.assert_called_once_with(
|
||||||
|
source_vserver=fake.VSERVER1, dest_vserver=fake.VSERVER2)
|
||||||
|
src_mock_client.release_snapmirror_svm.assert_called_once_with(
|
||||||
|
fake.VSERVER1, fake.VSERVER2
|
||||||
|
)
|
||||||
|
|
|
@ -313,6 +313,13 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||||
|
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test__get_backend_snapmirror_policy_name_svm(self):
|
||||||
|
result = self.library._get_backend_snapmirror_policy_name_svm(
|
||||||
|
fake.SERVER_ID)
|
||||||
|
expected = 'snapmirror_policy_' + fake.SERVER_ID.replace('-', '_')
|
||||||
|
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
def test_get_aggregate_space_cluster_creds(self):
|
def test_get_aggregate_space_cluster_creds(self):
|
||||||
|
|
||||||
self.library._have_cluster_creds = True
|
self.library._have_cluster_creds = True
|
||||||
|
@ -1804,7 +1811,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||||
mock_get_export_addresses_with_metadata.assert_called_once_with(
|
mock_get_export_addresses_with_metadata.assert_called_once_with(
|
||||||
fake.SHARE, fake.SHARE_SERVER, fake.LIFS)
|
fake.SHARE, fake.SHARE_SERVER, fake.LIFS)
|
||||||
protocol_helper.create_share.assert_called_once_with(
|
protocol_helper.create_share.assert_called_once_with(
|
||||||
fake.SHARE, fake.SHARE_NAME, clear_current_export_policy=True)
|
fake.SHARE, fake.SHARE_NAME, clear_current_export_policy=True,
|
||||||
|
ensure_share_already_exists=False)
|
||||||
|
|
||||||
def test_create_export_lifs_not_found(self):
|
def test_create_export_lifs_not_found(self):
|
||||||
|
|
||||||
|
@ -3117,8 +3125,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||||
share_server=None)
|
share_server=None)
|
||||||
|
|
||||||
self.assertDictMatch(expected_model_update, model_update)
|
self.assertDictMatch(expected_model_update, model_update)
|
||||||
mock_dm_session.create_snapmirror.assert_called_once_with(fake.SHARE,
|
mock_dm_session.create_snapmirror.assert_called_once_with(
|
||||||
fake.SHARE)
|
fake.SHARE, fake.SHARE)
|
||||||
data_motion.get_client_for_backend.assert_called_once_with(
|
data_motion.get_client_for_backend.assert_called_once_with(
|
||||||
fake.BACKEND_NAME, vserver_name=fake.VSERVER1)
|
fake.BACKEND_NAME, vserver_name=fake.VSERVER1)
|
||||||
|
|
||||||
|
@ -3144,8 +3152,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||||
share_server=fake.SHARE_SERVER)
|
share_server=fake.SHARE_SERVER)
|
||||||
|
|
||||||
self.assertDictMatch(expected_model_update, model_update)
|
self.assertDictMatch(expected_model_update, model_update)
|
||||||
mock_dm_session.create_snapmirror.assert_called_once_with(fake.SHARE,
|
mock_dm_session.create_snapmirror.assert_called_once_with(
|
||||||
fake.SHARE)
|
fake.SHARE, fake.SHARE)
|
||||||
data_motion.get_client_for_backend.assert_called_once_with(
|
data_motion.get_client_for_backend.assert_called_once_with(
|
||||||
fake.BACKEND_NAME, vserver_name=fake.VSERVER1)
|
fake.BACKEND_NAME, vserver_name=fake.VSERVER1)
|
||||||
|
|
||||||
|
@ -3339,7 +3347,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||||
result = self.library.update_replica_state(
|
result = self.library.update_replica_state(
|
||||||
None, [replica], replica, None, [], share_server=None)
|
None, [replica], replica, None, [], share_server=None)
|
||||||
|
|
||||||
self.assertEqual(1, self.mock_dm_session.create_snapmirror.call_count)
|
self.assertEqual(1,
|
||||||
|
self.mock_dm_session.create_snapmirror.call_count)
|
||||||
self.assertEqual(constants.STATUS_OUT_OF_SYNC, result)
|
self.assertEqual(constants.STATUS_OUT_OF_SYNC, result)
|
||||||
|
|
||||||
def test_update_replica_state_broken_snapmirror(self):
|
def test_update_replica_state_broken_snapmirror(self):
|
||||||
|
@ -3364,7 +3373,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||||
fake.SHARE, None, [],
|
fake.SHARE, None, [],
|
||||||
share_server=None)
|
share_server=None)
|
||||||
|
|
||||||
vserver_client.resync_snapmirror.assert_called_once_with(
|
vserver_client.resync_snapmirror_vol.assert_called_once_with(
|
||||||
fake.VSERVER2, 'fake_volume', fake.VSERVER1, fake.SHARE['name']
|
fake.VSERVER2, 'fake_volume', fake.VSERVER1, fake.SHARE['name']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3428,13 +3437,14 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||||
vserver_client)))
|
vserver_client)))
|
||||||
self.mock_dm_session.get_snapmirrors = mock.Mock(
|
self.mock_dm_session.get_snapmirrors = mock.Mock(
|
||||||
return_value=[fake_snapmirror])
|
return_value=[fake_snapmirror])
|
||||||
vserver_client.resync_snapmirror.side_effect = netapp_api.NaApiError
|
vserver_client.resync_snapmirror_vol.side_effect = (
|
||||||
|
netapp_api.NaApiError)
|
||||||
|
|
||||||
result = self.library.update_replica_state(None, [fake.SHARE],
|
result = self.library.update_replica_state(None, [fake.SHARE],
|
||||||
fake.SHARE, None, [],
|
fake.SHARE, None, [],
|
||||||
share_server=None)
|
share_server=None)
|
||||||
|
|
||||||
vserver_client.resync_snapmirror.assert_called_once_with(
|
vserver_client.resync_snapmirror_vol.assert_called_once_with(
|
||||||
fake.VSERVER2, 'fake_volume', fake.VSERVER1, fake.SHARE['name']
|
fake.VSERVER2, 'fake_volume', fake.VSERVER1, fake.SHARE['name']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5820,3 +5830,37 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||||
fake.SHARE_SERVER,
|
fake.SHARE_SERVER,
|
||||||
cutover_action)
|
cutover_action)
|
||||||
mock_warning_log.assert_not_called()
|
mock_warning_log.assert_not_called()
|
||||||
|
|
||||||
|
@ddt.data({'total': 20, 'free': 5, 'reserved': 10, 'thin': False,
|
||||||
|
'over_sub': 0, 'size': 3, 'compatible': True, 'nb_pools': 1},
|
||||||
|
{'total': 20, 'free': 5, 'reserved': 10, 'thin': False,
|
||||||
|
'over_sub': 0, 'size': 4, 'compatible': False, 'nb_pools': 1},
|
||||||
|
{'total': 20, 'free': 5, 'reserved': 20, 'thin': False,
|
||||||
|
'over_sub': 1.1, 'size': 3, 'compatible': False, 'nb_pools': 1},
|
||||||
|
{'total': 20, 'free': 5, 'reserved': 10, 'thin': True,
|
||||||
|
'over_sub': 2.0, 'size': 6, 'compatible': True, 'nb_pools': 1},
|
||||||
|
{'total': 20, 'free': 5, 'reserved': 10, 'thin': True,
|
||||||
|
'over_sub': 1.0, 'size': 4, 'compatible': False, 'nb_pools': 1},
|
||||||
|
{'total': 'unknown', 'free': 5, 'reserved': 0, 'thin': False,
|
||||||
|
'over_sub': 3.0, 'size': 1, 'compatible': False, 'nb_pools': 1},
|
||||||
|
{'total': 20, 'free': 5, 'reserved': 10, 'thin': True,
|
||||||
|
'over_sub': 1.0, 'size': 6, 'compatible': True, 'nb_pools': 2},
|
||||||
|
{'total': 20, 'free': 5, 'reserved': 10, 'thin': True,
|
||||||
|
'over_sub': 1.0, 'size': 7, 'compatible': False, 'nb_pools': 2},
|
||||||
|
)
|
||||||
|
@ddt.unpack
|
||||||
|
def test__check_capacity_compatibility(self, total, free, reserved, thin,
|
||||||
|
over_sub, size, compatible,
|
||||||
|
nb_pools):
|
||||||
|
pools = []
|
||||||
|
for p in range(nb_pools):
|
||||||
|
pool = copy.deepcopy(fake.POOLS[0])
|
||||||
|
pool['total_capacity_gb'] = total
|
||||||
|
pool['free_capacity_gb'] = free
|
||||||
|
pool['reserved_percentage'] = reserved
|
||||||
|
pool['max_over_subscription_ratio'] = over_sub
|
||||||
|
pools.append(pool)
|
||||||
|
|
||||||
|
result = self.library._check_capacity_compatibility(pools, thin, size)
|
||||||
|
|
||||||
|
self.assertEqual(compatible, result)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -58,6 +58,7 @@ CONSISTENCY_GROUP_ID2 = '35f5c1ea-45fb-40c4-98ae-2a2a17554159'
|
||||||
CG_SNAPSHOT_ID = '6ddd8a6b-5df7-417b-a2ae-3f6e449f4eea'
|
CG_SNAPSHOT_ID = '6ddd8a6b-5df7-417b-a2ae-3f6e449f4eea'
|
||||||
CG_SNAPSHOT_MEMBER_ID1 = '629f79ef-b27e-4596-9737-30f084e5ba29'
|
CG_SNAPSHOT_MEMBER_ID1 = '629f79ef-b27e-4596-9737-30f084e5ba29'
|
||||||
CG_SNAPSHOT_MEMBER_ID2 = 'e876aa9c-a322-4391-bd88-9266178262be'
|
CG_SNAPSHOT_MEMBER_ID2 = 'e876aa9c-a322-4391-bd88-9266178262be'
|
||||||
|
SERVER_ID = 'd5e90724-6f28-4944-858a-553138bdbd29'
|
||||||
FREE_CAPACITY = 10000000000
|
FREE_CAPACITY = 10000000000
|
||||||
TOTAL_CAPACITY = 20000000000
|
TOTAL_CAPACITY = 20000000000
|
||||||
AGGREGATE = 'manila_aggr_1'
|
AGGREGATE = 'manila_aggr_1'
|
||||||
|
@ -71,6 +72,7 @@ NODE_DATA_PORT = 'e0c'
|
||||||
NODE_DATA_PORTS = ('e0c', 'e0d')
|
NODE_DATA_PORTS = ('e0c', 'e0d')
|
||||||
LIF_NAME_TEMPLATE = 'os_%(net_allocation_id)s'
|
LIF_NAME_TEMPLATE = 'os_%(net_allocation_id)s'
|
||||||
SHARE_TYPE_ID = '26e89a5b-960b-46bb-a8cf-0778e653098f'
|
SHARE_TYPE_ID = '26e89a5b-960b-46bb-a8cf-0778e653098f'
|
||||||
|
SHARE_TYPE_ID_2 = '2a06887e-25b5-486e-804a-d84c2d806feb'
|
||||||
SHARE_TYPE_NAME = 'fake_share_type'
|
SHARE_TYPE_NAME = 'fake_share_type'
|
||||||
IPSPACE = 'fake_ipspace'
|
IPSPACE = 'fake_ipspace'
|
||||||
IPSPACE_ID = '27d38c27-3e8b-4d7d-9d91-fcf295e3ac8f'
|
IPSPACE_ID = '27d38c27-3e8b-4d7d-9d91-fcf295e3ac8f'
|
||||||
|
@ -82,6 +84,10 @@ MANILA_HOST_NAME_2 = '%(host)s@%(backend)s#%(pool)s' % {
|
||||||
'host': HOST_NAME, 'backend': BACKEND_NAME, 'pool': POOL_NAME_2}
|
'host': HOST_NAME, 'backend': BACKEND_NAME, 'pool': POOL_NAME_2}
|
||||||
MANILA_HOST_NAME_3 = '%(host)s@%(backend)s#%(pool)s' % {
|
MANILA_HOST_NAME_3 = '%(host)s@%(backend)s#%(pool)s' % {
|
||||||
'host': HOST_NAME, 'backend': BACKEND_NAME_2, 'pool': POOL_NAME_2}
|
'host': HOST_NAME, 'backend': BACKEND_NAME_2, 'pool': POOL_NAME_2}
|
||||||
|
SERVER_HOST = '%(host)s@%(backend)s' % {
|
||||||
|
'host': HOST_NAME, 'backend': BACKEND_NAME}
|
||||||
|
SERVER_HOST_2 = '%(host)s@%(backend)s' % {
|
||||||
|
'host': HOST_NAME, 'backend': BACKEND_NAME_2}
|
||||||
QOS_EXTRA_SPEC = 'netapp:maxiops'
|
QOS_EXTRA_SPEC = 'netapp:maxiops'
|
||||||
QOS_SIZE_DEPENDENT_EXTRA_SPEC = 'netapp:maxbpspergib'
|
QOS_SIZE_DEPENDENT_EXTRA_SPEC = 'netapp:maxbpspergib'
|
||||||
QOS_NORMALIZED_SPEC = 'maxiops'
|
QOS_NORMALIZED_SPEC = 'maxiops'
|
||||||
|
@ -163,6 +169,15 @@ FLEXVOL = {
|
||||||
'owning-vserver-name': VSERVER1,
|
'owning-vserver-name': VSERVER1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SHARE_TYPE_EXTRA_SPEC = {
|
||||||
|
'snapshot_support': True,
|
||||||
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': True,
|
||||||
|
'mount_snapshot_support': False,
|
||||||
|
'driver_handles_share_servers': True,
|
||||||
|
'availability_zones': [],
|
||||||
|
}
|
||||||
|
|
||||||
EXTRA_SPEC = {
|
EXTRA_SPEC = {
|
||||||
'netapp:thin_provisioned': 'true',
|
'netapp:thin_provisioned': 'true',
|
||||||
'netapp:snapshot_policy': 'default',
|
'netapp:snapshot_policy': 'default',
|
||||||
|
@ -438,6 +453,7 @@ SHARE_SERVER = {
|
||||||
},
|
},
|
||||||
'network_allocations': (USER_NETWORK_ALLOCATIONS +
|
'network_allocations': (USER_NETWORK_ALLOCATIONS +
|
||||||
ADMIN_NETWORK_ALLOCATIONS),
|
ADMIN_NETWORK_ALLOCATIONS),
|
||||||
|
'host': SERVER_HOST,
|
||||||
}
|
}
|
||||||
|
|
||||||
SHARE_SERVER_2 = {
|
SHARE_SERVER_2 = {
|
||||||
|
@ -448,6 +464,14 @@ SHARE_SERVER_2 = {
|
||||||
},
|
},
|
||||||
'network_allocations': (USER_NETWORK_ALLOCATIONS +
|
'network_allocations': (USER_NETWORK_ALLOCATIONS +
|
||||||
ADMIN_NETWORK_ALLOCATIONS),
|
ADMIN_NETWORK_ALLOCATIONS),
|
||||||
|
'host': SERVER_HOST_2,
|
||||||
|
}
|
||||||
|
|
||||||
|
VSERVER_INFO = {
|
||||||
|
'name': 'fake_vserver_name',
|
||||||
|
'subtype': 'default',
|
||||||
|
'operational_state': 'running',
|
||||||
|
'state': 'running',
|
||||||
}
|
}
|
||||||
|
|
||||||
SHARE_SERVER_NFS_TCP = {
|
SHARE_SERVER_NFS_TCP = {
|
||||||
|
@ -456,6 +480,7 @@ SHARE_SERVER_NFS_TCP = {
|
||||||
'vserver_name': VSERVER2,
|
'vserver_name': VSERVER2,
|
||||||
'nfs_config': jsonutils.dumps(NFS_CONFIG_TCP_MAX),
|
'nfs_config': jsonutils.dumps(NFS_CONFIG_TCP_MAX),
|
||||||
},
|
},
|
||||||
|
'host': 'fake_host@fake_backend',
|
||||||
}
|
}
|
||||||
|
|
||||||
SHARE_SERVER_NFS_UDP = {
|
SHARE_SERVER_NFS_UDP = {
|
||||||
|
@ -464,6 +489,7 @@ SHARE_SERVER_NFS_UDP = {
|
||||||
'vserver_name': VSERVER2,
|
'vserver_name': VSERVER2,
|
||||||
'nfs_config': jsonutils.dumps(NFS_CONFIG_UDP_MAX),
|
'nfs_config': jsonutils.dumps(NFS_CONFIG_UDP_MAX),
|
||||||
},
|
},
|
||||||
|
'host': 'fake_host@fake_backend',
|
||||||
}
|
}
|
||||||
|
|
||||||
SHARE_SERVER_NFS_TCP_UDP = {
|
SHARE_SERVER_NFS_TCP_UDP = {
|
||||||
|
@ -472,6 +498,7 @@ SHARE_SERVER_NFS_TCP_UDP = {
|
||||||
'vserver_name': VSERVER2,
|
'vserver_name': VSERVER2,
|
||||||
'nfs_config': jsonutils.dumps(NFS_CONFIG_TCP_UDP_MAX),
|
'nfs_config': jsonutils.dumps(NFS_CONFIG_TCP_UDP_MAX),
|
||||||
},
|
},
|
||||||
|
'host': 'fake_host@fake_backend',
|
||||||
}
|
}
|
||||||
|
|
||||||
SHARE_SERVER_NO_NFS_NONE = {
|
SHARE_SERVER_NO_NFS_NONE = {
|
||||||
|
@ -479,10 +506,12 @@ SHARE_SERVER_NO_NFS_NONE = {
|
||||||
'backend_details': {
|
'backend_details': {
|
||||||
'vserver_name': VSERVER2,
|
'vserver_name': VSERVER2,
|
||||||
},
|
},
|
||||||
|
'host': 'fake_host@fake_backend',
|
||||||
}
|
}
|
||||||
|
|
||||||
SHARE_SERVER_NO_DETAILS = {
|
SHARE_SERVER_NO_DETAILS = {
|
||||||
'id': 'id_no_datails',
|
'id': 'id_no_datails',
|
||||||
|
'host': 'fake_host@fake_backend',
|
||||||
}
|
}
|
||||||
|
|
||||||
SHARE_SERVER_NFS_DEFAULT = {
|
SHARE_SERVER_NFS_DEFAULT = {
|
||||||
|
@ -491,6 +520,7 @@ SHARE_SERVER_NFS_DEFAULT = {
|
||||||
'vserver_name': VSERVER2,
|
'vserver_name': VSERVER2,
|
||||||
'nfs_config': jsonutils.dumps(NFS_CONFIG_DEFAULT),
|
'nfs_config': jsonutils.dumps(NFS_CONFIG_DEFAULT),
|
||||||
},
|
},
|
||||||
|
'host': 'fake_host@fake_backend',
|
||||||
}
|
}
|
||||||
|
|
||||||
SHARE_SERVERS = [
|
SHARE_SERVERS = [
|
||||||
|
@ -1441,6 +1471,100 @@ EXPANDED_PROCESSOR_COUNTERS = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
SERVER_MIGRATION_CHECK_NOT_COMPATIBLE = {
|
||||||
|
'compatible': False,
|
||||||
|
'writable': None,
|
||||||
|
'nondisruptive': None,
|
||||||
|
'preserve_snapshots': None,
|
||||||
|
'migration_cancel': None,
|
||||||
|
'migration_get_progress': None,
|
||||||
|
'share_network_id': None
|
||||||
|
}
|
||||||
|
|
||||||
|
CIFS_SECURITY_SERVICE = {
|
||||||
|
'id': 'fake_id',
|
||||||
|
'type': 'active_directory',
|
||||||
|
'password': 'fake_password',
|
||||||
|
'user': 'fake_user',
|
||||||
|
'ou': 'fake_ou',
|
||||||
|
'domain': 'fake_domain',
|
||||||
|
'dns_ip': 'fake_dns_ip',
|
||||||
|
'server': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
SHARE_NETWORK_SUBNET = {
|
||||||
|
'id': 'fake_share_net_subnet_d',
|
||||||
|
'neutron_subnet_id': '34950f50-a142-4328-8026-418ad4410b09',
|
||||||
|
'neutron_net_id': 'fa202676-531a-4446-bc0c-bcec15a72e82',
|
||||||
|
'network_type': 'fake_network_type',
|
||||||
|
'segmentation_id': 1234,
|
||||||
|
'ip_version': 4,
|
||||||
|
'cidr': 'fake_cidr',
|
||||||
|
'gateway': 'fake_gateway',
|
||||||
|
'mtu': 1509,
|
||||||
|
}
|
||||||
|
|
||||||
|
SHARE_NETWORK = {
|
||||||
|
'id': 'fake_share_net_id',
|
||||||
|
'project_id': 'fake_project_id',
|
||||||
|
'status': 'fake_status',
|
||||||
|
'name': 'fake_name',
|
||||||
|
'description': 'fake_description',
|
||||||
|
'security_services': [CIFS_SECURITY_SERVICE],
|
||||||
|
'subnets': [SHARE_NETWORK_SUBNET],
|
||||||
|
}
|
||||||
|
|
||||||
|
SHARE_TYPE_2 = copy.deepcopy(SHARE_TYPE)
|
||||||
|
SHARE_TYPE_2['id'] = SHARE_TYPE_ID_2
|
||||||
|
SHARE_TYPE_2['extra_specs'].update(SHARE_TYPE_EXTRA_SPEC)
|
||||||
|
|
||||||
|
SHARE_REQ_SPEC = {
|
||||||
|
'share_properties': {
|
||||||
|
'size': SHARE['size'],
|
||||||
|
'project_id': SHARE['project_id'],
|
||||||
|
'snapshot_support': SHARE_TYPE_EXTRA_SPEC['snapshot_support'],
|
||||||
|
'create_share_from_snapshot_support':
|
||||||
|
SHARE_TYPE_EXTRA_SPEC['create_share_from_snapshot_support'],
|
||||||
|
'revert_to_snapshot_support':
|
||||||
|
SHARE_TYPE_EXTRA_SPEC['revert_to_snapshot_support'],
|
||||||
|
'mount_snapshot_support':
|
||||||
|
SHARE_TYPE_EXTRA_SPEC['mount_snapshot_support'],
|
||||||
|
'share_proto': SHARE['share_proto'],
|
||||||
|
'share_type_id': SHARE_TYPE_2['id'],
|
||||||
|
'is_public': True,
|
||||||
|
'share_group_id': None,
|
||||||
|
'source_share_group_snapshot_member_id': None,
|
||||||
|
'snapshot_id': None,
|
||||||
|
},
|
||||||
|
'share_instance_properties': {
|
||||||
|
'availability_zone_id': 'fake_az_1',
|
||||||
|
'share_network_id': SHARE_NETWORK['id'],
|
||||||
|
'share_server_id': SHARE_SERVER['id'],
|
||||||
|
'share_id': SHARE_ID,
|
||||||
|
'host': SHARE_INSTANCE['host'],
|
||||||
|
'status': SHARE_INSTANCE['status'],
|
||||||
|
},
|
||||||
|
'share_type': SHARE_TYPE_2,
|
||||||
|
'share_id': SHARE_ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
SERVER_MIGRATION_REQUEST_SPEC = {
|
||||||
|
'shares_size': 10,
|
||||||
|
'snapshots_size': 10,
|
||||||
|
'shares_req_spec': [SHARE_REQ_SPEC],
|
||||||
|
}
|
||||||
|
|
||||||
|
CLIENT_GET_VOLUME_RESPONSE = {
|
||||||
|
'aggregate': AGGREGATE,
|
||||||
|
'junction-path': '/%s' % SHARE_NAME,
|
||||||
|
'name': SHARE_NAME,
|
||||||
|
'type': 'rw',
|
||||||
|
'style': 'flex',
|
||||||
|
'size': SHARE_SIZE,
|
||||||
|
'owning-vserver-name': VSERVER1,
|
||||||
|
'qos-policy-group-name': QOS_POLICY_GROUP_NAME,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_config_cmode():
|
def get_config_cmode():
|
||||||
config = na_fakes.create_configuration_cmode()
|
config = na_fakes.create_configuration_cmode()
|
||||||
|
|
|
@ -40,9 +40,14 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase):
|
||||||
self.helper = cifs_cmode.NetAppCmodeCIFSHelper()
|
self.helper = cifs_cmode.NetAppCmodeCIFSHelper()
|
||||||
self.helper.set_client(self.mock_client)
|
self.helper.set_client(self.mock_client)
|
||||||
|
|
||||||
def test_create_share(self):
|
@ddt.data({'clear_export_policy': True, 'ensure_share_exists': False},
|
||||||
|
{'clear_export_policy': False, 'ensure_share_exists': True})
|
||||||
result = self.helper.create_share(fake.CIFS_SHARE, fake.SHARE_NAME)
|
@ddt.unpack
|
||||||
|
def test_create_share(self, clear_export_policy, ensure_share_exists):
|
||||||
|
result = self.helper.create_share(
|
||||||
|
fake.CIFS_SHARE, fake.SHARE_NAME,
|
||||||
|
clear_current_export_policy=clear_export_policy,
|
||||||
|
ensure_share_already_exists=ensure_share_exists)
|
||||||
|
|
||||||
export_addresses = [fake.SHARE_ADDRESS_1, fake.SHARE_ADDRESS_2]
|
export_addresses = [fake.SHARE_ADDRESS_1, fake.SHARE_ADDRESS_2]
|
||||||
export_paths = [result(address) for address in export_addresses]
|
export_paths = [result(address) for address in export_addresses]
|
||||||
|
@ -51,10 +56,17 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase):
|
||||||
r'\\%s\%s' % (fake.SHARE_ADDRESS_2, fake.SHARE_NAME),
|
r'\\%s\%s' % (fake.SHARE_ADDRESS_2, fake.SHARE_NAME),
|
||||||
]
|
]
|
||||||
self.assertEqual(expected_paths, export_paths)
|
self.assertEqual(expected_paths, export_paths)
|
||||||
self.mock_client.create_cifs_share.assert_called_once_with(
|
if ensure_share_exists:
|
||||||
fake.SHARE_NAME)
|
self.mock_client.cifs_share_exists.assert_called_once_with(
|
||||||
self.mock_client.remove_cifs_share_access.assert_called_once_with(
|
fake.SHARE_NAME)
|
||||||
fake.SHARE_NAME, 'Everyone')
|
self.mock_client.create_cifs_share.assert_not_called()
|
||||||
|
else:
|
||||||
|
self.mock_client.create_cifs_share.assert_called_once_with(
|
||||||
|
fake.SHARE_NAME)
|
||||||
|
self.mock_client.cifs_share_exists.assert_not_called()
|
||||||
|
if clear_export_policy:
|
||||||
|
self.mock_client.remove_cifs_share_access.assert_called_once_with(
|
||||||
|
fake.SHARE_NAME, 'Everyone')
|
||||||
self.mock_client.set_volume_security_style.assert_called_once_with(
|
self.mock_client.set_volume_security_style.assert_called_once_with(
|
||||||
fake.SHARE_NAME, security_style='ntfs')
|
fake.SHARE_NAME, security_style='ntfs')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
The NetApp ONTAP driver now supports migration of share servers across
|
||||||
|
clusters. While migrating a share server, the source remains writable
|
||||||
|
during the first phase of the migration, until the cutover is issued. It is
|
||||||
|
possible to specify a new share network for the destination share server,
|
||||||
|
only if the associated security services remain unchanged.
|
||||||
|
Share server migration relies on ONTAP features available only in versions
|
||||||
|
equal and greater than ``9.4``. In order to have share server migration
|
||||||
|
working across ONTAP clusters, they must be peered in advance.
|
||||||
|
In order to adapt to different workloads and provide more flexibility on
|
||||||
|
managing cluster’s free space a new configuration option was added:
|
||||||
|
|
||||||
|
- ``netapp_server_migration_check_capacity``:
|
||||||
|
Specifies if a capacity validation at the destination backend must be
|
||||||
|
made before proceeding with the share server migration. When enabled,
|
||||||
|
the NetApp driver will validate if the destination pools can hold all
|
||||||
|
shares and snapshots belonging to the source share server.
|
Loading…
Reference in New Issue