class NetAppONTAPMetroCluster(object):
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            state=dict(choices=['present'], default='present'),
            dr_pairs=dict(required=True, type='list', elements='dict', options=dict(
                node_name=dict(required=True, type='str'),
                partner_node_name=dict(required=True, type='str')
            )),
            partner_cluster_name=dict(required=True, type='str')
        ))
        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True
        )
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        self.restApi = OntapRestAPI(self.module)
        self.use_rest = self.restApi.is_rest()

        if not self.use_rest:
            self.module.fail_json(msg="na_ontap_metrocluster only supports REST API")

    def get_metrocluster(self):
        attrs = None
        api = 'cluster/metrocluster'
        options = {'fields': '*'}
        message, error = self.restApi.get(api, options)
        if error:
            self.module.fail_json(msg=error)
        if message is not None:
            local = message['local']
            if local['configuration_state'] != "not_configured":
                attrs = {
                    'configuration_state': local['configuration_state'],
                    'partner_cluster_reachable': local['partner_cluster_reachable'],
                    'partner_cluster_name': local['cluster']['name']
                }
        return attrs

    def create_metrocluster(self):
        api = 'cluster/metrocluster'
        options = {}
        dr_pairs = []
        for pair in self.parameters['dr_pairs']:
            dr_pairs.append({'node': {'name': pair['node_name']},
                             'partner': {'name': pair['partner_node_name']}})
        partner_cluster = {'name': self.parameters['partner_cluster_name']}
        data = {'dr_pairs': dr_pairs, 'partner_cluster': partner_cluster}
        message, error = self.restApi.post(api, data, options)
        if error is not None:
            self.module.fail_json(msg="%s" % error)
        self.restApi.wait_on_job(message['job'], self.parameters['hostname'])

    def apply(self):
        current = self.get_metrocluster()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_metrocluster()
                # Since there is no modify or delete, we will return no change
                else:
                    self.module.fail_json(msg="Modify and Delete currently not support in API")
        self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapQTree(object):
    '''Class with qtree operations'''

    def __init__(self):
        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
            name=dict(required=True, type='str'),
            from_name=dict(required=False, type='str'),
            flexvol_name=dict(required=True, type='str'),
            vserver=dict(required=True, type='str'),
            export_policy=dict(required=False, type='str'),
            security_style=dict(required=False, type='str', choices=['unix', 'ntfs', 'mixed']),
            oplocks=dict(required=False, type='str', choices=['enabled', 'disabled']),
            unix_permissions=dict(required=False, type='str'),
            force_delete=dict(required=False, type='bool', default=True),
            wait_for_completion=dict(required=False, type='bool', default=True),
            time_out=dict(required=False, type='int', default=180),
        ))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            required_if=[
                ('state', 'present', ['flexvol_name'])
            ],
            supports_check_mode=True
        )
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)

        self.rest_api = OntapRestAPI(self.module)
        if self.rest_api.is_rest():
            self.use_rest = True
        else:
            if HAS_NETAPP_LIB is False:
                self.module.fail_json(
                    msg="the python NetApp-Lib module is required")
            else:
                self.server = netapp_utils.setup_na_ontap_zapi(
                    module=self.module, vserver=self.parameters['vserver'])

    def get_qtree(self, name=None):
        """
        Checks if the qtree exists.
        :param:
            name : qtree name
        :return:
            Details about the qtree
            False if qtree is not found
        :rtype: bool
        """
        if name is None:
            name = self.parameters['name']
        if self.use_rest:
            api = "storage/qtrees"
            query = {'fields': 'export_policy,unix_permissions,security_style,volume',
                     'svm.name': self.parameters['vserver'],
                     'volume': self.parameters['flexvol_name'],
                     'name': name}
            message, error = self.rest_api.get(api, query)
            if error:
                self.module.fail_json(msg=error)
            if len(message.keys()) == 0:
                return None
            elif 'records' in message and len(message['records']) == 0:
                return None
            elif 'records' not in message:
                error = "Unexpected response in get_qtree from %s: %s" % (api, repr(message))
                self.module.fail_json(msg=error)
            return message['records'][0]
        else:
            qtree_list_iter = netapp_utils.zapi.NaElement('qtree-list-iter')
            query_details = netapp_utils.zapi.NaElement.create_node_with_children(
                'qtree-info', **{'vserver': self.parameters['vserver'],
                                 'volume': self.parameters['flexvol_name'],
                                 'qtree': name})
            query = netapp_utils.zapi.NaElement('query')
            query.add_child_elem(query_details)
            qtree_list_iter.add_child_elem(query)
            result = self.server.invoke_successfully(qtree_list_iter,
                                                     enable_tunneling=True)
            return_q = None
            if (result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1):
                return_q = {'export_policy': result['attributes-list']['qtree-info']['export-policy'],
                            'oplocks': result['attributes-list']['qtree-info']['oplocks'],
                            'security_style': result['attributes-list']['qtree-info']['security-style']}

                if result['attributes-list']['qtree-info'].get_child_by_name('mode'):
                    return_q['unix_permissions'] = result['attributes-list']['qtree-info']['mode']
                else:
                    return_q['unix_permissions'] = ''

            return return_q

    def create_qtree(self):
        """
        Create a qtree
        """
        if self.use_rest:
            api = "storage/qtrees"
            body = {'name': self.parameters['name'], 'volume': {'name': self.parameters['flexvol_name']},
                    'svm': {'name': self.parameters['vserver']}}
            if self.parameters.get('export_policy'):
                body['export_policy'] = self.parameters['export_policy']
            if self.parameters.get('security_style'):
                body['security_style'] = self.parameters['security_style']
            if self.parameters.get('unix_permissions'):
                body['unix_permissions'] = self.parameters['unix_permissions']
            __, error = self.rest_api.post(api, body)
            if error:
                self.module.fail_json(msg=error)
        else:
            options = {'qtree': self.parameters['name'], 'volume': self.parameters['flexvol_name']}
            if self.parameters.get('export_policy'):
                options['export-policy'] = self.parameters['export_policy']
            if self.parameters.get('security_style'):
                options['security-style'] = self.parameters['security_style']
            if self.parameters.get('oplocks'):
                options['oplocks'] = self.parameters['oplocks']
            if self.parameters.get('unix_permissions'):
                options['mode'] = self.parameters['unix_permissions']
            qtree_create = netapp_utils.zapi.NaElement.create_node_with_children(
                'qtree-create', **options)
            try:
                self.server.invoke_successfully(qtree_create,
                                                enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg="Error provisioning qtree %s: %s"
                                      % (self.parameters['name'], to_native(error)),
                                      exception=traceback.format_exc())

    def delete_qtree(self, current):
        """
        Delete a qtree
        """
        if self.use_rest:
            uuid = current['volume']['uuid']
            qid = str(current['id'])
            api = "storage/qtrees/%s/%s" % (uuid, qid)
            query = {'return_timeout': 3}
            response, error = self.rest_api.delete(api, params=query)
            if error:
                self.module.fail_json(msg=error)
            if 'job' in response and self.parameters['wait_for_completion']:
                message, error = self.rest_api.wait_on_job(response['job'], timeout=self.parameters['time_out'], increment=10)
                if error:
                    self.module.fail_json(msg="%s" % error)

        else:
            path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name'])
            options = {'qtree': path}
            if self.parameters['force_delete']:
                options['force'] = "true"
            qtree_delete = netapp_utils.zapi.NaElement.create_node_with_children(
                'qtree-delete', **options)

            try:
                self.server.invoke_successfully(qtree_delete,
                                                enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg="Error deleting qtree %s: %s" % (path, to_native(error)),
                                      exception=traceback.format_exc())

    def rename_qtree(self, current):
        """
        Rename a qtree
        """
        if self.use_rest:
            body = {'name': self.parameters['name']}
            uuid = current['volume']['uuid']
            qid = str(current['id'])
            api = "storage/qtrees/%s/%s" % (uuid, qid)
            dummy, error = self.rest_api.patch(api, body)
            if error:
                self.module.fail_json(msg=error)
        else:
            path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['from_name'])
            new_path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name'])
            qtree_rename = netapp_utils.zapi.NaElement.create_node_with_children(
                'qtree-rename', **{'qtree': path,
                                   'new-qtree-name': new_path})

            try:
                self.server.invoke_successfully(qtree_rename,
                                                enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg="Error renaming qtree %s: %s"
                                      % (self.parameters['from_name'], to_native(error)),
                                      exception=traceback.format_exc())

    def modify_qtree(self, current):
        """
        Modify a qtree
        """
        if self.use_rest:
            now = datetime.datetime.now()
            body = {}
            if self.parameters.get('security_style'):
                body['security_style'] = self.parameters['security_style']
            if self.parameters.get('unix_permissions'):
                body['unix_permissions'] = self.parameters['unix_permissions']
            if self.parameters.get('export_policy'):
                body['export_policy'] = {'name': self.parameters['export_policy']}
            uuid = current['volume']['uuid']
            qid = str(current['id'])
            api = "storage/qtrees/%s/%s" % (uuid, qid)
            timeout = 120
            query = {'return_timeout': timeout}
            dummy, error = self.rest_api.patch(api, body, query)

            later = datetime.datetime.now()
            time_elapsed = later - now
            # modify will not return any error if return_timeout is 0, so we set it to 120 seconds as default
            if time_elapsed.seconds > (timeout - 1):
                self.module.fail_json(msg="Too long to run")
            if error:
                self.module.fail_json(msg=error)
        else:
            options = {'qtree': self.parameters['name'], 'volume': self.parameters['flexvol_name']}
            if self.parameters.get('export_policy'):
                options['export-policy'] = self.parameters['export_policy']
            if self.parameters.get('security_style'):
                options['security-style'] = self.parameters['security_style']
            if self.parameters.get('oplocks'):
                options['oplocks'] = self.parameters['oplocks']
            if self.parameters.get('unix_permissions'):
                options['mode'] = self.parameters['unix_permissions']
            qtree_modify = netapp_utils.zapi.NaElement.create_node_with_children(
                'qtree-modify', **options)
            try:
                self.server.invoke_successfully(qtree_modify, enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error modifying qtree %s: %s'
                                      % (self.parameters['name'], to_native(error)),
                                      exception=traceback.format_exc())

    def apply(self):
        '''Call create/delete/modify/rename operations'''
        if not self.use_rest:
            netapp_utils.ems_log_event("na_ontap_qtree", self.server)
        current = self.get_qtree()
        rename, cd_action, modify = None, None, None
        if self.parameters.get('from_name'):
            from_qtree = self.get_qtree(self.parameters['from_name'])
            rename = self.na_helper.is_rename_action(from_qtree, current)
            if rename is None:
                self.module.fail_json(msg='Error renaming: qtree %s does not exist' % self.parameters['from_name'])
            if rename:
                current = from_qtree
        else:
            cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if cd_action is None and self.parameters['state'] == 'present':
            if self.parameters.get('security_style') and self.parameters['security_style'] != current['security_style']:
                modify = True
            if self.parameters.get('unix_permissions') and \
                    self.parameters['unix_permissions'] != str(current['unix_permissions']):
                modify = True
            # rest and zapi handle export policy differently
            if self.use_rest:
                if self.parameters.get('export_policy') and \
                        self.parameters['export_policy'] != current['export_policy']['name']:
                    modify = True
            else:
                if self.parameters.get('export_policy') and \
                        self.parameters['export_policy'] != current['export_policy']:
                    modify = True
        if self.use_rest and cd_action == 'delete' and not self.parameters['force_delete']:
            self.module.fail_json(msg='Error: force_delete option is not supported for REST, unless set to true.')

        if modify:
            self.na_helper.changed = True
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_qtree()
                elif cd_action == 'delete':
                    self.delete_qtree(current)
                else:
                    if rename:
                        self.rename_qtree(current)
                    if modify:
                        self.modify_qtree(current)
        self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPGatherInfo(object):
    '''Class with gather info methods'''
    def __init__(self):
        """
        Parse arguments, setup state variables,
        check paramenters and ensure request module is installed
        """
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(type='str',
                            choices=['info'],
                            default='info',
                            required=False),
                 gather_subset=dict(default=['all'],
                                    type='list',
                                    elements='str',
                                    required=False),
                 max_records=dict(type='int', default=1024, required=False),
                 fields=dict(type='list', elements='str', required=False),
                 parameters=dict(type='dict', required=False)))

        self.module = AnsibleModule(argument_spec=self.argument_spec,
                                    supports_check_mode=True)

        # set up variables
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        self.fields = list()

        self.rest_api = OntapRestAPI(self.module)

    def validate_ontap_version(self):
        """
            Method to validate the ONTAP version
        """

        api = 'cluster'
        data = {'fields': ['version']}

        ontap_version, error = self.rest_api.get(api, data)

        if error:
            self.module.fail_json(msg=error)

        return ontap_version

    def get_subset_info(self, gather_subset_info):
        """
            Gather ONTAP information for the given subset using REST APIs
            Input for REST APIs call : (api, data)
            return gathered_ontap_info
        """

        api = gather_subset_info['api_call']
        if gather_subset_info.pop('post', False):
            self.run_post(gather_subset_info)
        data = {
            'max_records': self.parameters['max_records'],
            'fields': self.fields
        }
        # allow for passing in any additional rest api fields
        if self.parameters.get('parameters'):
            for each in self.parameters['parameters']:
                data[each] = self.parameters['parameters'][each]

        gathered_ontap_info, error = self.rest_api.get(api, data)

        if error:
            # Fail the module if error occurs from REST APIs call
            if int(error.get('code', 0)) == 6:
                self.module.fail_json(
                    msg="%s user is not authorized to make %s api call" %
                    (self.parameters.get('username'), api))
            # if Aggr recommender can't make a recommendation it will fail with the following error code.
            # We don't want to fail
            elif int(
                    error.get('code', 0)
            ) == 19726344 and "No recommendation can be made for this cluster" in error.get(
                    'message'):
                return error.get('message')
            # If the API doesn't exist (using an older system) we don't want to fail
            elif int(error.get('code', 0)) == 3:
                return error.get('message')
            else:
                self.module.fail_json(msg=error)
        else:
            return gathered_ontap_info

        return None

    def run_post(self, gather_subset_info):
        api = gather_subset_info['api_call']
        post_return, error = self.rest_api.post(api, None)
        if error:
            return None
        message, error = self.rest_api.wait_on_job(post_return['job'],
                                                   increment=5)
        if error:
            self.module.fail_json(msg="%s" % error)

    def get_next_records(self, api):
        """
            Gather next set of ONTAP information for the specified api
            Input for REST APIs call : (api, data)
            return gather_subset_info
        """

        data = {}
        gather_subset_info, error = self.rest_api.get(api, data)

        if error:
            self.module.fail_json(msg=error)

        return gather_subset_info

    def convert_subsets(self):
        """
        Convert an info to the REST API
        """
        info_to_rest_mapping = {
            "aggregate_info": "storage/aggregates",
            "application_info": "application/applications",
            "application_template_info": "application/templates",
            "autosupport_config_info": "support/autosupport",
            "autosupport_messages_history": "support/autosupport/messages",
            "broadcast_domains_info": "network/ethernet/broadcast-domains",
            "cifs_services_info": "protocols/cifs/services",
            "cifs_share_info": "protocols/cifs/shares",
            "cloud_targets_info": "cloud/targets",
            "cluster_chassis_info": "cluster/chassis",
            "cluster_jobs_info": "cluster/jobs",
            "cluster_metrocluster_diagnostics":
            "cluster/metrocluster/diagnostics",
            "cluster_metrics_info": "cluster/metrics",
            "cluster_node_info": "cluster/nodes",
            "cluster_peer_info": "cluster/peers",
            "cluster_schedules": "cluster/schedules",
            "cluster_software_history": "cluster/software/history",
            "cluster_software_packages": "cluster/software/packages",
            "disk_info": "storage/disks",
            "initiator_groups_info": "protocols/san/igroups",
            "ip_interfaces_info": "network/ip/interfaces",
            "ip_routes_info": "network/ip/routes",
            "ip_service_policies": "network/ip/service-policies",
            "network_ipspaces_info": "network/ipspaces",
            "network_ports_info": "network/ethernet/ports",
            "ontap_system_version": "cluster/software",
            "san_fc_logins_info": "network/fc/logins",
            "san_fc_wppn-aliases": "network/fc/wwpn-aliases",
            "san_fcp_services": "protocols/san/fcp/services",
            "san_iscsi_credentials": "protocols/san/iscsi/credentials",
            "san_iscsi_services": "protocols/san/iscsi/services",
            "san_lun_maps": "protocols/san/lun-maps",
            "storage_flexcaches_info": "storage/flexcache/flexcaches",
            "storage_flexcaches_origin_info": "storage/flexcache/origins",
            "storage_luns_info": "storage/luns",
            "storage_NVMe_namespaces": "storage/namespaces",
            "storage_ports_info": "storage/ports",
            "storage_qos_policies": "storage/qos/policies",
            "storage_qtrees_config": "storage/qtrees",
            "storage_quota_reports": "storage/quota/reports",
            "storage_quota_policy_rules": "storage/quota/rules",
            "storage_shelves_config": "storage/shelves",
            "storage_snapshot_policies": "storage/snapshot-policies",
            "support_ems_config": "support/ems",
            "support_ems_events": "support/ems/events",
            "support_ems_filters": "support/ems/filters",
            "svm_dns_config_info": "name-services/dns",
            "svm_ldap_config_info": "name-services/ldap",
            "svm_name_mapping_config_info": "name-services/name-mappings",
            "svm_nis_config_info": "name-services/nis",
            "svm_peers_info": "svm/peers",
            "svm_peer-permissions_info": "svm/peer-permissions",
            "vserver_info": "svm/svms",
            "volume_info": "storage/volumes"
        }
        # Add rest API names as there info version, also make sure we don't add a duplicate
        subsets = []
        for subset in self.parameters['gather_subset']:
            if subset in info_to_rest_mapping:
                if info_to_rest_mapping[subset] not in subsets:
                    subsets.append(info_to_rest_mapping[subset])
            else:
                if subset not in subsets:
                    subsets.append(subset)
        return subsets

    def apply(self):
        """
        Perform pre-checks, call functions and exit
        """

        result_message = dict()

        # Validating ONTAP version
        self.validate_ontap_version()

        # Defining gather_subset and appropriate api_call
        get_ontap_subset_info = {
            'application/applications': {
                'api_call': 'application/applications',
            },
            'application/templates': {
                'api_call': 'application/templates',
            },
            'cloud/targets': {
                'api_call': 'cloud/targets',
            },
            'cluster/chassis': {
                'api_call': 'cluster/chassis',
            },
            'cluster/jobs': {
                'api_call': 'cluster/jobs',
            },
            'cluster/metrocluster/diagnostics': {
                'api_call': 'cluster/metrocluster/diagnostics',
                'post': True
            },
            'cluster/metrics': {
                'api_call': 'cluster/metrics',
            },
            'cluster/nodes': {
                'api_call': 'cluster/nodes',
            },
            'cluster/peers': {
                'api_call': 'cluster/peers',
            },
            'cluster/schedules': {
                'api_call': 'cluster/schedules',
            },
            'cluster/software': {
                'api_call': 'cluster/software',
            },
            'cluster/software/history': {
                'api_call': 'cluster/software/history',
            },
            'cluster/software/packages': {
                'api_call': 'cluster/software/packages',
            },
            'name-services/dns': {
                'api_call': 'name-services/dns',
            },
            'name-services/ldap': {
                'api_call': 'name-services/ldap',
            },
            'name-services/name-mappings': {
                'api_call': 'name-services/name-mappings',
            },
            'name-services/nis': {
                'api_call': 'name-services/nis',
            },
            'network/ethernet/broadcast-domains': {
                'api_call': 'network/ethernet/broadcast-domains',
            },
            'network/ethernet/ports': {
                'api_call': 'network/ethernet/ports',
            },
            'network/fc/logins': {
                'api_call': 'network/fc/logins',
            },
            'network/fc/wwpn-aliases': {
                'api_call': 'network/fc/wwpn-aliases',
            },
            'network/ip/interfaces': {
                'api_call': 'network/ip/interfaces',
            },
            'network/ip/routes': {
                'api_call': 'network/ip/routes',
            },
            'network/ip/service-policies': {
                'api_call': 'network/ip/service-policies',
            },
            'network/ipspaces': {
                'api_call': 'network/ipspaces',
            },
            'protocols/cifs/services': {
                'api_call': 'protocols/cifs/services',
            },
            'protocols/cifs/shares': {
                'api_call': 'protocols/cifs/shares',
            },
            'protocols/san/fcp/services': {
                'api_call': 'protocols/san/fcp/services',
            },
            'protocols/san/igroups': {
                'api_call': 'protocols/san/igroups',
            },
            'protocols/san/iscsi/credentials': {
                'api_call': 'protocols/san/iscsi/credentials',
            },
            'protocols/san/iscsi/services': {
                'api_call': 'protocols/san/iscsi/services',
            },
            'protocols/san/lun-maps': {
                'api_call': 'protocols/san/lun-maps',
            },
            'storage/aggregates': {
                'api_call': 'storage/aggregates',
            },
            'storage/disks': {
                'api_call': 'storage/disks',
            },
            'storage/flexcache/flexcaches': {
                'api_call': 'storage/flexcache/flexcaches',
            },
            'storage/flexcache/origins': {
                'api_call': 'storage/flexcache/origins',
            },
            'storage/luns': {
                'api_call': 'storage/luns',
            },
            'storage/namespaces': {
                'api_call': 'storage/namespaces',
            },
            'storage/ports': {
                'api_call': 'storage/ports',
            },
            'storage/qos/policies': {
                'api_call': 'storage/qos/policies',
            },
            'storage/qtrees': {
                'api_call': 'storage/qtrees',
            },
            'storage/quota/reports': {
                'api_call': 'storage/quota/reports',
            },
            'storage/quota/rules': {
                'api_call': 'storage/quota/rules',
            },
            'storage/shelves': {
                'api_call': 'storage/shelves',
            },
            'storage/snapshot-policies': {
                'api_call': 'storage/snapshot-policies',
            },
            'storage/volumes': {
                'api_call': 'storage/volumes',
            },
            'support/autosupport': {
                'api_call': 'support/autosupport',
            },
            'support/autosupport/messages': {
                'api_call': 'support/autosupport/messages',
            },
            'support/ems': {
                'api_call': 'support/ems',
            },
            'support/ems/events': {
                'api_call': 'support/ems/events',
            },
            'support/ems/filters': {
                'api_call': 'support/ems/filters',
            },
            'svm/peers': {
                'api_call': 'svm/peers',
            },
            'svm/peer-permissions': {
                'api_call': 'svm/peer-permissions',
            },
            'svm/svms': {
                'api_call': 'svm/svms',
            }
        }

        if 'all' in self.parameters['gather_subset']:
            # If all in subset list, get the information of all subsets
            self.parameters['gather_subset'] = sorted(
                get_ontap_subset_info.keys())

        length_of_subsets = len(self.parameters['gather_subset'])

        if self.parameters.get('fields') is not None:
            # If multiple fields specified to return, convert list to string
            self.fields = ','.join(self.parameters.get('fields'))

            if self.fields != '*' and length_of_subsets > 1:
                # Restrict gather subsets to one subset if fields section is list_of_fields
                self.module.fail_json(
                    msg="Error: fields: %s, only one subset will be allowed." %
                    self.parameters.get('fields'))
        converted_subsets = self.convert_subsets()

        for subset in converted_subsets:
            try:
                # Verify whether the supported subset passed
                specified_subset = get_ontap_subset_info[subset]
            except KeyError:
                self.module.fail_json(
                    msg=
                    "Specified subset %s is not found, supported subsets are %s"
                    % (subset, list(get_ontap_subset_info.keys())))

            result_message[subset] = self.get_subset_info(specified_subset)

            if result_message[subset] is not None:
                if isinstance(result_message[subset], dict):
                    while result_message[subset]['_links'].get('next'):
                        # Get all the set of records if next link found in subset_info for the specified subset
                        next_api = result_message[subset]['_links']['next'][
                            'href']
                        gathered_subset_info = self.get_next_records(
                            next_api.replace('/api', ''))

                        # Update the subset info for the specified subset
                        result_message[subset][
                            '_links'] = gathered_subset_info['_links']
                        result_message[subset]['records'].extend(
                            gathered_subset_info['records'])

                    # metrocluster doesn't have a records field, so we need to skip this
                    if result_message[subset].get('records') is not None:
                        # Getting total number of records
                        result_message[subset]['num_records'] = len(
                            result_message[subset]['records'])

        self.module.exit_json(changed='False',
                              state=self.parameters['state'],
                              ontap_info=result_message)
