diff --git a/manila/share/drivers/netapp/dataontap/client/client_cmode.py b/manila/share/drivers/netapp/dataontap/client/client_cmode.py index 1d58ba33b4..90d25b20d0 100644 --- a/manila/share/drivers/netapp/dataontap/client/client_cmode.py +++ b/manila/share/drivers/netapp/dataontap/client/client_cmode.py @@ -3085,6 +3085,23 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): else: raise + @na_utils.trace + def get_cluster_name(self): + """Gets cluster name.""" + api_args = { + 'desired-attributes': { + 'cluster-identity-info': { + 'cluster-name': None, + } + } + } + result = self.send_request('cluster-identity-get', api_args, + enable_tunneling=False) + attributes = result.get_child_by_name('attributes') + cluster_identity = attributes.get_child_by_name( + 'cluster-identity-info') + return cluster_identity.get_child_content('cluster-name') + @na_utils.trace def create_cluster_peer(self, addresses, username=None, password=None, passphrase=None): @@ -3102,7 +3119,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): if passphrase: api_args['passphrase'] = passphrase - self.send_request('cluster-peer-create', api_args) + self.send_request('cluster-peer-create', api_args, + enable_tunneling=False) @na_utils.trace def get_cluster_peers(self, remote_cluster_name=None): @@ -3162,7 +3180,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): """Deletes a cluster peer relationship.""" api_args = {'cluster-name': cluster_name} - self.send_request('cluster-peer-delete', api_args) + self.send_request('cluster-peer-delete', api_args, + enable_tunneling=False) @na_utils.trace def get_cluster_peer_policy(self): @@ -3221,7 +3240,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): self.send_request('cluster-peer-policy-modify', api_args) @na_utils.trace - def create_vserver_peer(self, vserver_name, peer_vserver_name): + def create_vserver_peer(self, vserver_name, peer_vserver_name, + peer_cluster_name=None): """Creates a Vserver peer relationship for SnapMirrors.""" api_args = { 'vserver': vserver_name, @@ -3230,21 +3250,26 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): {'vserver-peer-application': 'snapmirror'}, ], } - self.send_request('vserver-peer-create', api_args) + if peer_cluster_name: + api_args['peer-cluster'] = peer_cluster_name + self.send_request('vserver-peer-create', api_args, + enable_tunneling=False) @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) + self.send_request('vserver-peer-delete', api_args, + enable_tunneling=False) @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) + self.send_request('vserver-peer-accept', api_args, + enable_tunneling=False) @na_utils.trace def get_vserver_peers(self, vserver_name=None, peer_vserver_name=None): diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/data_motion.py b/manila/share/drivers/netapp/dataontap/cluster_mode/data_motion.py index cef0e9ccc2..a77151dd94 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/data_motion.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/data_motion.py @@ -353,8 +353,10 @@ class DataMotionSession(object): 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 + 2. For DHSS=True scenarios, creates a new vserver peer relationship if + it does not exists + 3. Ensure a new source -> replica snapmirror exists + 4. Resync new source -> replica snapmirror relationship """ replica_volume_name, replica_vserver, replica_backend = ( @@ -362,7 +364,7 @@ class DataMotionSession(object): replica_client = get_client_for_backend(replica_backend, vserver_name=replica_vserver) - new_src_volume_name, new_src_vserver, __ = ( + new_src_volume_name, new_src_vserver, new_src_backend = ( self.get_backend_info_for_share(new_source_replica)) # 1. delete @@ -376,14 +378,31 @@ class DataMotionSession(object): self.delete_snapmirror(other_replica, replica, release=False) self.delete_snapmirror(replica, other_replica, release=False) - # 2. create + # 2. vserver operations when driver handles share servers + replica_config = get_backend_configuration(replica_backend) + if replica_config.driver_handles_share_servers: + # create vserver peering if does not exists + if not replica_client.get_vserver_peers(replica_vserver, + new_src_vserver): + new_src_client = get_client_for_backend( + new_src_backend, vserver_name=new_src_vserver) + # Cluster name is needed for setting up the vserver peering + new_src_cluster_name = new_src_client.get_cluster_name() + + replica_client.create_vserver_peer( + replica_vserver, new_src_vserver, + peer_cluster_name=new_src_cluster_name) + new_src_client.accept_vserver_peer(new_src_vserver, + replica_vserver) + + # 3. 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 + # 4. resync replica_client.resync_snapmirror(new_src_vserver, new_src_volume_name, replica_vserver, diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py index 333f012d7f..f24c3fd955 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py @@ -133,42 +133,57 @@ 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, + def create_replica(self, context, replica_list, new_replica, access_rules, replica_snapshots, **kwargs): - raise NotImplementedError() + return self.library.create_replica(context, replica_list, new_replica, + access_rules, replica_snapshots) def delete_replica(self, context, replica_list, replica_snapshots, replica, **kwargs): - raise NotImplementedError() + self.library.delete_replica(context, replica_list, replica, + replica_snapshots) def promote_replica(self, context, replica_list, replica, access_rules, share_server=None): - raise NotImplementedError() + 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, replica_snapshots, share_server=None): - raise NotImplementedError() + return self.library.update_replica_state(context, replica_list, + replica, access_rules, + replica_snapshots, + share_server) def create_replicated_snapshot(self, context, replica_list, replica_snapshots, share_server=None): - raise NotImplementedError() + return self.library.create_replicated_snapshot( + context, replica_list, replica_snapshots, + share_server=share_server) def delete_replicated_snapshot(self, context, replica_list, replica_snapshots, share_server=None): - raise NotImplementedError() + return self.library.delete_replicated_snapshot( + context, replica_list, replica_snapshots, + share_server=share_server) def update_replicated_snapshot(self, context, replica_list, share_replica, replica_snapshots, replica_snapshot, share_server=None): - raise NotImplementedError() + return self.library.update_replicated_snapshot( + replica_list, share_replica, replica_snapshots, replica_snapshot, + share_server=share_server) def revert_to_replicated_snapshot(self, context, active_replica, replica_list, active_replica_snapshot, replica_snapshots, share_access_rules, snapshot_access_rules, - share_server=None): - raise NotImplementedError() + **kwargs): + return self.library.revert_to_replicated_snapshot( + context, active_replica, replica_list, active_replica_snapshot, + replica_snapshots, **kwargs) def migration_check_compatibility(self, context, source_share, destination_share, share_server=None, diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py index 1bebc4e833..e6fd947f20 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py @@ -294,8 +294,7 @@ class NetAppCmodeFileStorageLibrary(object): }, } - if (self.configuration.replication_domain and - not self.configuration.driver_handles_share_servers): + if self.configuration.replication_domain: data['replication_type'] = 'dr' data['replication_domain'] = self.configuration.replication_domain @@ -1463,7 +1462,7 @@ class NetAppCmodeFileStorageLibrary(object): 'netapp_disk_type': disk_types, }) - def _find_active_replica(self, replica_list): + 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: @@ -1478,7 +1477,7 @@ class NetAppCmodeFileStorageLibrary(object): def create_replica(self, context, replica_list, new_replica, access_rules, share_snapshots, share_server=None): """Creates the new replica on this backend and sets up SnapMirror.""" - active_replica = self._find_active_replica(replica_list) + active_replica = self.find_active_replica(replica_list) dm_session = data_motion.DataMotionSession() # 1. Create the destination share @@ -1532,7 +1531,7 @@ class NetAppCmodeFileStorageLibrary(object): def update_replica_state(self, context, replica_list, replica, access_rules, share_snapshots, share_server=None): """Returns the status of the given replica on this backend.""" - active_replica = self._find_active_replica(replica_list) + 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) @@ -1624,7 +1623,7 @@ class NetAppCmodeFileStorageLibrary(object): :param share_server: ShareServer class instance of replica :return: Updated replica_list """ - orig_active_replica = self._find_active_replica(replica_list) + orig_active_replica = self.find_active_replica(replica_list) dm_session = data_motion.DataMotionSession() @@ -1640,7 +1639,7 @@ class NetAppCmodeFileStorageLibrary(object): LOG.exception("Could not communicate with the backend " "for replica %s during promotion.", replica['id']) - new_active_replica = copy.deepcopy(replica) + new_active_replica = replica.copy() new_active_replica['replica_state'] = ( constants.STATUS_ERROR) new_active_replica['status'] = constants.STATUS_ERROR @@ -1760,7 +1759,7 @@ class NetAppCmodeFileStorageLibrary(object): dm_session.break_snapmirror(orig_active_replica, replica) # 3. Setup access rules - new_active_replica = copy.deepcopy(replica) + new_active_replica = replica.copy() helper = self._get_helper(replica) helper.set_client(vserver_client) try: @@ -1817,7 +1816,7 @@ class NetAppCmodeFileStorageLibrary(object): def create_replicated_snapshot(self, context, replica_list, snapshot_instances, share_server=None): - active_replica = self._find_active_replica(replica_list) + active_replica = self.find_active_replica(replica_list) active_snapshot = [x for x in snapshot_instances if x['share_id'] == active_replica['id']][0] snapshot_name = self._get_backend_snapshot_name(active_snapshot['id']) @@ -1849,7 +1848,7 @@ class NetAppCmodeFileStorageLibrary(object): def delete_replicated_snapshot(self, context, replica_list, snapshot_instances, share_server=None): - active_replica = self._find_active_replica(replica_list) + active_replica = self.find_active_replica(replica_list) active_snapshot = [x for x in snapshot_instances if x['share_id'] == active_replica['id']][0] @@ -1878,7 +1877,7 @@ class NetAppCmodeFileStorageLibrary(object): def update_replicated_snapshot(self, replica_list, share_replica, snapshot_instances, snapshot_instance, share_server=None): - active_replica = self._find_active_replica(replica_list) + active_replica = self.find_active_replica(replica_list) vserver, vserver_client = self._get_vserver(share_server=share_server) share_name = self._get_backend_share_name( snapshot_instance['share_id']) diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py index 9c7bac8039..6d08a1d3c1 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py @@ -29,11 +29,12 @@ from oslo_utils import excutils from manila import exception from manila.i18n import _ 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 import utils as na_utils +from manila.share import utils as share_utils from manila import utils - LOG = log.getLogger(__name__) SUPPORTED_NETWORK_TYPES = (None, 'flat', 'vlan') SEGMENTED_NETWORK_TYPES = ('vlan',) @@ -383,6 +384,10 @@ class NetAppCmodeMultiSVMFileStorageLibrary( vlan_id = None def _delete_vserver_without_lock(): + # NOTE(dviroel): Attempt to delete all vserver peering + # created by replication + self._delete_vserver_peers(vserver) + self._client.delete_vserver(vserver, vserver_client, security_services=security_services) @@ -414,6 +419,13 @@ class NetAppCmodeMultiSVMFileStorageLibrary( except exception.NetAppException: LOG.exception("Deleting Vserver VLAN failed.") + @na_utils.trace + def _delete_vserver_peers(self, vserver): + vserver_peers = self._get_vserver_peers(vserver=vserver) + for peer in vserver_peers: + self._delete_vserver_peer(peer.get('vserver'), + peer.get('peer-vserver')) + def get_configured_ip_versions(self): versions = [4] options = self._client.get_net_options() @@ -421,6 +433,70 @@ class NetAppCmodeMultiSVMFileStorageLibrary( versions.append(6) return versions + @na_utils.trace + def create_replica(self, context, replica_list, new_replica, + access_rules, share_snapshots, share_server=None): + """Creates the new replica on this backend and sets up SnapMirror. + + It creates the peering between the associated vservers before creating + the share replica and setting up the SnapMirror. + """ + # 1. Retrieve source and destination vservers from both replicas, + # active and and new_replica + src_vserver, dst_vserver = self._get_vservers_from_replicas( + context, replica_list, new_replica) + + # 2. Retrieve the active replica host's client and cluster name + src_replica = self.find_active_replica(replica_list) + + src_replica_host = share_utils.extract_host( + src_replica['host'], level='backend_name') + src_replica_client = data_motion.get_client_for_backend( + src_replica_host, vserver_name=src_vserver) + # Cluster name is needed for setting up the vserver peering + src_replica_cluster_name = src_replica_client.get_cluster_name() + + # 3. Retrieve new replica host's client + new_replica_host = share_utils.extract_host( + new_replica['host'], level='backend_name') + new_replica_client = data_motion.get_client_for_backend( + new_replica_host, vserver_name=dst_vserver) + + if not self._get_vserver_peers(dst_vserver, src_vserver): + # 3.1. Request vserver peer creation from new_replica's host + # to active replica's host + new_replica_client.create_vserver_peer( + dst_vserver, src_vserver, + peer_cluster_name=src_replica_cluster_name) + + # 3.2. Accepts the vserver peering using active replica host's + # client + src_replica_client.accept_vserver_peer(src_vserver, dst_vserver) + + return (super(NetAppCmodeMultiSVMFileStorageLibrary, self). + create_replica(context, replica_list, new_replica, + access_rules, share_snapshots)) + + def delete_replica(self, context, replica_list, replica, share_snapshots, + share_server=None): + """Removes the replica on this backend and destroys SnapMirror. + + Removes the replica, destroys the SnapMirror and delete the vserver + peering if needed. + """ + vserver, peer_vserver = self._get_vservers_from_replicas( + context, replica_list, replica) + super(NetAppCmodeMultiSVMFileStorageLibrary, self).delete_replica( + context, replica_list, replica, share_snapshots) + + # Check if there are no remaining SnapMirror connections and if a + # vserver peering exists and delete it. + snapmirrors = self._get_snapmirrors(vserver, peer_vserver) + snapmirrors_from_peer = self._get_snapmirrors(peer_vserver, vserver) + peers = self._get_vserver_peers(peer_vserver, vserver) + if not (snapmirrors or snapmirrors_from_peer) and peers: + self._delete_vserver_peer(peer_vserver, vserver) + def manage_server(self, context, share_server, identifier, driver_options): """Manages a vserver by renaming it and returning backend_details.""" new_vserver_name = self._get_vserver_name(share_server['id']) @@ -454,3 +530,26 @@ class NetAppCmodeMultiSVMFileStorageLibrary( if not self._client.vserver_exists(identifier): return self._get_vserver_name(identifier) return identifier + + def _get_snapmirrors(self, vserver, peer_vserver): + return self._client.get_snapmirrors( + source_vserver=vserver, source_volume=None, + destination_vserver=peer_vserver, destination_volume=None) + + def _get_vservers_from_replicas(self, context, replica_list, new_replica): + active_replica = self.find_active_replica(replica_list) + + dm_session = data_motion.DataMotionSession() + vserver = dm_session.get_vserver_from_share(active_replica) + peer_vserver = dm_session.get_vserver_from_share(new_replica) + + return vserver, peer_vserver + + def _get_vserver_peers(self, vserver=None, peer_vserver=None): + return self._client.get_vserver_peers(vserver, peer_vserver) + + def _create_vserver_peer(self, context, vserver, peer_vserver): + self._client.create_vserver_peer(vserver, peer_vserver) + + def _delete_vserver_peer(self, vserver, peer_vserver): + self._client.delete_vserver_peer(vserver, peer_vserver) diff --git a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py index dc9661f622..1c2bc973ea 100644 --- a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py +++ b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py @@ -39,6 +39,7 @@ NODE_NAME = 'fake_node1' NODE_NAMES = ('fake_node1', 'fake_node2') VSERVER_NAME = 'fake_vserver' VSERVER_NAME_2 = 'fake_vserver_2' +VSERVER_PEER_NAME = 'fake_vserver_peer' ADMIN_VSERVER_NAME = 'fake_admin_vserver' NODE_VSERVER_NAME = 'fake_node_vserver' NFS_VERSIONS = ['nfs3', 'nfs4.0'] @@ -2249,6 +2250,23 @@ CLUSTER_PEER_POLICY_GET_RESPONSE = etree.XML(""" """) +CLUSTER_GET_CLUSTER_NAME = etree.XML(""" + + + + + - + %(cluster_name)s + 1-80-000000 + fake_uuid + fake_rdb + + + +""" % { + 'cluster_name': CLUSTER_NAME, +}) + VSERVER_PEER_GET_ITER_RESPONSE = etree.XML(""" diff --git a/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py b/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py index 0d28e6722c..cc8bf21221 100644 --- a/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py +++ b/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py @@ -5451,7 +5451,8 @@ class NetAppClientCmodeTestCase(test.TestCase): 'passphrase': 'fake_passphrase', } self.client.send_request.assert_has_calls([ - mock.call('cluster-peer-create', cluster_peer_create_args)]) + mock.call('cluster-peer-create', cluster_peer_create_args, + enable_tunneling=False)]) def test_get_cluster_peers(self): @@ -5524,7 +5525,8 @@ class NetAppClientCmodeTestCase(test.TestCase): 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)]) + mock.call('cluster-peer-delete', cluster_peer_delete_args, + enable_tunneling=False)]) def test_get_cluster_peer_policy(self): @@ -5585,21 +5587,28 @@ class NetAppClientCmodeTestCase(test.TestCase): mock.call('cluster-peer-policy-modify', cluster_peer_policy_modify_args)]) - def test_create_vserver_peer(self): + @ddt.data(None, 'cluster_name') + def test_create_vserver_peer(self, cluster_name): self.mock_object(self.client, 'send_request') - self.client.create_vserver_peer('fake_vserver', 'fake_vserver_peer') + self.client.create_vserver_peer(fake.VSERVER_NAME, + fake.VSERVER_PEER_NAME, + peer_cluster_name=cluster_name) vserver_peer_create_args = { - 'vserver': 'fake_vserver', - 'peer-vserver': 'fake_vserver_peer', + 'vserver': fake.VSERVER_NAME, + 'peer-vserver': fake.VSERVER_PEER_NAME, 'applications': [ {'vserver-peer-application': 'snapmirror'}, ], } + if cluster_name: + vserver_peer_create_args['peer-cluster'] = cluster_name + self.client.send_request.assert_has_calls([ - mock.call('vserver-peer-create', vserver_peer_create_args)]) + mock.call('vserver-peer-create', vserver_peer_create_args, + enable_tunneling=False)]) def test_delete_vserver_peer(self): @@ -5612,7 +5621,8 @@ class NetAppClientCmodeTestCase(test.TestCase): 'peer-vserver': 'fake_vserver_peer', } self.client.send_request.assert_has_calls([ - mock.call('vserver-peer-delete', vserver_peer_delete_args)]) + mock.call('vserver-peer-delete', vserver_peer_delete_args, + enable_tunneling=False)]) def test_accept_vserver_peer(self): @@ -5625,7 +5635,8 @@ class NetAppClientCmodeTestCase(test.TestCase): 'peer-vserver': 'fake_vserver_peer', } self.client.send_request.assert_has_calls([ - mock.call('vserver-peer-accept', vserver_peer_accept_args)]) + mock.call('vserver-peer-accept', vserver_peer_accept_args, + enable_tunneling=False)]) def test_get_vserver_peers(self): @@ -6637,3 +6648,22 @@ class NetAppClientCmodeTestCase(test.TestCase): 'qos-policy-group-delete-iter', qos_policy_group_delete_iter_args, False) self.assertIs(failed, client_cmode.LOG.debug.called) + + def test_get_cluster_name(self): + api_response = netapp_api.NaElement( + fake.CLUSTER_GET_CLUSTER_NAME) + self.mock_object(self.client, + 'send_request', + mock.Mock(return_value=api_response)) + api_args = { + 'desired-attributes': { + 'cluster-identity-info': { + 'cluster-name': None, + } + } + } + result = self.client.get_cluster_name() + + self.assertEqual(fake.CLUSTER_NAME, result) + self.client.send_request.assert_called_once_with( + 'cluster-identity-get', api_args, enable_tunneling=False) diff --git a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_data_motion.py b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_data_motion.py index 026b70bed4..f5d4f2d0ed 100644 --- a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_data_motion.py +++ b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_data_motion.py @@ -476,6 +476,56 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase): self.fake_dest_vol_name ) + def test_change_snapmirror_source_dhss_true(self): + fake_new_src_share = copy.deepcopy(self.fake_src_share) + fake_new_src_share['id'] = 'd02d497a-236c-4852-812a-0d39373e312a' + fake_new_src_share_name = 'share_d02d497a_236c_4852_812a_0d39373e312a' + fake_new_src_share_server = fake_new_src_share['share_server'] + fake_new_src_ss_name = ( + fake_new_src_share_server['backend_details']['vserver_name']) + 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])) + mock_backend_config = na_fakes.create_configuration() + mock_backend_config.driver_handles_share_servers = True + self.mock_object(data_motion, 'get_backend_configuration', + mock.Mock(return_value=mock_backend_config)) + self.mock_object(self.mock_dest_client, 'get_vserver_peers', + mock.Mock(return_value=[])) + peer_cluster_name = 'new_src_cluster_name' + self.mock_object(self.mock_src_client, 'get_cluster_name', + mock.Mock(return_value=peer_cluster_name)) + + 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.assertEqual(4, self.dm_session.delete_snapmirror.call_count) + + self.mock_dest_client.get_vserver_peers.assert_called_once_with( + self.dest_vserver, fake_new_src_ss_name + ) + self.assertTrue(self.mock_src_client.get_cluster_name.called) + self.mock_dest_client.create_vserver_peer.assert_called_once_with( + self.dest_vserver, fake_new_src_ss_name, + peer_cluster_name=peer_cluster_name + ) + self.mock_src_client.accept_vserver_peer.assert_called_once_with( + fake_new_src_ss_name, self.dest_vserver + ) + 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') diff --git a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py index 9671015561..0f723d3be6 100644 --- a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py +++ b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py @@ -22,12 +22,15 @@ import mock from oslo_log import log from oslo_serialization import jsonutils +from manila.common import constants from manila import context from manila import exception from manila.share.drivers.netapp.dataontap.client import api as netapp_api +from manila.share.drivers.netapp.dataontap.cluster_mode import data_motion from manila.share.drivers.netapp.dataontap.cluster_mode import lib_base from manila.share.drivers.netapp.dataontap.cluster_mode import lib_multi_svm from manila.share.drivers.netapp import utils as na_utils +from manila.share import utils as share_utils from manila import test from manila.tests.share.drivers.netapp.dataontap.client import fakes as c_fake from manila.tests.share.drivers.netapp.dataontap import fakes as fake @@ -61,6 +64,26 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): self.library._client = mock.Mock() self.library._client.get_ontapi_version.return_value = (1, 21) self.client = self.library._client + self.fake_new_replica = copy.deepcopy(fake.SHARE) + self.fake_new_ss = copy.deepcopy(fake.SHARE_SERVER) + self.fake_new_vserver_name = 'fake_new_vserver' + self.fake_new_ss['backend_details']['vserver_name'] = ( + self.fake_new_vserver_name + ) + self.fake_new_replica['share_server'] = self.fake_new_ss + self.fake_new_replica_host = 'fake_new_host' + self.fake_replica = copy.deepcopy(fake.SHARE) + self.fake_replica['id'] = fake.SHARE_ID2 + fake_ss = copy.deepcopy(fake.SHARE_SERVER) + self.fake_vserver = 'fake_vserver' + fake_ss['backend_details']['vserver_name'] = ( + self.fake_vserver + ) + self.fake_replica['share_server'] = fake_ss + self.fake_replica_host = 'fake_host' + + self.fake_new_client = mock.Mock() + self.fake_client = mock.Mock() def test_check_for_setup_error_cluster_creds_no_vserver(self): self.library._have_cluster_creds = True @@ -793,7 +816,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): self.assertFalse(mock_delete_vserver.called) self.assertTrue(lib_multi_svm.LOG.warning.called) - def test_delete_vserver_no_ipspace(self): + @ddt.data(True, False) + def test_delete_vserver_no_ipspace(self, lock): self.mock_object(self.library._client, 'get_vserver_ipspace', @@ -812,19 +836,24 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): 'get_network_interfaces', mock.Mock(return_value=net_interfaces)) security_services = fake.NETWORK_INFO['security_services'] + self.mock_object(self.library, '_delete_vserver_peers') self.library._delete_vserver(fake.VSERVER1, - security_services=security_services) + security_services=security_services, + needs_lock=lock) self.library._client.get_vserver_ipspace.assert_called_once_with( fake.VSERVER1) + self.library._delete_vserver_peers.assert_called_once_with( + fake.VSERVER1) self.library._client.delete_vserver.assert_called_once_with( fake.VSERVER1, vserver_client, security_services=security_services) self.assertFalse(self.library._client.delete_ipspace.called) mock_delete_vserver_vlans.assert_called_once_with( net_interfaces_with_vlans) - def test_delete_vserver_ipspace_has_data_vservers(self): + @ddt.data(True, False) + def test_delete_vserver_ipspace_has_data_vservers(self, lock): self.mock_object(self.library._client, 'get_vserver_ipspace', @@ -838,18 +867,22 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): mock.Mock(return_value=True)) mock_delete_vserver_vlans = self.mock_object(self.library, '_delete_vserver_vlans') + self.mock_object(self.library, '_delete_vserver_peers') self.mock_object( vserver_client, 'get_network_interfaces', mock.Mock(return_value=c_fake.NETWORK_INTERFACES_MULTIPLE)) security_services = fake.NETWORK_INFO['security_services'] self.library._delete_vserver(fake.VSERVER1, - security_services=security_services) + security_services=security_services, + needs_lock=lock) self.library._client.get_vserver_ipspace.assert_called_once_with( fake.VSERVER1) self.library._client.delete_vserver.assert_called_once_with( fake.VSERVER1, vserver_client, security_services=security_services) + self.library._delete_vserver_peers.assert_called_once_with( + fake.VSERVER1) self.assertFalse(self.library._client.delete_ipspace.called) mock_delete_vserver_vlans.assert_called_once_with( [c_fake.NETWORK_INTERFACES_MULTIPLE[0]]) @@ -869,6 +902,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): mock.Mock(return_value=False)) mock_delete_vserver_vlans = self.mock_object(self.library, '_delete_vserver_vlans') + self.mock_object(self.library, '_delete_vserver_peers') self.mock_object(vserver_client, 'get_network_interfaces', mock.Mock(return_value=interfaces)) @@ -877,7 +911,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): self.library._delete_vserver(fake.VSERVER1, security_services=security_services) - + self.library._delete_vserver_peers.assert_called_once_with( + fake.VSERVER1 + ) self.library._client.get_vserver_ipspace.assert_called_once_with( fake.VSERVER1) self.library._client.delete_vserver.assert_called_once_with( @@ -886,6 +922,23 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): fake.IPSPACE) mock_delete_vserver_vlans.assert_called_once_with(interfaces) + def test__delete_vserver_peers(self): + + self.mock_object(self.library, + '_get_vserver_peers', + mock.Mock(return_value=fake.VSERVER_PEER)) + self.mock_object(self.library, '_delete_vserver_peer') + + self.library._delete_vserver_peers(fake.VSERVER1) + + self.library._get_vserver_peers.assert_called_once_with( + vserver=fake.VSERVER1 + ) + self.library._delete_vserver_peer.asser_called_once_with( + fake.VSERVER_PEER[0]['vserver'], + fake.VSERVER_PEER[0]['peer-vserver'] + ) + def test_delete_vserver_vlans(self): self.library._delete_vserver_vlans(c_fake.NETWORK_INTERFACES) @@ -912,3 +965,146 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): self.library._client.delete_vlan.assert_called_once_with( node, port, vlan) self.assertEqual(1, mock_exception_log.call_count) + + @ddt.data([], [{'vserver': c_fake.VSERVER_NAME, + 'peer-vserver': c_fake.VSERVER_PEER_NAME, + 'applications': [ + {'vserver-peer-application': 'snapmirror'}] + }]) + def test_create_replica(self, vserver_peers): + fake_cluster_name = 'fake_cluster' + self.mock_object(self.library, '_get_vservers_from_replicas', + mock.Mock(return_value=(self.fake_vserver, + self.fake_new_vserver_name))) + self.mock_object(self.library, 'find_active_replica', + mock.Mock(return_value=self.fake_replica)) + self.mock_object(share_utils, 'extract_host', + mock.Mock(side_effect=[self.fake_new_replica_host, + self.fake_replica_host])) + self.mock_object(data_motion, 'get_client_for_backend', + mock.Mock(side_effect=[self.fake_new_client, + self.fake_client])) + self.mock_object(self.library, '_get_vserver_peers', + mock.Mock(return_value=vserver_peers)) + self.mock_object(self.fake_new_client, 'get_cluster_name', + mock.Mock(return_value=fake_cluster_name)) + self.mock_object(self.fake_client, 'create_vserver_peer') + self.mock_object(self.fake_new_client, 'accept_vserver_peer') + lib_base_model_update = { + 'export_locations': [], + 'replica_state': constants.REPLICA_STATE_OUT_OF_SYNC, + 'access_rules_status': constants.STATUS_ACTIVE, + } + self.mock_object(lib_base.NetAppCmodeFileStorageLibrary, + 'create_replica', + mock.Mock(return_value=lib_base_model_update)) + + model_update = self.library.create_replica( + None, [self.fake_replica], self.fake_new_replica, [], [], + share_server=None) + + self.assertDictMatch(lib_base_model_update, model_update) + self.library._get_vservers_from_replicas.assert_called_once_with( + None, [self.fake_replica], self.fake_new_replica + ) + self.library.find_active_replica.assert_called_once_with( + [self.fake_replica] + ) + self.assertEqual(2, share_utils.extract_host.call_count) + self.assertEqual(2, data_motion.get_client_for_backend.call_count) + self.library._get_vserver_peers.assert_called_once_with( + self.fake_new_vserver_name, self.fake_vserver + ) + self.fake_new_client.get_cluster_name.assert_called_once_with() + if not vserver_peers: + self.fake_client.create_vserver_peer.assert_called_once_with( + self.fake_new_vserver_name, self.fake_vserver, + peer_cluster_name=fake_cluster_name + ) + self.fake_new_client.accept_vserver_peer.assert_called_once_with( + self.fake_vserver, self.fake_new_vserver_name + ) + base_class = lib_base.NetAppCmodeFileStorageLibrary + base_class.create_replica.assert_called_once_with( + None, [self.fake_replica], self.fake_new_replica, [], [] + ) + + def test_delete_replica(self): + base_class = lib_base.NetAppCmodeFileStorageLibrary + vserver_peers = copy.deepcopy(fake.VSERVER_PEER) + vserver_peers[0]['vserver'] = self.fake_vserver + vserver_peers[0]['peer-vserver'] = self.fake_new_vserver_name + self.mock_object(self.library, '_get_vservers_from_replicas', + mock.Mock(return_value=(self.fake_vserver, + self.fake_new_vserver_name))) + self.mock_object(base_class, 'delete_replica') + self.mock_object(self.library, '_get_snapmirrors', + mock.Mock(return_value=[])) + self.mock_object(self.library, '_get_vserver_peers', + mock.Mock(return_value=vserver_peers)) + self.mock_object(self.library, '_delete_vserver_peer') + + self.library.delete_replica(None, [self.fake_replica], + self.fake_new_replica, [], + share_server=None) + + self.library._get_vservers_from_replicas.assert_called_once_with( + None, [self.fake_replica], self.fake_new_replica + ) + base_class.delete_replica.assert_called_once_with( + None, [self.fake_replica], self.fake_new_replica, [] + ) + self.library._get_snapmirrors.assert_has_calls( + [mock.call(self.fake_vserver, self.fake_new_vserver_name), + mock.call(self.fake_new_vserver_name, self.fake_vserver)] + ) + self.library._get_vserver_peers.assert_called_once_with( + self.fake_new_vserver_name, self.fake_vserver + ) + self.library._delete_vserver_peer.assert_called_once_with( + self.fake_new_vserver_name, self.fake_vserver + ) + + def test_get_vservers_from_replicas(self): + self.mock_object(self.library, 'find_active_replica', + mock.Mock(return_value=self.fake_replica)) + + vserver, peer_vserver = self.library._get_vservers_from_replicas( + None, [self.fake_replica], self.fake_new_replica) + + self.library.find_active_replica.assert_called_once_with( + [self.fake_replica] + ) + self.assertEqual(self.fake_vserver, vserver) + self.assertEqual(self.fake_new_vserver_name, peer_vserver) + + def test_get_vserver_peers(self): + self.mock_object(self.library._client, 'get_vserver_peers') + + self.library._get_vserver_peers( + vserver=self.fake_vserver, peer_vserver=self.fake_new_vserver_name) + + self.library._client.get_vserver_peers.assert_called_once_with( + self.fake_vserver, self.fake_new_vserver_name + ) + + def test_create_vserver_peer(self): + self.mock_object(self.library._client, 'create_vserver_peer') + + self.library._create_vserver_peer( + None, vserver=self.fake_vserver, + peer_vserver=self.fake_new_vserver_name) + + self.library._client.create_vserver_peer.assert_called_once_with( + self.fake_vserver, self.fake_new_vserver_name + ) + + def test_delete_vserver_peer(self): + self.mock_object(self.library._client, 'delete_vserver_peer') + + self.library._delete_vserver_peer( + vserver=self.fake_vserver, peer_vserver=self.fake_new_vserver_name) + + self.library._client.delete_vserver_peer.assert_called_once_with( + self.fake_vserver, self.fake_new_vserver_name + ) diff --git a/manila/tests/share/drivers/netapp/dataontap/fakes.py b/manila/tests/share/drivers/netapp/dataontap/fakes.py index c7828ae777..0fd7b60551 100644 --- a/manila/tests/share/drivers/netapp/dataontap/fakes.py +++ b/manila/tests/share/drivers/netapp/dataontap/fakes.py @@ -365,6 +365,13 @@ SHARE_SERVER = { ADMIN_NETWORK_ALLOCATIONS), } +VSERVER_PEER = [{ + 'vserver': VSERVER1, + 'peer-vserver': VSERVER2, + 'peer-state': 'peered', + 'peer-cluster': 'fake_cluster' +}] + SNAPSHOT = { 'id': SNAPSHOT_ID, 'project_id': TENANT_ID, diff --git a/releasenotes/notes/netapp-replication-dhss-true-5b2887de8e9a2cb5.yaml b/releasenotes/notes/netapp-replication-dhss-true-5b2887de8e9a2cb5.yaml new file mode 100644 index 0000000000..b496fef82c --- /dev/null +++ b/releasenotes/notes/netapp-replication-dhss-true-5b2887de8e9a2cb5.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + The NetApp driver now supports replication with + ``driver_handles_share_servers`` set to True, in addition to the mode where + the driver does not handle the creation and management of share servers. + For replication to work across ONTAP clusters, clusters must be peered in + advance.