NetApp: Add Replication support in cDOT

Add support for the replication driver workflows
within the NetApp single-svm cDOT driver.

Implements: blueprint netapp-cdot-single-svm-replication
Co-Authored-By: Clinton Knight <cknight@netapp.com>
Co-Authored-By: Goutham Pacha Ravi <gouthamr@netapp.com>

Change-Id: I0a55433f1953facdd5169a2991c67d3f046bcf06
This commit is contained in:
Alex Meade 2015-09-29 15:08:18 -04:00
parent 21cc6fea34
commit d9dea3e182
15 changed files with 3360 additions and 54 deletions

View File

@ -130,6 +130,7 @@ _global_opt_lists = [
manila.share.drivers.netapp.options.netapp_transport_opts,
manila.share.drivers.netapp.options.netapp_basicauth_opts,
manila.share.drivers.netapp.options.netapp_provisioning_opts,
manila.share.drivers.netapp.options.netapp_replication_opts,
manila.share.drivers.quobyte.quobyte.quobyte_manila_share_opts,
manila.share.drivers.service_instance.common_opts,
manila.share.drivers.service_instance.no_share_servers_handling_mode_opts,

View File

@ -43,6 +43,12 @@ EVOL_NOT_MOUNTED = '14716'
ESIS_CLONE_NOT_LICENSED = '14956'
EOBJECTNOTFOUND = '15661'
E_VIFMGR_PORT_ALREADY_ASSIGNED_TO_BROADCAST_DOMAIN = '18605'
ERELATION_EXISTS = '17122'
ENOTRANSFER_IN_PROGRESS = '17130'
ETRANSFER_IN_PROGRESS = '17137'
EANOTHER_OP_ACTIVE = '17131'
ERELATION_NOT_QUIESCED = '17127'
ESOURCE_IS_DIFFERENT = '17105'
class NaServer(object):

View File

@ -47,10 +47,35 @@ class NetAppBaseClient(object):
minor = result.get_child_content('minor-version')
return major, minor
@na_utils.trace
def get_system_version(self):
"""Gets the current Data ONTAP version."""
result = self.send_request('system-get-version')
version_tuple = result.get_child_by_name(
'version-tuple') or netapp_api.NaElement('none')
system_version_tuple = version_tuple.get_child_by_name(
'system-version-tuple') or netapp_api.NaElement('none')
version = {}
version['version'] = result.get_child_content('version')
version['version-tuple'] = (
system_version_tuple.get_child_content('generation'),
system_version_tuple.get_child_content('major'),
system_version_tuple.get_child_content('minor'))
return version
def _init_features(self):
"""Set up the repository of available Data ONTAP features."""
self.features = Features()
def _strip_xml_namespace(self, string):
if string.startswith('{') and '}' in string:
return string.split('}', 1)[1]
return string
def send_request(self, api_name, api_args=None, enable_tunneling=True):
"""Sends request to Ontapi."""
request = netapp_api.NaElement(api_name)

View File

@ -56,11 +56,14 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
super(NetAppCmodeClient, self)._init_features()
ontapi_version = self.get_ontapi_version(cached=True)
ontapi_1_20 = ontapi_version >= (1, 20)
ontapi_1_30 = ontapi_version >= (1, 30)
self.features.add_feature('SNAPMIRROR_V2', supported=ontapi_1_20)
self.features.add_feature('BROADCAST_DOMAINS', supported=ontapi_1_30)
self.features.add_feature('IPSPACES', supported=ontapi_1_30)
self.features.add_feature('SUBNETS', supported=ontapi_1_30)
self.features.add_feature('CLUSTER_PEER_POLICY', supported=ontapi_1_30)
def _invoke_vserver_api(self, na_element, vserver):
server = copy.copy(self.connection)
@ -1128,15 +1131,17 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
thin_provisioned=False, snapshot_policy=None,
language=None, dedup_enabled=False,
compression_enabled=False, max_files=None,
snapshot_reserve=None):
snapshot_reserve=None, volume_type='rw'):
"""Creates a volume."""
api_args = {
'containing-aggr-name': aggregate_name,
'size': six.text_type(size_gb) + 'g',
'volume': volume_name,
'junction-path': '/%s' % volume_name,
'volume-type': volume_type,
}
if volume_type != 'dp':
api_args['junction-path'] = '/%s' % volume_name
if thin_provisioned:
api_args['space-reserve'] = 'none'
if snapshot_policy is not None:
@ -2190,3 +2195,494 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
return False
else:
raise e
@na_utils.trace
def create_cluster_peer(self, addresses, username=None, password=None,
passphrase=None):
"""Creates a cluster peer relationship."""
api_args = {
'peer-addresses': [
{'remote-inet-address': address} for address in addresses
],
}
if username:
api_args['user-name'] = username
if password:
api_args['password'] = password
if passphrase:
api_args['passphrase'] = passphrase
self.send_request('cluster-peer-create', api_args)
@na_utils.trace
def get_cluster_peers(self, remote_cluster_name=None):
"""Gets one or more cluster peer relationships."""
api_args = {'max-records': 1000}
if remote_cluster_name:
api_args['query'] = {
'cluster-peer-info': {
'remote-cluster-name': remote_cluster_name,
}
}
result = self.send_request('cluster-peer-get-iter', api_args)
if not self._has_records(result):
return []
cluster_peers = []
for cluster_peer_info in result.get_child_by_name(
'attributes-list').get_children():
cluster_peer = {
'active-addresses': [],
'peer-addresses': []
}
active_addresses = cluster_peer_info.get_child_by_name(
'active-addresses') or netapp_api.NaElement('none')
for address in active_addresses.get_children():
cluster_peer['active-addresses'].append(address.get_content())
peer_addresses = cluster_peer_info.get_child_by_name(
'peer-addresses') or netapp_api.NaElement('none')
for address in peer_addresses.get_children():
cluster_peer['peer-addresses'].append(address.get_content())
cluster_peer['availability'] = cluster_peer_info.get_child_content(
'availability')
cluster_peer['cluster-name'] = cluster_peer_info.get_child_content(
'cluster-name')
cluster_peer['cluster-uuid'] = cluster_peer_info.get_child_content(
'cluster-uuid')
cluster_peer['remote-cluster-name'] = (
cluster_peer_info.get_child_content('remote-cluster-name'))
cluster_peer['serial-number'] = (
cluster_peer_info.get_child_content('serial-number'))
cluster_peer['timeout'] = cluster_peer_info.get_child_content(
'timeout')
cluster_peers.append(cluster_peer)
return cluster_peers
@na_utils.trace
def delete_cluster_peer(self, cluster_name):
"""Deletes a cluster peer relationship."""
api_args = {'cluster-name': cluster_name}
self.send_request('cluster-peer-delete', api_args)
@na_utils.trace
def get_cluster_peer_policy(self):
"""Gets the cluster peering policy configuration."""
if not self.features.CLUSTER_PEER_POLICY:
return {}
result = self.send_request('cluster-peer-policy-get')
attributes = result.get_child_by_name(
'attributes') or netapp_api.NaElement('none')
cluster_peer_policy = attributes.get_child_by_name(
'cluster-peer-policy') or netapp_api.NaElement('none')
policy = {
'is-unauthenticated-access-permitted':
cluster_peer_policy.get_child_content(
'is-unauthenticated-access-permitted'),
'passphrase-minimum-length':
cluster_peer_policy.get_child_content(
'passphrase-minimum-length'),
}
if policy['is-unauthenticated-access-permitted'] is not None:
policy['is-unauthenticated-access-permitted'] = (
strutils.bool_from_string(
policy['is-unauthenticated-access-permitted']))
if policy['passphrase-minimum-length'] is not None:
policy['passphrase-minimum-length'] = int(
policy['passphrase-minimum-length'])
return policy
@na_utils.trace
def set_cluster_peer_policy(self, is_unauthenticated_access_permitted=None,
passphrase_minimum_length=None):
"""Modifies the cluster peering policy configuration."""
if not self.features.CLUSTER_PEER_POLICY:
return
if (is_unauthenticated_access_permitted is None and
passphrase_minimum_length is None):
return
api_args = {}
if is_unauthenticated_access_permitted is not None:
api_args['is-unauthenticated-access-permitted'] = (
'true' if strutils.bool_from_string(
is_unauthenticated_access_permitted) else 'false')
if passphrase_minimum_length is not None:
api_args['passphrase-minlength'] = six.text_type(
passphrase_minimum_length)
self.send_request('cluster-peer-policy-modify', api_args)
@na_utils.trace
def create_vserver_peer(self, vserver_name, peer_vserver_name):
"""Creates a Vserver peer relationship for SnapMirrors."""
api_args = {
'vserver': vserver_name,
'peer-vserver': peer_vserver_name,
'applications': [
{'vserver-peer-application': 'snapmirror'},
],
}
self.send_request('vserver-peer-create', api_args)
@na_utils.trace
def delete_vserver_peer(self, vserver_name, peer_vserver_name):
"""Deletes a Vserver peer relationship."""
api_args = {'vserver': vserver_name, 'peer-vserver': peer_vserver_name}
self.send_request('vserver-peer-delete', api_args)
@na_utils.trace
def accept_vserver_peer(self, vserver_name, peer_vserver_name):
"""Accepts a pending Vserver peer relationship."""
api_args = {'vserver': vserver_name, 'peer-vserver': peer_vserver_name}
self.send_request('vserver-peer-accept', api_args)
@na_utils.trace
def get_vserver_peers(self, vserver_name=None, peer_vserver_name=None):
"""Gets one or more Vserver peer relationships."""
api_args = None
if vserver_name or peer_vserver_name:
api_args = {'query': {'vserver-peer-info': {}}}
if vserver_name:
api_args['query']['vserver-peer-info']['vserver'] = (
vserver_name)
if peer_vserver_name:
api_args['query']['vserver-peer-info']['peer-vserver'] = (
peer_vserver_name)
api_args['max-records'] = 1000
result = self.send_request('vserver-peer-get-iter', api_args)
if not self._has_records(result):
return []
vserver_peers = []
for vserver_peer_info in result.get_child_by_name(
'attributes-list').get_children():
vserver_peer = {
'vserver': vserver_peer_info.get_child_content('vserver'),
'peer-vserver':
vserver_peer_info.get_child_content('peer-vserver'),
'peer-state':
vserver_peer_info.get_child_content('peer-state'),
'peer-cluster':
vserver_peer_info.get_child_content('peer-cluster'),
}
vserver_peers.append(vserver_peer)
return vserver_peers
def _ensure_snapmirror_v2(self):
"""Verify support for SnapMirror control plane v2."""
if not self.features.SNAPMIRROR_V2:
msg = _('SnapMirror features require Data ONTAP 8.2 or later.')
raise exception.NetAppException(msg)
@na_utils.trace
def create_snapmirror(self, source_vserver, source_volume,
destination_vserver, destination_volume,
schedule=None, policy=None,
relationship_type='data_protection'):
"""Creates a SnapMirror relationship (cDOT 8.2 or later only)."""
self._ensure_snapmirror_v2()
api_args = {
'source-volume': source_volume,
'source-vserver': source_vserver,
'destination-volume': destination_volume,
'destination-vserver': destination_vserver,
'relationship-type': relationship_type,
}
if schedule:
api_args['schedule'] = schedule
if policy:
api_args['policy'] = policy
try:
self.send_request('snapmirror-create', api_args)
except netapp_api.NaApiError as e:
if e.code != netapp_api.ERELATION_EXISTS:
raise
@na_utils.trace
def initialize_snapmirror(self, source_vserver, source_volume,
destination_vserver, destination_volume,
source_snapshot=None, transfer_priority=None):
"""Initializes a SnapMirror relationship (cDOT 8.2 or later only)."""
self._ensure_snapmirror_v2()
api_args = {
'source-volume': source_volume,
'source-vserver': source_vserver,
'destination-volume': destination_volume,
'destination-vserver': destination_vserver,
}
if source_snapshot:
api_args['source-snapshot'] = source_snapshot
if transfer_priority:
api_args['transfer-priority'] = transfer_priority
result = self.send_request('snapmirror-initialize', api_args)
result_info = {}
result_info['operation-id'] = result.get_child_content(
'result-operation-id')
result_info['status'] = result.get_child_content('result-status')
result_info['jobid'] = result.get_child_content('result-jobid')
result_info['error-code'] = result.get_child_content(
'result-error-code')
result_info['error-message'] = result.get_child_content(
'result-error-message')
return result_info
@na_utils.trace
def release_snapmirror(self, source_vserver, source_volume,
destination_vserver, destination_volume,
relationship_info_only=False):
"""Removes a SnapMirror relationship on the source endpoint."""
self._ensure_snapmirror_v2()
api_args = {
'query': {
'snapmirror-destination-info': {
'source-volume': source_volume,
'source-vserver': source_vserver,
'destination-volume': destination_volume,
'destination-vserver': destination_vserver,
'relationship-info-only': ('true' if relationship_info_only
else 'false'),
}
}
}
self.send_request('snapmirror-release-iter', api_args)
@na_utils.trace
def quiesce_snapmirror(self, source_vserver, source_volume,
destination_vserver, destination_volume):
"""Disables future transfers to a SnapMirror destination."""
self._ensure_snapmirror_v2()
api_args = {
'source-volume': source_volume,
'source-vserver': source_vserver,
'destination-volume': destination_volume,
'destination-vserver': destination_vserver,
}
self.send_request('snapmirror-quiesce', api_args)
@na_utils.trace
def abort_snapmirror(self, source_vserver, source_volume,
destination_vserver, destination_volume,
clear_checkpoint=False):
"""Stops ongoing transfers for a SnapMirror relationship."""
self._ensure_snapmirror_v2()
api_args = {
'source-volume': source_volume,
'source-vserver': source_vserver,
'destination-volume': destination_volume,
'destination-vserver': destination_vserver,
'clear-checkpoint': 'true' if clear_checkpoint else 'false',
}
try:
self.send_request('snapmirror-abort', api_args)
except netapp_api.NaApiError as e:
if e.code != netapp_api.ENOTRANSFER_IN_PROGRESS:
raise
@na_utils.trace
def break_snapmirror(self, source_vserver, source_volume,
destination_vserver, destination_volume):
"""Breaks a data protection SnapMirror relationship."""
self._ensure_snapmirror_v2()
api_args = {
'source-volume': source_volume,
'source-vserver': source_vserver,
'destination-volume': destination_volume,
'destination-vserver': destination_vserver,
}
self.send_request('snapmirror-break', api_args)
@na_utils.trace
def modify_snapmirror(self, source_vserver, source_volume,
destination_vserver, destination_volume,
schedule=None, policy=None, tries=None,
max_transfer_rate=None):
"""Modifies a SnapMirror relationship."""
self._ensure_snapmirror_v2()
api_args = {
'source-volume': source_volume,
'source-vserver': source_vserver,
'destination-volume': destination_volume,
'destination-vserver': destination_vserver,
}
if schedule:
api_args['schedule'] = schedule
if policy:
api_args['policy'] = policy
if tries is not None:
api_args['tries'] = tries
if max_transfer_rate is not None:
api_args['max-transfer-rate'] = max_transfer_rate
self.send_request('snapmirror-modify', api_args)
@na_utils.trace
def delete_snapmirror(self, source_vserver, source_volume,
destination_vserver, destination_volume):
"""Destroys a SnapMirror relationship."""
self._ensure_snapmirror_v2()
api_args = {
'query': {
'snapmirror-info': {
'source-volume': source_volume,
'source-vserver': source_vserver,
'destination-volume': destination_volume,
'destination-vserver': destination_vserver,
}
}
}
self.send_request('snapmirror-destroy-iter', api_args)
@na_utils.trace
def update_snapmirror(self, source_vserver, source_volume,
destination_vserver, destination_volume):
"""Schedules a snapmirror update."""
self._ensure_snapmirror_v2()
api_args = {
'source-volume': source_volume,
'source-vserver': source_vserver,
'destination-volume': destination_volume,
'destination-vserver': destination_vserver,
}
try:
self.send_request('snapmirror-update', api_args)
except netapp_api.NaApiError as e:
if (e.code != netapp_api.ETRANSFER_IN_PROGRESS and
e.code != netapp_api.EANOTHER_OP_ACTIVE):
raise
@na_utils.trace
def resume_snapmirror(self, source_vserver, source_volume,
destination_vserver, destination_volume):
"""Resume a SnapMirror relationship if it is quiesced."""
self._ensure_snapmirror_v2()
api_args = {
'source-volume': source_volume,
'source-vserver': source_vserver,
'destination-volume': destination_volume,
'destination-vserver': destination_vserver,
}
try:
self.send_request('snapmirror-resume', api_args)
except netapp_api.NaApiError as e:
if e.code != netapp_api.ERELATION_NOT_QUIESCED:
raise
@na_utils.trace
def resync_snapmirror(self, source_vserver, source_volume,
destination_vserver, destination_volume):
"""Resync a SnapMirror relationship."""
self._ensure_snapmirror_v2()
api_args = {
'source-volume': source_volume,
'source-vserver': source_vserver,
'destination-volume': destination_volume,
'destination-vserver': destination_vserver,
}
self.send_request('snapmirror-resync', api_args)
@na_utils.trace
def _get_snapmirrors(self, source_vserver=None, source_volume=None,
destination_vserver=None, destination_volume=None,
desired_attributes=None):
query = None
if (source_vserver or source_volume or destination_vserver or
destination_volume):
query = {'snapmirror-info': {}}
if source_volume:
query['snapmirror-info']['source-volume'] = source_volume
if destination_volume:
query['snapmirror-info']['destination-volume'] = (
destination_volume)
if source_vserver:
query['snapmirror-info']['source-vserver'] = source_vserver
if destination_vserver:
query['snapmirror-info']['destination-vserver'] = (
destination_vserver)
api_args = {}
if query:
api_args['query'] = query
if desired_attributes:
api_args['desired-attributes'] = desired_attributes
result = self.send_request('snapmirror-get-iter', api_args)
if not self._has_records(result):
return []
else:
return result.get_child_by_name('attributes-list').get_children()
@na_utils.trace
def get_snapmirrors(self, source_vserver, source_volume,
destination_vserver, destination_volume,
desired_attributes=None):
"""Gets one or more SnapMirror relationships.
Either the source or destination info may be omitted.
Desired attributes should be a flat list of attribute names.
"""
self._ensure_snapmirror_v2()
if desired_attributes is not None:
desired_attributes = {
'snapmirror-info': {attr: None for attr in desired_attributes},
}
result = self._get_snapmirrors(
source_vserver=source_vserver,
source_volume=source_volume,
destination_vserver=destination_vserver,
destination_volume=destination_volume,
desired_attributes=desired_attributes)
snapmirrors = []
for snapmirror_info in result:
snapmirror = {}
for child in snapmirror_info.get_children():
name = self._strip_xml_namespace(child.get_name())
snapmirror[name] = child.get_content()
snapmirrors.append(snapmirror)
return snapmirrors

