diff --git a/lib/ansible/modules/storage/netapp/na_ontap_snapmirror.py b/lib/ansible/modules/storage/netapp/na_ontap_snapmirror.py index b8c8a60caaa..9af82dfe764 100644 --- a/lib/ansible/modules/storage/netapp/na_ontap_snapmirror.py +++ b/lib/ansible/modules/storage/netapp/na_ontap_snapmirror.py @@ -1,6 +1,6 @@ #!/usr/bin/python -# (c) 2018, NetApp, Inc +# (c) 2018-2019, NetApp, Inc # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type @@ -51,6 +51,10 @@ options: description: - Specify the name of the current schedule, which is used to update the SnapMirror relationship. - Optional for create, modifiable. + policy: + description: + - Specify the name of the SnapMirror policy that applies to this relationship. + version_added: "2.8" source_hostname: description: - Source hostname or IP address. @@ -76,6 +80,8 @@ EXAMPLES = """ destination_volume: test_dest source_vserver: ansible_src destination_vserver: ansible_dest + schedule: hourly + policy: MirrorAllSnapshots hostname: "{{ netapp_hostname }}" username: "{{ netapp_username }}" password: "{{ netapp_password }}" @@ -136,6 +142,7 @@ class NetAppONTAPSnapmirror(object): source_path=dict(required=False, type='str'), destination_path=dict(required=False, type='str'), schedule=dict(required=False, type='str'), + policy=dict(required=False, type='str'), relationship_type=dict(required=False, type='str', choices=['data_protection', 'load_sharing', 'vault', 'restore', @@ -196,15 +203,46 @@ class NetAppONTAPSnapmirror(object): snap_info['mirror_state'] = snapmirror_info.get_child_content('mirror-state') snap_info['status'] = snapmirror_info.get_child_content('relationship-status') snap_info['schedule'] = snapmirror_info.get_child_content('schedule') + snap_info['policy'] = snapmirror_info.get_child_content('policy') if snap_info['schedule'] is None: snap_info['schedule'] = "" return snap_info return None + def check_if_remote_volume_exists(self): + """ + Validate existence of source volume + :return: True if volume exists, False otherwise + """ + self.set_source_cluster_connection() + # do a get volume to check if volume exists or not + volume_info = netapp_utils.zapi.NaElement('volume-get-iter') + volume_attributes = netapp_utils.zapi.NaElement('volume-attributes') + volume_id_attributes = netapp_utils.zapi.NaElement('volume-id-attributes') + volume_id_attributes.add_new_child('name', self.parameters['source_volume']) + # if source_volume is present, then source_vserver is also guaranteed to be present + volume_id_attributes.add_new_child('vserver-name', self.parameters['source_vserver']) + volume_attributes.add_child_elem(volume_id_attributes) + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(volume_attributes) + volume_info.add_child_elem(query) + try: + result = self.source_server.invoke_successfully(volume_info, True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error fetching source volume details %s : %s' + % (self.parameters['source_volume'], to_native(error)), + exception=traceback.format_exc()) + if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0: + return True + return False + def snapmirror_create(self): """ Create a SnapMirror relationship """ + if self.parameters.get('source_hostname') and self.parameters.get('source_volume'): + if not self.check_if_remote_volume_exists(): + self.module.fail_json(msg='Source volume does not exist. Please specify a volume that exists') options = {'source-location': self.parameters['source_path'], 'destination-location': self.parameters['destination_path']} snapmirror_create = netapp_utils.zapi.NaElement.create_node_with_children('snapmirror-create', **options) @@ -212,6 +250,8 @@ class NetAppONTAPSnapmirror(object): snapmirror_create.add_new_child('relationship-type', self.parameters['relationship_type']) if self.parameters.get('schedule'): snapmirror_create.add_new_child('schedule', self.parameters['schedule']) + if self.parameters.get('policy'): + snapmirror_create.add_new_child('policy', self.parameters['policy']) try: self.server.invoke_successfully(snapmirror_create, enable_tunneling=True) self.snapmirror_initialize() @@ -219,23 +259,30 @@ class NetAppONTAPSnapmirror(object): self.module.fail_json(msg='Error creating SnapMirror %s' % to_native(error), exception=traceback.format_exc()) + def set_source_cluster_connection(self): + """ + Setup ontap ZAPI server connection for source hostname + :return: None + """ + if self.parameters.get('source_username'): + self.module.params['username'] = self.parameters['source_username'] + if self.parameters.get('source_password'): + self.module.params['password'] = self.parameters['source_password'] + self.module.params['hostname'] = self.parameters['source_hostname'] + self.source_server = netapp_utils.setup_ontap_zapi(module=self.module) + def delete_snapmirror(self): """ Delete a SnapMirror relationship #1. Quiesce the SnapMirror relationship at destination - #2. Break the SnapMirror relationship at the source + #2. Break the SnapMirror relationship at the destination #3. Release the SnapMirror at source #4. Delete SnapMirror at destination """ if not self.parameters.get('source_hostname'): self.module.fail_json(msg='Missing parameters for delete: Please specify the ' 'source cluster hostname to release the SnapMirror relation') - if self.parameters.get('source_username'): - self.module.params['username'] = self.parameters['source_username'] - if self.parameters.get('source_password'): - self.module.params['password'] = self.parameters['source_password'] - self.module.params['hostname'] = self.parameters['source_hostname'] - self.source_server = netapp_utils.setup_ontap_zapi(module=self.module) + self.set_source_cluster_connection() self.snapmirror_quiesce() if self.parameters.get('relationship_type') and \ self.parameters.get('relationship_type') not in ['load_sharing', 'vault']: @@ -345,17 +392,20 @@ class NetAppONTAPSnapmirror(object): def snapmirror_modify(self, modify): """ - Modify SnapMirror schedule + Modify SnapMirror schedule or policy """ - options = {'destination-location': self.parameters['destination_path'], - 'schedule': modify.get('schedule')} + options = {'destination-location': self.parameters['destination_path']} snapmirror_modify = netapp_utils.zapi.NaElement.create_node_with_children( 'snapmirror-modify', **options) + if modify.get('schedule') is not None: + snapmirror_modify.add_new_child('schedule', modify.get('schedule')) + if modify.get('policy'): + snapmirror_modify.add_new_child('policy', modify.get('policy')) try: self.server.invoke_successfully(snapmirror_modify, enable_tunneling=True) except netapp_utils.zapi.NaApiError as error: - self.module.fail_json(msg='Error modifying SnapMirror schedule : %s' + self.module.fail_json(msg='Error modifying SnapMirror schedule or policy : %s' % (to_native(error)), exception=traceback.format_exc()) @@ -394,6 +444,7 @@ class NetAppONTAPSnapmirror(object): self.parameters['destination_path'] = self.parameters['destination_vserver'] + ":" def get_destination(self): + result = None release_get = netapp_utils.zapi.NaElement('snapmirror-get-destination-iter') query = netapp_utils.zapi.NaElement('query') snapmirror_dest_info = netapp_utils.zapi.NaElement('snapmirror-destination-info') @@ -414,11 +465,13 @@ class NetAppONTAPSnapmirror(object): """ Apply action to SnapMirror """ + results = netapp_utils.get_cserver(self.server) + cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) + netapp_utils.ems_log_event("na_ontap_snapmirror", cserver) self.check_parameters() current = self.snapmirror_get() cd_action = self.na_helper.get_cd_action(current, self.parameters) modify = self.na_helper.get_modified_attributes(current, self.parameters) - if cd_action == 'create': self.snapmirror_create() elif cd_action == 'delete':