예제 #4
0
class NetAppOntapSnapMirrorPolicy(object):
    """
        Create, Modifies and Destroys a SnapMirror policy
    """
    def __init__(self):
        """
            Initialize the Ontap SnapMirror policy class
        """

        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(
                state=dict(required=False,
                           choices=['present', 'absent'],
                           default='present'),
                vserver=dict(required=True, type='str'),
                policy_name=dict(required=True, type='str'),
                comment=dict(required=False, type='str'),
                policy_type=dict(required=False,
                                 type='str',
                                 choices=[
                                     'vault', 'async_mirror', 'mirror_vault',
                                     'strict_sync_mirror', 'sync_mirror'
                                 ]),
                tries=dict(required=False, type='str'),
                transfer_priority=dict(required=False,
                                       type='str',
                                       choices=['low', 'normal']),
                common_snapshot_schedule=dict(required=False, type='str'),
                ignore_atime=dict(required=False, type='bool'),
                is_network_compression_enabled=dict(required=False,
                                                    type='bool'),
                owner=dict(required=False,
                           type='str',
                           choices=['cluster_admin', 'vserver_admin']),
                restart=dict(required=False,
                             type='str',
                             choices=['always', 'never', 'default']),
                snapmirror_label=dict(required=False,
                                      type="list",
                                      elements="str"),
                keep=dict(required=False, type="list", elements="int"),
                prefix=dict(required=False, type="list", elements="str"),
                schedule=dict(required=False, type="list", elements="str"),
            ))

        self.module = AnsibleModule(argument_spec=self.argument_spec,
                                    supports_check_mode=True)

        # set up variables
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)

        # API should be used for ONTAP 9.6 or higher, Zapi for lower version
        self.restApi = OntapRestAPI(self.module)
        # some attributes are not supported in earlier REST implementation
        unsupported_rest_properties = [
            'owner', 'restart', 'transfer_priority', 'tries', 'ignore_atime',
            'common_snapshot_schedule'
        ]
        used_unsupported_rest_properties = [
            x for x in unsupported_rest_properties if x in self.parameters
        ]
        self.use_rest, error = self.restApi.is_rest(
            used_unsupported_rest_properties)

        if error:
            self.module.fail_json(msg=error)
        if not self.use_rest:
            if HAS_NETAPP_LIB is False:
                self.module.fail_json(
                    msg='The python NetApp-Lib module is required')
            else:
                self.server = netapp_utils.setup_na_ontap_zapi(
                    module=self.module, vserver=self.parameters['vserver'])

    def get_snapmirror_policy(self):

        if self.use_rest:
            data = {
                'fields':
                'uuid,name,svm.name,comment,network_compression_enabled,type,retention',
                'name': self.parameters['policy_name'],
                'svm.name': self.parameters['vserver']
            }
            api = "snapmirror/policies"
            message, error = self.restApi.get(api, data)
            if error:
                self.module.fail_json(msg=error)
            if len(message['records']) != 0:
                return_value = {
                    'uuid':
                    message['records'][0]['uuid'],
                    'vserver':
                    message['records'][0]['svm']['name'],
                    'policy_name':
                    message['records'][0]['name'],
                    'comment':
                    '',
                    'is_network_compression_enabled':
                    message['records'][0]['network_compression_enabled'],
                    'snapmirror_label':
                    list(),
                    'keep':
                    list(),
                    'prefix':
                    list(),
                    'schedule':
                    list()
                }
                if 'type' in message['records'][0]:
                    policy_type = message['records'][0]['type']
                    if policy_type == 'async':
                        policy_type = 'async_mirror'
                    elif policy_type == 'sync':
                        policy_type = 'sync_mirror'
                    return_value['policy_type'] = policy_type
                if 'comment' in message['records'][0]:
                    return_value['comment'] = message['records'][0]['comment']
                if 'retention' in message['records'][0]:
                    for rule in message['records'][0]['retention']:
                        return_value['snapmirror_label'].append(rule['label'])
                        return_value['keep'].append(int(rule['count']))
                        if rule['prefix'] == '-':
                            return_value['prefix'].append('')
                        else:
                            return_value['prefix'].append(rule['prefix'])
                        if rule['creation_schedule']['name'] == '-':
                            return_value['schedule'].append('')
                        else:
                            return_value['schedule'].append(
                                rule['creation_schedule']['name'])
                return return_value
            return None
        else:
            return_value = None

            snapmirror_policy_get_iter = netapp_utils.zapi.NaElement(
                'snapmirror-policy-get-iter')
            snapmirror_policy_info = netapp_utils.zapi.NaElement(
                'snapmirror-policy-info')
            snapmirror_policy_info.add_new_child(
                'policy-name', self.parameters['policy_name'])
            snapmirror_policy_info.add_new_child('vserver',
                                                 self.parameters['vserver'])
            query = netapp_utils.zapi.NaElement('query')
            query.add_child_elem(snapmirror_policy_info)
            snapmirror_policy_get_iter.add_child_elem(query)

            try:
                result = self.server.invoke_successfully(
                    snapmirror_policy_get_iter, True)
                if result.get_child_by_name('attributes-list'):
                    snapmirror_policy_attributes = result['attributes-list'][
                        'snapmirror-policy-info']

                    return_value = {
                        'policy_name':
                        snapmirror_policy_attributes['policy-name'],
                        'tries':
                        snapmirror_policy_attributes['tries'],
                        'transfer_priority':
                        snapmirror_policy_attributes['transfer-priority'],
                        'is_network_compression_enabled':
                        self.na_helper.get_value_for_bool(
                            True, snapmirror_policy_attributes[
                                'is-network-compression-enabled']),
                        'restart':
                        snapmirror_policy_attributes['restart'],
                        'ignore_atime':
                        self.na_helper.get_value_for_bool(
                            True,
                            snapmirror_policy_attributes['ignore-atime']),
                        'vserver':
                        snapmirror_policy_attributes['vserver-name'],
                        'comment':
                        '',
                        'snapmirror_label':
                        list(),
                        'keep':
                        list(),
                        'prefix':
                        list(),
                        'schedule':
                        list()
                    }
                    if snapmirror_policy_attributes.get_child_content(
                            'comment') is not None:
                        return_value['comment'] = snapmirror_policy_attributes[
                            'comment']

                    if snapmirror_policy_attributes.get_child_content(
                            'type') is not None:
                        return_value[
                            'policy_type'] = snapmirror_policy_attributes[
                                'type']

                    if snapmirror_policy_attributes.get_child_by_name(
                            'snapmirror-policy-rules'):
                        for rule in snapmirror_policy_attributes[
                                'snapmirror-policy-rules'].get_children():
                            # Ignore builtin rules
                            if rule.get_child_content('snapmirror-label') == "sm_created" or \
                                    rule.get_child_content('snapmirror-label') == "all_source_snapshots":
                                continue

                            return_value['snapmirror_label'].append(
                                rule.get_child_content('snapmirror-label'))
                            return_value['keep'].append(
                                int(rule.get_child_content('keep')))

                            prefix = rule.get_child_content('prefix')
                            if prefix is None or prefix == '-':
                                prefix = ''
                            return_value['prefix'].append(prefix)

                            schedule = rule.get_child_content('schedule')
                            if schedule is None or schedule == '-':
                                schedule = ''
                            return_value['schedule'].append(schedule)

            except netapp_utils.zapi.NaApiError as error:
                if 'NetApp API failed. Reason - 13001:' in to_native(error):
                    # Policy does not exist
                    pass
                else:
                    self.module.fail_json(
                        msg='Error getting snapmirror policy %s: %s' %
                        (self.parameters['policy_name'], to_native(error)),
                        exception=traceback.format_exc())
            return return_value

    def validate_parameters(self):
        """
        Validate snapmirror policy rules
        :return: None
        """

        # For snapmirror policy rules, 'snapmirror_label' is required.
        if 'snapmirror_label' in self.parameters:

            # Check size of 'snapmirror_label' list is 0-10. Can have zero rules.
            # Take builtin 'sm_created' rule into account for 'mirror_vault'.
            if (('policy_type' in self.parameters
                 and self.parameters['policy_type'] == 'mirror_vault'
                 and len(self.parameters['snapmirror_label']) > 9)
                    or len(self.parameters['snapmirror_label']) > 10):
                self.module.fail_json(
                    msg="Error: A SnapMirror Policy can have up to a maximum of "
                    "10 rules (including builtin rules), with a 'keep' value "
                    "representing the maximum number of Snapshot copies for each rule"
                )

            # 'keep' must be supplied as long as there is at least one snapmirror_label
            if len(self.parameters['snapmirror_label']
                   ) > 0 and 'keep' not in self.parameters:
                self.module.fail_json(
                    msg="Error: Missing 'keep' parameter. When specifying the "
                    "'snapmirror_label' parameter, the 'keep' parameter must "
                    "also be supplied")

            # Make sure other rule values match same number of 'snapmirror_label' values.
            for rule_parameter in ['keep', 'prefix', 'schedule']:
                if rule_parameter in self.parameters:
                    if len(self.parameters['snapmirror_label']) > len(
                            self.parameters[rule_parameter]):
                        self.module.fail_json(
                            msg="Error: Each 'snapmirror_label' value must have "
                            "an accompanying '%s' value" % rule_parameter)
                    if len(self.parameters[rule_parameter]) > len(
                            self.parameters['snapmirror_label']):
                        self.module.fail_json(
                            msg=
                            "Error: Each '%s' value must have an accompanying "
                            "'snapmirror_label' value" % rule_parameter)
        else:
            # 'snapmirror_label' not supplied.
            # Bail out if other rule parameters have been supplied.
            for rule_parameter in ['keep', 'prefix', 'schedule']:
                if rule_parameter in self.parameters:
                    self.module.fail_json(
                        msg="Error: Missing 'snapmirror_label' parameter. When "
                        "specifying the '%s' parameter, the 'snapmirror_label' "
                        "parameter must also be supplied" % rule_parameter)

        # Schedule must be supplied if prefix is supplied.
        if 'prefix' in self.parameters and 'schedule' not in self.parameters:
            self.module.fail_json(
                msg="Error: Missing 'schedule' parameter. When "
                "specifying the 'prefix' parameter, the 'schedule' "
                "parameter must also be supplied")

    def create_snapmirror_policy(self):
        """
        Creates a new storage efficiency policy
        """
        self.validate_parameters()
        if self.use_rest:
            data = {
                'name': self.parameters['policy_name'],
                'svm': {
                    'name': self.parameters['vserver']
                }
            }
            if 'policy_type' in self.parameters.keys():
                if 'async_mirror' in self.parameters['policy_type']:
                    data['type'] = 'async'
                elif 'sync_mirror' in self.parameters['policy_type']:
                    data['type'] = 'sync'
                    data['sync_type'] = 'sync'
                else:
                    self.module.fail_json(
                        msg=
                        'policy type in REST only supports options async_mirror or sync_mirror, given %s'
                        % (self.parameters['policy_type']))
                data = self.create_snapmirror_policy_obj_for_rest(
                    data, data['type'])
            else:
                data = self.create_snapmirror_policy_obj_for_rest(data)
            api = "snapmirror/policies"
            response, error = self.restApi.post(api, data)
            if error:
                self.module.fail_json(msg=error)
            if 'job' in response:
                self.restApi.wait_on_job(response['job'], increment=5)
        else:
            snapmirror_policy_obj = netapp_utils.zapi.NaElement(
                "snapmirror-policy-create")
            snapmirror_policy_obj.add_new_child("policy-name",
                                                self.parameters['policy_name'])
            if 'policy_type' in self.parameters.keys():
                snapmirror_policy_obj.add_new_child(
                    "type", self.parameters['policy_type'])
            snapmirror_policy_obj = self.create_snapmirror_policy_obj(
                snapmirror_policy_obj)

            try:
                self.server.invoke_successfully(snapmirror_policy_obj, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg='Error creating snapmirror policy %s: %s' %
                    (self.parameters['policy_name'], to_native(error)),
                    exception=traceback.format_exc())

    def create_snapmirror_policy_obj(self, snapmirror_policy_obj):
        if 'comment' in self.parameters.keys():
            snapmirror_policy_obj.add_new_child("comment",
                                                self.parameters['comment'])
        if 'common_snapshot_schedule' in self.parameters.keys(
        ) and 'sync_mirror' in self.parameters['policy_type']:
            snapmirror_policy_obj.add_new_child(
                "common-snapshot-schedule",
                self.parameters['common_snapshot_schedule'])
        if 'ignore_atime' in self.parameters.keys():
            snapmirror_policy_obj.add_new_child(
                "ignore-atime",
                self.na_helper.get_value_for_bool(
                    False, self.parameters['ignore_atime']))
        if 'is_network_compression_enabled' in self.parameters.keys():
            snapmirror_policy_obj.add_new_child(
                "is-network-compression-enabled",
                self.na_helper.get_value_for_bool(
                    False, self.parameters['is_network_compression_enabled']))
        if 'owner' in self.parameters.keys():
            snapmirror_policy_obj.add_new_child("owner",
                                                self.parameters['owner'])
        if 'restart' in self.parameters.keys():
            snapmirror_policy_obj.add_new_child("restart",
                                                self.parameters['restart'])
        if 'transfer_priority' in self.parameters.keys():
            snapmirror_policy_obj.add_new_child(
                "transfer-priority", self.parameters['transfer_priority'])
        if 'tries' in self.parameters.keys():
            snapmirror_policy_obj.add_new_child("tries",
                                                self.parameters['tries'])
        return snapmirror_policy_obj

    def create_snapmirror_policy_obj_for_rest(self,
                                              snapmirror_policy_obj,
                                              policy_type=None):
        if 'comment' in self.parameters.keys():
            snapmirror_policy_obj["comment"] = self.parameters['comment']
        if 'is_network_compression_enabled' in self.parameters:
            if policy_type == 'async':
                snapmirror_policy_obj[
                    "network_compression_enabled"] = self.parameters[
                        'is_network_compression_enabled']
            elif policy_type == 'sync':
                self.module.fail_json(
                    msg=
                    "Input parameter network_compression_enabled is not valid for SnapMirror policy type sync"
                )
        return snapmirror_policy_obj

    def create_snapmirror_policy_retention_obj_for_rest(self, rules=None):
        """
        Create SnapMirror policy retention REST object.
        :param list rules: e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': 'daily', 'schedule': 'daily'}, ... ]
        :return: List of retention REST objects.
                 e.g. [{'label': 'daily', 'count': 7, 'prefix': 'daily', 'creation_schedule': {'name': 'daily'}}, ... ]
        """
        snapmirror_policy_retention_objs = list()
        if rules is not None:
            for rule in rules:
                retention = {
                    'label': rule['snapmirror_label'],
                    'count': str(rule['keep'])
                }
                if 'prefix' in rule and rule['prefix'] != '':
                    retention['prefix'] = rule['prefix']
                if 'schedule' in rule and rule['schedule'] != '':
                    retention['creation_schedule'] = {'name': rule['schedule']}
                snapmirror_policy_retention_objs.append(retention)
        return snapmirror_policy_retention_objs

    def delete_snapmirror_policy(self, uuid=None):
        """
        Deletes a snapmirror policy
        """
        if self.use_rest:
            api = "snapmirror/policies"
            data = {'uuid': uuid}
            dummy, error = self.restApi.delete(api, data)
            if error:
                self.module.fail_json(msg=error)
        else:
            snapmirror_policy_obj = netapp_utils.zapi.NaElement(
                "snapmirror-policy-delete")
            snapmirror_policy_obj.add_new_child("policy-name",
                                                self.parameters['policy_name'])

            try:
                self.server.invoke_successfully(snapmirror_policy_obj, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg='Error deleting snapmirror policy %s: %s' %
                    (self.parameters['policy_name'], to_native(error)),
                    exception=traceback.format_exc())

    def modify_snapmirror_policy(self, uuid=None, policy_type=None):
        """
        Modifies a snapmirror policy
        """
        if self.use_rest:
            api = "snapmirror/policies/" + uuid
            data = self.create_snapmirror_policy_obj_for_rest(
                dict(), policy_type)
            dummy, error = self.restApi.patch(api, data)
            if error:
                self.module.fail_json(msg=error)
        else:
            snapmirror_policy_obj = netapp_utils.zapi.NaElement(
                "snapmirror-policy-modify")
            snapmirror_policy_obj = self.create_snapmirror_policy_obj(
                snapmirror_policy_obj)
            # Only modify snapmirror policy if a specific snapmirror policy attribute needs
            # modifying. It may be that only snapmirror policy rules are being modified.
            if snapmirror_policy_obj.get_children():
                snapmirror_policy_obj.add_new_child(
                    "policy-name", self.parameters['policy_name'])

                try:
                    self.server.invoke_successfully(snapmirror_policy_obj,
                                                    True)
                except netapp_utils.zapi.NaApiError as error:
                    self.module.fail_json(
                        msg='Error modifying snapmirror policy %s: %s' %
                        (self.parameters['policy_name'], to_native(error)),
                        exception=traceback.format_exc())

    def identify_new_snapmirror_policy_rules(self, current=None):
        """
        Identify new rules that should be added.
        :return: List of new rules to be added
                 e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ]
        """
        new_rules = list()
        if 'snapmirror_label' in self.parameters:
            for snapmirror_label in self.parameters['snapmirror_label']:
                snapmirror_label = snapmirror_label.strip()

                # Construct new rule. prefix and schedule are optional.
                snapmirror_label_index = self.parameters[
                    'snapmirror_label'].index(snapmirror_label)
                rule = dict({
                    'snapmirror_label':
                    snapmirror_label,
                    'keep':
                    self.parameters['keep'][snapmirror_label_index]
                })
                if 'prefix' in self.parameters:
                    rule['prefix'] = self.parameters['prefix'][
                        snapmirror_label_index]
                else:
                    rule['prefix'] = ''
                if 'schedule' in self.parameters:
                    rule['schedule'] = self.parameters['schedule'][
                        snapmirror_label_index]
                else:
                    rule['schedule'] = ''

                if current is not None and 'snapmirror_label' in current:
                    if snapmirror_label not in current['snapmirror_label']:
                        # Rule doesn't exist. Add new rule.
                        new_rules.append(rule)
                else:
                    # No current or any rules. Add new rule.
                    new_rules.append(rule)
        return new_rules

    def identify_obsolete_snapmirror_policy_rules(self, current=None):
        """
        Identify existing rules that should be deleted
        :return: List of rules to be deleted
                 e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ]
        """
        obsolete_rules = list()
        if 'snapmirror_label' in self.parameters:
            if current is not None and 'snapmirror_label' in current:
                # Iterate existing rules.
                for snapmirror_label in current['snapmirror_label']:
                    snapmirror_label = snapmirror_label.strip()
                    if snapmirror_label not in [
                            item.strip()
                            for item in self.parameters['snapmirror_label']
                    ]:
                        # Existing rule isn't in parameters. Delete existing rule.
                        current_snapmirror_label_index = current[
                            'snapmirror_label'].index(snapmirror_label)
                        rule = dict({
                            'snapmirror_label':
                            snapmirror_label,
                            'keep':
                            current['keep'][current_snapmirror_label_index],
                            'prefix':
                            current['prefix'][current_snapmirror_label_index],
                            'schedule':
                            current['schedule'][current_snapmirror_label_index]
                        })
                        obsolete_rules.append(rule)
        return obsolete_rules

    def identify_modified_snapmirror_policy_rules(self, current=None):
        """
        Identify self.parameters rules that will be modified or not.
        :return: List of 'modified' rules and a list of 'unmodified' rules
                 e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, ... ]
        """
        modified_rules = list()
        unmodified_rules = list()
        if 'snapmirror_label' in self.parameters:
            for snapmirror_label in self.parameters['snapmirror_label']:
                snapmirror_label = snapmirror_label.strip()
                if current is not None and 'snapmirror_label' in current:
                    if snapmirror_label in current['snapmirror_label']:
                        # Rule exists. Identify whether it requires modification or not.
                        modified = False
                        rule = dict()
                        rule['snapmirror_label'] = snapmirror_label

                        # Get indexes of current and supplied rule.
                        current_snapmirror_label_index = current[
                            'snapmirror_label'].index(snapmirror_label)
                        snapmirror_label_index = self.parameters[
                            'snapmirror_label'].index(snapmirror_label)

                        # Check if keep modified
                        if self.parameters['keep'][
                                snapmirror_label_index] != current['keep'][
                                    current_snapmirror_label_index]:
                            modified = True
                            rule['keep'] = self.parameters['keep'][
                                snapmirror_label_index]
                        else:
                            rule['keep'] = current['keep'][
                                current_snapmirror_label_index]

                        # Check if prefix modified
                        if 'prefix' in self.parameters:
                            if self.parameters['prefix'][
                                    snapmirror_label_index] != current[
                                        'prefix'][
                                            current_snapmirror_label_index]:
                                modified = True
                                rule['prefix'] = self.parameters['prefix'][
                                    snapmirror_label_index]
                            else:
                                rule['prefix'] = current['prefix'][
                                    current_snapmirror_label_index]
                        else:
                            rule['prefix'] = current['prefix'][
                                current_snapmirror_label_index]

                        # Check if schedule modified
                        if 'schedule' in self.parameters:
                            if self.parameters['schedule'][
                                    snapmirror_label_index] != current[
                                        'schedule'][
                                            current_snapmirror_label_index]:
                                modified = True
                                rule['schedule'] = self.parameters['schedule'][
                                    snapmirror_label_index]
                            else:
                                rule['schedule'] = current['schedule'][
                                    current_snapmirror_label_index]
                        else:
                            rule['schedule'] = current['schedule'][
                                current_snapmirror_label_index]

                        if modified:
                            modified_rules.append(rule)
                        else:
                            unmodified_rules.append(rule)
        return modified_rules, unmodified_rules

    def identify_snapmirror_policy_rules_with_schedule(self, rules=None):
        """
        Identify rules that are using a schedule or not. At least one
        non-schedule rule must be added to a policy before schedule rules
        are added.
        :return: List of rules with schedules and a list of rules without schedules
                 e.g. [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': 'daily', 'schedule': 'daily'}, ... ],
                      [{'snapmirror_label': 'weekly', 'keep': 5, 'prefix': '', 'schedule': ''}, ... ]
        """
        schedule_rules = list()
        non_schedule_rules = list()
        if rules is not None:
            for rule in rules:
                if 'schedule' in rule:
                    schedule_rules.append(rule)
                else:
                    non_schedule_rules.append(rule)
        return schedule_rules, non_schedule_rules

    def modify_snapmirror_policy_rules(self, current=None, uuid=None):
        """
        Modify existing rules in snapmirror policy
        :return: None
        """
        self.validate_parameters()

        # Need 'snapmirror_label' to add/modify/delete rules
        if 'snapmirror_label' not in self.parameters:
            return

        obsolete_rules = self.identify_obsolete_snapmirror_policy_rules(
            current)
        new_rules = self.identify_new_snapmirror_policy_rules(current)
        modified_rules, unmodified_rules = self.identify_modified_snapmirror_policy_rules(
            current)

        if self.use_rest:
            api = "snapmirror/policies/" + uuid
            data = {'retention': list()}

            # As rule 'prefix' can't be unset, have to delete existing rules first.
            # Builtin rules remain.
            dummy, error = self.restApi.patch(api, data)
            if error:
                self.module.fail_json(msg=error)

            # Re-add desired rules.
            rules = unmodified_rules + modified_rules + new_rules
            data[
                'retention'] = self.create_snapmirror_policy_retention_obj_for_rest(
                    rules)

            if len(data['retention']) > 0:
                dummy, error = self.restApi.patch(api, data)
                if error:
                    self.module.fail_json(msg=error)
        else:
            delete_rules = obsolete_rules + modified_rules
            add_schedule_rules, add_non_schedule_rules = self.identify_snapmirror_policy_rules_with_schedule(
                new_rules + modified_rules)
            # Delete rules no longer required or modified rules that will be re-added.
            for rule in delete_rules:
                options = {
                    'policy-name': self.parameters['policy_name'],
                    'snapmirror-label': rule['snapmirror_label']
                }
                self.modify_snapmirror_policy_rule(
                    options, 'snapmirror-policy-remove-rule')

            # Add rules. At least one non-schedule rule must exist before
            # a rule with a schedule can be added, otherwise zapi will complain.
            for rule in add_non_schedule_rules + add_schedule_rules:
                options = {
                    'policy-name': self.parameters['policy_name'],
                    'snapmirror-label': rule['snapmirror_label'],
                    'keep': str(rule['keep'])
                }
                if 'prefix' in rule and rule['prefix'] != '':
                    options['prefix'] = rule['prefix']
                if 'schedule' in rule and rule['schedule'] != '':
                    options['schedule'] = rule['schedule']
                self.modify_snapmirror_policy_rule(
                    options, 'snapmirror-policy-add-rule')

    def modify_snapmirror_policy_rule(self, options, zapi):
        """
        Add, modify or remove a rule to/from a snapmirror policy
        """
        snapmirror_obj = netapp_utils.zapi.NaElement.create_node_with_children(
            zapi, **options)
        try:
            self.server.invoke_successfully(snapmirror_obj,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error modifying snapmirror policy rule %s: %s' %
                (self.parameters['policy_name'], to_native(error)),
                exception=traceback.format_exc())

    def asup_log_for_cserver(self):
        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_policy", cserver)

    def apply(self):
        uuid = None
        if not self.use_rest:
            self.asup_log_for_cserver()
        current, modify = self.get_snapmirror_policy(), None
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if current and cd_action is None and self.parameters[
                'state'] == 'present':
            modify = self.na_helper.get_modified_attributes(
                current, self.parameters)

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_snapmirror_policy()
                    if self.use_rest:
                        current = self.get_snapmirror_policy()
                        uuid = current['uuid']
                        self.modify_snapmirror_policy_rules(current, uuid)
                    else:
                        self.modify_snapmirror_policy_rules(current)
                elif cd_action == 'delete':
                    if self.use_rest:
                        uuid = current['uuid']
                    self.delete_snapmirror_policy(uuid)
                elif modify:
                    if self.use_rest:
                        uuid = current['uuid']
                        self.modify_snapmirror_policy(uuid,
                                                      current['policy_type'])
                        self.modify_snapmirror_policy_rules(current, uuid)
                    else:
                        self.modify_snapmirror_policy()
                        self.modify_snapmirror_policy_rules(current)
        self.module.exit_json(changed=self.na_helper.changed)