View File

@ -0,0 +1,372 @@
# Copyright (c) 2016 Alex Meade. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
NetApp Data ONTAP data motion library.
This library handles transferring data from a source to a destination. Its
responsibility is to handle this as efficiently as possible given the
location of the data's source and destination. This includes cloning,
SnapMirror, and copy-offload as improvements to brute force data transfer.
"""
from oslo_config import cfg
from oslo_log import log
from oslo_utils import excutils
from manila import exception
from manila.i18n import _LE, _LI, _LW
from manila.share import configuration
from manila.share import driver
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 import options as na_opts
from manila.share.drivers.netapp import utils as na_utils
from manila.share import utils as share_utils
from manila import utils
LOG = log.getLogger(__name__)
CONF = cfg.CONF
def get_backend_configuration(backend_name):
for section in CONF.list_all_sections():
config = configuration.Configuration(driver.share_opts,
config_group=section)
config.append_config_values(na_opts.netapp_cluster_opts)
config.append_config_values(na_opts.netapp_connection_opts)
config.append_config_values(na_opts.netapp_basicauth_opts)
config.append_config_values(na_opts.netapp_transport_opts)
config.append_config_values(na_opts.netapp_support_opts)
config.append_config_values(na_opts.netapp_provisioning_opts)
config.append_config_values(na_opts.netapp_replication_opts)
if (config.share_backend_name and
config.share_backend_name.lower() == backend_name.lower()):
return config
msg = _LW("Could not find backend %s in configuration.")
LOG.warning(msg % backend_name)
def get_client_for_backend(backend_name, vserver_name=None):
config = get_backend_configuration(backend_name)
client = client_cmode.NetAppCmodeClient(
transport_type=config.netapp_transport_type,
username=config.netapp_login,
password=config.netapp_password,
hostname=config.netapp_server_hostname,
port=config.netapp_server_port,
vserver=vserver_name or config.netapp_vserver,
trace=na_utils.TRACE_API)
return client
class DataMotionSession(object):
def _get_backend_volume_name(self, config, share_obj):
"""Return the calculated backend name of the share.
Uses the netapp_volume_name_template configuration value for the
backend to calculate the volume name on the array for the share.
"""
volume_name = config.netapp_volume_name_template % {
'share_id': share_obj['id'].replace('-', '_')}
return volume_name
def get_vserver_from_share(self, share_obj):
share_server = share_obj.get('share_server')
if share_server:
backend_details = share_server.get('backend_details')
if backend_details:
return backend_details.get('vserver_name')
def get_backend_info_for_share(self, share_obj):
backend_name = share_utils.extract_host(
share_obj['host'], level='backend_name')
config = get_backend_configuration(backend_name)
vserver = (self.get_vserver_from_share(share_obj) or
config.netapp_vserver)
volume_name = self._get_backend_volume_name(
config, share_obj)
return volume_name, vserver, backend_name
def get_snapmirrors(self, source_share_obj, dest_share_obj):
dest_volume_name, dest_vserver, dest_backend = (
self.get_backend_info_for_share(dest_share_obj))
dest_client = get_client_for_backend(dest_backend,
vserver_name=dest_vserver)
src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
source_share_obj)
snapmirrors = dest_client.get_snapmirrors(
src_vserver, src_volume_name,
dest_vserver, dest_volume_name,
desired_attributes=['relationship-status',
'mirror-state',
'source-vserver',
'source-volume',
'last-transfer-end-timestamp'])
return snapmirrors
def create_snapmirror(self, source_share_obj, dest_share_obj):
"""Sets up a SnapMirror relationship between two volumes.
1. Create SnapMirror relationship
2. Initialize data transfer asynchronously
"""
dest_volume_name, dest_vserver, dest_backend = (
self.get_backend_info_for_share(dest_share_obj))
dest_client = get_client_for_backend(dest_backend,
vserver_name=dest_vserver)
src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
source_share_obj)
# 1. Create SnapMirror relationship
# TODO(ameade): Change the schedule from hourly to a config value
dest_client.create_snapmirror(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,
dest_vserver,
dest_volume_name)
def delete_snapmirror(self, source_share_obj, dest_share_obj,
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
"""
dest_volume_name, dest_vserver, dest_backend = (
self.get_backend_info_for_share(dest_share_obj))
dest_client = get_client_for_backend(dest_backend,
vserver_name=dest_vserver)
src_volume_name, src_vserver, src_backend = (
self.get_backend_info_for_share(source_share_obj))
# 1. Abort any ongoing transfers
try:
dest_client.abort_snapmirror(src_vserver,
src_volume_name,
dest_vserver,
dest_volume_name,
clear_checkpoint=False)
except netapp_api.NaApiError as e:
# Snapmirror is already deleted
pass
# 2. Delete SnapMirror Relationship and cleanup destination snapshots
try:
dest_client.delete_snapmirror(src_vserver,
src_volume_name,
dest_vserver,
dest_volume_name)
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(_LI('No snapmirror relationship to delete'))
exc_context.reraise = False
if release:
# If the source is unreachable, do not perform the release
try:
src_client = get_client_for_backend(src_backend,
vserver_name=src_vserver)
except Exception:
src_client = None
# 3. Cleanup SnapMirror relationship on source
try:
if src_client:
src_client.release_snapmirror(src_vserver,
src_volume_name,
dest_vserver,
dest_volume_name)
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):
# Handle the case where the snapmirror is already
# cleaned up
exc_context.reraise = False
def update_snapmirror(self, source_share_obj, dest_share_obj):
"""Schedule a snapmirror update to happen on the backend."""
dest_volume_name, dest_vserver, dest_backend = (
self.get_backend_info_for_share(dest_share_obj))
dest_client = get_client_for_backend(dest_backend,
vserver_name=dest_vserver)
src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
source_share_obj)
# Update SnapMirror
dest_client.update_snapmirror(src_vserver,
src_volume_name,
dest_vserver,
dest_volume_name)
def quiesce_then_abort(self, source_share_obj, dest_share_obj):
dest_volume_name, dest_vserver, dest_backend = (
self.get_backend_info_for_share(dest_share_obj))
dest_client = get_client_for_backend(dest_backend,
vserver_name=dest_vserver)
src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
source_share_obj)
# 1. Attempt to quiesce, then abort
dest_client.quiesce_snapmirror(src_vserver,
src_volume_name,
dest_vserver,
dest_volume_name)
config = get_backend_configuration(share_utils.extract_host(
source_share_obj['host'], level='backend_name'))
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(
src_vserver, src_volume_name, dest_vserver,
dest_volume_name, desired_attributes=['relationship-status',
'mirror-state']
)[0]
if snapmirror.get('relationship-status') != 'quiesced':
raise exception.ReplicationException(
reason=_LE("Snapmirror relationship is not quiesced."))
try:
wait_for_quiesced()
except exception.ReplicationException:
dest_client.abort_snapmirror(src_vserver,
src_volume_name,
dest_vserver,
dest_volume_name,
clear_checkpoint=False)
def break_snapmirror(self, source_share_obj, dest_share_obj):
"""Breaks SnapMirror relationship.
1. Quiesce any ongoing snapmirror transfers
2. Wait until snapmirror finishes transfers and enters quiesced state
3. Break snapmirror
4. Mount the destination volume so it is exported as a share
"""
dest_volume_name, dest_vserver, dest_backend = (
self.get_backend_info_for_share(dest_share_obj))
dest_client = get_client_for_backend(dest_backend,
vserver_name=dest_vserver)
src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
source_share_obj)
# 1. Attempt to quiesce, then abort
self.quiesce_then_abort(source_share_obj, dest_share_obj)
# 2. Break SnapMirror
dest_client.break_snapmirror(src_vserver,
src_volume_name,
dest_vserver,
dest_volume_name)
# 3. Mount the destination volume and create a junction path
dest_client.mount_volume(dest_volume_name)
def resync_snapmirror(self, source_share_obj, dest_share_obj):
"""Resync SnapMirror relationship. """
dest_volume_name, dest_vserver, dest_backend = (
self.get_backend_info_for_share(dest_share_obj))
dest_client = get_client_for_backend(dest_backend,
vserver_name=dest_vserver)
src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
source_share_obj)
dest_client.resync_snapmirror(src_vserver,
src_volume_name,
dest_vserver,
dest_volume_name)
def resume_snapmirror(self, source_share_obj, dest_share_obj):
"""Resume SnapMirror relationship from a quiesced state."""
dest_volume_name, dest_vserver, dest_backend = (
self.get_backend_info_for_share(dest_share_obj))
dest_client = get_client_for_backend(dest_backend,
vserver_name=dest_vserver)
src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
source_share_obj)
dest_client.resume_snapmirror(src_vserver,
src_volume_name,
dest_vserver,
dest_volume_name)
def change_snapmirror_source(self, replica,
orig_source_replica,
new_source_replica, replica_list):
"""Creates SnapMirror relationship from the new source to destination.
1. Delete all snapmirrors involving the replica, but maintain
snapmirror metadata and snapshots for efficiency
2. Ensure a new source -> replica snapmirror exists
3. Resync new source -> replica snapmirror relationship
"""
replica_volume_name, replica_vserver, replica_backend = (
self.get_backend_info_for_share(replica))
replica_client = get_client_for_backend(replica_backend,
vserver_name=replica_vserver)
new_src_volume_name, new_src_vserver, __ = (
self.get_backend_info_for_share(new_source_replica))
# 1. delete
for other_replica in replica_list:
if other_replica['id'] == replica['id']:
continue
# We need to delete ALL snapmirror relationships
# involving this replica but do not remove snapmirror metadata
# so that the new snapmirror relationship is efficient.
self.delete_snapmirror(other_replica, replica, release=False)
self.delete_snapmirror(replica, other_replica, release=False)
# 2. create
# TODO(ameade): Update the schedule if needed.
replica_client.create_snapmirror(new_src_vserver,
new_src_volume_name,
replica_vserver,
replica_volume_name,
schedule='hourly')
# 3. resync
replica_client.resync_snapmirror(new_src_vserver,
new_src_volume_name,
replica_vserver,
replica_volume_name)

View File

@ -118,3 +118,18 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
def _teardown_server(self, server_details, **kwargs):
self.library.teardown_server(server_details, **kwargs)
def create_replica(self, context, replica_list, replica, access_rules,
**kwargs):
raise NotImplementedError()
def delete_replica(self, context, replica_list, replica, **kwargs):
raise NotImplementedError()
def promote_replica(self, context, replica_list, replica, access_rules,
share_server=None):
raise NotImplementedError()
def update_replica_state(self, context, replica_list, replica,
access_rules, share_server=None):
raise NotImplementedError()

View File

@ -117,3 +117,25 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
def _teardown_server(self, server_details, **kwargs):
self.library.teardown_server(server_details, **kwargs)
def create_replica(self, context, replica_list, replica, access_rules,
**kwargs):
return self.library.create_replica(context, replica_list, replica,
access_rules, **kwargs)
def delete_replica(self, context, replica_list, replica, **kwargs):
self.library.delete_replica(context, replica_list, replica, **kwargs)
def promote_replica(self, context, replica_list, replica, access_rules,
share_server=None):
return self.library.promote_replica(context, replica_list, replica,
access_rules,
share_server=share_server)
def update_replica_state(self, context, replica_list, replica,
access_rules, share_server=None):
return self.library.update_replica_state(context,
replica_list,
replica,
access_rules,
share_server=share_server)

View File

