#!/usr/bin/python

# (c) 2018-2023, NetApp, Inc
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

'''
na_ontap_svm
'''

from __future__ import absolute_import, division, print_function
__metaclass__ = type


DOCUMENTATION = '''

module: na_ontap_svm

short_description: NetApp ONTAP SVM
extends_documentation_fragment:
    - netapp.ontap.netapp.na_ontap
version_added: 2.6.0
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>

description:
- Create, modify or delete SVM on NetApp ONTAP

options:

  state:
    description:
      - Whether the specified SVM should exist or not.
    choices: ['present', 'absent']
    default: 'present'
    type: str

  name:
    description:
      - The name of the SVM to manage.
      - vserver is a convenient alias when using module_defaults.
    type: str
    required: true
    aliases:
      - vserver

  from_name:
    description:
      - Name of the SVM to be renamed
    type: str
    version_added: 2.7.0

  admin_state:
    description:
      - when the SVM is created, it will be in the running state, unless specified otherwise.
      - This is ignored with ZAPI.
    choices: ['running', 'stopped']
    type: str
    version_added: 21.15.0

  root_volume:
    description:
      - Root volume of the SVM.
      - Cannot be modified after creation.
    type: str

  root_volume_aggregate:
    description:
      - The aggregate on which the root volume will be created.
      - Cannot be modified after creation.
    type: str

  root_volume_security_style:
    description:
      -   Security Style of the root volume.
      -   When specified as part of the vserver-create,
          this field represents the security style for the Vserver root volume.
      -   When specified as part of vserver-get-iter call,
          this will return the list of matching Vservers.
      -   The 'unified' security style, which applies only to Infinite Volumes,
          cannot be applied to a Vserver's root volume.
      -   Cannot be modified after creation.
    choices: ['unix', 'ntfs', 'mixed', 'unified']
    type: str

  allowed_protocols:
    description:
      - Allowed Protocols.
      - This field represent the list of protocols allowed on the Vserver.
      - When part of modify,
        this field should include the existing list
        along with new protocol list to be added to prevent data disruptions.
      - Possible values
      - nfs   NFS protocol,
      - cifs  CIFS protocol,
      - fcp   FCP protocol,
      - iscsi iSCSI protocol,
      - ndmp  NDMP protocol,
      - http  HTTP protocol - ZAPI only,
      - nvme  NVMe protocol
    type: list
    elements: str

  services:
    description:
      - Enabled Protocols, only available with REST.
      - The service will be started if needed.  A valid license may be required.
      - C(enabled) is not supported for CIFS, to enable it use na_ontap_cifs_server.
      - If a service is not present, it is left unchanged.
    type: dict
    version_added: 21.10.0
    suboptions:
      cifs:
        description:
          - CIFS protocol service
        type: dict
        suboptions:
          allowed:
            description: If true, an SVM administrator can manage the CIFS service. If false, only the cluster administrator can manage the service.
            type: bool
      iscsi:
        description:
          - iSCSI protocol service
        type: dict
        suboptions:
          allowed:
            description: If true, an SVM administrator can manage the iSCSI service. If false, only the cluster administrator can manage the service.
            type: bool
          enabled:
            description: If allowed, setting to true enables the iSCSI service.
            type: bool
      fcp:
        description:
          - FCP protocol service
        type: dict
        suboptions:
          allowed:
            description: If true, an SVM administrator can manage the FCP service. If false, only the cluster administrator can manage the service.
            type: bool
          enabled:
            description: If allowed, setting to true enables the FCP service.
            type: bool
      nfs:
        description:
          - NFS protocol service
        type: dict
        suboptions:
          allowed:
            description: If true, an SVM administrator can manage the NFS service. If false, only the cluster administrator can manage the service.
            type: bool
          enabled:
            description: If allowed, setting to true enables the NFS service.
            type: bool
      nvme:
        description:
          - nvme protocol service
        type: dict
        suboptions:
          allowed:
            description: If true, an SVM administrator can manage the NVMe service. If false, only the cluster administrator can manage the service.
            type: bool
          enabled:
            description: If allowed, setting to true enables the NVMe service.
            type: bool
      ndmp:
        description:
          - Network Data Management Protocol service
        type: dict
        suboptions:
          allowed:
            description:
              - If this is set to true, an SVM administrator can manage the NDMP service
              - If it is false, only the cluster administrator can manage the service.
              - Requires ONTAP 9.10.1 or later.
            type: bool
        version_added: 21.24.0
  aggr_list:
    description:
      - List of aggregates assigned for volume operations.
      - These aggregates could be shared for use with other Vservers.
      - When specified as part of a vserver-create,
        this field represents the list of aggregates
        that are assigned to the Vserver for volume operations.
      - When part of vserver-get-iter call,
        this will return the list of Vservers
        which have any of the aggregates specified as part of the aggr list.
    type: list
    elements: str

  ipspace:
    description:
    - IPSpace name
    - Cannot be modified after creation.
    type: str
    version_added: 2.7.0

  snapshot_policy:
    description:
      - Default snapshot policy setting for all volumes of the Vserver.
        This policy will be assigned to all volumes created in this
        Vserver unless the volume create request explicitly provides a
        snapshot policy or volume is modified later with a specific
        snapshot policy. A volume-level snapshot policy always overrides
        the default Vserver-wide snapshot policy.
    version_added: 2.7.0
    type: str

  language:
    description:
      - Language to use for the SVM
      - Default to C.UTF-8
      - Possible values   Language
      - c                 POSIX
      - ar                Arabic
      - cs                Czech
      - da                Danish
      - de                German
      - en                English
      - en_us             English (US)
      - es                Spanish
      - fi                Finnish
      - fr                French
      - he                Hebrew
      - hr                Croatian
      - hu                Hungarian
      - it                Italian
      - ja                Japanese euc-j
      - ja_v1             Japanese euc-j
      - ja_jp.pck         Japanese PCK (sjis)
      - ja_jp.932         Japanese cp932
      - ja_jp.pck_v2      Japanese PCK (sjis)
      - ko                Korean
      - no                Norwegian
      - nl                Dutch
      - pl                Polish
      - pt                Portuguese
      - ro                Romanian
      - ru                Russian
      - sk                Slovak
      - sl                Slovenian
      - sv                Swedish
      - tr                Turkish
      - zh                Simplified Chinese
      - zh.gbk            Simplified Chinese (GBK)
      - zh_tw             Traditional Chinese euc-tw
      - zh_tw.big5        Traditional Chinese Big 5
      - utf8mb4
      - Most of the values accept a .utf_8 suffix, e.g. fr.utf_8
    type: str
    version_added: 2.7.0

  subtype:
    description:
      - The subtype for vserver to be created.
      - Cannot be modified after creation.
    choices: ['default', 'dp_destination', 'sync_source', 'sync_destination']
    type: str
    version_added: 2.7.0

  comment:
    description:
      - When specified as part of a vserver-create, this field represents the comment associated with the Vserver.
      - When part of vserver-get-iter call, this will return the list of matching Vservers.
    type: str
    version_added: 2.8.0

  ignore_rest_unsupported_options:
    description:
      - When true, ignore C(root_volume), C(root_volume_aggregate), C(root_volume_security_style) options if target supports REST.
      - Ignored when C(use_rest) is set to never.
    type: bool
    default: false
    version_added: 21.10.0

  max_volumes:
    description:
      - Maximum number of volumes that can be created on the vserver.
      - Expects an integer or C(unlimited).
    type: str
    version_added: 21.12.0

  web:
    description:
      - web services security configuration.
      - requires ONTAP 9.8 or later for certificate name.
      - requires ONTAP 9.10.1 or later for the other options.
    type: dict
    suboptions:
      certificate:
        description:
          - name of certificate used by cluster and node management interfaces for TLS connection requests.
          - The certificate must be of type "server".
        type: str
      client_enabled:
        description: whether client authentication is enabled.
        type: bool
      ocsp_enabled:
        description: whether online certificate status protocol verification is enabled.
        type: bool
'''

