[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:
Douglas Viroel 2020-06-23 20:37:46 +00:00
parent 1886bf6fe5
commit 4bcf21eaf1
19 changed files with 3939 additions and 416 deletions

View File

@ -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")

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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}

View File

@ -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')

View File

@ -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)

View File

@ -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)

View File

@ -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>

View File

@ -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)

View File

@ -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
)

View File

@ -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)

View File

@ -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()

View File

@ -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')

View File

@ -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 clusters 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.