@ -24,14 +24,19 @@ import math
import socket
import time
from oslo_config import cfg
from oslo_log import log
from oslo_service import loopingcall
from oslo_utils import timeutils
from oslo_utils import units
import six
from manila.common import constants
from manila import exception
from manila.i18n import _, _LE, _LI, _LW
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.cluster_mode import data_motion
from manila.share.drivers.netapp.dataontap.protocols import cifs_cmode
from manila.share.drivers.netapp.dataontap.protocols import nfs_cmode
from manila.share.drivers.netapp import options as na_opts
@ -40,6 +45,7 @@ from manila.share import share_types
from manila.share import utils as share_utils
LOG = log.getLogger(__name__)
CONF = cfg.CONF
class NetAppCmodeFileStorageLibrary(object):
@ -84,6 +90,8 @@ class NetAppCmodeFileStorageLibrary(object):
self.configuration.append_config_values(na_opts.netapp_cluster_opts)
self.configuration.append_config_values(
na_opts.netapp_provisioning_opts)
self.configuration.append_config_values(
na_opts.netapp_replication_opts)
self._licenses = []
self._client = None
@ -177,16 +185,16 @@ class NetAppCmodeFileStorageLibrary(object):
housekeeping_periodic_task.start(
interval=self.HOUSEKEEPING_INTERVAL_SECONDS, initial_delay=0)
def _get_valid_share_name(self, share_id):
def _get_backend_share_name(self, share_id):
"""Get share name according to share name template."""
return self.configuration.netapp_volume_name_template % {
'share_id': share_id.replace('-', '_')}
def _get_valid_snapshot_name(self, snapshot_id):
def _get_backend_snapshot_name(self, snapshot_id):
"""Get snapshot name according to snapshot name template."""
return 'share_snapshot_' + snapshot_id.replace('-', '_')
def _get_valid_cg_snapshot_name(self, snapshot_id):
def _get_backend_cg_snapshot_name(self, snapshot_id):
"""Get snapshot name according to snapshot name template."""
return 'share_cg_snapshot_' + snapshot_id.replace('-', '_')
@ -222,6 +230,12 @@ class NetAppCmodeFileStorageLibrary(object):
'consistency_group_support': 'host',
'pools': self._get_pools(),
}
if (self.configuration.replication_domain and
not self.configuration.driver_handles_share_servers):
data['replication_type'] = 'dr'
data['replication_domain'] = self.configuration.replication_domain
return data
@na_utils.trace
@ -345,7 +359,7 @@ class NetAppCmodeFileStorageLibrary(object):
if pool:
return pool
share_name = self._get_valid_share_name(share['id'])
share_name = self._get_backend_share_name(share['id'])
return self._client.get_aggregate_for_volume(share_name)
@na_utils.trace
@ -366,9 +380,9 @@ class NetAppCmodeFileStorageLibrary(object):
vserver_client)
@na_utils.trace
def _allocate_container(self, share, vserver_client):
def _allocate_container(self, share, vserver_client, replica=False):
"""Create new share on aggregate."""
share_name = self._get_valid_share_name(share['id'])
share_name = self._get_backend_share_name(share['id'])
# Get Data ONTAP aggregate name as pool name.
pool_name = share_utils.extract_host(share['host'], level='pool')
@ -380,6 +394,10 @@ class NetAppCmodeFileStorageLibrary(object):
extra_specs = self._remap_standard_boolean_extra_specs(extra_specs)
self._check_extra_specs_validity(share, extra_specs)
provisioning_options = self._get_provisioning_options(extra_specs)
if replica:
# If this volume is intended to be a replication destination,
# create it as the 'data-protection' type
provisioning_options['volume_type'] = 'dp'
LOG.debug('Creating share %(share)s on pool %(pool)s with '
'provisioning options %(options)s',
@ -541,10 +559,10 @@ class NetAppCmodeFileStorageLibrary(object):
@na_utils.trace
def _allocate_container_from_snapshot(
self, share, snapshot, vserver_client,
snapshot_name_func=_get_valid_snapshot_name):
snapshot_name_func=_get_backend_snapshot_name):
"""Clones existing share."""
share_name = self._get_valid_share_name(share['id'])
parent_share_name = self._get_valid_share_name(snapshot['share_id'])
share_name = self._get_backend_share_name(share['id'])
parent_share_name = self._get_backend_share_name(snapshot['share_id'])
parent_snapshot_name = snapshot_name_func(self, snapshot['id'])
LOG.debug('Creating share from snapshot %s', snapshot['id'])
@ -571,7 +589,7 @@ class NetAppCmodeFileStorageLibrary(object):
{'share': share['id'], 'error': error})
return
share_name = self._get_valid_share_name(share['id'])
share_name = self._get_backend_share_name(share['id'])
if self._share_exists(share_name, vserver_client):
self._remove_export(share, vserver_client)
self._deallocate_container(share_name, vserver_client)
@ -590,7 +608,7 @@ class NetAppCmodeFileStorageLibrary(object):
"""Creates NAS storage."""
helper = self._get_helper(share)
helper.set_client(vserver_client)
share_name = self._get_valid_share_name(share['id'])
share_name = self._get_backend_share_name(share['id'])
interfaces = vserver_client.get_network_interfaces(
protocols=[share['share_proto']])
@ -683,7 +701,7 @@ class NetAppCmodeFileStorageLibrary(object):
"""Deletes NAS storage."""
helper = self._get_helper(share)
helper.set_client(vserver_client)
share_name = self._get_valid_share_name(share['id'])
share_name = self._get_backend_share_name(share['id'])
target = helper.get_target(share)
# Share may be in error state, so there's no share and target.
if target:
@ -693,8 +711,8 @@ class NetAppCmodeFileStorageLibrary(object):
def create_snapshot(self, context, snapshot, share_server=None):
"""Creates a snapshot of a share."""
vserver, vserver_client = self._get_vserver(share_server=share_server)
share_name = self._get_valid_share_name(snapshot['share_id'])
snapshot_name = self._get_valid_snapshot_name(snapshot['id'])
share_name = self._get_backend_share_name(snapshot['share_id'])
snapshot_name = self._get_backend_snapshot_name(snapshot['id'])
LOG.debug('Creating snapshot %s', snapshot_name)
vserver_client.create_snapshot(share_name, snapshot_name)
@ -713,8 +731,8 @@ class NetAppCmodeFileStorageLibrary(object):
{'snap': snapshot['id'], 'error': error})
return
share_name = self._get_valid_share_name(snapshot['share_id'])
snapshot_name = self._get_valid_snapshot_name(snapshot['id'])
share_name = self._get_backend_share_name(snapshot['share_id'])
snapshot_name = self._get_backend_snapshot_name(snapshot['id'])
try:
self._handle_busy_snapshot(vserver_client, share_name,
@ -785,7 +803,7 @@ class NetAppCmodeFileStorageLibrary(object):
msg_args = {'export': share['export_location']}
raise exception.ManageInvalidShare(reason=msg % msg_args)
share_name = self._get_valid_share_name(share['id'])
share_name = self._get_backend_share_name(share['id'])
aggregate_name = share_utils.extract_host(share['host'], level='pool')
# Get existing volume info
@ -886,7 +904,7 @@ class NetAppCmodeFileStorageLibrary(object):
self._allocate_container_from_snapshot(
clone['share'], clone['snapshot'], vserver_client,
NetAppCmodeFileStorageLibrary._get_valid_cg_snapshot_name)
NetAppCmodeFileStorageLibrary._get_backend_cg_snapshot_name)
export_locations = self._create_export(clone['share'],
share_server,
@ -955,9 +973,9 @@ class NetAppCmodeFileStorageLibrary(object):
"""Creates a consistency group snapshot."""
vserver, vserver_client = self._get_vserver(share_server=share_server)
share_names = [self._get_valid_share_name(member['share_id'])
share_names = [self._get_backend_share_name(member['share_id'])
for member in snap_dict.get('cgsnapshot_members', [])]
snapshot_name = self._get_valid_cg_snapshot_name(snap_dict['id'])
snapshot_name = self._get_backend_cg_snapshot_name(snap_dict['id'])
if share_names:
LOG.debug('Creating CG snapshot %s.', snapshot_name)
@ -980,9 +998,9 @@ class NetAppCmodeFileStorageLibrary(object):
{'snap': snap_dict['id'], 'error': error})
return None, None
share_names = [self._get_valid_share_name(member['share_id'])
share_names = [self._get_backend_share_name(member['share_id'])
for member in snap_dict.get('cgsnapshot_members', [])]
snapshot_name = self._get_valid_cg_snapshot_name(snap_dict['id'])
snapshot_name = self._get_backend_cg_snapshot_name(snap_dict['id'])
for share_name in share_names:
try:
@ -1004,7 +1022,7 @@ class NetAppCmodeFileStorageLibrary(object):
def extend_share(self, share, new_size, share_server=None):
"""Extends size of existing share."""
vserver, vserver_client = self._get_vserver(share_server=share_server)
share_name = self._get_valid_share_name(share['id'])
share_name = self._get_backend_share_name(share['id'])
LOG.debug('Extending share %(name)s to %(size)s GB.',
{'name': share_name, 'size': new_size})
vserver_client.set_volume_size(share_name, new_size)
@ -1013,7 +1031,7 @@ class NetAppCmodeFileStorageLibrary(object):
def shrink_share(self, share, new_size, share_server=None):
"""Shrinks size of existing share."""
vserver, vserver_client = self._get_vserver(share_server=share_server)
share_name = self._get_valid_share_name(share['id'])
share_name = self._get_backend_share_name(share['id'])
LOG.debug('Shrinking share %(name)s to %(size)s GB.',
{'name': share_name, 'size': new_size})
vserver_client.set_volume_size(share_name, new_size)
@ -1022,6 +1040,12 @@ class NetAppCmodeFileStorageLibrary(object):
def update_access(self, context, share, access_rules, add_rules=None,
delete_rules=None, share_server=None):
"""Updates access rules for a share."""
# NOTE(ameade): We do not need to add export rules to a non-active
# replica as it will fail.
replica_state = share.get('replica_state')
if (replica_state is not None and
replica_state != constants.REPLICA_STATE_ACTIVE):
return
try:
vserver, vserver_client = self._get_vserver(
share_server=share_server)
@ -1034,7 +1058,7 @@ class NetAppCmodeFileStorageLibrary(object):
{'share': share['id'], 'error': error})
return
share_name = self._get_valid_share_name(share['id'])
share_name = self._get_backend_share_name(share['id'])
if self._share_exists(share_name, vserver_client):
helper = self._get_helper(share)
helper.set_client(vserver_client)
@ -1096,3 +1120,237 @@ class NetAppCmodeFileStorageLibrary(object):
disk_types = self._client.get_aggregate_disk_types(aggregate_names)
for aggregate_name, disk_type in disk_types.items():
ssc_stats[aggregate_name]['netapp_disk_type'] = disk_type
def _find_active_replica(self, replica_list):
# NOTE(ameade): Find current active replica. There can only be one
# active replica (SnapMirror source volume) at a time in cDOT.
for r in replica_list:
if r['replica_state'] == constants.REPLICA_STATE_ACTIVE:
return r
def create_replica(self, context, replica_list, new_replica,
access_rules=None, share_server=None):
"""Creates the new replica on this backend and sets up SnapMirror."""
active_replica = self._find_active_replica(replica_list)
dm_session = data_motion.DataMotionSession()
# 1. Create the destination share
dest_backend = share_utils.extract_host(new_replica['host'],
level='backend_name')
vserver = (dm_session.get_vserver_from_share(new_replica) or
self.configuration.netapp_vserver)
vserver_client = data_motion.get_client_for_backend(
dest_backend, vserver_name=vserver)
self._allocate_container(new_replica, vserver_client, replica=True)
# 2. Setup SnapMirror
dm_session.create_snapmirror(active_replica, new_replica)
model_update = {
'export_locations': [],
'replica_state': constants.REPLICA_STATE_OUT_OF_SYNC,
'access_rules_status': constants.STATUS_ACTIVE,
}
return model_update
def delete_replica(self, context, replica_list, replica,
share_server=None):
"""Removes the replica on this backend and destroys SnapMirror."""
dm_session = data_motion.DataMotionSession()
# 1. Remove SnapMirror
dest_backend = share_utils.extract_host(replica['host'],
level='backend_name')
vserver = (dm_session.get_vserver_from_share(replica) or
self.configuration.netapp_vserver)
# Ensure that all potential snapmirror relationships and their metadata
# involving the replica are destroyed.
for other_replica in replica_list:
dm_session.delete_snapmirror(other_replica, replica)
dm_session.delete_snapmirror(replica, other_replica)
# 2. Delete share
vserver_client = data_motion.get_client_for_backend(
dest_backend, vserver_name=vserver)
share_name = self._get_backend_share_name(replica['id'])
self._deallocate_container(share_name, vserver_client)
def update_replica_state(self, context, replica_list, replica,
access_rules, share_server=None):
"""Returns the status of the given replica on this backend."""
active_replica = self._find_active_replica(replica_list)
share_name = self._get_backend_share_name(replica['id'])
vserver, vserver_client = self._get_vserver(share_server=share_server)
dm_session = data_motion.DataMotionSession()
try:
snapmirrors = dm_session.get_snapmirrors(active_replica, replica)
except netapp_api.NaApiError:
LOG.exception(_LE("Could not get snapmirrors for replica %s."),
replica['id'])
return constants.STATUS_ERROR
if not snapmirrors:
if replica['status'] != constants.STATUS_CREATING:
try:
dm_session.create_snapmirror(active_replica, replica)
except netapp_api.NaApiError:
LOG.exception(_LE("Could not create snapmirror for "
"replica %s."), replica['id'])
return constants.STATUS_ERROR
return constants.REPLICA_STATE_OUT_OF_SYNC
snapmirror = snapmirrors[0]
if (snapmirror.get('mirror-state') != 'snapmirrored' and
snapmirror.get('relationship-status') == 'transferring'):
return constants.REPLICA_STATE_OUT_OF_SYNC
if snapmirror.get('mirror-state') != 'snapmirrored':
try:
vserver_client.resume_snapmirror(snapmirror['source-vserver'],
snapmirror['source-volume'],
vserver,
share_name)
vserver_client.resync_snapmirror(snapmirror['source-vserver'],
snapmirror['source-volume'],
vserver,
share_name)
return constants.REPLICA_STATE_OUT_OF_SYNC
except netapp_api.NaApiError:
LOG.exception(_LE("Could not resync snapmirror."))
return constants.STATUS_ERROR
last_update_timestamp = float(
snapmirror.get('last-transfer-end-timestamp', 0))
# TODO(ameade): Have a configurable RPO for replicas, for now it is
# one hour.
if (last_update_timestamp and
(timeutils.is_older_than(
timeutils.iso8601_from_timestamp(last_update_timestamp),
3600))):
return constants.REPLICA_STATE_OUT_OF_SYNC
return constants.REPLICA_STATE_IN_SYNC
def promote_replica(self, context, replica_list, replica, access_rules,
share_server=None):
"""Switch SnapMirror relationships and allow r/w ops on replica.
Creates a DataMotion session and switches the direction of the
SnapMirror relationship between the currently 'active' instance (
SnapMirror source volume) and the replica. Also attempts setting up
SnapMirror relationships between the other replicas and the new
SnapMirror source volume ('active' instance).
:param context: Request Context
:param replica_list: List of replicas, including the 'active' instance
:param replica: Replica to promote to SnapMirror source
:param access_rules: Access rules to apply to the replica
:param share_server: ShareServer class instance of replica
:return: Updated replica_list
"""
orig_active_replica = self._find_active_replica(replica_list)
dm_session = data_motion.DataMotionSession()
new_replica_list = []
# Setup the new active replica
new_active_replica = (
self._convert_destination_replica_to_independent(
context, dm_session, orig_active_replica, replica,
access_rules, share_server=share_server))
new_replica_list.append(new_active_replica)
# Change the source replica for all destinations to the new
# active replica.
for r in replica_list:
if r['id'] != replica['id']:
r = self._safe_change_replica_source(dm_session, r,
orig_active_replica,
replica,
replica_list)
new_replica_list.append(r)
return new_replica_list
def _convert_destination_replica_to_independent(
self, context, dm_session, orig_active_replica, replica,
access_rules, share_server=None):
"""Breaks SnapMirror and allows r/w ops on the destination replica.
For promotion, the existing SnapMirror relationship must be broken
and access rules have to be granted to the broken off replica to
use it as an independent share.
:param context: Request Context
:param dm_session: Data motion object for SnapMirror operations
:param orig_active_replica: Original SnapMirror source
:param replica: Replica to promote to SnapMirror source
:param access_rules: Access rules to apply to the replica
:param share_server: ShareServer class instance of replica
:return: Updated replica
"""
vserver, vserver_client = self._get_vserver(share_server=share_server)
share_name = self._get_backend_share_name(replica['id'])
try:
# 1. Start an update to try to get a last minute transfer before we
# quiesce and break
dm_session.update_snapmirror(orig_active_replica, replica)
except netapp_api.NaApiError:
# Ignore any errors since the current source replica may be
# unreachable
pass
# 2. Break SnapMirror
dm_session.break_snapmirror(orig_active_replica, replica)
# 3. Setup access rules
new_active_replica = copy.deepcopy(replica)
helper = self._get_helper(replica)
helper.set_client(vserver_client)
try:
helper.update_access(replica, share_name, access_rules)
except Exception:
new_active_replica['access_rules_status'] = (
constants.STATUS_OUT_OF_SYNC)
else:
new_active_replica['access_rules_status'] = constants.STATUS_ACTIVE
new_active_replica['export_locations'] = self._create_export(
new_active_replica, share_server, vserver, vserver_client)
new_active_replica['replica_state'] = constants.REPLICA_STATE_ACTIVE
return new_active_replica
def _safe_change_replica_source(self, dm_session, replica,
orig_source_replica,
new_source_replica, replica_list):
"""Attempts to change the SnapMirror source to new source.
If the attempt fails, 'replica_state' is set to 'error'.
:param dm_session: Data motion object for SnapMirror operations
:param replica: Replica that requires a change of source
:param orig_source_replica: Original SnapMirror source volume
:param new_source_replica: New SnapMirror source volume
:return: Updated replica
"""
try:
dm_session.change_snapmirror_source(replica,
orig_source_replica,
new_source_replica,
replica_list)
except Exception:
replica['replica_state'] = constants.STATUS_ERROR
replica['export_locations'] = []
msg = _LE("Failed to change replica (%s) to a SnapMirror "
"destination."), replica['id']
LOG.exception(msg)
return replica
replica['replica_state'] = constants.REPLICA_STATE_OUT_OF_SYNC
replica['export_locations'] = []
return replica

View File

@ -107,6 +107,14 @@ netapp_support_opts = [
'trace info is written to the debug logs. Values '
'include method and api.')), ]
netapp_replication_opts = [
cfg.IntOpt('netapp_snapmirror_quiesce_timeout',
min=0,
default=3600, # One Hour
help='The maximum time in seconds to wait for existing '
'snapmirror transfers to complete before aborting when '
'promoting a replica.'), ]
CONF = cfg.CONF
CONF.register_opts(netapp_proxy_opts)
CONF.register_opts(netapp_connection_opts)
@ -114,3 +122,4 @@ CONF.register_opts(netapp_transport_opts)
CONF.register_opts(netapp_basicauth_opts)
CONF.register_opts(netapp_provisioning_opts)
CONF.register_opts(netapp_support_opts)
CONF.register_opts(netapp_replication_opts)