EXAMPLES = """

    - name: Create SVM
      netapp.ontap.na_ontap_svm:
        state: present
        name: ansibleVServer
        root_volume: vol1
        root_volume_aggregate: aggr1
        root_volume_security_style: mixed
        hostname: "{{ netapp_hostname }}"
        username: "{{ netapp_username }}"
        password: "{{ netapp_password }}"

    - name: Create SVM
      netapp.ontap.na_ontap_svm:
        state: present
        services:
          cifs:
            allowed: true
          fcp:
            allowed: true
          nfs:
            allowed: true
            enabled: true
        hostname: "{{ netapp_hostname }}"
        username: "{{ netapp_username }}"
        password: "{{ netapp_password }}"
        https: true
        validate_certs: false

    - name: Stop SVM REST
      netapp.ontap.na_ontap_svm:
        state: present
        name: ansibleVServer
        admin_state: stopped
        use_rest: always
        hostname: "{{ netapp_hostname }}"
        username: "{{ netapp_username }}"
        password: "{{ netapp_password }}"
"""

RETURN = """
"""
import copy
import traceback

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
from ansible_collections.netapp.ontap.plugins.module_utils.netapp import OntapRestAPI
from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.ontap.plugins.module_utils import rest_generic, rest_vserver, zapis_svm


