1#!/usr/bin/python
2
3# (c) 2018-2019, NetApp, Inc
4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5from __future__ import absolute_import, division, print_function
6__metaclass__ = type
7
8ANSIBLE_METADATA = {'metadata_version': '1.1',
9                    'status': ['preview'],
10                    'supported_by': 'certified'}
11
12
13DOCUMENTATION = '''
14author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
15description:
16  - Create/Delete/Initialize SnapMirror volume/vserver relationships for ONTAP/ONTAP
17  - Create/Delete/Initialize SnapMirror volume relationship between ElementSW and ONTAP
18  - Modify schedule for a SnapMirror relationship for ONTAP/ONTAP and ElementSW/ONTAP
19  - Pre-requisite for ElementSW to ONTAP relationship or vice-versa is an established SnapMirror endpoint for ONTAP cluster with ElementSW UI
20  - Pre-requisite for ElementSW to ONTAP relationship or vice-versa is to have SnapMirror enabled in the ElementSW volume
21  - For creating a SnapMirror ElementSW/ONTAP relationship, an existing ONTAP/ElementSW relationship should be present
22extends_documentation_fragment:
23  - netapp.na_ontap
24module: na_ontap_snapmirror
25options:
26  state:
27    choices: ['present', 'absent']
28    description:
29      - Whether the specified relationship should exist or not.
30    default: present
31  source_volume:
32    description:
33      - Specifies the name of the source volume for the SnapMirror.
34  destination_volume:
35    description:
36      - Specifies the name of the destination volume for the SnapMirror.
37  source_vserver:
38    description:
39      - Name of the source vserver for the SnapMirror.
40  destination_vserver:
41    description:
42      - Name of the destination vserver for the SnapMirror.
43  source_path:
44    description:
45      - Specifies the source endpoint of the SnapMirror relationship.
46      - If the source is an ONTAP volume, format should be <[vserver:][volume]> or <[[cluster:]//vserver/]volume>
47      - If the source is an ElementSW volume, format should be <[Element_SVIP]:/lun/[Element_VOLUME_ID]>
48      - If the source is an ElementSW volume, the volume should have SnapMirror enabled.
49  destination_path:
50    description:
51      - Specifies the destination endpoint of the SnapMirror relationship.
52  relationship_type:
53    choices: ['data_protection', 'load_sharing', 'vault', 'restore', 'transition_data_protection',
54    'extended_data_protection']
55    description:
56      - Specify the type of SnapMirror relationship.
57  schedule:
58    description:
59      - Specify the name of the current schedule, which is used to update the SnapMirror relationship.
60      - Optional for create, modifiable.
61  policy:
62    description:
63      - Specify the name of the SnapMirror policy that applies to this relationship.
64    version_added: "2.8"
65  source_hostname:
66    description:
67     - Source hostname or management IP address for ONTAP or ElementSW cluster.
68     - Required for SnapMirror delete
69  source_username:
70    description:
71     - Source username for ONTAP or ElementSW cluster.
72     - Optional if this is same as destination username.
73  source_password:
74    description:
75     - Source password for ONTAP or ElementSW cluster.
76     - Optional if this is same as destination password.
77  connection_type:
78    description:
79     - Type of SnapMirror relationship.
80     - Pre-requisite for either elementsw_ontap or ontap_elementsw the ElementSW volume should have enableSnapmirror option set to true.
81     - For using ontap_elementsw, elementsw_ontap snapmirror relationship should exist.
82    choices: ['ontap_ontap', 'elementsw_ontap', 'ontap_elementsw']
83    default: ontap_ontap
84    version_added: '2.9'
85  max_transfer_rate:
86    description:
87     - Specifies the upper bound, in kilobytes per second, at which data is transferred.
88     - Default is unlimited, it can be explicitly set to 0 as unlimited.
89    type: int
90    version_added: '2.9'
91  identity_preserve:
92    description:
93     - Specifies whether or not the identity of the source Vserver is replicated to the destination Vserver.
94     - If this parameter is set to true, the source Vserver's configuration will additionally be replicated to the destination.
95     - If the parameter is set to false, then only the source Vserver's volumes and RBAC configuration are replicated to the destination.
96    type: bool
97    version_added: '2.9'
98short_description: "NetApp ONTAP or ElementSW Manage SnapMirror"
99version_added: "2.7"
100'''
101
102EXAMPLES = """
103
104    # creates and initializes the snapmirror
105    - name: Create ONTAP/ONTAP SnapMirror
106      na_ontap_snapmirror:
107        state: present
108        source_volume: test_src
109        destination_volume: test_dest
110        source_vserver: ansible_src
111        destination_vserver: ansible_dest
112        schedule: hourly
113        policy: MirrorAllSnapshots
114        max_transfer_rate: 1000
115        hostname: "{{ destination_cluster_hostname }}"
116        username: "{{ destination_cluster_username }}"
117        password: "{{ destination_cluster_password }}"
118
119    # creates and initializes the snapmirror between vservers
120    - name: Create ONTAP/ONTAP vserver SnapMirror
121      na_ontap_snapmirror:
122        state: present
123        source_vserver: ansible_src
124        destination_vserver: ansible_dest
125        identity_preserve: true
126        hostname: "{{ destination_cluster_hostname }}"
127        username: "{{ destination_cluster_username }}"
128        password: "{{ destination_cluster_password }}"
129
130    # existing snapmirror relation with status 'snapmirrored' will be initialized
131    - name: Initialize ONTAP/ONTAP SnapMirror
132      na_ontap_snapmirror:
133        state: present
134        source_path: 'ansible:test'
135        destination_path: 'ansible:dest'
136        hostname: "{{ destination_cluster_hostname }}"
137        username: "{{ destination_cluster_username }}"
138        password: "{{ destination_cluster_password }}"
139
140    - name: Delete SnapMirror
141      na_ontap_snapmirror:
142        state: absent
143        destination_path: <path>
144        source_hostname: "{{ source_hostname }}"
145        hostname: "{{ destination_cluster_hostname }}"
146        username: "{{ destination_cluster_username }}"
147        password: "{{ destination_cluster_password }}"
148
149    - name: Set schedule to NULL
150      na_ontap_snapmirror:
151        state: present
152        destination_path: <path>
153        schedule: ""
154        hostname: "{{ destination_cluster_hostname }}"
155        username: "{{ destination_cluster_username }}"
156        password: "{{ destination_cluster_password }}"
157
158    - name: Create SnapMirror from ElementSW to ONTAP
159      na_ontap_snapmirror:
160        state: present
161        connection_type: elementsw_ontap
162        source_path: '10.10.10.10:/lun/300'
163        destination_path: 'ansible_test:ansible_dest_vol'
164        schedule: hourly
165        policy: MirrorLatest
166        hostname: "{{ netapp_hostname }}"
167        username: "{{ netapp_username }}"
168        password: "{{ netapp_password }}"
169        source_hostname: " {{ Element_cluster_mvip }}"
170        source_username: "{{ Element_cluster_username }}"
171        source_password: "{{ Element_cluster_password }}"
172
173    - name: Create SnapMirror from ONTAP to ElementSW
174      na_ontap_snapmirror:
175        state: present
176        connection_type: ontap_elementsw
177        destination_path: '10.10.10.10:/lun/300'
178        source_path: 'ansible_test:ansible_dest_vol'
179        policy: MirrorLatest
180        hostname: "{{ Element_cluster_mvip }}"
181        username: "{{ Element_cluster_username }}"
182        password: "{{ Element_cluster_password }}"
183        source_hostname: " {{ netapp_hostname }}"
184        source_username: "{{ netapp_username }}"
185        source_password: "{{ netapp_password }}"
186"""
187
188RETURN = """
189"""
190
191import re
192import traceback
193from ansible.module_utils.basic import AnsibleModule
194from ansible.module_utils._text import to_native
195import ansible.module_utils.netapp as netapp_utils
196from ansible.module_utils.netapp_elementsw_module import NaElementSWModule
197from ansible.module_utils.netapp_module import NetAppModule
198
199HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
200
201HAS_SF_SDK = netapp_utils.has_sf_sdk()
202try:
203    import solidfire.common
204except ImportError:
205    HAS_SF_SDK = False
206
207
208class NetAppONTAPSnapmirror(object):
209    """
210    Class with Snapmirror methods
211    """
212
213    def __init__(self):
214
215        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
216        self.argument_spec.update(dict(
217            state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
218            source_vserver=dict(required=False, type='str'),
219            destination_vserver=dict(required=False, type='str'),
220            source_volume=dict(required=False, type='str'),
221            destination_volume=dict(required=False, type='str'),
222            source_path=dict(required=False, type='str'),
223            destination_path=dict(required=False, type='str'),
224            schedule=dict(required=False, type='str'),
225            policy=dict(required=False, type='str'),
226            relationship_type=dict(required=False, type='str',
227                                   choices=['data_protection', 'load_sharing',
228                                            'vault', 'restore',
229                                            'transition_data_protection',
230                                            'extended_data_protection']
231                                   ),
232            source_hostname=dict(required=False, type='str'),
233            connection_type=dict(required=False, type='str',
234                                 choices=['ontap_ontap', 'elementsw_ontap', 'ontap_elementsw'],
235                                 default='ontap_ontap'),
236            source_username=dict(required=False, type='str'),
237            source_password=dict(required=False, type='str', no_log=True),
238            max_transfer_rate=dict(required=False, type='int'),
239            identity_preserve=dict(required=False, type='bool')
240        ))
241
242        self.module = AnsibleModule(
243            argument_spec=self.argument_spec,
244            required_together=(['source_volume', 'destination_volume'],
245                               ['source_vserver', 'destination_vserver']),
246            supports_check_mode=True
247        )
248
249        self.na_helper = NetAppModule()
250        self.parameters = self.na_helper.set_parameters(self.module.params)
251        # setup later if required
252        self.source_server = None
253        # only for ElementSW -> ONTAP snapmirroring, validate if ElementSW SDK is available
254        if self.parameters.get('connection_type') in ['elementsw_ontap', 'ontap_elementsw']:
255            if HAS_SF_SDK is False:
256                self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
257        if HAS_NETAPP_LIB is False:
258            self.module.fail_json(msg="the python NetApp-Lib module is required")
259        if self.parameters.get('connection_type') != 'ontap_elementsw':
260            self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
261        else:
262            if self.parameters.get('source_username'):
263                self.module.params['username'] = self.parameters['source_username']
264            if self.parameters.get('source_password'):
265                self.module.params['password'] = self.parameters['source_password']
266            self.module.params['hostname'] = self.parameters['source_hostname']
267            self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
268
269    def set_element_connection(self, kind):
270        if kind == 'source':
271            self.module.params['hostname'] = self.parameters['source_hostname']
272            self.module.params['username'] = self.parameters['source_username']
273            self.module.params['password'] = self.parameters['source_password']
274        elif kind == 'destination':
275            self.module.params['hostname'] = self.parameters['hostname']
276            self.module.params['username'] = self.parameters['username']
277            self.module.params['password'] = self.parameters['password']
278        elem = netapp_utils.create_sf_connection(module=self.module)
279        elementsw_helper = NaElementSWModule(elem)
280        return elementsw_helper, elem
281
282    def snapmirror_get_iter(self, destination=None):
283        """
284        Compose NaElement object to query current SnapMirror relations using destination-path
285        SnapMirror relation for a destination path is unique
286        :return: NaElement object for SnapMirror-get-iter
287        """
288        snapmirror_get_iter = netapp_utils.zapi.NaElement('snapmirror-get-iter')
289        query = netapp_utils.zapi.NaElement('query')
290        snapmirror_info = netapp_utils.zapi.NaElement('snapmirror-info')
291        if destination is None:
292            destination = self.parameters['destination_path']
293        snapmirror_info.add_new_child('destination-location', destination)
294        query.add_child_elem(snapmirror_info)
295        snapmirror_get_iter.add_child_elem(query)
296        return snapmirror_get_iter
297
298    def snapmirror_get(self, destination=None):
299        """
300        Get current SnapMirror relations
301        :return: Dictionary of current SnapMirror details if query successful, else None
302        """
303        snapmirror_get_iter = self.snapmirror_get_iter(destination)
304        snap_info = dict()
305        try:
306            result = self.server.invoke_successfully(snapmirror_get_iter, enable_tunneling=True)
307        except netapp_utils.zapi.NaApiError as error:
308            self.module.fail_json(msg='Error fetching snapmirror info: %s' % to_native(error),
309                                  exception=traceback.format_exc())
310        if result.get_child_by_name('num-records') and \
311                int(result.get_child_content('num-records')) > 0:
312            snapmirror_info = result.get_child_by_name('attributes-list').get_child_by_name(
313                'snapmirror-info')
314            snap_info['mirror_state'] = snapmirror_info.get_child_content('mirror-state')
315            snap_info['status'] = snapmirror_info.get_child_content('relationship-status')
316            snap_info['schedule'] = snapmirror_info.get_child_content('schedule')
317            snap_info['policy'] = snapmirror_info.get_child_content('policy')
318            snap_info['relationship'] = snapmirror_info.get_child_content('relationship-type')
319            if snapmirror_info.get_child_by_name('max-transfer-rate'):
320                snap_info['max_transfer_rate'] = int(snapmirror_info.get_child_content('max-transfer-rate'))
321            if snap_info['schedule'] is None:
322                snap_info['schedule'] = ""
323            return snap_info
324        return None
325
326    def check_if_remote_volume_exists(self):
327        """
328        Validate existence of source volume
329        :return: True if volume exists, False otherwise
330        """
331        self.set_source_cluster_connection()
332        # do a get volume to check if volume exists or not
333        volume_info = netapp_utils.zapi.NaElement('volume-get-iter')
334        volume_attributes = netapp_utils.zapi.NaElement('volume-attributes')
335        volume_id_attributes = netapp_utils.zapi.NaElement('volume-id-attributes')
336        volume_id_attributes.add_new_child('name', self.parameters['source_volume'])
337        # if source_volume is present, then source_vserver is also guaranteed to be present
338        volume_id_attributes.add_new_child('vserver-name', self.parameters['source_vserver'])
339        volume_attributes.add_child_elem(volume_id_attributes)
340        query = netapp_utils.zapi.NaElement('query')
341        query.add_child_elem(volume_attributes)
342        volume_info.add_child_elem(query)
343        try:
344            result = self.source_server.invoke_successfully(volume_info, True)
345        except netapp_utils.zapi.NaApiError as error:
346            self.module.fail_json(msg='Error fetching source volume details %s : %s'
347                                      % (self.parameters['source_volume'], to_native(error)),
348                                  exception=traceback.format_exc())
349        if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0:
350            return True
351        return False
352
353    def snapmirror_create(self):
354        """
355        Create a SnapMirror relationship
356        """
357        if self.parameters.get('source_hostname') and self.parameters.get('source_volume'):
358            if not self.check_if_remote_volume_exists():
359                self.module.fail_json(msg='Source volume does not exist. Please specify a volume that exists')
360        options = {'source-location': self.parameters['source_path'],
361                   'destination-location': self.parameters['destination_path']}
362        snapmirror_create = netapp_utils.zapi.NaElement.create_node_with_children('snapmirror-create', **options)
363        if self.parameters.get('relationship_type'):
364            snapmirror_create.add_new_child('relationship-type', self.parameters['relationship_type'])
365        if self.parameters.get('schedule'):
366            snapmirror_create.add_new_child('schedule', self.parameters['schedule'])
367        if self.parameters.get('policy'):
368            snapmirror_create.add_new_child('policy', self.parameters['policy'])
369        if self.parameters.get('max_transfer_rate'):
370            snapmirror_create.add_new_child('max-transfer-rate', str(self.parameters['max_transfer_rate']))
371        if self.parameters.get('identity_preserve'):
372            snapmirror_create.add_new_child('identity-preserve', str(self.parameters['identity_preserve']))
373        try:
374            self.server.invoke_successfully(snapmirror_create, enable_tunneling=True)
375            self.snapmirror_initialize()
376        except netapp_utils.zapi.NaApiError as error:
377            self.module.fail_json(msg='Error creating SnapMirror %s' % to_native(error),
378                                  exception=traceback.format_exc())
379
380    def set_source_cluster_connection(self):
381        """
382        Setup ontap ZAPI server connection for source hostname
383        :return: None
384        """
385        if self.parameters.get('source_username'):
386            self.module.params['username'] = self.parameters['source_username']
387        if self.parameters.get('source_password'):
388            self.module.params['password'] = self.parameters['source_password']
389        self.module.params['hostname'] = self.parameters['source_hostname']
390        self.source_server = netapp_utils.setup_na_ontap_zapi(module=self.module)
391
392    def delete_snapmirror(self, is_hci, relationship_type):
393        """
394        Delete a SnapMirror relationship
395        #1. Quiesce the SnapMirror relationship at destination
396        #2. Break the SnapMirror relationship at the destination
397        #3. Release the SnapMirror at source
398        #4. Delete SnapMirror at destination
399        """
400        if not is_hci:
401            if not self.parameters.get('source_hostname'):
402                self.module.fail_json(msg='Missing parameters for delete: Please specify the '
403                                          'source cluster hostname to release the SnapMirror relation')
404        # Quiesce at destination
405        self.snapmirror_quiesce()
406        # Break at destination
407        if relationship_type not in ['load_sharing', 'vault']:
408            self.snapmirror_break()
409        # if source is ONTAP, release the destination at source cluster
410        if not is_hci:
411            self.set_source_cluster_connection()
412            if self.get_destination():
413                # Release at source
414                self.snapmirror_release()
415        # Delete at destination
416        self.snapmirror_delete()
417
418    def snapmirror_quiesce(self):
419        """
420        Quiesce SnapMirror relationship - disable all future transfers to this destination
421        """
422        options = {'destination-location': self.parameters['destination_path']}
423
424        snapmirror_quiesce = netapp_utils.zapi.NaElement.create_node_with_children(
425            'snapmirror-quiesce', **options)
426        try:
427            self.server.invoke_successfully(snapmirror_quiesce,
428                                            enable_tunneling=True)
429        except netapp_utils.zapi.NaApiError as error:
430            self.module.fail_json(msg='Error Quiescing SnapMirror : %s'
431                                      % (to_native(error)),
432                                  exception=traceback.format_exc())
433
434    def snapmirror_delete(self):
435        """
436        Delete SnapMirror relationship at destination cluster
437        """
438        options = {'destination-location': self.parameters['destination_path']}
439
440        snapmirror_delete = netapp_utils.zapi.NaElement.create_node_with_children(
441            'snapmirror-destroy', **options)
442        try:
443            self.server.invoke_successfully(snapmirror_delete,
444                                            enable_tunneling=True)
445        except netapp_utils.zapi.NaApiError as error:
446            self.module.fail_json(msg='Error deleting SnapMirror : %s'
447                                  % (to_native(error)),
448                                  exception=traceback.format_exc())
449
450    def snapmirror_break(self, destination=None):
451        """
452        Break SnapMirror relationship at destination cluster
453        """
454        if destination is None:
455            destination = self.parameters['destination_path']
456        options = {'destination-location': destination}
457        snapmirror_break = netapp_utils.zapi.NaElement.create_node_with_children(
458            'snapmirror-break', **options)
459        try:
460            self.server.invoke_successfully(snapmirror_break,
461                                            enable_tunneling=True)
462        except netapp_utils.zapi.NaApiError as error:
463            self.module.fail_json(msg='Error breaking SnapMirror relationship : %s'
464                                      % (to_native(error)),
465                                  exception=traceback.format_exc())
466
467    def snapmirror_release(self):
468        """
469        Release SnapMirror relationship from source cluster
470        """
471        options = {'destination-location': self.parameters['destination_path']}
472        snapmirror_release = netapp_utils.zapi.NaElement.create_node_with_children(
473            'snapmirror-release', **options)
474        try:
475            self.source_server.invoke_successfully(snapmirror_release,
476                                                   enable_tunneling=True)
477        except netapp_utils.zapi.NaApiError as error:
478            self.module.fail_json(msg='Error releasing SnapMirror relationship : %s'
479                                      % (to_native(error)),
480                                  exception=traceback.format_exc())
481
482    def snapmirror_abort(self):
483        """
484        Abort a SnapMirror relationship in progress
485        """
486        options = {'destination-location': self.parameters['destination_path']}
487        snapmirror_abort = netapp_utils.zapi.NaElement.create_node_with_children(
488            'snapmirror-abort', **options)
489        try:
490            self.server.invoke_successfully(snapmirror_abort,
491                                            enable_tunneling=True)
492        except netapp_utils.zapi.NaApiError as error:
493            self.module.fail_json(msg='Error aborting SnapMirror relationship : %s'
494                                      % (to_native(error)),
495                                  exception=traceback.format_exc())
496
497    def snapmirror_initialize(self):
498        """
499        Initialize SnapMirror based on relationship type
500        """
501        current = self.snapmirror_get()
502        if current['mirror_state'] != 'snapmirrored':
503            initialize_zapi = 'snapmirror-initialize'
504            if self.parameters.get('relationship_type') and self.parameters['relationship_type'] == 'load_sharing':
505                initialize_zapi = 'snapmirror-initialize-ls-set'
506                options = {'source-location': self.parameters['source_path']}
507            else:
508                options = {'destination-location': self.parameters['destination_path']}
509            snapmirror_init = netapp_utils.zapi.NaElement.create_node_with_children(
510                initialize_zapi, **options)
511            try:
512                self.server.invoke_successfully(snapmirror_init,
513                                                enable_tunneling=True)
514            except netapp_utils.zapi.NaApiError as error:
515                self.module.fail_json(msg='Error initializing SnapMirror : %s'
516                                          % (to_native(error)),
517                                      exception=traceback.format_exc())
518
519    def snapmirror_modify(self, modify):
520        """
521        Modify SnapMirror schedule or policy
522        """
523        options = {'destination-location': self.parameters['destination_path']}
524        snapmirror_modify = netapp_utils.zapi.NaElement.create_node_with_children(
525            'snapmirror-modify', **options)
526        if modify.get('schedule') is not None:
527            snapmirror_modify.add_new_child('schedule', modify.get('schedule'))
528        if modify.get('policy'):
529            snapmirror_modify.add_new_child('policy', modify.get('policy'))
530        if modify.get('max_transfer_rate'):
531            snapmirror_modify.add_new_child('max-transfer-rate', str(modify.get('max_transfer_rate')))
532        try:
533            self.server.invoke_successfully(snapmirror_modify,
534                                            enable_tunneling=True)
535        except netapp_utils.zapi.NaApiError as error:
536            self.module.fail_json(msg='Error modifying SnapMirror schedule or policy : %s'
537                                      % (to_native(error)),
538                                  exception=traceback.format_exc())
539
540    def snapmirror_update(self):
541        """
542        Update data in destination endpoint
543        """
544        options = {'destination-location': self.parameters['destination_path']}
545        snapmirror_update = netapp_utils.zapi.NaElement.create_node_with_children(
546            'snapmirror-update', **options)
547        try:
548            result = self.server.invoke_successfully(snapmirror_update,
549                                                     enable_tunneling=True)
550        except netapp_utils.zapi.NaApiError as error:
551            self.module.fail_json(msg='Error updating SnapMirror : %s'
552                                      % (to_native(error)),
553                                  exception=traceback.format_exc())
554
555    def check_parameters(self):
556        """
557        Validate parameters and fail if one or more required params are missing
558        Update source and destination path from vserver and volume parameters
559        """
560        if self.parameters['state'] == 'present'\
561                and (self.parameters.get('source_path') or self.parameters.get('destination_path')):
562            if not self.parameters.get('destination_path') or not self.parameters.get('source_path'):
563                self.module.fail_json(msg='Missing parameters: Source path or Destination path')
564        elif self.parameters.get('source_volume'):
565            if not self.parameters.get('source_vserver') or not self.parameters.get('destination_vserver'):
566                self.module.fail_json(msg='Missing parameters: source vserver or destination vserver or both')
567            self.parameters['source_path'] = self.parameters['source_vserver'] + ":" + self.parameters['source_volume']
568            self.parameters['destination_path'] = self.parameters['destination_vserver'] + ":" +\
569                self.parameters['destination_volume']
570        elif self.parameters.get('source_vserver'):
571            self.parameters['source_path'] = self.parameters['source_vserver'] + ":"
572            self.parameters['destination_path'] = self.parameters['destination_vserver'] + ":"
573
574    def get_destination(self):
575        result = None
576        release_get = netapp_utils.zapi.NaElement('snapmirror-get-destination-iter')
577        query = netapp_utils.zapi.NaElement('query')
578        snapmirror_dest_info = netapp_utils.zapi.NaElement('snapmirror-destination-info')
579        snapmirror_dest_info.add_new_child('destination-location', self.parameters['destination_path'])
580        query.add_child_elem(snapmirror_dest_info)
581        release_get.add_child_elem(query)
582        try:
583            result = self.source_server.invoke_successfully(release_get, enable_tunneling=True)
584        except netapp_utils.zapi.NaApiError as error:
585            self.module.fail_json(msg='Error fetching snapmirror destinations info: %s' % to_native(error),
586                                  exception=traceback.format_exc())
587        if result.get_child_by_name('num-records') and \
588                int(result.get_child_content('num-records')) > 0:
589            return True
590        return None
591
592    @staticmethod
593    def element_source_path_format_matches(value):
594        return re.match(pattern=r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\/lun\/[0-9]+",
595                        string=value)
596
597    def check_elementsw_parameters(self, kind='source'):
598        """
599        Validate all ElementSW cluster parameters required for managing the SnapMirror relationship
600        Validate if both source and destination paths are present
601        Validate if source_path follows the required format
602        Validate SVIP
603        Validate if ElementSW volume exists
604        :return: None
605        """
606        path = None
607        if kind == 'destination':
608            path = self.parameters.get('destination_path')
609        elif kind == 'source':
610            path = self.parameters.get('source_path')
611        if path is None:
612            self.module.fail_json(msg="Error: Missing required parameter %s_path for "
613                                      "connection_type %s" % (kind, self.parameters['connection_type']))
614        else:
615            if NetAppONTAPSnapmirror.element_source_path_format_matches(path) is None:
616                self.module.fail_json(msg="Error: invalid %s_path %s. "
617                                          "If the path is a ElementSW cluster, the value should be of the format"
618                                          " <Element_SVIP>:/lun/<Element_VOLUME_ID>" % (kind, path))
619        # validate source_path
620        elementsw_helper, elem = self.set_element_connection(kind)
621        self.validate_elementsw_svip(path, elem)
622        self.check_if_elementsw_volume_exists(path, elementsw_helper)
623
624    def validate_elementsw_svip(self, path, elem):
625        """
626        Validate ElementSW cluster SVIP
627        :return: None
628        """
629        result = None
630        try:
631            result = elem.get_cluster_info()
632        except solidfire.common.ApiServerError as err:
633            self.module.fail_json(msg="Error fetching SVIP", exception=to_native(err))
634        if result and result.cluster_info.svip:
635            cluster_svip = result.cluster_info.svip
636            svip = path.split(':')[0]  # split IP address from source_path
637            if svip != cluster_svip:
638                self.module.fail_json(msg="Error: Invalid SVIP")
639
640    def check_if_elementsw_volume_exists(self, path, elementsw_helper):
641        """
642        Check if remote ElementSW volume exists
643        :return: None
644        """
645        volume_id, vol_id = None, path.split('/')[-1]
646        try:
647            volume_id = elementsw_helper.volume_id_exists(int(vol_id))
648        except solidfire.common.ApiServerError as err:
649            self.module.fail_json(msg="Error fetching Volume details", exception=to_native(err))
650
651        if volume_id is None:
652            self.module.fail_json(msg="Error: Source volume does not exist in the ElementSW cluster")
653
654    def asup_log_for_cserver(self, event_name):
655        """
656        Fetch admin vserver for the given cluster
657        Create and Autosupport log event with the given module name
658        :param event_name: Name of the event log
659        :return: None
660        """
661        results = netapp_utils.get_cserver(self.server)
662        cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
663        netapp_utils.ems_log_event(event_name, cserver)
664
665    def apply(self):
666        """
667        Apply action to SnapMirror
668        """
669        self.asup_log_for_cserver("na_ontap_snapmirror")
670        # source is ElementSW
671        if self.parameters['state'] == 'present' and self.parameters.get('connection_type') == 'elementsw_ontap':
672            self.check_elementsw_parameters()
673        elif self.parameters.get('connection_type') == 'ontap_elementsw':
674            self.check_elementsw_parameters('destination')
675        else:
676            self.check_parameters()
677        if self.parameters['state'] == 'present' and self.parameters.get('connection_type') == 'ontap_elementsw':
678            current_elementsw_ontap = self.snapmirror_get(self.parameters['source_path'])
679            if current_elementsw_ontap is None:
680                self.module.fail_json(msg='Error: creating an ONTAP to ElementSW snapmirror relationship requires an '
681                                          'established SnapMirror relation from ElementSW to ONTAP cluster')
682        current = self.snapmirror_get()
683        cd_action = self.na_helper.get_cd_action(current, self.parameters)
684        modify = self.na_helper.get_modified_attributes(current, self.parameters)
685        element_snapmirror = False
686        if cd_action == 'create':
687            self.snapmirror_create()
688        elif cd_action == 'delete':
689            if current['status'] == 'transferring':
690                self.snapmirror_abort()
691            else:
692                if self.parameters.get('connection_type') == 'elementsw_ontap':
693                    element_snapmirror = True
694                self.delete_snapmirror(element_snapmirror, current['relationship'])
695        else:
696            if modify:
697                self.snapmirror_modify(modify)
698            # check for initialize
699            if current and current['mirror_state'] != 'snapmirrored':
700                self.snapmirror_initialize()
701                # set changed explicitly for initialize
702                self.na_helper.changed = True
703            # Update when create is called again, or modify is being called
704            if self.parameters['state'] == 'present':
705                self.snapmirror_update()
706        self.module.exit_json(changed=self.na_helper.changed)
707
708
709def main():
710    """Execute action"""
711    community_obj = NetAppONTAPSnapmirror()
712    community_obj.apply()
713
714
715if __name__ == '__main__':
716    main()
717