1#!/usr/local/bin/python3.8
2
3# (c) 2019-2020, NetApp, Inc
4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
6'''
7na_ontap_snapmirror_policy
8'''
9
10from __future__ import absolute_import, division, print_function
11
12__metaclass__ = type
13
14ANSIBLE_METADATA = {'metadata_version': '1.1',
15                    'status': ['preview'],
16                    'supported_by': 'certified'}
17
18DOCUMENTATION = """
19module: na_ontap_snapmirror_policy
20short_description: NetApp ONTAP create, delete or modify SnapMirror policies
21extends_documentation_fragment:
22    - netapp.ontap.netapp.na_ontap
23version_added: '20.3.0'
24author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
25description:
26- NetApp ONTAP create, modify, or destroy the SnapMirror policy
27- Add, modify and remove SnapMirror policy rules
28- Following parameters are not supported in REST; 'owner', 'restart', 'transfer_priority', 'tries', 'ignore_atime', 'common_snapshot_schedule'
29options:
30  state:
31    description:
32    - Whether the specified SnapMirror policy should exist or not.
33    choices: ['present', 'absent']
34    default: present
35    type: str
36  vserver:
37    description:
38    - Specifies the vserver for the SnapMirror policy.
39    required: true
40    type: str
41  policy_name:
42    description:
43    - Specifies the SnapMirror policy name.
44    required: true
45    type: str
46  policy_type:
47    description:
48    - Specifies the SnapMirror policy type. Modifying the type of an existing SnapMirror policy is not supported
49    choices: ['vault', 'async_mirror', 'mirror_vault', 'strict_sync_mirror', 'sync_mirror']
50    type: str
51  comment:
52    description:
53    - Specifies the SnapMirror policy comment.
54    type: str
55  tries:
56    description:
57    - Specifies the number of tries.
58    type: str
59  transfer_priority:
60    description:
61    - Specifies the priority at which a SnapMirror transfer runs.
62    choices: ['low', 'normal']
63    type: str
64  common_snapshot_schedule:
65    description:
66    - Specifies the common Snapshot copy schedule associated with the policy, only required for strict_sync_mirror and sync_mirror.
67    type: str
68  owner:
69    description:
70    - Specifies the owner of the SnapMirror policy.
71    choices: ['cluster_admin', 'vserver_admin']
72    type: str
73  is_network_compression_enabled:
74    description:
75    - Specifies whether network compression is enabled for transfers.
76    type: bool
77  ignore_atime:
78    description:
79    - Specifies whether incremental transfers will ignore files which have only their access time changed. Applies to SnapMirror vault relationships only.
80    type: bool
81  restart:
82    description:
83    - Defines the behavior of SnapMirror if an interrupted transfer exists, applies to data protection only.
84    choices: ['always', 'never', 'default']
85    type: str
86  snapmirror_label:
87    description:
88    - SnapMirror policy rule label.
89    - Required when defining policy rules.
90    - Use an empty list to remove all user-defined rules.
91    type: list
92    elements: str
93    version_added: '20.7.0'
94  keep:
95    description:
96    - SnapMirror policy rule retention count for snapshots created.
97    - Required when defining policy rules.
98    type: list
99    elements: int
100    version_added: '20.7.0'
101  prefix:
102    description:
103    - SnapMirror policy rule prefix.
104    - Optional when defining policy rules.
105    - Set to '' to not set or remove an existing custom prefix.
106    - Prefix name should be unique within the policy.
107    - When specifying a custom prefix, schedule must also be specified.
108    type: list
109    elements: str
110    version_added: '20.7.0'
111  schedule:
112    description:
113    - SnapMirror policy rule schedule.
114    - Optional when defining policy rules.
115    - Set to '' to not set or remove a schedule.
116    - When specifying a schedule a custom prefix can be set otherwise the prefix will be set to snapmirror_label.
117    type: list
118    elements: str
119    version_added: '20.7.0'
120
121"""
122
123EXAMPLES = """
124    - name: Create SnapMirror policy
125      na_ontap_snapmirror_policy:
126        state: present
127        vserver: "SVM1"
128        policy_name: "ansible_policy"
129        policy_type: "mirror_vault"
130        comment: "created by ansible"
131        hostname: "{{ hostname }}"
132        username: "{{ username }}"
133        password: "{{ password }}"
134        https: true
135        validate_certs: false
136
137    - name: Modify SnapMirror policy
138      na_ontap_snapmirror_policy:
139        state: present
140        vserver: "SVM1"
141        policy_name: "ansible_policy"
142        policy_type: "async_mirror"
143        transfer_priority: "low"
144        hostname: "{{ hostname }}"
145        username: "{{ username }}"
146        password: "{{ password }}"
147        https: true
148        validate_certs: false
149
150    - name: Create SnapMirror policy with basic rules
151      na_ontap_snapmirror_policy:
152        state: present
153        vserver: "SVM1"
154        policy_name: "ansible_policy"
155        policy_type: "async_mirror"
156        snapmirror_label: ['daily', 'weekly', 'monthly']
157        keep: [7, 5, 12]
158        hostname: "{{ hostname }}"
159        username: "{{ username }}"
160        password: "{{ password }}"
161        https: true
162        validate_certs: false
163
164    - name: Create SnapMirror policy with rules and schedules (no schedule for daily rule)
165      na_ontap_snapmirror_policy:
166        state: present
167        vserver: "SVM1"
168        policy_name: "ansible_policy"
169        policy_type: "mirror_vault"
170        snapmirror_label: ['daily', 'weekly', 'monthly']
171        keep: [7, 5, 12]
172        schedule: ['','weekly','monthly']
173        prefix: ['','','monthly_mv']
174        hostname: "{{ hostname }}"
175        username: "{{ username }}"
176        password: "{{ password }}"
177        https: true
178        validate_certs: false
179
180    - name: Modify SnapMirror policy with rules, remove existing schedules and prefixes
181      na_ontap_snapmirror_policy:
182        state: present
183        vserver: "SVM1"
184        policy_name: "ansible_policy"
185        policy_type: "mirror_vault"
186        snapmirror_label: ['daily', 'weekly', 'monthly']
187        keep: [7, 5, 12]
188        schedule: ['','','']
189        prefix: ['','','']
190        hostname: "{{ hostname }}"
191        username: "{{ username }}"
192        password: "{{ password }}"
193        https: true
194        validate_certs: false
195
196    - name: Modify SnapMirror policy, delete all rules (excludes builtin rules)
197      na_ontap_snapmirror_policy:
198        state: present
199        vserver: "SVM1"
200        policy_name: "ansible_policy"
201        policy_type: "mirror_vault"
202        snapmirror_label: []
203        hostname: "{{ hostname }}"
204        username: "{{ username }}"
205        password: "{{ password }}"
206        https: true
207        validate_certs: false
208
209    - name: Delete SnapMirror policy
210      na_ontap_snapmirror_policy:
211        state: absent
212        vserver: "SVM1"
213        policy_type: "async_mirror"
214        policy_name: "ansible_policy"
215        hostname: "{{ hostname }}"
216        username: "{{ username }}"
217        password: "{{ password }}"
218        https: true
219        validate_certs: false
220"""
221
222RETURN = """
223
224"""
225
226import traceback
227
228from ansible.module_utils.basic import AnsibleModule
229from ansible.module_utils._text import to_native
230import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
231from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule
232from ansible_collections.netapp.ontap.plugins.module_utils.netapp import OntapRestAPI
233
234HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
235
236
237class NetAppOntapSnapMirrorPolicy(object):
238    """
239        Create, Modifies and Destroys a SnapMirror policy
240    """
241    def __init__(self):
242        """
243            Initialize the Ontap SnapMirror policy class
244        """
245
246        self.use_rest = False
247        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
248        self.argument_spec.update(dict(
249            state=dict(required=False, choices=['present', 'absent'], default='present'),
250            vserver=dict(required=True, type='str'),
251            policy_name=dict(required=True, type='str'),
252            comment=dict(required=False, type='str'),
253            policy_type=dict(required=False, type='str',
254                             choices=['vault', 'async_mirror', 'mirror_vault', 'strict_sync_mirror', 'sync_mirror']),
255            tries=dict(required=False, type='str'),
256            transfer_priority=dict(required=False, type='str', choices=['low', 'normal']),
257            common_snapshot_schedule=dict(required=False, type='str'),
258            ignore_atime=dict(required=False, type='bool'),
259            is_network_compression_enabled=dict(required=False, type='bool'),
260            owner=dict(required=False, type='str', choices=['cluster_admin', 'vserver_admin']),
261            restart=dict(required=False, type='str', choices=['always', 'never', 'default']),
262            snapmirror_label=dict(required=False, type="list", elements="str"),
263            keep=dict(required=False, type="list", elements="int"),
264            prefix=dict(required=False, type="list", elements="str"),
265            schedule=dict(required=False, type="list", elements="str"),
266        ))
267
268        self.module = AnsibleModule(
269            argument_spec=self.argument_spec,
270            supports_check_mode=True
271        )
272
273        # set up variables
274        self.na_helper = NetAppModule()
275        self.parameters = self.na_helper.set_parameters(self.module.params)
276
277        # API should be used for ONTAP 9.6 or higher, Zapi for lower version
278        self.rest_api = OntapRestAPI(self.module)
279        # some attributes are not supported in earlier REST implementation
280        unsupported_rest_properties = ['owner', 'restart', 'transfer_priority', 'tries', 'ignore_atime',
281                                       'common_snapshot_schedule']
282        used_unsupported_rest_properties = [x for x in unsupported_rest_properties if x in self.parameters]
283        self.use_rest, error = self.rest_api.is_rest(used_unsupported_rest_properties)
284
285        if error:
286            self.module.fail_json(msg=error)
287        if not self.use_rest:
288            if HAS_NETAPP_LIB is False:
289                self.module.fail_json(msg='The python NetApp-Lib module is required')
290            else:
291                self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
292
293    def get_snapmirror_policy(self):
294
295        if self.use_rest:
296            data = {'fields': 'uuid,name,svm.name,comment,network_compression_enabled,type,retention',
297                    'name': self.parameters['policy_name'],
298                    'svm.name': self.parameters['vserver']}
299            api = "snapmirror/policies"
300            message, error = self.rest_api.get(api, data)
301            if error:
302                self.module.fail_json(msg=error)
303            if len(message['records']) != 0:
304                return_value = {
305                    'uuid': message['records'][0]['uuid'],
306                    'vserver': message['records'][0]['svm']['name'],
307                    'policy_name': message['records'][0]['name'],
308                    'comment': '',
309                    'is_network_compression_enabled': message['records'][0]['network_compression_enabled'],
310                    'snapmirror_label': list(),
311                    'keep': list(),
312                    'prefix': list(),
313                    'schedule': list()
314                }
315                if 'type' in message['records'][0]:
316                    policy_type = message['records'][0]['type']
317                    if policy_type == 'async':
318                        policy_type = 'async_mirror'
319                    elif policy_type == 'sync':
320                        policy_type = 'sync_mirror'
321                    return_value['policy_type'] = policy_type
322                if 'comment' in message['records'][0]:
323                    return_value['comment'] = message['records'][0]['comment']
324                if 'retention' in message['records'][0]:
325                    for rule in message['records'][0]['retention']:
326                        return_value['snapmirror_label'].append(rule['label'])
327                        return_value['keep'].append(int(rule['count']))
328                        if rule['prefix'] == '-':
329                            return_value['prefix'].append('')
330                        else:
331                            return_value['prefix'].append(rule['prefix'])
332                        if rule['creation_schedule']['name'] == '-':
333                            return_value['schedule'].append('')
334                        else:
335                            return_value['schedule'].append(rule['creation_schedule']['name'])
336                return return_value
337            return None
338        else:
339            return_value = None
340
341            snapmirror_policy_get_iter = netapp_utils.zapi.NaElement('snapmirror-policy-get-iter')
342            snapmirror_policy_info = netapp_utils.zapi.NaElement('snapmirror-policy-info')
343            snapmirror_policy_info.add_new_child('policy-name', self.parameters['policy_name'])
344            snapmirror_policy_info.add_new_child('vserver', self.parameters['vserver'])
345            query = netapp_utils.zapi.NaElement('query')
346            query.add_child_elem(snapmirror_policy_info)
347            snapmirror_policy_get_iter.add_child_elem(query)
348
349            try:
350                result = self.server.invoke_successfully(snapmirror_policy_get_iter, True)
351                if result.get_child_by_name('attributes-list'):
352                    snapmirror_policy_attributes = result['attributes-list']['snapmirror-policy-info']
353
354                    return_value = {
355                        'policy_name': snapmirror_policy_attributes['policy-name'],
356                        'tries': snapmirror_policy_attributes['tries'],
357                        'transfer_priority': snapmirror_policy_attributes['transfer-priority'],
358                        'is_network_compression_enabled': self.na_helper.get_value_for_bool(True,
359                                                                                            snapmirror_policy_attributes['is-network-compression-enabled']),
360                        'restart': snapmirror_policy_attributes['restart'],
361                        'ignore_atime': self.na_helper.get_value_for_bool(True, snapmirror_policy_attributes['ignore-atime']),
362                        'vserver': snapmirror_policy_attributes['vserver-name'],
363                        'comment': '',
364                        'snapmirror_label': list(),
365                        'keep': list(),
366                        'prefix': list(),
367                        'schedule': list()
368                    }
369                    if snapmirror_policy_attributes.get_child_content('comment') is not None:
370                        return_value['comment'] = snapmirror_policy_attributes['comment']
371
372                    if snapmirror_policy_attributes.get_child_content('type') is not None:
373                        return_value['policy_type'] = snapmirror_policy_attributes['type']
374
375                    if snapmirror_policy_attributes.get_child_by_name('snapmirror-policy-rules'):
376                        for rule in snapmirror_policy_attributes['snapmirror-policy-rules'].get_children():
377                            # Ignore builtin rules
378                            if rule.get_child_content('snapmirror-label') == "sm_created" or \
379                                    rule.get_child_content('snapmirror-label') == "all_source_snapshots":
380                                continue
381
382                            return_value['snapmirror_label'].append(rule.get_child_content('snapmirror-label'))
383                            return_value['keep'].append(int(rule.get_child_content('keep')))
384
385                            prefix = rule.get_child_content('prefix')
386                            if prefix is None or prefix == '-':
387                                prefix = ''
388                            return_value['prefix'].append(prefix)
389
390                            schedule = rule.get_child_content('schedule')
391                            if schedule is None or schedule == '-':
392                                schedule = ''
393                            return_value['schedule'].append(schedule)
394
395            except netapp_utils.zapi.NaApiError as error:
396                if 'NetApp API failed. Reason - 13001:' in to_native(error):
397                    # Policy does not exist
398                    pass
399                else:
400                    self.module.fail_json(msg='Error getting snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)),
401                                          exception=traceback.format_exc())
402            return return_value
403
404    def validate_parameters(self):
405        """
406        Validate snapmirror policy rules
407        :return: None
408        """
409
410        # For snapmirror policy rules, 'snapmirror_label' is required.
411        if 'snapmirror_label' in self.parameters:
412
413            # Check size of 'snapmirror_label' list is 0-10. Can have zero rules.
414            # Take builtin 'sm_created' rule into account for 'mirror_vault'.
415            if (('policy_type' in self.parameters and
416                 self.parameters['policy_type'] == 'mirror_vault' and
417                 len(self.parameters['snapmirror_label']) > 9) or
418                    len(self.parameters['snapmirror_label']) > 10):
419                self.module.fail_json(msg="Error: A SnapMirror Policy can have up to a maximum of "
420                                          "10 rules (including builtin rules), with a 'keep' value "
421                                          "representing the maximum number of Snapshot copies for each rule")
422
423            # 'keep' must be supplied as long as there is at least one snapmirror_label
424            if len(self.parameters['snapmirror_label']) > 0 and 'keep' not in self.parameters:
425                self.module.fail_json(msg="Error: Missing 'keep' parameter. When specifying the "
426                                          "'snapmirror_label' parameter, the 'keep' parameter must "
427                                          "also be supplied")
428
429            # Make sure other rule values match same number of 'snapmirror_label' values.
430            for rule_parameter in ['keep', 'prefix', 'schedule']:
431                if rule_parameter in self.parameters:
432                    if len(self.parameters['snapmirror_label']) > len(self.parameters[rule_parameter]):
433                        self.module.fail_json(msg="Error: Each 'snapmirror_label' value must have "
434                                                  "an accompanying '%s' value" % rule_parameter)
435                    if len(self.parameters[rule_parameter]) > len(self.parameters['snapmirror_label']):
436                        self.module.fail_json(msg="Error: Each '%s' value must have an accompanying "
437                                                  "'snapmirror_label' value" % rule_parameter)
438        else:
439            # 'snapmirror_label' not supplied.
440            # Bail out if other rule parameters have been supplied.
441            for rule_parameter in ['keep', 'prefix', 'schedule']:
442                if rule_parameter in self.parameters:
443                    self.module.fail_json(msg="Error: Missing 'snapmirror_label' parameter. When "
444                                              "specifying the '%s' parameter, the 'snapmirror_label' "
445                                              "parameter must also be supplied" % rule_parameter)
446
447        # Schedule must be supplied if prefix is supplied.
448        if 'prefix' in self.parameters and 'schedule' not in self.parameters:
449            self.module.fail_json(msg="Error: Missing 'schedule' parameter. When "
450                                      "specifying the 'prefix' parameter, the 'schedule' "
451                                      "parameter must also be supplied")
452
453    def create_snapmirror_policy(self):
454        """
455        Creates a new storage efficiency policy
456        """
457        self.validate_parameters()
458        if self.use_rest:
459            data = {'name': self.parameters['policy_name'],
460                    'svm': {'name': self.parameters['vserver']}}
461            if 'policy_type' in self.parameters.keys():
462                if 'async_mirror' in self.parameters['policy_type']:
463                    data['type'] = 'async'
464                elif 'sync_mirror' in self.parameters['policy_type']:
465                    data['type'] = 'sync'
466                    data['sync_type'] = 'sync'
467                else:
468                    self.module.fail_json(msg='policy type in REST only supports options async_mirror or sync_mirror, given %s'
469                                          % (self.parameters['policy_type']))
470                data = self.create_snapmirror_policy_obj_for_rest(data, data['type'])
471            else:
472                data = self.create_snapmirror_policy_obj_for_rest(data)
473            api = "snapmirror/policies"
474            response, error = self.rest_api.post(api, data)
475            if error:
476                self.module.fail_json(msg=error)
477            if 'job' in response:
478                message, error = self.rest_api.wait_on_job(response['job'], increment=5)
479                if error:
480                    self.module.fail_json(msg="%s" % error)
481        else:
482            snapmirror_policy_obj = netapp_utils.zapi.NaElement("snapmirror-policy-create")
483            snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name'])
484            if 'policy_type' in self.parameters.keys():
485                snapmirror_policy_obj.add_new_child("type", self.parameters['policy_type'])
486            snapmirror_policy_obj = self.create_snapmirror_policy_obj(snapmirror_policy_obj)
487
488            try:
489                self.server.invoke_successfully(snapmirror_policy_obj, True)
490            except netapp_utils.zapi.NaApiError as error:
491                self.module.fail_json(msg='Error creating snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)),
492                                      exception=traceback.format_exc())
493
494    def create_snapmirror_policy_obj(self, snapmirror_policy_obj):
495        if 'comment' in self.parameters.keys():
496            snapmirror_policy_obj.add_new_child("comment", self.parameters['comment'])
497        if 'common_snapshot_schedule' in self.parameters.keys() and 'sync_mirror' in self.parameters['policy_type']:
498            snapmirror_policy_obj.add_new_child("common-snapshot-schedule", self.parameters['common_snapshot_schedule'])
499        if 'ignore_atime' in self.parameters.keys():
500            snapmirror_policy_obj.add_new_child("ignore-atime", self.na_helper.get_value_for_bool(False, self.parameters['ignore_atime']))
501        if 'is_network_compression_enabled' in self.parameters.keys():
502            snapmirror_policy_obj.add_new_child("is-network-compression-enabled",
503                                                self.na_helper.get_value_for_bool(False, self.parameters['is_network_compression_enabled']))
504        if 'owner' in self.parameters.keys():
505            snapmirror_policy_obj.add_new_child("owner", self.parameters['owner'])
506        if 'restart' in self.parameters.keys():
507            snapmirror_policy_obj.add_new_child("restart", self.parameters['restart'])
508        if 'transfer_priority' in self.parameters.keys():
509            snapmirror_policy_obj.add_new_child("transfer-priority", self.parameters['transfer_priority'])
510        if 'tries' in self.parameters.keys():
511            snapmirror_policy_obj.add_new_child("tries", self.parameters['tries'])
512        return snapmirror_policy_obj
513
514    def create_snapmirror_policy_obj_for_rest(self, snapmirror_policy_obj, policy_type=None):
515        if 'comment' in self.parameters.keys():
516            snapmirror_policy_obj["comment"] = self.parameters['comment']
517        if 'is_network_compression_enabled' in self.parameters:
518            if policy_type == 'async':
519                snapmirror_policy_obj["network_compression_enabled"] = self.parameters['is_network_compression_enabled']
520            elif policy_type == 'sync':
521                self.module.fail_json(msg="Input parameter network_compression_enabled is not valid for SnapMirror policy type sync")
522        return snapmirror_policy_obj
523
524    def create_snapmirror_policy_retention_obj_for_rest(self, rules=None):
525        """
526        Create SnapMirror policy retention REST object.
527        :param list rules: e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': 'daily', 'schedule': 'daily'}, ... ]
528        :return: List of retention REST objects.
529                 e.g. [{'label': 'daily', 'count': 7, 'prefix': 'daily', 'creation_schedule': {'name': 'daily'}}, ... ]
530        """
531        snapmirror_policy_retention_objs = list()
532        if rules is not None:
533            for rule in rules:
534                retention = {'label': rule['snapmirror_label'], 'count': str(rule['keep'])}
535                if 'prefix' in rule and rule['prefix'] != '':
536                    retention['prefix'] = rule['prefix']
537                if 'schedule' in rule and rule['schedule'] != '':
538                    retention['creation_schedule'] = {'name': rule['schedule']}
539                snapmirror_policy_retention_objs.append(retention)
540        return snapmirror_policy_retention_objs
541
542    def delete_snapmirror_policy(self, uuid=None):
543        """
544        Deletes a snapmirror policy
545        """
546        if self.use_rest:
547            api = "snapmirror/policies/%s" % uuid
548            dummy, error = self.rest_api.delete(api)
549            if error:
550                self.module.fail_json(msg=error)
551        else:
552            snapmirror_policy_obj = netapp_utils.zapi.NaElement("snapmirror-policy-delete")
553            snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name'])
554
555            try:
556                self.server.invoke_successfully(snapmirror_policy_obj, True)
557            except netapp_utils.zapi.NaApiError as error:
558                self.module.fail_json(msg='Error deleting snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)),
559                                      exception=traceback.format_exc())
560
561    def modify_snapmirror_policy(self, uuid=None, policy_type=None):
562        """
563        Modifies a snapmirror policy
564        """
565        if self.use_rest:
566            api = "snapmirror/policies/" + uuid
567            data = self.create_snapmirror_policy_obj_for_rest(dict(), policy_type)
568            dummy, error = self.rest_api.patch(api, data)
569            if error:
570                self.module.fail_json(msg=error)
571        else:
572            snapmirror_policy_obj = netapp_utils.zapi.NaElement("snapmirror-policy-modify")
573            snapmirror_policy_obj = self.create_snapmirror_policy_obj(snapmirror_policy_obj)
574            # Only modify snapmirror policy if a specific snapmirror policy attribute needs
575            # modifying. It may be that only snapmirror policy rules are being modified.
576            if snapmirror_policy_obj.get_children():
577                snapmirror_policy_obj.add_new_child("policy-name", self.parameters['policy_name'])
578
579                try:
580                    self.server.invoke_successfully(snapmirror_policy_obj, True)
581                except netapp_utils.zapi.NaApiError as error:
582                    self.module.fail_json(msg='Error modifying snapmirror policy %s: %s' % (self.parameters['policy_name'], to_native(error)),
583                                          exception=traceback.format_exc())
584
585    def identify_new_snapmirror_policy_rules(self, current=None):
586        """
587        Identify new rules that should be added.
588        :return: List of new rules to be added
589                 e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ]
590        """
591        new_rules = list()
592        if 'snapmirror_label' in self.parameters:
593            for snapmirror_label in self.parameters['snapmirror_label']:
594                snapmirror_label = snapmirror_label.strip()
595
596                # Construct new rule. prefix and schedule are optional.
597                snapmirror_label_index = self.parameters['snapmirror_label'].index(snapmirror_label)
598                rule = dict({
599                    'snapmirror_label': snapmirror_label,
600                    'keep': self.parameters['keep'][snapmirror_label_index]
601                })
602                if 'prefix' in self.parameters:
603                    rule['prefix'] = self.parameters['prefix'][snapmirror_label_index]
604                else:
605                    rule['prefix'] = ''
606                if 'schedule' in self.parameters:
607                    rule['schedule'] = self.parameters['schedule'][snapmirror_label_index]
608                else:
609                    rule['schedule'] = ''
610
611                if current is not None and 'snapmirror_label' in current:
612                    if snapmirror_label not in current['snapmirror_label']:
613                        # Rule doesn't exist. Add new rule.
614                        new_rules.append(rule)
615                else:
616                    # No current or any rules. Add new rule.
617                    new_rules.append(rule)
618        return new_rules
619
620    def identify_obsolete_snapmirror_policy_rules(self, current=None):
621        """
622        Identify existing rules that should be deleted
623        :return: List of rules to be deleted
624                 e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ]
625        """
626        obsolete_rules = list()
627        if 'snapmirror_label' in self.parameters:
628            if current is not None and 'snapmirror_label' in current:
629                # Iterate existing rules.
630                for snapmirror_label in current['snapmirror_label']:
631                    snapmirror_label = snapmirror_label.strip()
632                    if snapmirror_label not in [item.strip() for item in self.parameters['snapmirror_label']]:
633                        # Existing rule isn't in parameters. Delete existing rule.
634                        current_snapmirror_label_index = current['snapmirror_label'].index(snapmirror_label)
635                        rule = dict({
636                            'snapmirror_label': snapmirror_label,
637                            'keep': current['keep'][current_snapmirror_label_index],
638                            'prefix': current['prefix'][current_snapmirror_label_index],
639                            'schedule': current['schedule'][current_snapmirror_label_index]
640                        })
641                        obsolete_rules.append(rule)
642        return obsolete_rules
643
644    def identify_modified_snapmirror_policy_rules(self, current=None):
645        """
646        Identify self.parameters rules that will be modified or not.
647        :return: List of 'modified' rules and a list of 'unmodified' rules
648                 e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ]
649        """
650        modified_rules = list()
651        unmodified_rules = list()
652        if 'snapmirror_label' in self.parameters:
653            for snapmirror_label in self.parameters['snapmirror_label']:
654                snapmirror_label = snapmirror_label.strip()
655                if current is not None and 'snapmirror_label' in current:
656                    if snapmirror_label in current['snapmirror_label']:
657                        # Rule exists. Identify whether it requires modification or not.
658                        modified = False
659                        rule = dict()
660                        rule['snapmirror_label'] = snapmirror_label
661
662                        # Get indexes of current and supplied rule.
663                        current_snapmirror_label_index = current['snapmirror_label'].index(snapmirror_label)
664                        snapmirror_label_index = self.parameters['snapmirror_label'].index(snapmirror_label)
665
666                        # Check if keep modified
667                        if self.parameters['keep'][snapmirror_label_index] != current['keep'][current_snapmirror_label_index]:
668                            modified = True
669                            rule['keep'] = self.parameters['keep'][snapmirror_label_index]
670                        else:
671                            rule['keep'] = current['keep'][current_snapmirror_label_index]
672
673                        # Check if prefix modified
674                        if 'prefix' in self.parameters:
675                            if self.parameters['prefix'][snapmirror_label_index] != current['prefix'][current_snapmirror_label_index]:
676                                modified = True
677                                rule['prefix'] = self.parameters['prefix'][snapmirror_label_index]
678                            else:
679                                rule['prefix'] = current['prefix'][current_snapmirror_label_index]
680                        else:
681                            rule['prefix'] = current['prefix'][current_snapmirror_label_index]
682
683                        # Check if schedule modified
684                        if 'schedule' in self.parameters:
685                            if self.parameters['schedule'][snapmirror_label_index] != current['schedule'][current_snapmirror_label_index]:
686                                modified = True
687                                rule['schedule'] = self.parameters['schedule'][snapmirror_label_index]
688                            else:
689                                rule['schedule'] = current['schedule'][current_snapmirror_label_index]
690                        else:
691                            rule['schedule'] = current['schedule'][current_snapmirror_label_index]
692
693                        if modified:
694                            modified_rules.append(rule)
695                        else:
696                            unmodified_rules.append(rule)
697        return modified_rules, unmodified_rules
698
699    def identify_snapmirror_policy_rules_with_schedule(self, rules=None):
700        """
701        Identify rules that are using a schedule or not. At least one
702        non-schedule rule must be added to a policy before schedule rules
703        are added.
704        :return: List of rules with schedules and a list of rules without schedules
705                 e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': 'daily', 'schedule': 'daily'}, ... ],
706                      [{'snapmirror_label': 'weekly', 'keep': 5, 'prefix': '', 'schedule': ''}, ... ]
707        """
708        schedule_rules = list()
709        non_schedule_rules = list()
710        if rules is not None:
711            for rule in rules:
712                if 'schedule' in rule:
713                    schedule_rules.append(rule)
714                else:
715                    non_schedule_rules.append(rule)
716        return schedule_rules, non_schedule_rules
717
718    def modify_snapmirror_policy_rules(self, current=None, uuid=None):
719        """
720        Modify existing rules in snapmirror policy
721        :return: None
722        """
723        self.validate_parameters()
724
725        # Need 'snapmirror_label' to add/modify/delete rules
726        if 'snapmirror_label' not in self.parameters:
727            return
728
729        obsolete_rules = self.identify_obsolete_snapmirror_policy_rules(current)
730        new_rules = self.identify_new_snapmirror_policy_rules(current)
731        modified_rules, unmodified_rules = self.identify_modified_snapmirror_policy_rules(current)
732
733        if self.use_rest:
734            api = "snapmirror/policies/" + uuid
735            data = {'retention': list()}
736
737            # As rule 'prefix' can't be unset, have to delete existing rules first.
738            # Builtin rules remain.
739            dummy, error = self.rest_api.patch(api, data)
740            if error:
741                self.module.fail_json(msg=error)
742
743            # Re-add desired rules.
744            rules = unmodified_rules + modified_rules + new_rules
745            data['retention'] = self.create_snapmirror_policy_retention_obj_for_rest(rules)
746
747            if len(data['retention']) > 0:
748                dummy, error = self.rest_api.patch(api, data)
749                if error:
750                    self.module.fail_json(msg=error)
751        else:
752            delete_rules = obsolete_rules + modified_rules
753            add_schedule_rules, add_non_schedule_rules = self.identify_snapmirror_policy_rules_with_schedule(new_rules + modified_rules)
754            # Delete rules no longer required or modified rules that will be re-added.
755            for rule in delete_rules:
756                options = {'policy-name': self.parameters['policy_name'],
757                           'snapmirror-label': rule['snapmirror_label']}
758                self.modify_snapmirror_policy_rule(options, 'snapmirror-policy-remove-rule')
759
760            # Add rules. At least one non-schedule rule must exist before
761            # a rule with a schedule can be added, otherwise zapi will complain.
762            for rule in add_non_schedule_rules + add_schedule_rules:
763                options = {'policy-name': self.parameters['policy_name'],
764                           'snapmirror-label': rule['snapmirror_label'],
765                           'keep': str(rule['keep'])}
766                if 'prefix' in rule and rule['prefix'] != '':
767                    options['prefix'] = rule['prefix']
768                if 'schedule' in rule and rule['schedule'] != '':
769                    options['schedule'] = rule['schedule']
770                self.modify_snapmirror_policy_rule(options, 'snapmirror-policy-add-rule')
771
772    def modify_snapmirror_policy_rule(self, options, zapi):
773        """
774        Add, modify or remove a rule to/from a snapmirror policy
775        """
776        snapmirror_obj = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options)
777        try:
778            self.server.invoke_successfully(snapmirror_obj, enable_tunneling=True)
779        except netapp_utils.zapi.NaApiError as error:
780            self.module.fail_json(msg='Error modifying snapmirror policy rule %s: %s' %
781                                  (self.parameters['policy_name'], to_native(error)),
782                                  exception=traceback.format_exc())
783
784    def asup_log_for_cserver(self):
785        results = netapp_utils.get_cserver(self.server)
786        cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
787        netapp_utils.ems_log_event("na_ontap_snapmirror_policy", cserver)
788
789    def apply(self):
790        uuid = None
791        if not self.use_rest:
792            self.asup_log_for_cserver()
793        current, modify = self.get_snapmirror_policy(), None
794        cd_action = self.na_helper.get_cd_action(current, self.parameters)
795        if current and cd_action is None and self.parameters['state'] == 'present':
796            modify = self.na_helper.get_modified_attributes(current, self.parameters)
797            if 'policy_type' in modify:
798                self.module.fail_json(msg='Error: policy type cannot be changed: current=%s, expected=%s' %
799                                      (current.get('policy_type'), modify['policy_type']))
800
801        if self.na_helper.changed:
802            if self.module.check_mode:
803                pass
804            else:
805                if cd_action == 'create':
806                    self.create_snapmirror_policy()
807                    if self.use_rest:
808                        current = self.get_snapmirror_policy()
809                        uuid = current['uuid']
810                        self.modify_snapmirror_policy_rules(current, uuid)
811                    else:
812                        self.modify_snapmirror_policy_rules(current)
813                elif cd_action == 'delete':
814                    if self.use_rest:
815                        uuid = current['uuid']
816                    self.delete_snapmirror_policy(uuid)
817                elif modify:
818                    if self.use_rest:
819                        uuid = current['uuid']
820                        self.modify_snapmirror_policy(uuid, current['policy_type'])
821                        self.modify_snapmirror_policy_rules(current, uuid)
822                    else:
823                        self.modify_snapmirror_policy()
824                        self.modify_snapmirror_policy_rules(current)
825        self.module.exit_json(changed=self.na_helper.changed)
826
827
828def main():
829    """
830    Creates the NetApp Ontap SnapMirror policy object and runs the correct play task
831    """
832    obj = NetAppOntapSnapMirrorPolicy()
833    obj.apply()
834
835
836if __name__ == '__main__':
837    main()
838