View File

@ -23,6 +23,11 @@ CONNECTION_INFO = {
'password': 'passw0rd'
}
CLUSTER_NAME = 'fake_cluster'
REMOTE_CLUSTER_NAME = 'fake_cluster_2'
CLUSTER_ADDRESS_1 = 'fake_cluster_address'
CLUSTER_ADDRESS_2 = 'fake_cluster_address_2'
VERSION = 'NetApp Release 8.2.1 Cluster-Mode: Fri Mar 21 14:25:07 PDT 2014'
NODE_NAME = 'fake_node'
VSERVER_NAME = 'fake_vserver'
VSERVER_NAME_2 = 'fake_vserver_2'
@ -68,6 +73,11 @@ LIF_NAME = LIF_NAME_TEMPLATE % {'net_allocation_id': NET_ALLOCATION_ID}
IPSPACE_NAME = 'fake_ipspace'
BROADCAST_DOMAIN = 'fake_domain'
MTU = 9000
SM_SOURCE_VSERVER = 'fake_source_vserver'
SM_SOURCE_VOLUME = 'fake_source_volume'
SM_DEST_VSERVER = 'fake_destination_vserver'
SM_DEST_VOLUME = 'fake_destination_volume'
IPSPACES = [{
'uuid': 'fake_uuid',
@ -203,6 +213,21 @@ ONTAPI_VERSION_RESPONSE = etree.XML("""
</results>
""")
SYSTEM_GET_VERSION_RESPONSE = etree.XML("""
<results status="passed">
<build-timestamp>1395426307</build-timestamp>
<is-clustered>true</is-clustered>
<version>%(version)s</version>
<version-tuple>
<system-version-tuple>
<generation>8</generation>
<major>2</major>
<minor>1</minor>
</system-version-tuple>
</version-tuple>
</results>
""" % {'version': VERSION})
LICENSE_V2_LIST_INFO_RESPONSE = etree.XML("""
<results status="passed">
<licenses>
@ -1568,3 +1593,122 @@ SIS_GET_ITER_RESPONSE = etree.XML("""
'vserver': VSERVER_NAME,
'volume': SHARE_NAME,
})
CLUSTER_PEER_GET_ITER_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
<cluster-peer-info>
<active-addresses>
<remote-inet-address>%(addr1)s</remote-inet-address>
<remote-inet-address>%(addr2)s</remote-inet-address>
</active-addresses>
<availability>available</availability>
<cluster-name>%(cluster)s</cluster-name>
<cluster-uuid>fake_uuid</cluster-uuid>
<peer-addresses>
<remote-inet-address>%(addr1)s</remote-inet-address>
</peer-addresses>
<remote-cluster-name>%(remote_cluster)s</remote-cluster-name>
<serial-number>fake_serial_number</serial-number>
<timeout>60</timeout>
</cluster-peer-info>
</attributes-list>
<num-records>1</num-records>
</results>
""" % {
'addr1': CLUSTER_ADDRESS_1,
'addr2': CLUSTER_ADDRESS_2,
'cluster': CLUSTER_NAME,
'remote_cluster': REMOTE_CLUSTER_NAME,
})
CLUSTER_PEER_POLICY_GET_RESPONSE = etree.XML("""
<results status="passed">
<attributes>
<cluster-peer-policy>
<is-unauthenticated-access-permitted>false</is-unauthenticated-access-permitted>
<passphrase-minimum-length>8</passphrase-minimum-length>
</cluster-peer-policy>
</attributes>
</results>
""")
VSERVER_PEER_GET_ITER_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
<vserver-peer-info>
<applications>
<vserver-peer-application>snapmirror</vserver-peer-application>
</applications>
<peer-cluster>%(cluster)s</peer-cluster>
<peer-state>peered</peer-state>
<peer-vserver>%(vserver2)s</peer-vserver>
<vserver>%(vserver1)s</vserver>
</vserver-peer-info>
</attributes-list>
<num-records>2</num-records>
</results>
""" % {
'cluster': CLUSTER_NAME,
'vserver1': VSERVER_NAME,
'vserver2': VSERVER_NAME_2
})
SNAPMIRROR_GET_ITER_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
<snapmirror-info>
<destination-volume>fake_destination_volume</destination-volume>
<destination-volume-node>fake_destination_node</destination-volume-node>
<destination-vserver>fake_destination_vserver</destination-vserver>
<exported-snapshot>fake_snapshot</exported-snapshot>
<exported-snapshot-timestamp>1442701782</exported-snapshot-timestamp>
<is-constituent>false</is-constituent>
<is-healthy>true</is-healthy>
<lag-time>2187</lag-time>
<last-transfer-duration>109</last-transfer-duration>
<last-transfer-end-timestamp>1442701890</last-transfer-end-timestamp>
<last-transfer-from>test:manila</last-transfer-from>
<last-transfer-size>1171456</last-transfer-size>
<last-transfer-type>initialize</last-transfer-type>
<max-transfer-rate>0</max-transfer-rate>
<mirror-state>snapmirrored</mirror-state>
<newest-snapshot>fake_snapshot</newest-snapshot>
<newest-snapshot-timestamp>1442701782</newest-snapshot-timestamp>
<policy>DPDefault</policy>
<relationship-control-plane>v2</relationship-control-plane>
<relationship-id>ea8bfcc6-5f1d-11e5-8446-123478563412</relationship-id>
<relationship-status>idle</relationship-status>
<relationship-type>data_protection</relationship-type>
<schedule>daily</schedule>
<source-volume>fake_source_volume</source-volume>
<source-vserver>fake_source_vserver</source-vserver>
<vserver>fake_destination_vserver</vserver>
</snapmirror-info>
</attributes-list>
<num-records>1</num-records>
</results>
""")
SNAPMIRROR_GET_ITER_FILTERED_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
<snapmirror-info>
<destination-vserver>fake_destination_vserver</destination-vserver>
<destination-volume>fake_destination_volume</destination-volume>
<is-healthy>true</is-healthy>
<mirror-state>snapmirrored</mirror-state>
<schedule>daily</schedule>
<source-vserver>fake_source_vserver</source-vserver>
<source-volume>fake_source_volume</source-volume>
</snapmirror-info>
</attributes-list>
<num-records>1</num-records>
</results>
""")
SNAPMIRROR_INITIALIZE_RESULT = etree.XML("""
<results status="passed">
<result-status>succeeded</result-status>
</results>
""")

View File

@ -23,6 +23,7 @@ from manila import test
from manila.tests.share.drivers.netapp.dataontap.client import fakes as fake
@ddt.ddt
class NetAppBaseClientTestCase(test.TestCase):
def setUp(self):
@ -57,12 +58,29 @@ class NetAppBaseClientTestCase(test.TestCase):
self.assertEqual(1, major)
self.assertEqual(20, minor)
def test_get_system_version(self):
version_response = netapp_api.NaElement(
fake.SYSTEM_GET_VERSION_RESPONSE)
self.connection.invoke_successfully.return_value = version_response
result = self.client.get_system_version()
self.assertEqual(fake.VERSION, result['version'])
self.assertEqual(('8', '2', '1'), result['version-tuple'])
def test_init_features(self):
self.client._init_features()
self.assertSetEqual(set(), self.client.features.defined_features)
@ddt.data('tag_name', '{http://www.netapp.com/filer/admin}tag_name')
def test_strip_xml_namespace(self, element):
result = self.client._strip_xml_namespace(element)
self.assertEqual('tag_name', result)
def test_send_request(self):
element = netapp_api.NaElement('fake-api')

View File