class NetAppOntapSVM():
    ''' create, delete, modify, rename SVM (aka vserver) '''

    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', aliases=['vserver']),
            from_name=dict(required=False, type='str'),
            admin_state=dict(required=False, type='str', choices=['running', 'stopped']),
            root_volume=dict(type='str'),
            root_volume_aggregate=dict(type='str'),
            root_volume_security_style=dict(type='str', choices=['unix',
                                                                 'ntfs',
                                                                 'mixed',
                                                                 'unified'
                                                                 ]),
            allowed_protocols=dict(type='list', elements='str'),
            aggr_list=dict(type='list', elements='str'),
            ipspace=dict(type='str', required=False),
            snapshot_policy=dict(type='str', required=False),
            language=dict(type='str', required=False),
            subtype=dict(type='str', choices=['default', 'dp_destination', 'sync_source', 'sync_destination']),
            comment=dict(type='str', required=False),
            ignore_rest_unsupported_options=dict(type='bool', default=False),
            max_volumes=dict(type='str'),
            # TODO: add CIFS options, and S3
            services=dict(type='dict', options=dict(
                cifs=dict(type='dict', options=dict(allowed=dict(type='bool'))),
                iscsi=dict(type='dict', options=dict(allowed=dict(type='bool'), enabled=dict(type='bool'))),
                fcp=dict(type='dict', options=dict(allowed=dict(type='bool'), enabled=dict(type='bool'))),
                nfs=dict(type='dict', options=dict(allowed=dict(type='bool'), enabled=dict(type='bool'))),
                nvme=dict(type='dict', options=dict(allowed=dict(type='bool'), enabled=dict(type='bool'))),
                ndmp=dict(type='dict', options=dict(allowed=dict(type='bool'))),
            )),
            web=dict(type='dict', options=dict(
                certificate=dict(type='str'),
                client_enabled=dict(type='bool'),
                ocsp_enabled=dict(type='bool'),
            ))
        ))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True,
            mutually_exclusive=[('allowed_protocols', 'services'),
                                ('services', 'root_volume'),
                                ('services', 'root_volume_aggregate'),
                                ('services', 'root_volume_security_style')]
        )
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        # Ontap documentation uses C.UTF-8, but actually stores as c.utf_8.
        if 'language' in self.parameters and self.parameters['language'].lower() == 'c.utf-8':
            self.parameters['language'] = 'c.utf_8'

        self.rest_api = OntapRestAPI(self.module)
        # with REST, to force synchronous operations
        self.timeout = self.rest_api.timeout
        # with REST, to know which protocols to look for
        self.allowable_protocols_rest = netapp_utils.get_feature(self.module, 'svm_allowable_protocols_rest')
        self.allowable_protocols_zapi = netapp_utils.get_feature(self.module, 'svm_allowable_protocols_zapi')
        self.use_rest = self.validate_options()
        if not self.use_rest:
            if not netapp_utils.has_netapp_lib():
                self.module.fail_json(msg=netapp_utils.netapp_lib_is_required())
            self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
            if self.parameters.get('admin_state') is not None:
                self.parameters.pop('admin_state')
                self.module.warn('admin_state is ignored when ZAPI is used.')

    def validate_int_or_string(self, value, astring):
        if value is None or value == astring:
            return
        try:
            int_value = int(value)
        except ValueError:
            int_value = None
        if int_value is None or str(int_value) != value:
            self.module.fail_json(msg="Error: expecting int value or '%s', got: %s - %s" % (astring, value, int_value))

    def validate_options(self):

        # root volume not supported with rest api
        unsupported_rest_properties = ['root_volume', 'root_volume_aggregate', 'root_volume_security_style']
        required_unsupported_rest_properties = [] if self.parameters['ignore_rest_unsupported_options'] else unsupported_rest_properties
        ignored_unsupported_rest_properties = unsupported_rest_properties if self.parameters['ignore_rest_unsupported_options'] else []
        used_required_unsupported_rest_properties = [x for x in required_unsupported_rest_properties if x in self.parameters]
        used_ignored_unsupported_rest_properties = [x for x in ignored_unsupported_rest_properties if x in self.parameters]
        use_rest, error = self.rest_api.is_rest(used_required_unsupported_rest_properties)
        if error is not None:
            self.module.fail_json(msg=error)
        if use_rest and used_ignored_unsupported_rest_properties:
            self.module.warn('Using REST and ignoring: %s' % used_ignored_unsupported_rest_properties)
            for attr in used_ignored_unsupported_rest_properties:
                del self.parameters[attr]
        if use_rest and 'aggr_list' in self.parameters and self.parameters['aggr_list'] == ['*']:
            self.module.warn("Using REST and ignoring aggr_list: '*'")
            del self.parameters['aggr_list']
        if use_rest and self.parameters.get('allowed_protocols') is not None:
            # python 2.6 does not support dict comprehension with k: v
            self.parameters['services'] = dict(
                # using old semantics, anything not present is disallowed
                (protocol, {'allowed': protocol in self.parameters['allowed_protocols']})
                for protocol in self.allowable_protocols_rest
            )

        if self.parameters.get('allowed_protocols'):
            allowable = self.allowable_protocols_rest if use_rest else self.allowable_protocols_zapi
            errors = [
                'Unexpected value %s in allowed_protocols.' % protocol
                for protocol in self.parameters['allowed_protocols']
                if protocol not in allowable
            ]
            if errors:
                self.module.fail_json(msg='Error - %s' % '  '.join(errors))
        if use_rest and self.parameters.get('services') and not self.parameters.get('allowed_protocols'):
            if self.parameters['services'].get('ndmp') and not self.rest_api.meets_rest_minimum_version(use_rest, 9, 10, 1):
                self.module.fail_json(msg=self.rest_api.options_require_ontap_version('ndmp', '9.10.1', use_rest=use_rest))
        if self.parameters.get('services') and not use_rest:
            self.module.fail_json(msg=self.rest_api.options_require_ontap_version('services', use_rest=use_rest))
        if self.parameters.get('web'):
            if not use_rest or not self.rest_api.meets_rest_minimum_version(use_rest, 9, 8, 0):
                self.module.fail_json(msg=self.rest_api.options_require_ontap_version('web', '9.8', use_rest=use_rest))
            if not self.rest_api.meets_rest_minimum_version(use_rest, 9, 10, 1):
                suboptions = ('client_enabled', 'ocsp_enabled')
                for suboption in suboptions:
                    if self.parameters['web'].get(suboption) is not None:
                        self.module.fail_json(msg=self.rest_api.options_require_ontap_version(suboptions, '9.10.1', use_rest=use_rest))
            if self.parameters['web'].get('certificate'):
                # so that we can compare UUIDs while using a more friendly name in the user interface
                self.parameters['web']['certificate'] = {'name': self.parameters['web']['certificate']}
                self.set_certificate_uuid()

        self.validate_int_or_string(self.parameters.get('max_volumes'), 'unlimited')
        return use_rest

    def clean_up_output(self, vserver_details):
        vserver_details['root_volume'] = None
        vserver_details['root_volume_aggregate'] = None
        vserver_details['root_volume_security_style'] = None
        vserver_details['aggr_list'] = [aggr['name'] for aggr in vserver_details['aggregates']]
        vserver_details.pop('aggregates')
        vserver_details['ipspace'] = vserver_details['ipspace']['name']
        vserver_details['snapshot_policy'] = vserver_details['snapshot_policy']['name']
        vserver_details['admin_state'] = vserver_details.pop('state')
        if 'max_volumes' in vserver_details:
            vserver_details['max_volumes'] = str(vserver_details['max_volumes'])
        if vserver_details.get('web') is None and self.parameters.get('web'):
            # force an entry to enable modify
            vserver_details['web'] = {
                'certificate': {
                    # ignore name, as only certificate UUID is supported in svm/svms/uuid/web
                    'uuid': vserver_details['certificate']['uuid'] if 'certificate' in vserver_details else None,
                },
                'client_enabled': None,
                'ocsp_enabled': None
            }

        services = {}
        # REST returns allowed: True/False with recent versions, and a list of protocols in allowed_protocols for older versions
        allowed_protocols = (None if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9, 1)
                             else vserver_details.get('allowed_protocols'))

        for protocol in self.allowable_protocols_rest:
            # protocols are not present when the vserver is stopped
            allowed = self.na_helper.safe_get(vserver_details, [protocol, 'allowed'])
            if allowed is None and allowed_protocols is not None:
                # earlier ONTAP versions
                allowed = protocol in allowed_protocols
            enabled = self.na_helper.safe_get(vserver_details, [protocol, 'enabled'])
            if allowed is not None or enabled is not None:
                services[protocol] = {}
            if allowed is not None:
                services[protocol]['allowed'] = allowed
            if enabled is not None:
                services[protocol]['enabled'] = enabled

        if services:
            vserver_details['services'] = services

        return vserver_details

    def get_certificates(self, cert_type):
        """Retrieve list of certificates"""
        api = 'security/certificates'
        query = {
            'svm.name': self.parameters['name'],
            'type': cert_type
        }
        records, error = rest_generic.get_0_or_more_records(self.rest_api, api, query)
        if error:
            self.module.fail_json(msg='Error retrieving certificates: %s' % error)
        return [record['name'] for record in records] if records else []

    def set_certificate_uuid(self):
        """Retrieve certicate uuid for 9.8 or later"""
        api = 'security/certificates'
        query = {
            'name': self.parameters['web']['certificate']['name'],
            'svm.name': self.parameters['name'],
            'type': 'server'
        }
        record, error = rest_generic.get_one_record(self.rest_api, api, query)
        if error:
            self.module.fail_json(msg='Error retrieving certificate %s: %s' % (self.parameters['web']['certificate'], error))
        if not record:
            self.module.fail_json(msg='Error certificate not found: %s.  Current certificates with type=server: %s'
                                  % (self.parameters['web']['certificate'], self.get_certificates('server')))
        self.parameters['web']['certificate']['uuid'] = record['uuid']

    def get_web_service(self, uuid):
        """Retrieve web service info for 9.10.1 or later"""
        api = 'svm/svms/%s/web' % uuid
        record, error = rest_generic.get_one_record(self.rest_api, api)
        if error:
            self.module.fail_json(msg='Error retrieving web info: %s' % error)
        return record

    def get_vserver(self, vserver_name=None):
        """
        Checks if vserver exists.

        :return:
            vserver object if vserver found
            None if vserver is not found
        :rtype: object/None
        """
        if vserver_name is None:
            vserver_name = self.parameters['name']

        if self.use_rest:
            fields = 'subtype,aggregates,language,snapshot_policy,ipspace,comment,nfs,cifs,fcp,iscsi,nvme,state'
            if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9, 1):
                fields += ',max_volumes'
            if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 8, 0) and not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 10, 1):
                # certificate is available starting with 9.7 and is deprecated with 9.10.1.
                # we don't use certificate with 9.7 as name is only supported with 9.8 in /security/certificates
                fields += ',certificate'
            if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 10, 1):
                fields += ',ndmp'

            record, error = rest_vserver.get_vserver(self.rest_api, vserver_name, fields)
            if error:
                self.module.fail_json(msg=error)
            if record:
                if self.parameters.get('web') and self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9, 1):
                    # only collect the info if the user wants to configure the web service, and ONTAP supports it
                    record['web'] = self.get_web_service(record['uuid'])
                if not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9, 1):
                    # 9.6 to 9.8 do not support max_volumes for svm/svms, using private/cli
                    record['allowed_protocols'], max_volumes = self.get_allowed_protocols_and_max_volumes()
                    if self.parameters.get('max_volumes') is not None:
                        record['max_volumes'] = max_volumes
                return self.clean_up_output(copy.deepcopy(record))
            return None

        return zapis_svm.get_vserver(self.server, vserver_name)

    def create_vserver(self):
        if self.use_rest:
            self.create_vserver_rest()
        else:
            options = {'vserver-name': self.parameters['name']}
            self.add_parameter_to_dict(options, 'root_volume', 'root-volume')
            self.add_parameter_to_dict(options, 'root_volume_aggregate', 'root-volume-aggregate')
            self.add_parameter_to_dict(options, 'root_volume_security_style', 'root-volume-security-style')
            self.add_parameter_to_dict(options, 'language', 'language')
            self.add_parameter_to_dict(options, 'ipspace', 'ipspace')
            self.add_parameter_to_dict(options, 'snapshot_policy', 'snapshot-policy')
            self.add_parameter_to_dict(options, 'subtype', 'vserver-subtype')
            self.add_parameter_to_dict(options, 'comment', 'comment')
            vserver_create = netapp_utils.zapi.NaElement.create_node_with_children('vserver-create', **options)
            try:
                self.server.invoke_successfully(vserver_create,
                                                enable_tunneling=False)
            except netapp_utils.zapi.NaApiError as exc:
                self.module.fail_json(msg='Error provisioning SVM %s: %s'
                                      % (self.parameters['name'], to_native(exc)),
                                      exception=traceback.format_exc())
            # add allowed-protocols, aggr-list, max_volume after creation
            # since vserver-create doesn't allow these attributes during creation
            # python 2.6 does not support dict comprehension {k: v for ...}
            options = dict(
                (key, self.parameters[key])
                for key in ('allowed_protocols', 'aggr_list', 'max_volumes')
                if self.parameters.get(key)
            )
            if options:
                self.modify_vserver(options)

    def create_body_contents(self, modify=None):
        keys_to_modify = self.parameters.keys() if modify is None else modify.keys()
        protocols_to_modify = self.parameters.get('services', {}) if modify is None else modify.get('services', {})
        simple_keys = ['name', 'language', 'ipspace', 'snapshot_policy', 'subtype', 'comment']
        if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9, 1):
            simple_keys.append('max_volumes')
        body = dict(
            (key, self.parameters[key])
            for key in simple_keys
            if self.parameters.get(key) and key in keys_to_modify
        )
        # admin_state is only supported in modify
        if modify and 'admin_state' in keys_to_modify:
            body['state'] = self.parameters['admin_state']
        if 'aggr_list' in keys_to_modify:
            body['aggregates'] = [{'name': aggr} for aggr in self.parameters['aggr_list']]
        if 'certificate' in keys_to_modify:
            body['certificate'] = modify['certificate']
        allowed_protocols = {}
        for protocol, config in protocols_to_modify.items():
            # Ansible sets unset suboptions to None
            if not config:
                continue
            # Ansible sets unset suboptions to None
            acopy = self.na_helper.filter_out_none_entries(config)
            if modify is not None:
                # REST does not allow to modify this directly
                acopy.pop('enabled', None)
            if not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9, 1):
                # allowed is not supported in earlier REST versions
                allowed = acopy.pop('allowed', None)
                # if allowed is not set, retrieve current value
                if allowed is not None:
                    allowed_protocols[protocol] = allowed
            if acopy:
                body[protocol] = acopy
        return body, allowed_protocols

    def get_allowed_protocols_and_max_volumes(self):
        # use REST CLI for older versions of ONTAP
        query = {'vserver': self.parameters['name']}
        fields = 'allowed_protocols'
        if self.parameters.get('max_volumes') is not None:
            fields += ',max_volumes'
        response, error = rest_generic.get_one_record(self.rest_api, 'private/cli/vserver', query, fields)
        if error:
            self.module.fail_json(msg='Error getting vserver info: %s - %s' % (error, response))
        if response and 'max_volumes' in response:
            max_volumes = str(response['max_volumes'])
        allowed_protocols, max_volumes = [], None
        if response and 'allowed_protocols' in response:
            allowed_protocols = response['allowed_protocols']
        if response and 'max_volumes' in response:
            max_volumes = str(response['max_volumes'])
        return allowed_protocols, max_volumes

    def rest_cli_set_max_volumes(self):
        # use REST CLI for older versions of ONTAP
        query = {'vserver': self.parameters['name']}
        body = {'max_volumes': self.parameters['max_volumes']}
        response, error = rest_generic.patch_async(self.rest_api, 'private/cli/vserver', None, body, query)
        if error:
            self.module.fail_json(msg='Error updating max_volumes: %s - %s' % (error, response))

    def rest_cli_add_remove_protocols(self, protocols):
        protocols_to_add = [protocol for protocol, value in protocols.items() if value]
        if protocols_to_add:
            self.rest_cli_add_protocols(protocols_to_add)
        protocols_to_delete = [protocol for protocol, value in protocols.items() if not value]
        if protocols_to_delete:
            self.rest_cli_remove_protocols(protocols_to_delete)

    def rest_cli_add_protocols(self, protocols):
        # use REST CLI for older versions of ONTAP
        query = {'vserver': self.parameters['name']}
        body = {'protocols': protocols}
        response, error = rest_generic.patch_async(self.rest_api, 'private/cli/vserver/add-protocols', None, body, query)
        if error:
            self.module.fail_json(msg='Error adding protocols: %s - %s' % (error, response))

    def rest_cli_remove_protocols(self, protocols):
        # use REST CLI for older versions of ONTAP
        query = {'vserver': self.parameters['name']}
        body = {'protocols': protocols}
        response, error = rest_generic.patch_async(self.rest_api, 'private/cli/vserver/remove-protocols', None, body, query)
        if error:
            self.module.fail_json(msg='Error removing protocols: %s - %s' % (error, response))

    def create_vserver_rest(self):
        # python 2.6 does not support dict comprehension {k: v for ...}
        body, allowed_protocols = self.create_body_contents()
        dummy, error = rest_generic.post_async(self.rest_api, 'svm/svms', body, timeout=self.timeout)
        if error:
            self.module.fail_json(msg='Error in create: %s' % error)
        # add max_volumes and update allowed protocols after creation for older ONTAP versions
        if self.parameters.get('max_volumes') is not None and not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9, 1):
            self.rest_cli_set_max_volumes()
        if allowed_protocols:
            self.rest_cli_add_remove_protocols(allowed_protocols)

    def delete_vserver(self, current=None):
        if self.use_rest:
            if current is None:
                self.module.fail_json(msg='Internal error, expecting SVM object in delete')
            dummy, error = rest_generic.delete_async(self.rest_api, 'svm/svms', current['uuid'], timeout=self.timeout)
            if error:
                self.module.fail_json(msg='Error in delete: %s' % error)
        else:
            vserver_delete = netapp_utils.zapi.NaElement.create_node_with_children(
                'vserver-destroy', **{'vserver-name': self.parameters['name']})

            try:
                self.server.invoke_successfully(vserver_delete,
                                                enable_tunneling=False)
            except netapp_utils.zapi.NaApiError as exc:
                self.module.fail_json(msg='Error deleting SVM %s: %s'
                                      % (self.parameters['name'], to_native(exc)),
                                      exception=traceback.format_exc())

    def rename_vserver(self):
        ''' ZAPI only, for REST it is handled as a modify'''
        vserver_rename = netapp_utils.zapi.NaElement.create_node_with_children(
            'vserver-rename', **{'vserver-name': self.parameters['from_name'],
                                 'new-name': self.parameters['name']})

        try:
            self.server.invoke_successfully(vserver_rename,
                                            enable_tunneling=False)
        except netapp_utils.zapi.NaApiError as exc:
            self.module.fail_json(msg='Error renaming SVM %s: %s'
                                  % (self.parameters['from_name'], to_native(exc)),
                                  exception=traceback.format_exc())

    def modify_vserver(self, modify, current=None):
        '''
        Modify vserver.
        :param modify: list of modify attributes
        :param current: with rest, SVM object to modify
        '''
        if self.use_rest:
            if current is None:
                self.module.fail_json(msg='Internal error, expecting SVM object in modify.')
            if not modify:
                self.module.fail_json(msg='Internal error, expecting something to modify in modify.')
            # REST reports an error if we modify the name and something else at the same time
            if 'name' in modify:
                body = {'name': modify['name']}
                dummy, error = rest_generic.patch_async(self.rest_api, 'svm/svms', current['uuid'], body, timeout=self.timeout)
                if error:
                    self.module.fail_json(msg='Error in rename: %s' % error, modify=modify)
                del modify['name']
            if 'web' in modify and not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 10, 1):
                # certificate is a deprecated field for 9.10.1, only use it for 9.8 and 9.9
                uuid = self.na_helper.safe_get(modify, ['web', 'certificate', 'uuid'])
                if uuid:
                    modify['certificate'] = {'uuid': uuid}
                modify.pop('web')
            body, allowed_protocols = self.create_body_contents(modify)
            if body:
                dummy, error = rest_generic.patch_async(self.rest_api, 'svm/svms', current['uuid'], body, timeout=self.timeout)
                if error:
                    self.module.fail_json(msg='Error in modify: %s' % error, modify=modify)
            # use REST CLI for max_volumes and allowed protocols with older ONTAP versions
            if 'max_volumes' in modify and not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9, 1):
                self.rest_cli_set_max_volumes()
            if allowed_protocols:
                self.rest_cli_add_remove_protocols(allowed_protocols)
            if 'services' in modify:
                self.modify_services(modify, current)
            if 'web' in modify and self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 10, 1):
                self.modify_web_services(modify['web'], current)
        else:
            zapis_svm.modify_vserver(self.server, self.module, self.parameters['name'], modify, self.parameters)

    def modify_services(self, modify, current):
        apis = {
            'fcp': 'protocols/san/fcp/services',
            'iscsi': 'protocols/san/iscsi/services',
            'nfs': 'protocols/nfs/services',
            'nvme': 'protocols/nvme/services',
            'ndmp': 'protocols/ndmp/svms'
        }
        for protocol, config in modify['services'].items():
            enabled = config.get('enabled')
            if enabled is None:
                # nothing to do
                continue
            api = apis.get(protocol)
            if not api:
                self.module.fail_json(msg='Internal error, unexpecting service: %s.' % protocol)
            if enabled:
                # we don't know if the service is already started or not, link will tell us
                link = self.na_helper.safe_get(current, [protocol, '_links', 'self', 'href'])
            body = {'enabled': enabled}
            if enabled and not link:
                body['svm.name'] = self.parameters['name']
                dummy, error = rest_generic.post_async(self.rest_api, api, body)
            else:
                dummy, error = rest_generic.patch_async(self.rest_api, api, current['uuid'], body)
            if error:
                self.module.fail_json(msg='Error in modify service for %s: %s' % (protocol, error))

    def modify_web_services(self, record, current):
        """Patch web service for 9.10.1 or later"""
        api = 'svm/svms/%s/web' % current['uuid']
        if 'certificate' in record:
            # API only accepts a UUID
            record['certificate'].pop('name', None)
        body = self.na_helper.filter_out_none_entries(copy.deepcopy(record))
        if not body:
            self.module.warn('Nothing to change: %s' % record)
            return
        dummy, error = rest_generic.patch_async(self.rest_api, api, None, body)
        if error:
            self.module.fail_json(msg='Error in modify web service for %s: %s' % (body, error))

    def add_parameter_to_dict(self, adict, name, key=None, tostr=False):
        '''
        add defined parameter (not None) to adict using key.
        :param adict: a dictionary.
        :param name: name in self.parameters.
        :param key:  key in adict.
        :param tostr: boolean.
        '''
        if key is None:
            key = name
        if self.parameters.get(name) is not None:
            if tostr:
                adict[key] = str(self.parameters.get(name))
            else:
                adict[key] = self.parameters.get(name)

    def warn_when_possible_language_match(self, desired, current):
        transformed = desired.lower().replace('-', '_')
        if transformed == current:
            self.module.warn("Attempting to change language from ONTAP value %s to %s.  Use %s to suppress this warning and maintain idempotency."
                             % (current, desired, current))

    def apply(self):
        '''Call create/modify/delete operations.'''
        current = self.get_vserver()
        cd_action, rename = None, None
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if cd_action == 'create' and self.parameters.get('from_name'):
            # create by renaming existing SVM
            old_svm = self.get_vserver(self.parameters['from_name'])
            rename = self.na_helper.is_rename_action(old_svm, current)
            if rename is None:
                self.module.fail_json(msg='Error renaming SVM %s: no SVM with from_name %s.' % (self.parameters['name'], self.parameters['from_name']))
            if rename:
                current = old_svm
                cd_action = None
        modify = self.na_helper.get_modified_attributes(current, self.parameters) if cd_action is None else {}
        if 'language' in modify:
            self.warn_when_possible_language_match(modify['language'], current['language'])
        fixed_attributes = ['root_volume', 'root_volume_aggregate', 'root_volume_security_style', 'subtype', 'ipspace']
        msgs = ['%s - current: %s - desired: %s' % (attribute, current[attribute], self.parameters[attribute])
                for attribute in fixed_attributes
                if attribute in modify]
        if msgs:
            self.module.fail_json(msg='Error modifying SVM %s: cannot modify %s.' % (self.parameters['name'], ', '.join(msgs)))

        if self.na_helper.changed and not self.module.check_mode:
            if rename:
                if self.use_rest:
                    modify['name'] = self.parameters['name']
                else:
                    self.rename_vserver()
                    modify.pop('name', None)
            # If rename is True, cd_action is None, but modify could be true or false.
            if cd_action == 'create':
                self.create_vserver()
                if self.parameters.get('admin_state') == 'stopped':
                    current = self.get_vserver()
                    modify = {'admin_state': 'stopped'}
            elif cd_action == 'delete':
                self.delete_vserver(current)
            if modify:
                self.modify_vserver(modify, current)
            if modify and 'aggr_list' in modify and '*' in modify['aggr_list']:
                self.module.warn("na_ontap_svm: changed always 'True' when aggr_list is '*'.")
        results = netapp_utils.generate_result(self.na_helper.changed, cd_action, modify)
        self.module.exit_json(**results)


def main():
    '''Apply vserver operations from playbook'''
    svm = NetAppOntapSVM()
    svm.apply()


if __name__ == '__main__':
    main()