@ -2039,6 +2039,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
'containing-aggr-name': fake.SHARE_AGGREGATE_NAME,
'size': '100g',
'volume': fake.SHARE_NAME,
'volume-type': 'rw',
'junction-path': '/%s' % fake.SHARE_NAME,
}
@ -2065,6 +2066,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
'junction-path': '/%s' % fake.SHARE_NAME,
'space-reserve': 'none',
'language-code': 'en-US',
'volume-type': 'rw',
'snapshot-policy': 'default',
'percentage-snapshot-reserve': '15',
}
@ -3901,3 +3903,756 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.assertRaises(netapp_api.NaApiError,
self.client.check_for_cluster_credentials)
def test_create_cluster_peer(self):
self.mock_object(self.client, 'send_request')
self.client.create_cluster_peer(['fake_address_1', 'fake_address_2'],
'fake_user', 'fake_password',
'fake_passphrase')
cluster_peer_create_args = {
'peer-addresses': [
{'remote-inet-address': 'fake_address_1'},
{'remote-inet-address': 'fake_address_2'},
],
'user-name': 'fake_user',
'password': 'fake_password',
'passphrase': 'fake_passphrase',
}
self.client.send_request.assert_has_calls([
mock.call('cluster-peer-create', cluster_peer_create_args)])
def test_get_cluster_peers(self):
api_response = netapp_api.NaElement(
fake.CLUSTER_PEER_GET_ITER_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
result = self.client.get_cluster_peers()
cluster_peer_get_iter_args = {'max-records': 1000}
self.client.send_request.assert_has_calls([
mock.call('cluster-peer-get-iter', cluster_peer_get_iter_args)])
expected = [{
'active-addresses': [
fake.CLUSTER_ADDRESS_1,
fake.CLUSTER_ADDRESS_2
],
'availability': 'available',
'cluster-name': fake.CLUSTER_NAME,
'cluster-uuid': 'fake_uuid',
'peer-addresses': [fake.CLUSTER_ADDRESS_1],
'remote-cluster-name': fake.REMOTE_CLUSTER_NAME,
'serial-number': 'fake_serial_number',
'timeout': '60',
}]
self.assertEqual(expected, result)
def test_get_cluster_peers_single(self):
api_response = netapp_api.NaElement(
fake.CLUSTER_PEER_GET_ITER_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
self.client.get_cluster_peers(remote_cluster_name=fake.CLUSTER_NAME)
cluster_peer_get_iter_args = {
'query': {
'cluster-peer-info': {
'remote-cluster-name': fake.CLUSTER_NAME,
}
},
'max-records': 1000,
}
self.client.send_request.assert_has_calls([
mock.call('cluster-peer-get-iter', cluster_peer_get_iter_args)])
def test_get_cluster_peers_not_found(self):
api_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
result = self.client.get_cluster_peers(
remote_cluster_name=fake.CLUSTER_NAME)
self.assertEqual([], result)
self.assertTrue(self.client.send_request.called)
def test_delete_cluster_peer(self):
self.mock_object(self.client, 'send_request')
self.client.delete_cluster_peer(fake.CLUSTER_NAME)
cluster_peer_delete_args = {'cluster-name': fake.CLUSTER_NAME}
self.client.send_request.assert_has_calls([
mock.call('cluster-peer-delete', cluster_peer_delete_args)])
def test_get_cluster_peer_policy(self):
self.client.features.add_feature('CLUSTER_PEER_POLICY')
api_response = netapp_api.NaElement(
fake.CLUSTER_PEER_POLICY_GET_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
result = self.client.get_cluster_peer_policy()
expected = {
'is-unauthenticated-access-permitted': False,
'passphrase-minimum-length': 8
}
self.assertEqual(expected, result)
self.assertTrue(self.client.send_request.called)
def test_get_cluster_peer_policy_not_supported(self):
result = self.client.get_cluster_peer_policy()
self.assertEqual({}, result)
def test_set_cluster_peer_policy_not_supported(self):
self.mock_object(self.client, 'send_request')
self.client.set_cluster_peer_policy()
self.assertFalse(self.client.send_request.called)
def test_set_cluster_peer_policy_no_arguments(self):
self.client.features.add_feature('CLUSTER_PEER_POLICY')
self.mock_object(self.client, 'send_request')
self.client.set_cluster_peer_policy()
self.assertFalse(self.client.send_request.called)
def test_set_cluster_peer_policy(self):
self.client.features.add_feature('CLUSTER_PEER_POLICY')
self.mock_object(self.client, 'send_request')
self.client.set_cluster_peer_policy(
is_unauthenticated_access_permitted=True,
passphrase_minimum_length=12)
cluster_peer_policy_modify_args = {
'is-unauthenticated-access-permitted': 'true',
'passphrase-minlength': '12',
}
self.client.send_request.assert_has_calls([
mock.call('cluster-peer-policy-modify',
cluster_peer_policy_modify_args)])
def test_create_vserver_peer(self):
self.mock_object(self.client, 'send_request')
self.client.create_vserver_peer('fake_vserver', 'fake_vserver_peer')
vserver_peer_create_args = {
'vserver': 'fake_vserver',
'peer-vserver': 'fake_vserver_peer',
'applications': [
{'vserver-peer-application': 'snapmirror'},
],
}
self.client.send_request.assert_has_calls([
mock.call('vserver-peer-create', vserver_peer_create_args)])
def test_delete_vserver_peer(self):
self.mock_object(self.client, 'send_request')
self.client.delete_vserver_peer('fake_vserver', 'fake_vserver_peer')
vserver_peer_delete_args = {
'vserver': 'fake_vserver',
'peer-vserver': 'fake_vserver_peer',
}
self.client.send_request.assert_has_calls([
mock.call('vserver-peer-delete', vserver_peer_delete_args)])
def test_accept_vserver_peer(self):
self.mock_object(self.client, 'send_request')
self.client.accept_vserver_peer('fake_vserver', 'fake_vserver_peer')
vserver_peer_accept_args = {
'vserver': 'fake_vserver',
'peer-vserver': 'fake_vserver_peer',
}
self.client.send_request.assert_has_calls([
mock.call('vserver-peer-accept', vserver_peer_accept_args)])
def test_get_vserver_peers(self):
api_response = netapp_api.NaElement(
fake.VSERVER_PEER_GET_ITER_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
result = self.client.get_vserver_peers(
vserver_name=fake.VSERVER_NAME,
peer_vserver_name=fake.VSERVER_NAME_2)
vserver_peer_get_iter_args = {
'query': {
'vserver-peer-info': {
'vserver': fake.VSERVER_NAME,
'peer-vserver': fake.VSERVER_NAME_2,
}
},
'max-records': 1000,
}
self.client.send_request.assert_has_calls([
mock.call('vserver-peer-get-iter', vserver_peer_get_iter_args)])
expected = [{
'vserver': 'fake_vserver',
'peer-vserver': 'fake_vserver_2',
'peer-state': 'peered',
'peer-cluster': 'fake_cluster'
}]
self.assertEqual(expected, result)
def test_get_vserver_peers_not_found(self):
api_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
result = self.client.get_vserver_peers(
vserver_name=fake.VSERVER_NAME,
peer_vserver_name=fake.VSERVER_NAME_2)
self.assertEqual([], result)
self.assertTrue(self.client.send_request.called)
def test_ensure_snapmirror_v2(self):
self.assertIsNone(self.client._ensure_snapmirror_v2())
def test_ensure_snapmirror_v2_not_supported(self):
self.client.features.add_feature('SNAPMIRROR_V2', supported=False)
self.assertRaises(exception.NetAppException,
self.client._ensure_snapmirror_v2)
@ddt.data({'schedule': 'fake_schedule', 'policy': 'fake_policy'},
{'schedule': None, 'policy': None})
@ddt.unpack
def test_create_snapmirror(self, schedule, policy):
self.mock_object(self.client, 'send_request')
self.client.create_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
schedule=schedule, policy=policy)
snapmirror_create_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
'relationship-type': 'data_protection',
}
if schedule:
snapmirror_create_args['schedule'] = schedule
if policy:
snapmirror_create_args['policy'] = policy
self.client.send_request.assert_has_calls([
mock.call('snapmirror-create', snapmirror_create_args)])
def test_create_snapmirror_already_exists(self):
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
code=netapp_api.ERELATION_EXISTS))
self.mock_object(self.client, 'send_request', mock_send_req)
self.client.create_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
snapmirror_create_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
'relationship-type': 'data_protection',
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-create', snapmirror_create_args)])
def test_create_snapmirror_error(self):
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
code=0))
self.mock_object(self.client, 'send_request', mock_send_req)
self.assertRaises(netapp_api.NaApiError, self.client.create_snapmirror,
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
self.assertTrue(self.client.send_request.called)
@ddt.data(
{
'source_snapshot': 'fake_snapshot',
'transfer_priority': 'fake_priority'
},
{
'source_snapshot': None,
'transfer_priority': None
}
)
@ddt.unpack
def test_initialize_snapmirror(self, source_snapshot, transfer_priority):
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(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
source_snapshot=source_snapshot,
transfer_priority=transfer_priority)
snapmirror_initialize_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
}
if source_snapshot:
snapmirror_initialize_args['source-snapshot'] = source_snapshot
if transfer_priority:
snapmirror_initialize_args['transfer-priority'] = transfer_priority
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)
def test_release_snapmirror(self, relationship_info_only):
self.mock_object(self.client, 'send_request')
self.client.release_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
relationship_info_only=relationship_info_only)
snapmirror_release_args = {
'query': {
'snapmirror-destination-info': {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
'relationship-info-only': ('true' if relationship_info_only
else 'false'),
}
}
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-release-iter', snapmirror_release_args)])
def test_quiesce_snapmirror(self):
self.mock_object(self.client, 'send_request')
self.client.quiesce_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
snapmirror_quiesce_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-quiesce', snapmirror_quiesce_args)])
@ddt.data(True, False)
def test_abort_snapmirror(self, clear_checkpoint):
self.mock_object(self.client, 'send_request')
self.client.abort_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
clear_checkpoint=clear_checkpoint)
snapmirror_abort_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
'clear-checkpoint': 'true' if clear_checkpoint else 'false',
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-abort', snapmirror_abort_args)])
def test_abort_snapmirror_no_transfer_in_progress(self):
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
code=netapp_api.ENOTRANSFER_IN_PROGRESS))
self.mock_object(self.client, 'send_request', mock_send_req)
self.client.abort_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
snapmirror_abort_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
'clear-checkpoint': 'false',
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-abort', snapmirror_abort_args)])
def test_abort_snapmirror_error(self):
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(code=0))
self.mock_object(self.client, 'send_request', mock_send_req)
self.assertRaises(netapp_api.NaApiError, self.client.abort_snapmirror,
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
def test_break_snapmirror(self):
self.mock_object(self.client, 'send_request')
self.client.break_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
snapmirror_break_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-break', snapmirror_break_args)])
@ddt.data(
{
'schedule': 'fake_schedule',
'policy': 'fake_policy',
'tries': 5,
'max_transfer_rate': 1024,
},
{
'schedule': None,
'policy': None,
'tries': None,
'max_transfer_rate': None,
}
)
@ddt.unpack
def test_modify_snapmirror(self, schedule, policy, tries,
max_transfer_rate):
self.mock_object(self.client, 'send_request')
self.client.modify_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
schedule=schedule, policy=policy, tries=tries,
max_transfer_rate=max_transfer_rate)
snapmirror_modify_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
}
if schedule:
snapmirror_modify_args['schedule'] = schedule
if policy:
snapmirror_modify_args['policy'] = policy
if tries:
snapmirror_modify_args['tries'] = tries
if max_transfer_rate:
snapmirror_modify_args['max-transfer-rate'] = max_transfer_rate
self.client.send_request.assert_has_calls([
mock.call('snapmirror-modify', snapmirror_modify_args)])
def test_update_snapmirror(self):
self.mock_object(self.client, 'send_request')
self.client.update_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
snapmirror_update_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-update', snapmirror_update_args)])
def test_update_snapmirror_already_transferring(self):
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
code=netapp_api.ETRANSFER_IN_PROGRESS))
self.mock_object(self.client, 'send_request', mock_send_req)
self.client.update_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
snapmirror_update_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-update', snapmirror_update_args)])
def test_update_snapmirror_already_transferring_two(self):
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
code=netapp_api.EANOTHER_OP_ACTIVE))
self.mock_object(self.client, 'send_request', mock_send_req)
self.client.update_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
snapmirror_update_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-update', snapmirror_update_args)])
def test_update_snapmirror_error(self):
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(code=0))
self.mock_object(self.client, 'send_request', mock_send_req)
self.assertRaises(netapp_api.NaApiError, self.client.update_snapmirror,
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
def test_delete_snapmirror(self):
self.mock_object(self.client, 'send_request')
self.client.delete_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
snapmirror_delete_args = {
'query': {
'snapmirror-info': {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
}
}
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-destroy-iter', snapmirror_delete_args)])
def test__get_snapmirrors(self):
api_response = netapp_api.NaElement(fake.SNAPMIRROR_GET_ITER_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
desired_attributes = {
'snapmirror-info': {
'source-vserver': None,
'source-volume': None,
'destination-vserver': None,
'destination-volume': None,
'is-healthy': None,
}
}
result = self.client._get_snapmirrors(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
desired_attributes=desired_attributes)
snapmirror_get_iter_args = {
'query': {
'snapmirror-info': {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
},
},
'desired-attributes': {
'snapmirror-info': {
'source-vserver': None,
'source-volume': None,
'destination-vserver': None,
'destination-volume': None,
'is-healthy': None,
},
},
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-get-iter', snapmirror_get_iter_args)])
self.assertEqual(1, len(result))
def test__get_snapmirrors_not_found(self):
api_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
result = self.client._get_snapmirrors()
self.client.send_request.assert_has_calls([
mock.call('snapmirror-get-iter', {})])
self.assertEqual([], result)
def test_get_snapmirrors(self):
api_response = netapp_api.NaElement(
fake.SNAPMIRROR_GET_ITER_FILTERED_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
desired_attributes = ['source-vserver', 'source-volume',
'destination-vserver', 'destination-volume',
'is-healthy', 'mirror-state', 'schedule']
result = self.client.get_snapmirrors(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
desired_attributes=desired_attributes)
snapmirror_get_iter_args = {
'query': {
'snapmirror-info': {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
},
},
'desired-attributes': {
'snapmirror-info': {
'source-vserver': None,
'source-volume': None,
'destination-vserver': None,
'destination-volume': None,
'is-healthy': None,
'mirror-state': None,
'schedule': None,
},
},
}
expected = [{
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
'is-healthy': 'true',
'mirror-state': 'snapmirrored',
'schedule': 'daily',
}]
self.client.send_request.assert_has_calls([
mock.call('snapmirror-get-iter', snapmirror_get_iter_args)])
self.assertEqual(expected, result)
def test_resume_snapmirror(self):
self.mock_object(self.client, 'send_request')
self.client.resume_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
snapmirror_resume_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-resume', snapmirror_resume_args)])
def test_resume_snapmirror_not_quiesed(self):
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
code=netapp_api.ERELATION_NOT_QUIESCED))
self.mock_object(self.client, 'send_request', mock_send_req)
self.client.resume_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
snapmirror_resume_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-resume', snapmirror_resume_args)])
def test_resume_snapmirror_error(self):
mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(code=0))
self.mock_object(self.client, 'send_request', mock_send_req)
self.assertRaises(netapp_api.NaApiError, self.client.resume_snapmirror,
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
def test_resync_snapmirror(self):
self.mock_object(self.client, 'send_request')
self.client.resync_snapmirror(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
snapmirror_resync_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-resync', snapmirror_resync_args)])

View File

@ -0,0 +1,497 @@
# Copyright (c) 2015 Alex Meade. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import time
import mock
from oslo_config import cfg
from manila.share import configuration
from manila.share import driver
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.cluster_mode import data_motion
from manila.share.drivers.netapp import options as na_opts
from manila import test
from manila.tests.share.drivers.netapp.dataontap import fakes as fake
from manila.tests.share.drivers.netapp import fakes as na_fakes
CONF = cfg.CONF
class NetAppCDOTDataMotionTestCase(test.TestCase):
def setUp(self):
super(NetAppCDOTDataMotionTestCase, self).setUp()
self.backend = 'backend1'
self.mock_cmode_client = self.mock_object(client_cmode,
"NetAppCmodeClient",
mock.Mock())
self.config = configuration.Configuration(driver.share_opts,
config_group=self.backend)
self.config.append_config_values(na_opts.netapp_cluster_opts)
self.config.append_config_values(na_opts.netapp_connection_opts)
self.config.append_config_values(na_opts.netapp_basicauth_opts)
self.config.append_config_values(na_opts.netapp_transport_opts)
self.config.append_config_values(na_opts.netapp_support_opts)
self.config.append_config_values(na_opts.netapp_provisioning_opts)
self.config.append_config_values(na_opts.netapp_replication_opts)
CONF.set_override("share_backend_name", self.backend,
group=self.backend)
CONF.set_override("netapp_transport_type", "https",
group=self.backend)
CONF.set_override("netapp_login", "fake_user",
group=self.backend)
CONF.set_override("netapp_password", "fake_password",
group=self.backend)
CONF.set_override("netapp_server_hostname", "fake_hostname",
group=self.backend)
CONF.set_override("netapp_server_port", 8866,
group=self.backend)
def test_get_client_for_backend(self):
self.mock_object(data_motion, "get_backend_configuration",
mock.Mock(return_value=self.config))
data_motion.get_client_for_backend(self.backend)
self.mock_cmode_client.assert_called_once_with(
hostname='fake_hostname', password='fake_password',
username='fake_user', transport_type='https', port=8866,
trace=mock.ANY, vserver=None)
def test_get_client_for_backend_with_vserver(self):
self.mock_object(data_motion, "get_backend_configuration",
mock.Mock(return_value=self.config))
CONF.set_override("netapp_vserver", 'fake_vserver',
group=self.backend)
data_motion.get_client_for_backend(self.backend)
self.mock_cmode_client.assert_called_once_with(
hostname='fake_hostname', password='fake_password',
username='fake_user', transport_type='https', port=8866,
trace=mock.ANY, vserver='fake_vserver')
def test_get_config_for_backend(self):
self.mock_object(data_motion, "CONF")
data_motion.CONF.list_all_sections.return_value = [self.backend]
config = data_motion.get_backend_configuration(self.backend)
self.assertEqual(self.backend, config.share_backend_name)
def test_get_config_for_backend_share_backend_name_mismatch(self):
self.mock_object(data_motion, "CONF")
configuration.Configuration(driver.share_opts,
config_group='my_happy_stanza')
self.config.append_config_values(na_opts.netapp_cluster_opts)
self.config.append_config_values(na_opts.netapp_connection_opts)
self.config.append_config_values(na_opts.netapp_basicauth_opts)
self.config.append_config_values(na_opts.netapp_transport_opts)
self.config.append_config_values(na_opts.netapp_support_opts)
self.config.append_config_values(na_opts.netapp_provisioning_opts)
self.config.append_config_values(na_opts.netapp_replication_opts)
CONF.set_override("share_backend_name", self.backend,
group='my_happy_stanza')
data_motion.CONF.list_all_sections.return_value = ['my_happy_stanza']
config = data_motion.get_backend_configuration(self.backend)
self.assertEqual(self.backend, config.share_backend_name)
def test_get_config_for_backend_not_configured(self):
self.mock_object(data_motion, "CONF")
data_motion.CONF.list_all_sections.return_value = []
config = data_motion.get_backend_configuration(self.backend)
self.assertIsNone(config)
class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
def setUp(self):
super(NetAppCDOTDataMotionSessionTestCase, self).setUp()
self.source_backend = 'backend1'
self.dest_backend = 'backend2'
config = configuration.Configuration(driver.share_opts,
config_group=self.source_backend)
config.append_config_values(na_opts.netapp_cluster_opts)
config.append_config_values(na_opts.netapp_connection_opts)
config.append_config_values(na_opts.netapp_basicauth_opts)
config.append_config_values(na_opts.netapp_transport_opts)
config.append_config_values(na_opts.netapp_support_opts)
config.append_config_values(na_opts.netapp_provisioning_opts)
config.append_config_values(na_opts.netapp_replication_opts)
self.mock_object(data_motion, "get_backend_configuration",
mock.Mock(return_value=config))
self.mock_cmode_client = self.mock_object(client_cmode,
"NetAppCmodeClient",
mock.Mock())
self.dm_session = data_motion.DataMotionSession()
self.fake_src_share = copy.deepcopy(fake.SHARE)
self.fake_src_share_server = copy.deepcopy(fake.SHARE_SERVER)
self.source_vserver = 'source_vserver'
self.fake_src_share_server['backend_details']['vserver_name'] = (
self.source_vserver
)
self.fake_src_share['share_server'] = self.fake_src_share_server
self.fake_src_share['id'] = '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_server = copy.deepcopy(fake.SHARE_SERVER)
self.dest_vserver = 'dest_vserver'
self.fake_dest_share_server['backend_details']['vserver_name'] = (
self.dest_vserver
)
self.fake_dest_share['share_server'] = self.fake_dest_share_server
self.fake_dest_share['id'] = '34fbaf57-745d-460f-8270-3378c2945e30'
self.fake_dest_vol_name = 'share_34fbaf57_745d_460f_8270_3378c2945e30'
self.mock_src_client = mock.Mock()
self.mock_dest_client = mock.Mock()
self.mock_object(data_motion, 'get_client_for_backend',
mock.Mock(side_effect=[self.mock_dest_client,
self.mock_src_client]))
def test_create_snapmirror(self):
mock_dest_client = mock.Mock()
self.mock_object(data_motion, 'get_client_for_backend',
mock.Mock(return_value=mock_dest_client))
self.dm_session.create_snapmirror(self.fake_src_share,
self.fake_dest_share)
mock_dest_client.create_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name, schedule='hourly'
)
mock_dest_client.initialize_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
)
def test_delete_snapmirror(self):
mock_src_client = mock.Mock()
mock_dest_client = mock.Mock()
self.mock_object(data_motion, 'get_client_for_backend',
mock.Mock(side_effect=[mock_dest_client,
mock_src_client]))
self.dm_session.delete_snapmirror(self.fake_src_share,
self.fake_dest_share)
mock_dest_client.abort_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name, clear_checkpoint=False
)
mock_dest_client.delete_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
)
mock_src_client.release_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
)
def test_delete_snapmirror_does_not_exist(self):
"""Ensure delete succeeds when the snapmirror does not exist."""
mock_src_client = mock.Mock()
mock_dest_client = mock.Mock()
mock_dest_client.abort_snapmirror.side_effect = netapp_api.NaApiError(
code=netapp_api.EAPIERROR
)
self.mock_object(data_motion, 'get_client_for_backend',
mock.Mock(side_effect=[mock_dest_client,
mock_src_client]))
self.dm_session.delete_snapmirror(self.fake_src_share,
self.fake_dest_share)
mock_dest_client.abort_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name, clear_checkpoint=False
)
mock_dest_client.delete_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
)
mock_src_client.release_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
)
def test_delete_snapmirror_error_deleting(self):
"""Ensure delete succeeds when the snapmirror does not exist."""
mock_src_client = mock.Mock()
mock_dest_client = mock.Mock()
mock_dest_client.delete_snapmirror.side_effect = netapp_api.NaApiError(
code=netapp_api.ESOURCE_IS_DIFFERENT
)
self.mock_object(data_motion, 'get_client_for_backend',
mock.Mock(side_effect=[mock_dest_client,
mock_src_client]))
self.dm_session.delete_snapmirror(self.fake_src_share,
self.fake_dest_share)
mock_dest_client.abort_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name, clear_checkpoint=False
)
mock_dest_client.delete_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
)
mock_src_client.release_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
)
def test_delete_snapmirror_error_releasing(self):
"""Ensure delete succeeds when the snapmirror does not exist."""
mock_src_client = mock.Mock()
mock_dest_client = mock.Mock()
mock_src_client.release_snapmirror.side_effect = (
netapp_api.NaApiError(code=netapp_api.EOBJECTNOTFOUND))
self.mock_object(data_motion, 'get_client_for_backend',
mock.Mock(side_effect=[mock_dest_client,
mock_src_client]))
self.dm_session.delete_snapmirror(self.fake_src_share,
self.fake_dest_share)
mock_dest_client.abort_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name, clear_checkpoint=False
)
mock_dest_client.delete_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
)
mock_src_client.release_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
)
def test_delete_snapmirror_without_release(self):
mock_src_client = mock.Mock()
mock_dest_client = mock.Mock()
self.mock_object(data_motion, 'get_client_for_backend',
mock.Mock(side_effect=[mock_dest_client,
mock_src_client]))
self.dm_session.delete_snapmirror(self.fake_src_share,
self.fake_dest_share,
release=False)
mock_dest_client.abort_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name, clear_checkpoint=False
)
mock_dest_client.delete_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
)
self.assertFalse(mock_src_client.release_snapmirror.called)
def test_delete_snapmirror_source_unreachable(self):
mock_src_client = mock.Mock()
mock_dest_client = mock.Mock()
self.mock_object(data_motion, 'get_client_for_backend',
mock.Mock(side_effect=[mock_dest_client,
Exception]))
self.dm_session.delete_snapmirror(self.fake_src_share,
self.fake_dest_share)
mock_dest_client.abort_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name, clear_checkpoint=False
)
mock_dest_client.delete_snapmirror.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
)
self.assertFalse(mock_src_client.release_snapmirror.called)
def test_break_snapmirror(self):
self.mock_object(self.dm_session, 'quiesce_then_abort')
self.dm_session.break_snapmirror(self.fake_src_share,
self.fake_dest_share)
self.mock_dest_client.break_snapmirror.assert_called_once_with(
self.source_vserver, self.fake_src_vol_name,
self.dest_vserver, self.fake_dest_vol_name)
self.dm_session.quiesce_then_abort.assert_called_once_with(
self.fake_src_share, self.fake_dest_share)
self.mock_dest_client.mount_volume.assert_called_once_with(
self.fake_dest_vol_name)
def test_break_snapmirror_wait_for_quiesced(self):
self.mock_object(self.dm_session, 'quiesce_then_abort')
self.dm_session.break_snapmirror(self.fake_src_share,
self.fake_dest_share)
self.dm_session.quiesce_then_abort.assert_called_once_with(
self.fake_src_share, self.fake_dest_share)
self.mock_dest_client.break_snapmirror.assert_called_once_with(
self.source_vserver, self.fake_src_vol_name,
self.dest_vserver, self.fake_dest_vol_name)
self.mock_dest_client.mount_volume.assert_called_once_with(
self.fake_dest_vol_name)
def test_quiesce_then_abort_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',
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(self.fake_src_share,
self.fake_dest_share)
self.mock_dest_client.get_snapmirrors.assert_called_with(
self.source_vserver, self.fake_src_vol_name,
self.dest_vserver, self.fake_dest_vol_name,
desired_attributes=['relationship-status', 'mirror-state']
)
self.assertEqual(2, self.mock_dest_client.get_snapmirrors.call_count)
self.mock_dest_client.quiesce_snapmirror.assert_called_with(
self.source_vserver, self.fake_src_vol_name,
self.dest_vserver, self.fake_dest_vol_name)
self.mock_dest_client.abort_snapmirror.assert_called_once_with(
self.source_vserver, self.fake_src_vol_name,
self.dest_vserver, self.fake_dest_vol_name,
clear_checkpoint=False
)
def test_quiesce_then_abort_wait_for_quiesced(self):
self.mock_object(time, 'sleep')
self.mock_object(self.mock_dest_client, 'get_snapmirrors',
mock.Mock(side_effect=[
[{'relationship-status': "transferring"}],
[{'relationship-status': "quiesced"}]]))
self.dm_session.quiesce_then_abort(self.fake_src_share,
self.fake_dest_share)
self.mock_dest_client.get_snapmirrors.assert_called_with(
self.source_vserver, self.fake_src_vol_name,
self.dest_vserver, self.fake_dest_vol_name,
desired_attributes=['relationship-status', 'mirror-state']
)
self.assertEqual(2, self.mock_dest_client.get_snapmirrors.call_count)
self.mock_dest_client.quiesce_snapmirror.assert_called_once_with(
self.source_vserver, self.fake_src_vol_name,
self.dest_vserver, self.fake_dest_vol_name)
def test_resync_snapmirror(self):
self.dm_session.resync_snapmirror(self.fake_src_share,
self.fake_dest_share)
self.mock_dest_client.resync_snapmirror.assert_called_once_with(
self.source_vserver, self.fake_src_vol_name,
self.dest_vserver, self.fake_dest_vol_name)
def test_change_snapmirror_source(self):
fake_new_src_share = copy.deepcopy(fake.SHARE)
fake_new_src_share['id'] = 'd02d497a-236c-4852-812a-0d39373e312a'
fake_new_src_share_name = 'share_d02d497a_236c_4852_812a_0d39373e312a'
mock_new_src_client = mock.Mock()
self.mock_object(self.dm_session, 'delete_snapmirror')
self.mock_object(data_motion, 'get_client_for_backend',
mock.Mock(side_effect=[self.mock_dest_client,
self.mock_src_client,
self.mock_dest_client,
mock_new_src_client]))
self.dm_session.change_snapmirror_source(
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.assertEqual(4, self.dm_session.delete_snapmirror.call_count)
self.dm_session.delete_snapmirror.assert_called_with(
mock.ANY, mock.ANY, release=False
)
self.mock_dest_client.create_snapmirror.assert_called_once_with(
mock.ANY, fake_new_src_share_name, mock.ANY,
self.fake_dest_vol_name, schedule='hourly'
)
self.mock_dest_client.resync_snapmirror.assert_called_once_with(
mock.ANY, fake_new_src_share_name, mock.ANY,
self.fake_dest_vol_name
)
def test_get_snapmirrors(self):
self.mock_object(self.mock_dest_client, 'get_snapmirrors')
self.dm_session.get_snapmirrors(self.fake_src_share,
self.fake_dest_share)
self.mock_dest_client.get_snapmirrors.assert_called_with(
self.source_vserver, self.fake_src_vol_name,
self.dest_vserver, self.fake_dest_vol_name,
desired_attributes=['relationship-status',
'mirror-state',
'source-vserver',
'source-volume',
'last-transfer-end-timestamp']
)
self.assertEqual(1, self.mock_dest_client.get_snapmirrors.call_count)
def test_update_snapmirror(self):
self.mock_object(self.mock_dest_client, 'get_snapmirrors')
self.dm_session.update_snapmirror(self.fake_src_share,
self.fake_dest_share)
self.mock_dest_client.update_snapmirror.assert_called_once_with(
self.source_vserver, self.fake_src_vol_name,
self.dest_vserver, self.fake_dest_vol_name)
def test_resume_snapmirror(self):
self.mock_object(self.mock_dest_client, 'get_snapmirrors')
self.dm_session.resume_snapmirror(self.fake_src_share,
self.fake_dest_share)
self.mock_dest_client.resume_snapmirror.assert_called_once_with(
self.source_vserver, self.fake_src_vol_name,
self.dest_vserver, self.fake_dest_vol_name)

View File

@ -25,10 +25,14 @@ import ddt
import mock
from oslo_log import log
from oslo_service import loopingcall
from oslo_utils import timeutils
from oslo_utils import units
from manila.common import constants
from manila import exception
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.cluster_mode import data_motion
from manila.share.drivers.netapp.dataontap.cluster_mode import lib_base
from manila.share.drivers.netapp.dataontap.protocols import cifs_cmode
from manila.share.drivers.netapp.dataontap.protocols import nfs_cmode
@ -73,6 +77,15 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library._client = mock.Mock()
self.client = self.library._client
self.context = mock.Mock()
self.fake_replica = copy.deepcopy(fake.SHARE)
self.fake_replica_2 = copy.deepcopy(fake.SHARE)
self.fake_replica_2['id'] = fake.SHARE_ID2
self.fake_replica_2['replica_state'] = (
constants.REPLICA_STATE_OUT_OF_SYNC)
self.mock_dm_session = mock.Mock()
self.mock_object(data_motion, "DataMotionSession",
mock.Mock(return_value=self.mock_dm_session))
self.mock_object(data_motion, 'get_client_for_backend')
def test_init(self):
self.assertEqual(fake.DRIVER_NAME, self.library.driver_name)
@ -234,21 +247,28 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertTrue(mock_ems_periodic_task.start.called)
self.assertTrue(mock_housekeeping_periodic_task.start.called)
def test_get_valid_share_name(self):
def test_get_backend_share_name(self):
result = self.library._get_valid_share_name(fake.SHARE_ID)
result = self.library._get_backend_share_name(fake.SHARE_ID)
expected = (fake.VOLUME_NAME_TEMPLATE %
{'share_id': fake.SHARE_ID.replace('-', '_')})
self.assertEqual(expected, result)
def test_get_valid_snapshot_name(self):
def test_get_backend_snapshot_name(self):
result = self.library._get_valid_snapshot_name(fake.SNAPSHOT_ID)
result = self.library._get_backend_snapshot_name(fake.SNAPSHOT_ID)
expected = 'share_snapshot_' + fake.SNAPSHOT_ID.replace('-', '_')
self.assertEqual(expected, result)
def test_get_backend_cg_snapshot_name(self):
result = self.library._get_backend_cg_snapshot_name(fake.SNAPSHOT_ID)
expected = 'share_cg_snapshot_' + fake.SNAPSHOT_ID.replace('-', '_')
self.assertEqual(expected, result)
def test_get_aggregate_space_cluster_creds(self):
self.library._have_cluster_creds = True
@ -326,6 +346,31 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
}
self.assertDictEqual(expected, result)
def test_get_share_stats_with_replication(self):
self.library.configuration.replication_domain = "fake_domain"
self.mock_object(self.library,
'_get_pools',
mock.Mock(return_value=fake.POOLS))
result = self.library.get_share_stats()
expected = {
'share_backend_name': fake.BACKEND_NAME,
'driver_name': fake.DRIVER_NAME,
'vendor_name': 'NetApp',
'driver_version': '1.0',
'netapp_storage_family': 'ontap_cluster',
'storage_protocol': 'NFS_CIFS',
'total_capacity_gb': 0.0,
'free_capacity_gb': 0.0,
'consistency_group_support': 'host',
'replication_type': 'dr',
'replication_domain': 'fake_domain',
'pools': fake.POOLS,
}
self.assertDictEqual(expected, result)
def test_get_share_server_pools(self):
self.mock_object(self.library,
@ -550,7 +595,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertEqual('fake_export_location', result)
def test_allocate_container(self):
self.mock_object(self.library, '_get_valid_share_name', mock.Mock(
self.mock_object(self.library, '_get_backend_share_name', mock.Mock(
return_value=fake.SHARE_NAME))
self.mock_object(share_utils, 'extract_host', mock.Mock(
return_value=fake.POOL_NAME))
@ -583,8 +628,31 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertDictEqual(fake.REMAPPED_OVERLAPPING_EXTRA_SPEC, result)
def test_allocate_container_as_replica(self):
self.mock_object(self.library, '_get_backend_share_name', mock.Mock(
return_value=fake.SHARE_NAME))
self.mock_object(share_utils, 'extract_host', mock.Mock(
return_value=fake.POOL_NAME))
self.mock_object(share_types, 'get_extra_specs_from_share',
mock.Mock(return_value=fake.EXTRA_SPEC))
self.mock_object(self.library, '_check_boolean_extra_specs_validity')
self.mock_object(self.library, '_get_boolean_provisioning_options',
mock.Mock(return_value=fake.PROVISIONING_OPTIONS))
vserver_client = mock.Mock()
self.library._allocate_container(fake.EXTRA_SPEC_SHARE,
vserver_client, replica=True)
vserver_client.create_volume.assert_called_once_with(
fake.POOL_NAME, fake.SHARE_NAME, fake.SHARE['size'],
thin_provisioned=True, snapshot_policy='default',
language='en-US', dedup_enabled=True,
compression_enabled=False, max_files=5000,
snapshot_reserve=8, volume_type='dp')
def test_allocate_container_no_pool_name(self):
self.mock_object(self.library, '_get_valid_share_name', mock.Mock(
self.mock_object(self.library, '_get_backend_share_name', mock.Mock(
return_value=fake.SHARE_NAME))
self.mock_object(share_utils, 'extract_host', mock.Mock(
return_value=None))
@ -596,7 +664,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library._allocate_container, fake.SHARE,
vserver_client)
self.library._get_valid_share_name.assert_called_once_with(
self.library._get_backend_share_name.assert_called_once_with(
fake.SHARE['id'])
share_utils.extract_host.assert_called_once_with(fake.SHARE['host'],
level='pool')
@ -788,10 +856,10 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.SNAPSHOT,
vserver_client)
share_name = self.library._get_valid_share_name(fake.SHARE['id'])
parent_share_name = self.library._get_valid_share_name(
share_name = self.library._get_backend_share_name(fake.SHARE['id'])
parent_share_name = self.library._get_backend_share_name(
fake.SNAPSHOT['share_id'])
parent_snapshot_name = self.library._get_valid_snapshot_name(
parent_snapshot_name = self.library._get_backend_snapshot_name(
fake.SNAPSHOT['id'])
vserver_client.create_volume_clone.assert_called_once_with(
share_name,
@ -835,7 +903,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.SHARE,
share_server=fake.SHARE_SERVER)
share_name = self.library._get_valid_share_name(fake.SHARE['id'])
share_name = self.library._get_backend_share_name(fake.SHARE['id'])
mock_share_exists.assert_called_once_with(share_name, vserver_client)
mock_remove_export.assert_called_once_with(fake.SHARE, vserver_client)
mock_deallocate_container.assert_called_once_with(share_name,
@ -884,7 +952,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.SHARE,
share_server=fake.SHARE_SERVER)
share_name = self.library._get_valid_share_name(fake.SHARE['id'])
share_name = self.library._get_backend_share_name(fake.SHARE['id'])
mock_share_exists.assert_called_once_with(share_name, vserver_client)
self.assertFalse(mock_remove_export.called)
self.assertFalse(mock_deallocate_container.called)
@ -1049,9 +1117,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.SNAPSHOT,
share_server=fake.SHARE_SERVER)
share_name = self.library._get_valid_share_name(
share_name = self.library._get_backend_share_name(
fake.SNAPSHOT['share_id'])
snapshot_name = self.library._get_valid_snapshot_name(
snapshot_name = self.library._get_backend_snapshot_name(
fake.SNAPSHOT['id'])
vserver_client.create_snapshot.assert_called_once_with(share_name,
snapshot_name)
@ -1070,9 +1138,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.SNAPSHOT,
share_server=fake.SHARE_SERVER)
share_name = self.library._get_valid_share_name(
share_name = self.library._get_backend_share_name(
fake.SNAPSHOT['share_id'])
snapshot_name = self.library._get_valid_snapshot_name(
snapshot_name = self.library._get_backend_snapshot_name(
fake.SNAPSHOT['id'])
self.assertTrue(mock_handle_busy_snapshot.called)
vserver_client.delete_snapshot.assert_called_once_with(share_name,
@ -1612,12 +1680,12 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
share_server=fake.SHARE_SERVER)
share_names = [
self.library._get_valid_share_name(
self.library._get_backend_share_name(
fake.CG_SNAPSHOT_MEMBER_1['share_id']),
self.library._get_valid_share_name(
self.library._get_backend_share_name(
fake.CG_SNAPSHOT_MEMBER_2['share_id'])
]
snapshot_name = self.library._get_valid_cg_snapshot_name(
snapshot_name = self.library._get_backend_cg_snapshot_name(
fake.CG_SNAPSHOT['id'])
vserver_client.create_cg_snapshot.assert_called_once_with(
share_names, snapshot_name)
@ -1660,12 +1728,12 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
share_server=fake.SHARE_SERVER)
share_names = [
self.library._get_valid_share_name(
self.library._get_backend_share_name(
fake.CG_SNAPSHOT_MEMBER_1['share_id']),
self.library._get_valid_share_name(
self.library._get_backend_share_name(
fake.CG_SNAPSHOT_MEMBER_2['share_id'])
]
snapshot_name = self.library._get_valid_cg_snapshot_name(
snapshot_name = self.library._get_backend_cg_snapshot_name(
fake.CG_SNAPSHOT['id'])
mock_handle_busy_snapshot.assert_has_calls([
@ -1720,12 +1788,12 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
share_server=fake.SHARE_SERVER)
share_names = [
self.library._get_valid_share_name(
self.library._get_backend_share_name(
fake.CG_SNAPSHOT_MEMBER_1['share_id']),
self.library._get_valid_share_name(
self.library._get_backend_share_name(
fake.CG_SNAPSHOT_MEMBER_2['share_id'])
]
snapshot_name = self.library._get_valid_cg_snapshot_name(
snapshot_name = self.library._get_backend_cg_snapshot_name(
fake.CG_SNAPSHOT['id'])
mock_handle_busy_snapshot.assert_has_calls([
@ -1809,7 +1877,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_get_vserver.assert_called_once_with(
share_server=fake.SHARE_SERVER)
share_name = self.library._get_valid_share_name(fake.SHARE['id'])
share_name = self.library._get_backend_share_name(fake.SHARE['id'])
mock_share_exists.assert_called_once_with(share_name, vserver_client)
protocol_helper.set_client.assert_called_once_with(vserver_client)
protocol_helper.update_access.assert_called_once_with(
@ -1863,11 +1931,48 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_get_vserver.assert_called_once_with(
share_server=fake.SHARE_SERVER)
share_name = self.library._get_valid_share_name(fake.SHARE['id'])
share_name = self.library._get_backend_share_name(fake.SHARE['id'])
mock_share_exists.assert_called_once_with(share_name, vserver_client)
self.assertFalse(protocol_helper.set_client.called)
self.assertFalse(protocol_helper.update_access.called)
def test_update_access_to_active_replica(self):
fake_share = copy.deepcopy(fake.SHARE)
fake_share['replica_state'] = constants.REPLICA_STATE_ACTIVE
vserver_client = mock.Mock()
mock_get_vserver = self.mock_object(
self.library, '_get_vserver',
mock.Mock(return_value=(fake.VSERVER1, vserver_client)))
protocol_helper = mock.Mock()
protocol_helper.update_access.return_value = None
self.mock_object(self.library,
'_get_helper',
mock.Mock(return_value=protocol_helper))
mock_share_exists = self.mock_object(self.library,
'_share_exists',
mock.Mock(return_value=True))
self.library.update_access(self.context,
fake_share,
[fake.SHARE_ACCESS],
share_server=fake.SHARE_SERVER)
mock_get_vserver.assert_called_once_with(
share_server=fake.SHARE_SERVER)
share_name = self.library._get_backend_share_name(fake.SHARE['id'])
mock_share_exists.assert_called_once_with(share_name, vserver_client)
protocol_helper.set_client.assert_called_once_with(vserver_client)
protocol_helper.update_access.assert_called_once_with(
fake.SHARE, fake.SHARE_NAME, [fake.SHARE_ACCESS])
def test_update_access_to_in_sync_replica(self):
fake_share = copy.deepcopy(fake.SHARE)
fake_share['replica_state'] = constants.REPLICA_STATE_IN_SYNC
self.library.update_access(self.context,
fake_share,
[fake.SHARE_ACCESS],
share_server=fake.SHARE_SERVER)
def test_setup_server(self):
self.assertRaises(NotImplementedError,
self.library.setup_server,
@ -1955,3 +2060,583 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertDictEqual({}, ssc_stats)
self.assertFalse(self.library._client.get_aggregate_raid_types.called)
def test_create_replica(self):
self.mock_object(self.library,
'_allocate_container')
mock_dm_session = mock.Mock()
self.mock_object(data_motion, "DataMotionSession",
mock.Mock(return_value=mock_dm_session))
self.mock_object(data_motion, 'get_client_for_backend')
self.mock_object(mock_dm_session, 'get_vserver_from_share',
mock.Mock(return_value=fake.VSERVER1))
expected_model_update = {
'export_locations': [],
'replica_state': constants.REPLICA_STATE_OUT_OF_SYNC,
'access_rules_status': constants.STATUS_ACTIVE,
}
model_update = self.library.create_replica(
None, [fake.SHARE], fake.SHARE, share_server=None)
self.assertDictMatch(expected_model_update, model_update)
mock_dm_session.create_snapmirror.assert_called_once_with(fake.SHARE,
fake.SHARE)
data_motion.get_client_for_backend.assert_called_once_with(
fake.BACKEND_NAME, vserver_name=fake.VSERVER1)
def test_create_replica_with_share_server(self):
self.mock_object(self.library,
'_allocate_container',
mock.Mock())
mock_dm_session = mock.Mock()
self.mock_object(data_motion, "DataMotionSession",
mock.Mock(return_value=mock_dm_session))
self.mock_object(data_motion, 'get_client_for_backend')
self.mock_object(mock_dm_session, 'get_vserver_from_share',
mock.Mock(return_value=fake.VSERVER1))
expected_model_update = {
'export_locations': [],
'replica_state': constants.REPLICA_STATE_OUT_OF_SYNC,
'access_rules_status': constants.STATUS_ACTIVE,
}
model_update = self.library.create_replica(
None, [fake.SHARE], fake.SHARE, share_server=fake.SHARE_SERVER)
self.assertDictMatch(expected_model_update, model_update)
mock_dm_session.create_snapmirror.assert_called_once_with(fake.SHARE,
fake.SHARE)
data_motion.get_client_for_backend.assert_called_once_with(
fake.BACKEND_NAME, vserver_name=fake.VSERVER1)
def test_delete_replica(self):
self.mock_object(self.library,
'_deallocate_container',
mock.Mock())
mock_dm_session = mock.Mock()
self.mock_object(data_motion, "DataMotionSession",
mock.Mock(return_value=mock_dm_session))
self.mock_object(data_motion, 'get_client_for_backend')
self.mock_object(mock_dm_session, 'get_vserver_from_share',
mock.Mock(return_value=fake.VSERVER1))
result = self.library.delete_replica(None,
[fake.SHARE],
fake.SHARE,
share_server=None)
self.assertEqual(None, result)
mock_dm_session.delete_snapmirror.assert_called_with(fake.SHARE,
fake.SHARE)
self.assertEqual(2, mock_dm_session.delete_snapmirror.call_count)
data_motion.get_client_for_backend.assert_called_with(
fake.BACKEND_NAME, vserver_name=mock.ANY)
self.assertEqual(1, data_motion.get_client_for_backend.call_count)
def test_delete_replica_with_share_server(self):
self.mock_object(self.library,
'_deallocate_container',
mock.Mock())
mock_dm_session = mock.Mock()
self.mock_object(data_motion, "DataMotionSession",
mock.Mock(return_value=mock_dm_session))
self.mock_object(data_motion, 'get_client_for_backend')
self.mock_object(mock_dm_session, 'get_vserver_from_share',
mock.Mock(return_value=fake.VSERVER1))
result = self.library.delete_replica(None,
[fake.SHARE],
fake.SHARE,
share_server=fake.SHARE_SERVER)
self.assertEqual(None, result)
mock_dm_session.delete_snapmirror.assert_called_with(fake.SHARE,
fake.SHARE)
self.assertEqual(2, mock_dm_session.delete_snapmirror.call_count)
data_motion.get_client_for_backend.assert_called_once_with(
fake.BACKEND_NAME, vserver_name=fake.VSERVER1)
def test_update_replica_state_no_snapmirror_share_creating(self):
vserver_client = mock.Mock()
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
vserver_client)))
self.mock_dm_session.get_snapmirrors = mock.Mock(return_value=[])
replica = copy.deepcopy(fake.SHARE)
replica['status'] = constants.STATUS_CREATING
result = self.library.update_replica_state(
None, [replica], replica, None, share_server=None)
self.assertFalse(self.mock_dm_session.create_snapmirror.called)
self.assertEqual(constants.STATUS_OUT_OF_SYNC, result)
def test_update_replica_state_no_snapmirror_create_failed(self):
vserver_client = mock.Mock()
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
vserver_client)))
self.mock_dm_session.get_snapmirrors = mock.Mock(return_value=[])
self.mock_dm_session.create_snapmirror.side_effect = (
netapp_api.NaApiError(code=0))
replica = copy.deepcopy(fake.SHARE)
replica['status'] = constants.REPLICA_STATE_OUT_OF_SYNC
result = self.library.update_replica_state(
None, [replica], replica, None, share_server=None)
self.assertTrue(self.mock_dm_session.create_snapmirror.called)
self.assertEqual(constants.STATUS_ERROR, result)
@ddt.data(constants.STATUS_ERROR, constants.STATUS_AVAILABLE)
def test_update_replica_state_no_snapmirror(self, status):
vserver_client = mock.Mock()
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
vserver_client)))
self.mock_dm_session.get_snapmirrors = mock.Mock(return_value=[])
replica = copy.deepcopy(fake.SHARE)
replica['status'] = status
result = self.library.update_replica_state(
None, [replica], replica, None, share_server=None)
self.assertEqual(1, self.mock_dm_session.create_snapmirror.call_count)
self.assertEqual(constants.STATUS_OUT_OF_SYNC, result)
def test_update_replica_state_broken_snapmirror(self):
fake_snapmirror = {
'mirror-state': 'broken-off',
'relationship-status': 'idle',
'source-vserver': fake.VSERVER2,
'source-volume': 'fake_volume',
'last-transfer-end-timestamp': '%s' % float(time.time() - 10000)
}
vserver_client = mock.Mock()
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
vserver_client)))
self.mock_dm_session.get_snapmirrors = mock.Mock(
return_value=[fake_snapmirror])
result = self.library.update_replica_state(None, [fake.SHARE],
fake.SHARE, None,
share_server=None)
vserver_client.resync_snapmirror.assert_called_once_with(
fake.VSERVER2, 'fake_volume', fake.VSERVER1, fake.SHARE['name']
)
self.assertEqual(constants.REPLICA_STATE_OUT_OF_SYNC, result)
def test_update_replica_state_snapmirror_still_initializing(self):
fake_snapmirror = {
'mirror-state': 'uninitialized',
'relationship-status': 'transferring',
'source-vserver': fake.VSERVER2,
'source-volume': 'fake_volume',
'last-transfer-end-timestamp': '%s' % float(time.time() - 10000)
}
vserver_client = mock.Mock()
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
vserver_client)))
self.mock_dm_session.get_snapmirrors = mock.Mock(
return_value=[fake_snapmirror])
result = self.library.update_replica_state(None, [fake.SHARE],
fake.SHARE, None,
share_server=None)
self.assertEqual(constants.REPLICA_STATE_OUT_OF_SYNC, result)
def test_update_replica_state_fail_to_get_snapmirrors(self):
vserver_client = mock.Mock()
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
vserver_client)))
self.mock_dm_session.get_snapmirrors.side_effect = (
netapp_api.NaApiError(code=0))
result = self.library.update_replica_state(None, [fake.SHARE],
fake.SHARE, None,
share_server=None)
self.assertTrue(self.mock_dm_session.get_snapmirrors.called)
self.assertEqual(constants.STATUS_ERROR, result)
def test_update_replica_state_broken_snapmirror_resync_error(self):
fake_snapmirror = {
'mirror-state': 'broken-off',
'relationship-status': 'idle',
'source-vserver': fake.VSERVER2,
'source-volume': 'fake_volume',
'last-transfer-end-timestamp': '%s' % float(time.time() - 10000)
}
vserver_client = mock.Mock()
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
vserver_client)))
self.mock_dm_session.get_snapmirrors = mock.Mock(
return_value=[fake_snapmirror])
vserver_client.resync_snapmirror.side_effect = netapp_api.NaApiError
result = self.library.update_replica_state(None, [fake.SHARE],
fake.SHARE, None,
share_server=None)
vserver_client.resync_snapmirror.assert_called_once_with(
fake.VSERVER2, 'fake_volume', fake.VSERVER1, fake.SHARE['name']
)
self.assertEqual(constants.STATUS_ERROR, result)
def test_update_replica_state_stale_snapmirror(self):
fake_snapmirror = {
'mirror-state': 'snapmirrored',
'last-transfer-end-timestamp': '%s' % float(
timeutils.utcnow_ts() - 10000)
}
vserver_client = mock.Mock()
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
vserver_client)))
self.mock_dm_session.get_snapmirrors = mock.Mock(
return_value=[fake_snapmirror])
result = self.library.update_replica_state(None, [fake.SHARE],
fake.SHARE, None,
share_server=None)
self.assertEqual(constants.REPLICA_STATE_OUT_OF_SYNC, result)
def test_update_replica_state_in_sync(self):
fake_snapmirror = {
'mirror-state': 'snapmirrored',
'relationship-status': 'idle',
'last-transfer-end-timestamp': '%s' % float(time.time())
}
vserver_client = mock.Mock()
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
vserver_client)))
self.mock_dm_session.get_snapmirrors = mock.Mock(
return_value=[fake_snapmirror])
result = self.library.update_replica_state(None, [fake.SHARE],
fake.SHARE, None,
share_server=None)
self.assertEqual(constants.REPLICA_STATE_IN_SYNC, result)
def test_promote_replica(self):
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
mock.Mock())))
self.mock_object(self.library,
'_get_helper',
mock.Mock(return_value=mock.Mock()))
self.mock_object(self.library, '_create_export',
mock.Mock(return_value='fake_export_location'))
replicas = self.library.promote_replica(
None, [self.fake_replica, self.fake_replica_2],
self.fake_replica_2, [], share_server=None)
self.mock_dm_session.change_snapmirror_source.assert_called_once_with(
self.fake_replica, self.fake_replica, self.fake_replica_2,
mock.ANY
)
self.assertEqual(2, len(replicas))
actual_replica_1 = list(filter(
lambda x: x['id'] == self.fake_replica['id'], replicas))[0]
self.assertEqual(constants.REPLICA_STATE_OUT_OF_SYNC,
actual_replica_1['replica_state'])
actual_replica_2 = list(filter(
lambda x: x['id'] == self.fake_replica_2['id'], replicas))[0]
self.assertEqual(constants.REPLICA_STATE_ACTIVE,
actual_replica_2['replica_state'])
self.assertEqual('fake_export_location',
actual_replica_2['export_locations'])
self.assertEqual(constants.STATUS_ACTIVE,
actual_replica_2['access_rules_status'])
def test_promote_replica_more_than_two_replicas(self):
fake_replica_3 = copy.deepcopy(self.fake_replica_2)
fake_replica_3['id'] = fake.SHARE_ID3
fake_replica_3['replica_state'] = constants.REPLICA_STATE_OUT_OF_SYNC
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
mock.Mock())))
self.mock_object(self.library,
'_get_helper',
mock.Mock(return_value=mock.Mock()))
self.mock_object(self.library, '_create_export',
mock.Mock(return_value='fake_export_location'))
replicas = self.library.promote_replica(
None, [self.fake_replica, self.fake_replica_2, fake_replica_3],
self.fake_replica_2, [], share_server=None)
self.mock_dm_session.change_snapmirror_source.assert_has_calls([
mock.call(fake_replica_3, self.fake_replica, self.fake_replica_2,
mock.ANY),
mock.call(self.fake_replica, self.fake_replica,
self.fake_replica_2, mock.ANY)
], any_order=True)
self.assertEqual(3, len(replicas))
actual_replica_1 = list(filter(
lambda x: x['id'] == self.fake_replica['id'], replicas))[0]
self.assertEqual(constants.REPLICA_STATE_OUT_OF_SYNC,
actual_replica_1['replica_state'])
actual_replica_2 = list(filter(
lambda x: x['id'] == self.fake_replica_2['id'], replicas))[0]
self.assertEqual(constants.REPLICA_STATE_ACTIVE,
actual_replica_2['replica_state'])
self.assertEqual('fake_export_location',
actual_replica_2['export_locations'])
actual_replica_3 = list(filter(
lambda x: x['id'] == fake_replica_3['id'], replicas))[0]
self.assertEqual(constants.REPLICA_STATE_OUT_OF_SYNC,
actual_replica_3['replica_state'])
def test_promote_replica_with_access_rules(self):
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
mock.Mock())))
mock_helper = mock.Mock()
self.mock_object(self.library,
'_get_helper',
mock.Mock(return_value=mock_helper))
self.mock_object(self.library, '_create_export',
mock.Mock(return_value='fake_export_location'))
replicas = self.library.promote_replica(
None, [self.fake_replica, self.fake_replica_2],
self.fake_replica_2, [fake.SHARE_ACCESS], share_server=None)
self.mock_dm_session.change_snapmirror_source.assert_has_calls([
mock.call(self.fake_replica, self.fake_replica,
self.fake_replica_2, mock.ANY)
], any_order=True)
self.assertEqual(2, len(replicas))
share_name = self.library._get_backend_share_name(
self.fake_replica_2['id'])
mock_helper.update_access.assert_called_once_with(self.fake_replica_2,
share_name,
[fake.SHARE_ACCESS])
def test_convert_destination_replica_to_independent(self):
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
mock.Mock())))
self.mock_object(self.library,
'_get_helper',
mock.Mock(return_value=mock.Mock()))
self.mock_object(self.library, '_create_export',
mock.Mock(return_value='fake_export_location'))
replica = self.library._convert_destination_replica_to_independent(
None, self.mock_dm_session, self.fake_replica,
self.fake_replica_2, [], share_server=None)
self.mock_dm_session.update_snapmirror.assert_called_once_with(
self.fake_replica, self.fake_replica_2)
self.mock_dm_session.break_snapmirror.assert_called_once_with(
self.fake_replica, self.fake_replica_2)
self.assertEqual('fake_export_location',
replica['export_locations'])
self.assertEqual(constants.REPLICA_STATE_ACTIVE,
replica['replica_state'])
def test_convert_destination_replica_to_independent_update_failed(self):
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
mock.Mock())))
self.mock_object(self.library,
'_get_helper',
mock.Mock(return_value=mock.Mock()))
self.mock_object(self.library, '_create_export',
mock.Mock(return_value='fake_export_location'))
self.mock_object(self.mock_dm_session, 'update_snapmirror',
mock.Mock(side_effect=netapp_api.NaApiError(code=0)))
replica = self.library._convert_destination_replica_to_independent(
None, self.mock_dm_session, self.fake_replica,
self.fake_replica_2, [], share_server=None)
self.mock_dm_session.update_snapmirror.assert_called_once_with(
self.fake_replica, self.fake_replica_2)
self.mock_dm_session.break_snapmirror.assert_called_once_with(
self.fake_replica, self.fake_replica_2)
self.assertEqual('fake_export_location',
replica['export_locations'])
self.assertEqual(constants.REPLICA_STATE_ACTIVE,
replica['replica_state'])
def test_promote_replica_fail_to_set_access_rules(self):
fake_helper = mock.Mock()
fake_helper.update_access.side_effect = Exception
fake_access_rules = [
{'access_to': "0.0.0.0",
'access_level': constants.ACCESS_LEVEL_RO},
{'access_to': "10.10.10.10",
'access_level': constants.ACCESS_LEVEL_RW},
]
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
mock.Mock())))
self.mock_object(self.library,
'_get_helper',
mock.Mock(return_value=fake_helper))
self.mock_object(self.library, '_create_export',
mock.Mock(return_value='fake_export_location'))
replicas = self.library.promote_replica(
None, [self.fake_replica, self.fake_replica_2],
self.fake_replica_2, fake_access_rules, share_server=None)
self.mock_dm_session.change_snapmirror_source.assert_called_once_with(
self.fake_replica, self.fake_replica, self.fake_replica_2,
mock.ANY
)
self.assertEqual(2, len(replicas))
actual_replica_1 = list(filter(
lambda x: x['id'] == self.fake_replica['id'], replicas))[0]
self.assertEqual(constants.REPLICA_STATE_OUT_OF_SYNC,
actual_replica_1['replica_state'])
actual_replica_2 = list(filter(
lambda x: x['id'] == self.fake_replica_2['id'], replicas))[0]
self.assertEqual(constants.REPLICA_STATE_ACTIVE,
actual_replica_2['replica_state'])
self.assertEqual('fake_export_location',
actual_replica_2['export_locations'])
self.assertEqual(constants.STATUS_OUT_OF_SYNC,
actual_replica_2['access_rules_status'])
def test_convert_destination_replica_to_independent_with_access_rules(
self):
fake_helper = mock.Mock()
fake_helper.update_access.side_effect = Exception
fake_access_rules = [
{'access_to': "0.0.0.0",
'access_level': constants.ACCESS_LEVEL_RO},
{'access_to': "10.10.10.10",
'access_level': constants.ACCESS_LEVEL_RW},
]
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
mock.Mock())))
self.mock_object(self.library,
'_get_helper',
mock.Mock(return_value=fake_helper))
self.mock_object(self.library, '_create_export',
mock.Mock(return_value='fake_export_location'))
replica = self.library._convert_destination_replica_to_independent(
None, self.mock_dm_session, self.fake_replica,
self.fake_replica_2, fake_access_rules, share_server=None)
self.mock_dm_session.update_snapmirror.assert_called_once_with(
self.fake_replica, self.fake_replica_2)
self.mock_dm_session.break_snapmirror.assert_called_once_with(
self.fake_replica, self.fake_replica_2)
self.assertEqual('fake_export_location',
replica['export_locations'])
self.assertEqual(constants.REPLICA_STATE_ACTIVE,
replica['replica_state'])
self.assertEqual(constants.STATUS_OUT_OF_SYNC,
replica['access_rules_status'])
def test_convert_destination_replica_to_independent_failed_access_rules(
self):
fake_helper = mock.Mock()
fake_access_rules = [
{'access_to': "0.0.0.0",
'access_level': constants.ACCESS_LEVEL_RO},
{'access_to': "10.10.10.10",
'access_level': constants.ACCESS_LEVEL_RW},
]
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
mock.Mock())))
self.mock_object(self.library,
'_get_helper',
mock.Mock(return_value=fake_helper))
self.mock_object(self.library, '_create_export',
mock.Mock(return_value='fake_export_location'))
replica = self.library._convert_destination_replica_to_independent(
None, self.mock_dm_session, self.fake_replica,
self.fake_replica_2, fake_access_rules, share_server=None)
self.mock_dm_session.update_snapmirror.assert_called_once_with(
self.fake_replica, self.fake_replica_2)
self.mock_dm_session.break_snapmirror.assert_called_once_with(
self.fake_replica, self.fake_replica_2)
fake_helper.assert_has_calls([
mock.call.set_client(mock.ANY),
mock.call.update_access(mock.ANY, mock.ANY, fake_access_rules),
])
self.assertEqual('fake_export_location',
replica['export_locations'])
self.assertEqual(constants.REPLICA_STATE_ACTIVE,
replica['replica_state'])
self.assertEqual(constants.STATUS_ACTIVE,
replica['access_rules_status'])
def test_safe_change_replica_source(self):
fake_replica_3 = copy.deepcopy(self.fake_replica_2)
fake_replica_3['id'] = fake.SHARE_ID3
fake_replica_3['replica_state'] = constants.REPLICA_STATE_OUT_OF_SYNC
replica = self.library._safe_change_replica_source(
self.mock_dm_session, self.fake_replica, self.fake_replica_2,
fake_replica_3, [self.fake_replica, self.fake_replica_2,
fake_replica_3]
)
self.assertEqual([], replica['export_locations'])
self.assertEqual(constants.REPLICA_STATE_OUT_OF_SYNC,
replica['replica_state'])
def test_safe_change_replica_source_error(self):
self.mock_dm_session.change_snapmirror_source.side_effect = Exception
fake_replica_3 = copy.deepcopy(self.fake_replica_2)
fake_replica_3['id'] = fake.SHARE_ID3
fake_replica_3['replica_state'] = constants.REPLICA_STATE_OUT_OF_SYNC
replica = self.library._safe_change_replica_source(
self.mock_dm_session, self.fake_replica, self.fake_replica_2,
fake_replica_3, [self.fake_replica, self.fake_replica_2,
fake_replica_3]
)
self.assertEqual([], replica['export_locations'])
self.assertEqual(constants.STATUS_ERROR,
replica['replica_state'])

View File

@ -15,6 +15,7 @@
import copy
from manila.common import constants
import manila.tests.share.drivers.netapp.fakes as na_fakes
@ -89,6 +90,7 @@ SHARE = {
'network_info': {
'network_allocations': [{'ip_address': 'ip'}]
},
'replica_state': constants.REPLICA_STATE_ACTIVE,
}
FLEXVOL_TO_MANAGE = {
@ -562,4 +564,5 @@ def get_config_cmode():
config.netapp_root_volume = ROOT_VOLUME
config.netapp_lif_name_template = LIF_NAME_TEMPLATE
config.netapp_volume_snapshot_reserve_percent = 8
config.netapp_vserver = VSERVER1
return config