Exemplo n.º 1
0
class NetAppONTAPCluster(object):
    """
    object initialize and class methods
    """
    def __init__(self):
        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'),
                 cluster_name=dict(required=False, type='str'),
                 cluster_ip_address=dict(required=False, type='str'),
                 cluster_location=dict(required=False, type='str'),
                 cluster_contact=dict(required=False, type='str'),
                 single_node_cluster=dict(required=False,
                                          type='bool',
                                          default=False),
                 node_name=dict(required=False, 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.warnings = list()

        if self.parameters['state'] == 'absent' and self.parameters.get(
                'node_name') is not None and self.parameters.get(
                    'cluster_ip_address') is not None:
            msg = 'when state is "absent", parameters are mutually exclusive: cluster_ip_address|node_name'
            self.module.fail_json(msg=msg)

        if self.parameters.get(
                'node_name') is not None and '-' in self.parameters.get(
                    'node_name'):
            self.warnings.append(
                'ONTAP ZAPI converts "-" to "_", node_name: %s may be changed or not matched'
                % self.parameters.get('node_name'))

        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)

    def get_cluster_identity(self, ignore_error=True):
        ''' get cluster information, but the cluster may not exist yet
            return:
                None if the cluster cannot be reached
                a dictionary of attributes
        '''
        zapi = netapp_utils.zapi.NaElement('cluster-identity-get')
        try:
            result = self.server.invoke_successfully(zapi,
                                                     enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            if ignore_error:
                return None
            self.module.fail_json(
                msg='Error fetching cluster identity info: %s' %
                to_native(error),
                exception=traceback.format_exc())
        cluster_identity = dict()
        if result.get_child_by_name('attributes'):
            identity_info = result.get_child_by_name(
                'attributes').get_child_by_name('cluster-identity-info')
            if identity_info:
                cluster_identity[
                    'cluster_contact'] = identity_info.get_child_content(
                        'cluster-contact')
                cluster_identity[
                    'cluster_location'] = identity_info.get_child_content(
                        'cluster-location')
                cluster_identity[
                    'cluster_name'] = identity_info.get_child_content(
                        'cluster-name')
            return cluster_identity
        return None

    def get_cluster_nodes(self, ignore_error=True):
        ''' get cluster node names, but the cluster may not exist yet
            return:
                None if the cluster cannot be reached
                a list of nodes
        '''
        zapi = netapp_utils.zapi.NaElement('cluster-node-get-iter')
        try:
            result = self.server.invoke_successfully(zapi,
                                                     enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            if ignore_error:
                return None
            self.module.fail_json(
                msg='Error fetching cluster identity info: %s' %
                to_native(error),
                exception=traceback.format_exc())
        cluster_nodes = list()
        if result.get_child_by_name('attributes-list'):
            for node_info in result.get_child_by_name(
                    'attributes-list').get_children():
                node_name = node_info.get_child_content('node-name')
                if node_name is not None:
                    cluster_nodes.append(node_name)
            return cluster_nodes
        return None

    def get_cluster_ip_addresses(self, cluster_ip_address, ignore_error=True):
        ''' get list of IP addresses for this cluster
            return:
                a list of dictionaries
        '''
        if_infos = list()
        zapi = netapp_utils.zapi.NaElement('net-interface-get-iter')
        if cluster_ip_address is not None:
            query = netapp_utils.zapi.NaElement('query')
            net_info = netapp_utils.zapi.NaElement('net-interface-info')
            net_info.add_new_child('address', cluster_ip_address)
            query.add_child_elem(net_info)
            zapi.add_child_elem(query)

        try:
            result = self.server.invoke_successfully(zapi,
                                                     enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            if ignore_error:
                return if_infos
            self.module.fail_json(msg='Error getting IP addresses: %s' %
                                  to_native(error),
                                  exception=traceback.format_exc())

        if result.get_child_by_name('attributes-list'):
            for net_info in result.get_child_by_name(
                    'attributes-list').get_children():
                if net_info:
                    if_info = dict()
                    if_info['address'] = net_info.get_child_content('address')
                    if_info['home_node'] = net_info.get_child_content(
                        'home-node')
                if_infos.append(if_info)
        return if_infos

    def get_cluster_ip_address(self, cluster_ip_address, ignore_error=True):
        ''' get node information if it is discoverable
            return:
                None if the cluster cannot be reached
                a dictionary of attributes
        '''
        if cluster_ip_address is None:
            return None
        nodes = self.get_cluster_ip_addresses(cluster_ip_address,
                                              ignore_error=ignore_error)
        return nodes if len(nodes) > 0 else None

    def create_cluster(self):
        """
        Create a cluster
        """
        dummy, minor = self.server.get_api_version()
        # Note: cannot use node_name here:
        # 13001:The "-node-names" parameter must be used with either the "-node-uuids" or the "-cluster-ips" parameters.
        options = {'cluster-name': self.parameters['cluster_name']}
        if minor >= 140:
            options['single-node-cluster'] = str(
                self.parameters.get('single_node_cluster'))
        cluster_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'cluster-create', **options)
        try:
            self.server.invoke_successfully(cluster_create,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            # Error 36503 denotes node already being used.
            if to_native(error.code) == "36503":
                return False
            self.module.fail_json(
                msg='Error creating cluster %s: %s' %
                (self.parameters['cluster_name'], to_native(error)),
                exception=traceback.format_exc())
        return True

    def add_node(self, older_api=False):
        """
        Add a node to an existing cluster
        9.2 and 9.3 do not support cluster-ips so fallback to node-ip
        """
        if self.parameters.get('cluster_ip_address') is not None:
            cluster_add_node = netapp_utils.zapi.NaElement('cluster-add-node')
            if older_api:
                cluster_add_node.add_new_child(
                    'node-ip', self.parameters.get('cluster_ip_address'))
            else:
                cluster_ips = netapp_utils.zapi.NaElement('cluster-ips')
                cluster_ips.add_new_child(
                    'ip-address', self.parameters.get('cluster_ip_address'))
                cluster_add_node.add_child_elem(cluster_ips)
                if self.parameters.get('node_name') is not None:
                    node_names = netapp_utils.zapi.NaElement('node-names')
                    node_names.add_new_child('string',
                                             self.parameters.get('node_name'))
                    cluster_add_node.add_child_elem(node_names)

        else:
            return False
        try:
            self.server.invoke_successfully(cluster_add_node,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            if error.message == "Extra input: cluster-ips":
                return self.add_node(older_api=True)
            # skip if error says no failed operations to retry.
            if to_native(
                    error
            ) == "NetApp API failed. Reason - 13001:There are no failed \"cluster create\" or \"cluster add-node\" operations to retry.":
                return False
            self.module.fail_json(
                msg='Error adding node with ip %s: %s' %
                (self.parameters.get('cluster_ip_address'), to_native(error)),
                exception=traceback.format_exc())
        return True

    def remove_node(self):
        """
        Remove a node from an existing cluster
        """
        cluster_remove_node = netapp_utils.zapi.NaElement(
            'cluster-remove-node')
        from_node = ''
        # cluster-ip and node-name are mutually exclusive:
        # 13115:Element "cluster-ip" within "cluster-remove-node" has been excluded by another element.
        if self.parameters.get('cluster_ip_address') is not None:
            cluster_remove_node.add_new_child(
                'cluster-ip', self.parameters.get('cluster_ip_address'))
            from_node = 'IP: %s' % self.parameters.get('cluster_ip_address')
        elif self.parameters.get('node_name') is not None:
            cluster_remove_node.add_new_child('node',
                                              self.parameters.get('node_name'))
            from_node = 'name: %s' % self.parameters.get('node_name')

        try:
            self.server.invoke_successfully(cluster_remove_node,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            if error.message == "Unable to find API: cluster-remove-node":
                msg = 'Error: ZAPI is not available.  Removing a node requires ONTAP 9.4 or newer.'
                self.module.fail_json(msg=msg)
            self.module.fail_json(msg='Error removing node with %s: %s' %
                                  (from_node, to_native(error)),
                                  exception=traceback.format_exc())

    def modify_cluster_identity(self, modify):
        """
        Modifies the cluster identity
        """
        cluster_modify = netapp_utils.zapi.NaElement('cluster-identity-modify')
        if modify.get('cluster_name') is not None:
            cluster_modify.add_new_child("cluster-name",
                                         modify.get('cluster_name'))
        if modify.get('cluster_location') is not None:
            cluster_modify.add_new_child("cluster-location",
                                         modify.get('cluster_location'))
        if modify.get('cluster_contact') is not None:
            cluster_modify.add_new_child("cluster-contact",
                                         modify.get('cluster_contact'))

        try:
            self.server.invoke_successfully(cluster_modify,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error modifying cluster idetity details %s: %s' %
                (self.parameters['cluster_name'], to_native(error)),
                exception=traceback.format_exc())
        return True

    def cluster_create_wait(self):
        """
        Wait whilst cluster creation completes
        """

        cluster_wait = netapp_utils.zapi.NaElement(
            'cluster-create-join-progress-get')
        is_complete = False
        status = ''
        wait = False  # do not wait on the first call

        while not is_complete and status not in ('failed', 'success'):
            if wait:
                time.sleep(10)
            else:
                wait = True
            try:
                result = self.server.invoke_successfully(cluster_wait,
                                                         enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:

                self.module.fail_json(
                    msg='Error creating cluster %s: %s' %
                    (self.parameters.get('cluster_name'), to_native(error)),
                    exception=traceback.format_exc())

            clus_progress = result.get_child_by_name('attributes')
            result = clus_progress.get_child_by_name(
                'cluster-create-join-progress-info')
            is_complete = self.na_helper.get_value_for_bool(
                from_zapi=True, value=result.get_child_content('is-complete'))
            status = result.get_child_content('status')

        if not is_complete and status != 'success':
            current_status_message = result.get_child_content(
                'current-status-message')

            self.module.fail_json(
                msg='Failed to create cluster %s: %s' %
                (self.parameters.get('cluster_name'), current_status_message))

        return is_complete

    def node_add_wait(self):
        """
        Wait whilst node is being added to the existing cluster
        """
        cluster_node_status = netapp_utils.zapi.NaElement(
            'cluster-add-node-status-get-iter')
        node_status_info = netapp_utils.zapi.NaElement(
            'cluster-create-add-node-status-info')
        node_status_info.add_new_child(
            'cluster-ip', self.parameters.get('cluster_ip_address'))
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(node_status_info)
        cluster_node_status.add_child_elem(query)

        is_complete = None
        failure_msg = None
        wait = False  # do not wait on the first call

        while is_complete != 'success' and is_complete != 'failure':
            if wait:
                time.sleep(10)
            else:
                wait = True
            try:
                result = self.server.invoke_successfully(cluster_node_status,
                                                         enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:
                if error.message == "Unable to find API: cluster-add-node-status-get-iter":
                    # This API is not supported for 9.3 or earlier releases, just wait a bit
                    time.sleep(60)
                    return
                self.module.fail_json(
                    msg='Error adding node with ip address %s: %s' %
                    (self.parameters.get('cluster_ip_address'),
                     to_native(error)),
                    exception=traceback.format_exc())

            attributes_list = result.get_child_by_name('attributes-list')
            join_progress = attributes_list.get_child_by_name(
                'cluster-create-add-node-status-info')
            is_complete = join_progress.get_child_content('status')
            failure_msg = join_progress.get_child_content('failure-msg')

        if is_complete != 'success':
            if 'Node is already in a cluster' in failure_msg:
                return
            else:
                self.module.fail_json(
                    msg='Error adding node with ip address %s' %
                    (self.parameters.get('cluster_ip_address')))

    def node_remove_wait(self):
        ''' wait for node name or clister IP address to disappear '''
        node_name = self.parameters.get('node_name')
        node_ip = self.parameters.get('cluster_ip_address')
        timer = 180  # 180 seconds
        while timer > 0:
            if node_name is not None and node_name not in self.get_cluster_nodes(
            ):
                return
            if node_ip is not None and self.get_cluster_ip_address(
                    node_ip) is None:
                return
            time.sleep(30)
            timer -= 30
        self.module.fail_json(
            msg='Timeout waiting for node to be removed from cluster.')

    def autosupport_log(self):
        """
        Autosupport log for cluster
        :return:
        """
        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_cluster", cserver)

    def apply(self):
        """
        Apply action to cluster
        """
        cluster_action = None
        node_action = None

        cluster_identity = self.get_cluster_identity(ignore_error=True)
        if self.parameters.get('cluster_name') is not None:
            cluster_action = self.na_helper.get_cd_action(
                cluster_identity, self.parameters)
        if self.parameters.get('cluster_ip_address') is not None:
            existing_interfaces = self.get_cluster_ip_address(
                self.parameters.get('cluster_ip_address'))
            if self.parameters.get('state') == 'present':
                node_action = 'add_node' if existing_interfaces is None else None
            else:
                node_action = 'remove_node' if existing_interfaces is not None else None
        if self.parameters.get('node_name') is not None and self.parameters[
                'state'] == 'absent':
            nodes = self.get_cluster_nodes()
            if self.parameters.get('node_name') in nodes:
                node_action = 'remove_node'
        modify = self.na_helper.get_modified_attributes(
            cluster_identity, self.parameters)

        if node_action is not None:
            self.na_helper.changed = True

        if not self.module.check_mode:
            if cluster_action == 'create':
                if self.create_cluster():
                    self.cluster_create_wait()
            if node_action == 'add_node':
                if self.add_node():
                    self.node_add_wait()
            elif node_action == 'remove_node':
                self.remove_node()
                self.node_remove_wait()
            if modify:
                self.modify_cluster_identity(modify)
        self.autosupport_log()
        self.module.exit_json(changed=self.na_helper.changed,
                              warnings=self.warnings)
Exemplo n.º 2
0
class NetAppONTAPCluster(object):
    """
    object initialize and class methods
    """
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(required=False,
                            type='str',
                            choices=['present'],
                            default='present'),
                 cluster_name=dict(required=False, type='str'),
                 cluster_ip_address=dict(required=False, type='str'),
                 cluster_location=dict(required=False, type='str'),
                 cluster_contact=dict(required=False, type='str'),
                 single_node_cluster=dict(required=False,
                                          type='bool',
                                          default=False)))

        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)

        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)

    def get_cluster_identity(self, ignore_error=True):
        ''' get cluster information, but the cluster may not exist yet
            return:
                None if the cluster cannot be reached
                a dictionary of attributes
        '''
        zapi = netapp_utils.zapi.NaElement('cluster-identity-get')
        try:
            result = self.server.invoke_successfully(zapi,
                                                     enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            if ignore_error:
                return None
            self.module.fail_json(
                msg='Error fetching cluster identity info: %s' %
                to_native(error),
                exception=traceback.format_exc())
        cluster_identity = dict()
        if result.get_child_by_name('attributes'):
            identity_info = result.get_child_by_name(
                'attributes').get_child_by_name('cluster-identity-info')
            if identity_info:
                cluster_identity[
                    'cluster_contact'] = identity_info.get_child_content(
                        'cluster-contact')
                cluster_identity[
                    'cluster_location'] = identity_info.get_child_content(
                        'cluster-location')
                cluster_identity[
                    'cluster_name'] = identity_info.get_child_content(
                        'cluster-name')
            return cluster_identity
        return None

    def create_cluster(self):
        """
        Create a cluster
        """
        dummy, minor = self.server.get_api_version()
        options = {'cluster-name': self.parameters['cluster_name']}
        if minor >= 140:
            options['single-node-cluster'] = str(
                self.parameters.get('single_node_cluster'))
        cluster_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'cluster-create', **options)
        try:
            self.server.invoke_successfully(cluster_create,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            # Error 36503 denotes node already being used.
            if to_native(error.code) == "36503":
                return False
            else:
                self.module.fail_json(
                    msg='Error creating cluster %s: %s' %
                    (self.parameters['cluster_name'], to_native(error)),
                    exception=traceback.format_exc())
        return True

    def cluster_join(self):
        """
        Add a node to an existing cluster
        """
        if self.parameters.get('cluster_ip_address') is not None:
            cluster_add_node = netapp_utils.zapi.NaElement('cluster-add-node')
            cluster_ips = netapp_utils.zapi.NaElement('cluster-ips')
            cluster_ips.add_new_child(
                'ip-address', self.parameters.get('cluster_ip_address'))
            cluster_add_node.add_child_elem(cluster_ips)
        else:
            return False
        try:
            self.server.invoke_successfully(cluster_add_node,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            # skip if error says no failed operations to retry.
            if to_native(
                    error
            ) == "NetApp API failed. Reason - 13001:There are no failed \"cluster create\" or \"cluster add-node\" operations to retry.":
                return False
            else:
                self.module.fail_json(
                    msg='Error adding node with ip %s: %s' %
                    (self.parameters.get('cluster_ip_address'),
                     to_native(error)),
                    exception=traceback.format_exc())
        return True

    def modify_cluster_identity(self, modify):
        """
        Modifies the cluster identity
        """
        cluster_modify = netapp_utils.zapi.NaElement('cluster-identity-modify')
        if modify.get('cluster_name') is not None:
            cluster_modify.add_new_child("cluster-name",
                                         modify.get('cluster_name'))
        if modify.get('cluster_location') is not None:
            cluster_modify.add_new_child("cluster-location",
                                         modify.get('cluster_location'))
        if modify.get('cluster_contact') is not None:
            cluster_modify.add_new_child("cluster-contact",
                                         modify.get('cluster_contact'))

        try:
            self.server.invoke_successfully(cluster_modify,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error modifying cluster idetity details %s: %s' %
                (self.parameters['cluster_name'], to_native(error)),
                exception=traceback.format_exc())
        return True

    def cluster_create_wait(self):
        """
        Wait whilst cluster creation completes
        """

        cluster_wait = netapp_utils.zapi.NaElement(
            'cluster-create-join-progress-get')
        is_complete = False
        status = ''
        wait = False  # do not wait on the first call

        while not is_complete and status not in ('failed', 'success'):
            if wait:
                time.sleep(10)
            else:
                wait = True
            try:
                result = self.server.invoke_successfully(cluster_wait,
                                                         enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:

                self.module.fail_json(
                    msg='Error creating cluster %s: %s' %
                    (self.parameters.get('cluster_name'), to_native(error)),
                    exception=traceback.format_exc())

            clus_progress = result.get_child_by_name('attributes')
            result = clus_progress.get_child_by_name(
                'cluster-create-join-progress-info')
            is_complete = self.na_helper.get_value_for_bool(
                from_zapi=True, value=result.get_child_content('is-complete'))
            status = result.get_child_content('status')

        if not is_complete and status != 'success':
            current_status_message = result.get_child_content(
                'current-status-message')

            self.module.fail_json(
                msg='Failed to create cluster %s: %s' %
                (self.parameters.get('cluster_name'), current_status_message))

        return is_complete

    def node_add_wait(self):
        """
        Wait whilst node is being added to the existing cluster
        """
        cluster_node_status = netapp_utils.zapi.NaElement(
            'cluster-add-node-status-get-iter')
        node_status_info = netapp_utils.zapi.NaElement(
            'cluster-create-add-node-status-info')
        node_status_info.add_new_child(
            'cluster-ip', self.parameters.get('cluster_ip_address'))
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(node_status_info)
        cluster_node_status.add_child_elem(query)
        result = self.server.invoke_successfully(cluster_node_status, True)

        is_complete = None
        failure_msg = None
        wait = False  # do not wait on the first call

        while is_complete != 'success' and is_complete != 'failure':
            if wait:
                time.sleep(10)
            else:
                wait = True
            try:
                result = self.server.invoke_successfully(cluster_node_status,
                                                         enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:

                self.module.fail_json(
                    msg='Error adding node with ip address %s: %s' %
                    (self.parameters.get('cluster_ip_address'),
                     to_native(error)),
                    exception=traceback.format_exc())

            attributes_list = result.get_child_by_name('attributes-list')
            join_progress = attributes_list.get_child_by_name(
                'cluster-create-add-node-status-info')
            is_complete = join_progress.get_child_content('status')
            failure_msg = join_progress.get_child_content('failure-msg')

        if is_complete != 'success':
            if 'Node is already in a cluster' in failure_msg:
                return is_complete
            else:
                self.module.fail_json(
                    msg='Error adding node with ip address %s' %
                    (self.parameters.get('cluster_ip_address')))
        return is_complete

    def autosupport_log(self):
        """
        Autosupport log for cluster
        :return:
        """
        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_cluster", cserver)

    def apply(self):
        """
        Apply action to cluster
        """
        changed = False
        create_flag = False
        cluster_identity = self.get_cluster_identity(ignore_error=True)
        modify = self.na_helper.get_modified_attributes(
            cluster_identity, self.parameters)
        # temporary hack to fix check_mode for modify
        changed = self.na_helper.changed
        # TODO: JIRA-3048 fix check_mode for create and join

        if not self.module.check_mode:
            if self.parameters.get('state') == 'present':
                if self.parameters.get('cluster_name') is not None:
                    create_flag = self.create_cluster()
                    if create_flag:
                        cluster_wait = self.cluster_create_wait()
                        changed = True if cluster_wait else changed
                if not create_flag:
                    join_flag = self.cluster_join()
                    if join_flag:
                        join_wait_flag = self.node_add_wait()
                        changed = True if join_wait_flag == 'success' else changed
                if modify:
                    self.modify_cluster_identity(modify)
        self.autosupport_log()
        self.module.exit_json(changed=changed)
Exemplo n.º 3
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']),
        ))

        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',
                    '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 message['records'][0]
            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'],
                    }
                    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']

            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 create_snapmirror_policy(self):
        """
        Creates a new storage efficiency policy
        """
        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'])
            api = "snapmirror/policies"
            message, error = self.restApi.post(api, data)
            if error:
                self.module.fail_json(msg=error)
        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, type=None):
        if 'comment' in self.parameters.keys():
            snapmirror_policy_obj["comment"] = self.parameters['comment']
        if 'is_network_compression_enabled' in self.parameters:
            if 'async' in type:
                snapmirror_policy_obj["network_compression_enabled"] = self.parameters['is_network_compression_enabled']
            elif 'sync' in type:
                self.module.fail_json(msg="Input parameter network_compression_enabled is not valid for SnapMirror policy type sync")
        return snapmirror_policy_obj

    def delete_snapmirror_policy(self, uuid=None):
        """
        Deletes a snapmirror policy
        """
        if self.use_rest:
            api = "snapmirror/policies"
            data = {'uuid': uuid}
            message, 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, type=None):
        """
        Modifies a snapmirror policy
        """
        if self.use_rest:
            api = "snapmirror/policies/" + uuid
            data = self.create_snapmirror_policy_obj_for_rest(dict(), type)
            message, 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.add_new_child("policy-name", self.parameters['policy_name'])
            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 modifying snapmirror policy %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("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()
                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['type'])
                    else:
                        self.modify_snapmirror_policy()
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 4
0
class NetAppontapExportRule(object):
    ''' object initialize and class methods '''
    def __init__(self):

        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=['policy_name']),
                protocol=dict(required=False,
                              type='list',
                              elements='str',
                              default=None,
                              choices=[
                                  'any', 'nfs', 'nfs3', 'nfs4', 'cifs',
                                  'flexcache'
                              ]),
                client_match=dict(required=False, type='list', elements='str'),
                ro_rule=dict(required=False,
                             type='list',
                             elements='str',
                             default=None,
                             choices=[
                                 'any', 'none', 'never', 'krb5', 'krb5i',
                                 'krb5p', 'ntlm', 'sys'
                             ]),
                rw_rule=dict(required=False,
                             type='list',
                             elements='str',
                             default=None,
                             choices=[
                                 'any', 'none', 'never', 'krb5', 'krb5i',
                                 'krb5p', 'ntlm', 'sys'
                             ]),
                super_user_security=dict(required=False,
                                         type='list',
                                         elements='str',
                                         default=None,
                                         choices=[
                                             'any', 'none', 'never', 'krb5',
                                             'krb5i', 'krb5p', 'ntlm', 'sys'
                                         ]),
                allow_suid=dict(required=False, type='bool'),
                rule_index=dict(required=False, type='int'),
                anonymous_user_id=dict(required=False, type='int'),
                vserver=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.set_playbook_zapi_key_map()

        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 set_playbook_zapi_key_map(self):
        self.na_helper.zapi_string_keys = {
            'client_match': 'client-match',
            'name': 'policy-name'
        }
        self.na_helper.zapi_list_keys = {
            'protocol': ('protocol', 'access-protocol'),
            'ro_rule': ('ro-rule', 'security-flavor'),
            'rw_rule': ('rw-rule', 'security-flavor'),
            'super_user_security': ('super-user-security', 'security-flavor'),
        }
        self.na_helper.zapi_bool_keys = {
            'allow_suid': 'is-allow-set-uid-enabled'
        }
        self.na_helper.zapi_int_keys = {
            'rule_index': 'rule-index',
            'anonymous_user_id': 'anonymous-user-id'
        }

    def set_query_parameters(self):
        """
        Return dictionary of query parameters and
        :return:
        """
        query = {
            'policy-name': self.parameters['name'],
            'vserver': self.parameters['vserver']
        }

        if self.parameters.get('rule_index'):
            query['rule-index'] = self.parameters['rule_index']
        elif self.parameters.get('client_match'):
            query['client-match'] = self.parameters['client_match']
        else:
            self.module.fail_json(
                msg=
                "Need to specify at least one of the rule_index and client_match option."
            )

        attributes = {'query': {'export-rule-info': query}}
        return attributes

    def get_export_policy_rule(self):
        """
        Return details about the export policy rule
        :param:
            name : Name of the export_policy
        :return: Details about the export_policy. None if not found.
        :rtype: dict
        """
        current, result = None, None
        rule_iter = netapp_utils.zapi.NaElement('export-rule-get-iter')
        rule_iter.translate_struct(self.set_query_parameters())
        try:
            result = self.server.invoke_successfully(rule_iter, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error getting export policy rule %s: %s' %
                (self.parameters['name'], to_native(error)),
                exception=traceback.format_exc())
        if result is not None and \
                result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
            current = dict()
            rule_info = result.get_child_by_name(
                'attributes-list').get_child_by_name('export-rule-info')
            for item_key, zapi_key in self.na_helper.zapi_string_keys.items():
                current[item_key] = rule_info.get_child_content(zapi_key)
            for item_key, zapi_key in self.na_helper.zapi_bool_keys.items():
                current[item_key] = self.na_helper.get_value_for_bool(
                    from_zapi=True, value=rule_info[zapi_key])
            for item_key, zapi_key in self.na_helper.zapi_int_keys.items():
                current[item_key] = self.na_helper.get_value_for_int(
                    from_zapi=True, value=rule_info[zapi_key])
            for item_key, zapi_key in self.na_helper.zapi_list_keys.items():
                parent, dummy = zapi_key
                current[item_key] = self.na_helper.get_value_for_list(
                    from_zapi=True,
                    zapi_parent=rule_info.get_child_by_name(parent))
            current['num_records'] = int(
                result.get_child_content('num-records'))
            if not self.parameters.get('rule_index'):
                self.parameters['rule_index'] = current['rule_index']
        return current

    def get_export_policy(self):
        """
        Return details about the export-policy
        :param:
            name : Name of the export-policy

        :return: Details about the export-policy. None if not found.
        :rtype: dict
        """
        export_policy_iter = netapp_utils.zapi.NaElement(
            'export-policy-get-iter')
        attributes = {
            'query': {
                'export-policy-info': {
                    'policy-name': self.parameters['name'],
                    'vserver': self.parameters['vserver']
                }
            }
        }

        export_policy_iter.translate_struct(attributes)
        try:
            result = self.server.invoke_successfully(export_policy_iter, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error getting export policy %s: %s' %
                                  (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())

        if result.get_child_by_name('num-records') and int(
                result.get_child_content('num-records')) == 1:
            return result

        return None

    def add_parameters_for_create_or_modify(self, na_element_object, values):
        """
            Add children node for create or modify NaElement object
            :param na_element_object: modify or create NaElement object
            :param values: dictionary of cron values to be added
            :return: None
        """
        for key in values:
            if key in self.na_helper.zapi_string_keys:
                zapi_key = self.na_helper.zapi_string_keys.get(key)
                na_element_object[zapi_key] = values[key]
            elif key in self.na_helper.zapi_list_keys:
                parent_key, child_key = self.na_helper.zapi_list_keys.get(key)
                na_element_object.add_child_elem(
                    self.na_helper.get_value_for_list(from_zapi=False,
                                                      zapi_parent=parent_key,
                                                      zapi_child=child_key,
                                                      data=values[key]))
            elif key in self.na_helper.zapi_int_keys:
                zapi_key = self.na_helper.zapi_int_keys.get(key)
                na_element_object[zapi_key] = self.na_helper.get_value_for_int(
                    from_zapi=False, value=values[key])
            elif key in self.na_helper.zapi_bool_keys:
                zapi_key = self.na_helper.zapi_bool_keys.get(key)
                na_element_object[
                    zapi_key] = self.na_helper.get_value_for_bool(
                        from_zapi=False, value=values[key])

    def create_export_policy_rule(self):
        """
        create rule for the export policy.
        """
        for key in ['client_match', 'ro_rule', 'rw_rule']:
            if self.parameters.get(key) is None:
                self.module.fail_json(
                    msg=
                    'Error: Missing required param for creating export policy rule %s'
                    % key)
        export_rule_create = netapp_utils.zapi.NaElement('export-rule-create')
        self.add_parameters_for_create_or_modify(export_rule_create,
                                                 self.parameters)
        try:
            self.server.invoke_successfully(export_rule_create,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error creating export policy rule %s: %s' %
                (self.parameters['name'], to_native(error)),
                exception=traceback.format_exc())

    def create_export_policy(self):
        """
        Creates an export policy
        """
        export_policy_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'export-policy-create', **{'policy-name': self.parameters['name']})
        try:
            self.server.invoke_successfully(export_policy_create,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error creating export-policy %s: %s' %
                                  (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())

    def delete_export_policy_rule(self, rule_index):
        """
        delete rule for the export policy.
        """
        export_rule_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'export-rule-destroy', **{
                'policy-name': self.parameters['name'],
                'rule-index': str(rule_index)
            })

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

    def modify_export_policy_rule(self, params):
        '''
        Modify an existing export policy rule
        :param params: dict() of attributes with desired values
        :return: None
        '''
        export_rule_modify = netapp_utils.zapi.NaElement.create_node_with_children(
            'export-rule-modify', **{
                'policy-name': self.parameters['name'],
                'rule-index': str(self.parameters['rule_index'])
            })
        self.add_parameters_for_create_or_modify(export_rule_modify, params)
        try:
            self.server.invoke_successfully(export_rule_modify,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error modifying allow_suid %s: %s' %
                (self.parameters['allow_suid'], to_native(error)),
                exception=traceback.format_exc())

    def autosupport_log(self):
        netapp_utils.ems_log_event("na_ontap_export_policy_rules", self.server)

    def apply(self):
        ''' Apply required action from the play'''
        self.autosupport_log()
        # convert client_match list to comma-separated string
        if self.parameters.get('client_match') is not None:
            self.parameters['client_match'] = ','.join(
                self.parameters['client_match'])
            self.parameters['client_match'] = self.parameters[
                'client_match'].replace(' ', '')

        current, modify = self.get_export_policy_rule(), None
        action = self.na_helper.get_cd_action(current, self.parameters)
        if 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:
                # create export policy (if policy doesn't exist) only when changed=True
                if action == 'create':
                    if not self.get_export_policy():
                        self.create_export_policy()
                    self.create_export_policy_rule()
                elif action == 'delete':
                    if current['num_records'] > 1:
                        self.module.fail_json(
                            msg='Multiple export policy rules exist.'
                            'Please specify a rule_index to delete')
                    self.delete_export_policy_rule(current['rule_index'])
                elif modify:
                    self.modify_export_policy_rule(modify)
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 5
0
class NetAppOntapServiceProcessorNetwork(object):
    """
        Modify a Service Processor Network
    """

    def __init__(self):
        """
            Initialize the NetAppOntapServiceProcessorNetwork class
        """
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            state=dict(required=False, type='str', choices=['present'], default='present'),
            address_type=dict(required=True, type='str', choices=['ipv4', 'ipv6']),
            is_enabled=dict(required=True, type='bool'),
            node=dict(required=True, type='str'),
            dhcp=dict(required=False, type='str', choices=['v4', 'none']),
            gateway_ip_address=dict(required=False, type='str'),
            ip_address=dict(required=False, type='str'),
            netmask=dict(required=False, type='str'),
            prefix_length=dict(required=False, type='int'),
            wait_for_completion=dict(required=False, type='bool', default=False)
        ))

        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.set_playbook_zapi_key_map()

        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=None)
        return

    def set_playbook_zapi_key_map(self):
        self.na_helper.zapi_string_keys = {
            'address_type': 'address-type',
            'node': 'node',
            'dhcp': 'dhcp',
            'gateway_ip_address': 'gateway-ip-address',
            'ip_address': 'ip-address',
            'netmask': 'netmask'
        }
        self.na_helper.zapi_int_keys = {
            'prefix_length': 'prefix-length'
        }
        self.na_helper.zapi_bool_keys = {
            'is_enabled': 'is-enabled',
        }
        self.na_helper.zapi_required = {
            'address_type': 'address-type',
            'node': 'node',
            'is_enabled': 'is-enabled'
        }

    def get_sp_network_status(self):
        """
        Return status of service processor network
        :param:
            name : name of the node
        :return: Status of the service processor network
        :rtype: dict
        """
        spn_get_iter = netapp_utils.zapi.NaElement('service-processor-network-get-iter')
        query_info = {
            'query': {
                'service-processor-network-info': {
                    'node': self.parameters['node'],
                    'address-type': self.parameters['address_type']
                }
            }
        }
        spn_get_iter.translate_struct(query_info)
        result = self.server.invoke_successfully(spn_get_iter, True)
        if int(result['num-records']) >= 1:
            sp_attr_info = result['attributes-list']['service-processor-network-info']
            return sp_attr_info.get_child_content('setup-status')
        return None

    def get_service_processor_network(self):
        """
        Return details about service processor network
        :param:
            name : name of the node
        :return: Details about service processor network. None if not found.
        :rtype: dict
        """
        spn_get_iter = netapp_utils.zapi.NaElement('service-processor-network-get-iter')
        query_info = {
            'query': {
                'service-processor-network-info': {
                    'node': self.parameters['node']
                }
            }
        }
        spn_get_iter.translate_struct(query_info)
        result = self.server.invoke_successfully(spn_get_iter, True)
        sp_details = None
        # check if job exists
        if int(result['num-records']) >= 1:
            sp_details = dict()
            sp_attr_info = result['attributes-list']['service-processor-network-info']
            for item_key, zapi_key in self.na_helper.zapi_string_keys.items():
                sp_details[item_key] = sp_attr_info.get_child_content(zapi_key)
            for item_key, zapi_key in self.na_helper.zapi_bool_keys.items():
                sp_details[item_key] = self.na_helper.get_value_for_bool(from_zapi=True,
                                                                         value=sp_attr_info.get_child_content(zapi_key))
            for item_key, zapi_key in self.na_helper.zapi_int_keys.items():
                sp_details[item_key] = self.na_helper.get_value_for_int(from_zapi=True,
                                                                        value=sp_attr_info.get_child_content(zapi_key))
        return sp_details

    def modify_service_processor_network(self, params=None):
        """
        Modify a service processor network.
        :param params: A dict of modified options.
        When dhcp is not set to v4, ip_address, netmask, and gateway_ip_address must be specified even if remains the same.
        """
        if self.parameters['is_enabled'] is False:
            if params.get('is_enabled') and len(params) > 1:
                self.module.fail_json(msg='Error: Cannot modify any other parameter for a service processor network if option "is_enabled" is set to false.')
            elif params.get('is_enabled') is None and len(params) > 0:
                self.module.fail_json(msg='Error: Cannot modify a service processor network if it is disabled.')

        sp_modify = netapp_utils.zapi.NaElement('service-processor-network-modify')
        sp_modify.add_new_child("node", self.parameters['node'])
        sp_modify.add_new_child("address-type", self.parameters['address_type'])
        sp_attributes = dict()
        for item_key in self.parameters:
            if item_key in self.na_helper.zapi_string_keys:
                zapi_key = self.na_helper.zapi_string_keys.get(item_key)
                sp_attributes[zapi_key] = self.parameters[item_key]
            elif item_key in self.na_helper.zapi_bool_keys:
                zapi_key = self.na_helper.zapi_bool_keys.get(item_key)
                sp_attributes[zapi_key] = self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters[item_key])
            elif item_key in self.na_helper.zapi_int_keys:
                zapi_key = self.na_helper.zapi_int_keys.get(item_key)
                sp_attributes[zapi_key] = self.na_helper.get_value_for_int(from_zapi=False, value=self.parameters[item_key])
        sp_modify.translate_struct(sp_attributes)
        try:
            self.server.invoke_successfully(sp_modify, enable_tunneling=True)
            if self.parameters.get('wait_for_completion'):
                retries = 10
                while self.get_sp_network_status() == 'in_progress' and retries > 0:
                    time.sleep(10)
                    retries = retries - 1
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error modifying service processor network: %s' % (to_native(error)),
                                  exception=traceback.format_exc())

    def autosupport_log(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_service_processor_network", cserver)

    def apply(self):
        """
        Run Module based on play book
        """
        self.autosupport_log()
        current = self.get_service_processor_network()
        modify = self.na_helper.get_modified_attributes(current, self.parameters)
        if not current:
            self.module.fail_json(msg='Error No Service Processor for node: %s' % self.parameters['node'])
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                self.modify_service_processor_network(modify)
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 6
0
class NetAppONTAPSnapmirror(object):
    """
    Class with Snapmirror methods
    """
    def __init__(self):

        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'),
                 source_vserver=dict(required=False, type='str'),
                 destination_vserver=dict(required=False, type='str'),
                 source_volume=dict(required=False, type='str'),
                 destination_volume=dict(required=False, type='str'),
                 source_path=dict(required=False, type='str'),
                 destination_path=dict(required=False, type='str'),
                 schedule=dict(required=False, type='str'),
                 policy=dict(required=False, type='str'),
                 relationship_type=dict(required=False,
                                        type='str',
                                        choices=[
                                            'data_protection', 'load_sharing',
                                            'vault', 'restore',
                                            'transition_data_protection',
                                            'extended_data_protection'
                                        ]),
                 source_hostname=dict(required=False, type='str'),
                 connection_type=dict(required=False,
                                      type='str',
                                      choices=[
                                          'ontap_ontap', 'elementsw_ontap',
                                          'ontap_elementsw'
                                      ],
                                      default='ontap_ontap'),
                 source_username=dict(required=False, type='str'),
                 source_password=dict(required=False, type='str', no_log=True),
                 max_transfer_rate=dict(required=False, type='int'),
                 initialize=dict(required=False, type='bool', default=True),
                 update=dict(required=False, type='bool', default=True),
                 identity_preserve=dict(required=False, type='bool'),
                 relationship_state=dict(required=False,
                                         type='str',
                                         choices=['active', 'broken'],
                                         default='active'),
                 relationship_info_only=dict(required=False,
                                             type='bool',
                                             default=False)))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            required_together=(['source_volume', 'destination_volume'],
                               ['source_vserver', 'destination_vserver']),
            supports_check_mode=True)

        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        # setup later if required
        self.source_server = None
        # only for ElementSW -> ONTAP snapmirroring, validate if ElementSW SDK is available
        if self.parameters.get('connection_type') in [
                'elementsw_ontap', 'ontap_elementsw'
        ]:
            if HAS_SF_SDK is False:
                self.module.fail_json(
                    msg="Unable to import the SolidFire Python SDK")
        if HAS_NETAPP_LIB is False:
            self.module.fail_json(
                msg="the python NetApp-Lib module is required")
        if self.parameters.get('connection_type') != 'ontap_elementsw':
            self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
        else:
            if self.parameters.get('source_username'):
                self.module.params['username'] = self.parameters[
                    'source_username']
            if self.parameters.get('source_password'):
                self.module.params['password'] = self.parameters[
                    'source_password']
            self.module.params['hostname'] = self.parameters['source_hostname']
            self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)

    def set_element_connection(self, kind):
        if kind == 'source':
            self.module.params['hostname'] = self.parameters['source_hostname']
            self.module.params['username'] = self.parameters['source_username']
            self.module.params['password'] = self.parameters['source_password']
        elif kind == 'destination':
            self.module.params['hostname'] = self.parameters['hostname']
            self.module.params['username'] = self.parameters['username']
            self.module.params['password'] = self.parameters['password']
        elem = netapp_utils.create_sf_connection(module=self.module)
        elementsw_helper = NaElementSWModule(elem)
        return elementsw_helper, elem

    def snapmirror_get_iter(self, destination=None):
        """
        Compose NaElement object to query current SnapMirror relations using destination-path
        SnapMirror relation for a destination path is unique
        :return: NaElement object for SnapMirror-get-iter
        """
        snapmirror_get_iter = netapp_utils.zapi.NaElement(
            'snapmirror-get-iter')
        query = netapp_utils.zapi.NaElement('query')
        snapmirror_info = netapp_utils.zapi.NaElement('snapmirror-info')
        if destination is None:
            destination = self.parameters['destination_path']
        snapmirror_info.add_new_child('destination-location', destination)
        query.add_child_elem(snapmirror_info)
        snapmirror_get_iter.add_child_elem(query)
        return snapmirror_get_iter

    def snapmirror_get(self, destination=None):
        """
        Get current SnapMirror relations
        :return: Dictionary of current SnapMirror details if query successful, else None
        """
        snapmirror_get_iter = self.snapmirror_get_iter(destination)
        snap_info = dict()
        try:
            result = self.server.invoke_successfully(snapmirror_get_iter,
                                                     enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error fetching snapmirror info: %s' %
                                  to_native(error),
                                  exception=traceback.format_exc())
        if result.get_child_by_name('num-records') and \
                int(result.get_child_content('num-records')) > 0:
            snapmirror_info = result.get_child_by_name(
                'attributes-list').get_child_by_name('snapmirror-info')
            snap_info['mirror_state'] = snapmirror_info.get_child_content(
                'mirror-state')
            snap_info['status'] = snapmirror_info.get_child_content(
                'relationship-status')
            snap_info['schedule'] = snapmirror_info.get_child_content(
                'schedule')
            snap_info['policy'] = snapmirror_info.get_child_content('policy')
            snap_info['relationship'] = snapmirror_info.get_child_content(
                'relationship-type')
            if snapmirror_info.get_child_by_name('max-transfer-rate'):
                snap_info['max_transfer_rate'] = int(
                    snapmirror_info.get_child_content('max-transfer-rate'))
            if snap_info['schedule'] is None:
                snap_info['schedule'] = ""
            return snap_info
        return None

    def check_if_remote_volume_exists(self):
        """
        Validate existence of source volume
        :return: True if volume exists, False otherwise
        """
        self.set_source_cluster_connection()
        # do a get volume to check if volume exists or not
        volume_info = netapp_utils.zapi.NaElement('volume-get-iter')
        volume_attributes = netapp_utils.zapi.NaElement('volume-attributes')
        volume_id_attributes = netapp_utils.zapi.NaElement(
            'volume-id-attributes')
        volume_id_attributes.add_new_child('name',
                                           self.parameters['source_volume'])
        # if source_volume is present, then source_vserver is also guaranteed to be present
        volume_id_attributes.add_new_child('vserver-name',
                                           self.parameters['source_vserver'])
        volume_attributes.add_child_elem(volume_id_attributes)
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(volume_attributes)
        volume_info.add_child_elem(query)
        try:
            result = self.source_server.invoke_successfully(volume_info, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error fetching source volume details %s : %s' %
                (self.parameters['source_volume'], to_native(error)),
                exception=traceback.format_exc())
        if result.get_child_by_name('num-records') and int(
                result.get_child_content('num-records')) > 0:
            return True
        return False

    def snapmirror_create(self):
        """
        Create a SnapMirror relationship
        """
        if self.parameters.get('source_hostname') and self.parameters.get(
                'source_volume'):
            if not self.check_if_remote_volume_exists():
                self.module.fail_json(
                    msg=
                    'Source volume does not exist. Please specify a volume that exists'
                )
        options = {
            'source-location': self.parameters['source_path'],
            'destination-location': self.parameters['destination_path']
        }
        snapmirror_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'snapmirror-create', **options)
        if self.parameters.get('relationship_type'):
            snapmirror_create.add_new_child(
                'relationship-type', self.parameters['relationship_type'])
        if self.parameters.get('schedule'):
            snapmirror_create.add_new_child('schedule',
                                            self.parameters['schedule'])
        if self.parameters.get('policy'):
            snapmirror_create.add_new_child('policy',
                                            self.parameters['policy'])
        if self.parameters.get('max_transfer_rate'):
            snapmirror_create.add_new_child(
                'max-transfer-rate', str(self.parameters['max_transfer_rate']))
        if self.parameters.get('identity_preserve'):
            snapmirror_create.add_new_child(
                'identity-preserve', str(self.parameters['identity_preserve']))
        try:
            self.server.invoke_successfully(snapmirror_create,
                                            enable_tunneling=True)
            if self.parameters['initialize']:
                self.snapmirror_initialize()
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error creating SnapMirror %s' %
                                  to_native(error),
                                  exception=traceback.format_exc())

    def set_source_cluster_connection(self):
        """
        Setup ontap ZAPI server connection for source hostname
        :return: None
        """
        if self.parameters.get('source_username'):
            self.module.params['username'] = self.parameters['source_username']
        if self.parameters.get('source_password'):
            self.module.params['password'] = self.parameters['source_password']
        self.module.params['hostname'] = self.parameters['source_hostname']
        self.source_server = netapp_utils.setup_na_ontap_zapi(
            module=self.module)

    def delete_snapmirror(self, is_hci, relationship_type, mirror_state):
        """
        Delete a SnapMirror relationship
        #1. Quiesce the SnapMirror relationship at destination
        #2. Break the SnapMirror relationship at the destination
        #3. Release the SnapMirror at source
        #4. Delete SnapMirror at destination
        """
        if not is_hci:
            if not self.parameters.get('source_hostname'):
                self.module.fail_json(
                    msg='Missing parameters for delete: Please specify the '
                    'source cluster hostname to release the SnapMirror relationship'
                )
        # Quiesce at destination
        self.snapmirror_quiesce()
        # Break at destination
        if relationship_type not in [
                'load_sharing', 'vault'
        ] and mirror_state not in ['uninitialized', 'broken-off']:
            self.snapmirror_break()
        # if source is ONTAP, release the destination at source cluster
        if not is_hci:
            self.set_source_cluster_connection()
            if self.get_destination():
                # Release at source
                self.snapmirror_release()
        # Delete at destination
        self.snapmirror_delete()

    def snapmirror_quiesce(self):
        """
        Quiesce SnapMirror relationship - disable all future transfers to this destination
        """
        result = None
        options = {'destination-location': self.parameters['destination_path']}

        snapmirror_quiesce = netapp_utils.zapi.NaElement.create_node_with_children(
            'snapmirror-quiesce', **options)
        try:
            result = self.server.invoke_successfully(snapmirror_quiesce,
                                                     enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error Quiescing SnapMirror : %s' %
                                  (to_native(error)),
                                  exception=traceback.format_exc())
        # checking if quiesce was passed successfully
        if result is not None and result['status'] == 'passed':
            return
        elif result is not None and result['status'] != 'passed':
            retries = 5
            while retries > 0:
                time.sleep(5)
                retries = retries - 1
                status = self.snapmirror_get()
                if status['status'] == 'quiesced':
                    return
            if retries == 0:
                self.module.fail_json(
                    msg=
                    'Taking a long time to Quiescing SnapMirror, try again later'
                )

    def snapmirror_delete(self):
        """
        Delete SnapMirror relationship at destination cluster
        """
        options = {'destination-location': self.parameters['destination_path']}

        snapmirror_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'snapmirror-destroy', **options)
        try:
            self.server.invoke_successfully(snapmirror_delete,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error deleting SnapMirror : %s' %
                                  (to_native(error)),
                                  exception=traceback.format_exc())

    def snapmirror_break(self, destination=None):
        """
        Break SnapMirror relationship at destination cluster
        #1. Quiesce the SnapMirror relationship at destination
        #2. Break the SnapMirror relationship at the destination
        """
        self.snapmirror_quiesce()
        if destination is None:
            destination = self.parameters['destination_path']
        options = {'destination-location': destination}
        snapmirror_break = netapp_utils.zapi.NaElement.create_node_with_children(
            'snapmirror-break', **options)
        try:
            self.server.invoke_successfully(snapmirror_break,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error breaking SnapMirror relationship : %s' %
                (to_native(error)),
                exception=traceback.format_exc())

    def snapmirror_release(self):
        """
        Release SnapMirror relationship from source cluster
        """
        options = {
            'destination-location':
            self.parameters['destination_path'],
            'relationship-info-only':
            self.na_helper.get_value_for_bool(
                False, self.parameters['relationship_info_only'])
        }
        snapmirror_release = netapp_utils.zapi.NaElement.create_node_with_children(
            'snapmirror-release', **options)
        try:
            self.source_server.invoke_successfully(snapmirror_release,
                                                   enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error releasing SnapMirror relationship : %s' %
                (to_native(error)),
                exception=traceback.format_exc())

    def snapmirror_abort(self):
        """
        Abort a SnapMirror relationship in progress
        """
        options = {'destination-location': self.parameters['destination_path']}
        snapmirror_abort = netapp_utils.zapi.NaElement.create_node_with_children(
            'snapmirror-abort', **options)
        try:
            self.server.invoke_successfully(snapmirror_abort,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error aborting SnapMirror relationship : %s' %
                (to_native(error)),
                exception=traceback.format_exc())

    def snapmirror_initialize(self):
        """
        Initialize SnapMirror based on relationship type
        """
        current = self.snapmirror_get()
        if current['mirror_state'] != 'snapmirrored':
            initialize_zapi = 'snapmirror-initialize'
            if self.parameters.get('relationship_type') and self.parameters[
                    'relationship_type'] == 'load_sharing':
                initialize_zapi = 'snapmirror-initialize-ls-set'
                options = {'source-location': self.parameters['source_path']}
            else:
                options = {
                    'destination-location': self.parameters['destination_path']
                }
            snapmirror_init = netapp_utils.zapi.NaElement.create_node_with_children(
                initialize_zapi, **options)
            try:
                self.server.invoke_successfully(snapmirror_init,
                                                enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg='Error initializing SnapMirror : %s' %
                    (to_native(error)),
                    exception=traceback.format_exc())

    def snapmirror_resync(self):
        """
        resync SnapMirror based on relationship type
        """
        options = {'destination-location': self.parameters['destination_path']}
        snapmirror_resync = netapp_utils.zapi.NaElement.create_node_with_children(
            'snapmirror-resync', **options)
        try:
            self.server.invoke_successfully(snapmirror_resync,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error resyncing SnapMirror : %s' %
                                  (to_native(error)),
                                  exception=traceback.format_exc())

    def snapmirror_modify(self, modify):
        """
        Modify SnapMirror schedule or policy
        """
        options = {'destination-location': self.parameters['destination_path']}
        snapmirror_modify = netapp_utils.zapi.NaElement.create_node_with_children(
            'snapmirror-modify', **options)
        if modify.get('schedule') is not None:
            snapmirror_modify.add_new_child('schedule', modify.get('schedule'))
        if modify.get('policy'):
            snapmirror_modify.add_new_child('policy', modify.get('policy'))
        if modify.get('max_transfer_rate'):
            snapmirror_modify.add_new_child(
                'max-transfer-rate', str(modify.get('max_transfer_rate')))
        try:
            self.server.invoke_successfully(snapmirror_modify,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error modifying SnapMirror schedule or policy : %s' %
                (to_native(error)),
                exception=traceback.format_exc())

    def snapmirror_update(self):
        """
        Update data in destination endpoint
        """
        options = {'destination-location': self.parameters['destination_path']}
        snapmirror_update = netapp_utils.zapi.NaElement.create_node_with_children(
            'snapmirror-update', **options)
        try:
            result = self.server.invoke_successfully(snapmirror_update,
                                                     enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error updating SnapMirror : %s' %
                                  (to_native(error)),
                                  exception=traceback.format_exc())

    def check_parameters(self):
        """
        Validate parameters and fail if one or more required params are missing
        Update source and destination path from vserver and volume parameters
        """
        if self.parameters['state'] == 'present'\
                and (self.parameters.get('source_path') or self.parameters.get('destination_path')):
            if not self.parameters.get(
                    'destination_path') or not self.parameters.get(
                        'source_path'):
                self.module.fail_json(
                    msg='Missing parameters: Source path or Destination path')
        elif self.parameters.get('source_volume'):
            if not self.parameters.get(
                    'source_vserver') or not self.parameters.get(
                        'destination_vserver'):
                self.module.fail_json(
                    msg=
                    'Missing parameters: source vserver or destination vserver or both'
                )
            self.parameters['source_path'] = self.parameters[
                'source_vserver'] + ":" + self.parameters['source_volume']
            self.parameters['destination_path'] = self.parameters['destination_vserver'] + ":" +\
                self.parameters['destination_volume']
        elif self.parameters.get('source_vserver'):
            self.parameters[
                'source_path'] = self.parameters['source_vserver'] + ":"
            self.parameters['destination_path'] = self.parameters[
                'destination_vserver'] + ":"

    def get_destination(self):
        result = None
        release_get = netapp_utils.zapi.NaElement(
            'snapmirror-get-destination-iter')
        query = netapp_utils.zapi.NaElement('query')
        snapmirror_dest_info = netapp_utils.zapi.NaElement(
            'snapmirror-destination-info')
        snapmirror_dest_info.add_new_child('destination-location',
                                           self.parameters['destination_path'])
        query.add_child_elem(snapmirror_dest_info)
        release_get.add_child_elem(query)
        try:
            result = self.source_server.invoke_successfully(
                release_get, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error fetching snapmirror destinations info: %s' %
                to_native(error),
                exception=traceback.format_exc())
        if result.get_child_by_name('num-records') and \
                int(result.get_child_content('num-records')) > 0:
            return True
        return None

    @staticmethod
    def element_source_path_format_matches(value):
        return re.match(
            pattern=r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\/lun\/[0-9]+",
            string=value)

    def check_elementsw_parameters(self, kind='source'):
        """
        Validate all ElementSW cluster parameters required for managing the SnapMirror relationship
        Validate if both source and destination paths are present
        Validate if source_path follows the required format
        Validate SVIP
        Validate if ElementSW volume exists
        :return: None
        """
        path = None
        if kind == 'destination':
            path = self.parameters.get('destination_path')
        elif kind == 'source':
            path = self.parameters.get('source_path')
        if path is None:
            self.module.fail_json(
                msg="Error: Missing required parameter %s_path for "
                "connection_type %s" %
                (kind, self.parameters['connection_type']))
        else:
            if NetAppONTAPSnapmirror.element_source_path_format_matches(
                    path) is None:
                self.module.fail_json(
                    msg="Error: invalid %s_path %s. "
                    "If the path is a ElementSW cluster, the value should be of the format"
                    " <Element_SVIP>:/lun/<Element_VOLUME_ID>" % (kind, path))
        # validate source_path
        elementsw_helper, elem = self.set_element_connection(kind)
        self.validate_elementsw_svip(path, elem)
        self.check_if_elementsw_volume_exists(path, elementsw_helper)

    def validate_elementsw_svip(self, path, elem):
        """
        Validate ElementSW cluster SVIP
        :return: None
        """
        result = None
        try:
            result = elem.get_cluster_info()
        except solidfire.common.ApiServerError as err:
            self.module.fail_json(msg="Error fetching SVIP",
                                  exception=to_native(err))
        if result and result.cluster_info.svip:
            cluster_svip = result.cluster_info.svip
            svip = path.split(':')[0]  # split IP address from source_path
            if svip != cluster_svip:
                self.module.fail_json(msg="Error: Invalid SVIP")

    def check_if_elementsw_volume_exists(self, path, elementsw_helper):
        """
        Check if remote ElementSW volume exists
        :return: None
        """
        volume_id, vol_id = None, path.split('/')[-1]
        try:
            volume_id = elementsw_helper.volume_id_exists(int(vol_id))
        except solidfire.common.ApiServerError as err:
            self.module.fail_json(msg="Error fetching Volume details",
                                  exception=to_native(err))

        if volume_id is None:
            self.module.fail_json(
                msg=
                "Error: Source volume does not exist in the ElementSW cluster")

    def asup_log_for_cserver(self, event_name):
        """
        Fetch admin vserver for the given cluster
        Create and Autosupport log event with the given module name
        :param event_name: Name of the event log
        :return: None
        """
        results = netapp_utils.get_cserver(self.server)
        if results is None:
            # We may be running on a vserser
            try:
                netapp_utils.ems_log_event(event_name, self.server)
            except netapp_utils.zapi.NaApiError:
                # Don't fail if we cannot log usage
                pass
        else:
            cserver = netapp_utils.setup_na_ontap_zapi(module=self.module,
                                                       vserver=results)
            netapp_utils.ems_log_event(event_name, cserver)

    def apply(self):
        """
        Apply action to SnapMirror
        """
        self.asup_log_for_cserver("na_ontap_snapmirror")
        # source is ElementSW
        if self.parameters['state'] == 'present' and self.parameters.get(
                'connection_type') == 'elementsw_ontap':
            self.check_elementsw_parameters()
        elif self.parameters.get('connection_type') == 'ontap_elementsw':
            self.check_elementsw_parameters('destination')
        else:
            self.check_parameters()
        if self.parameters['state'] == 'present' and self.parameters.get(
                'connection_type') == 'ontap_elementsw':
            current_elementsw_ontap = self.snapmirror_get(
                self.parameters['source_path'])
            if current_elementsw_ontap is None:
                self.module.fail_json(
                    msg=
                    'Error: creating an ONTAP to ElementSW snapmirror relationship requires an '
                    'established SnapMirror relation from ElementSW to ONTAP cluster'
                )
        current = self.snapmirror_get()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        modify = self.na_helper.get_modified_attributes(
            current, self.parameters)
        element_snapmirror = False
        if cd_action == 'create':
            self.snapmirror_create()
        elif cd_action == 'delete':
            if current['status'] == 'transferring':
                self.snapmirror_abort()
            else:
                if self.parameters.get('connection_type') == 'elementsw_ontap':
                    element_snapmirror = True
                self.delete_snapmirror(element_snapmirror,
                                       current['relationship'],
                                       current['mirror_state'])
        else:
            if modify:
                self.snapmirror_modify(modify)
            # break relationship when 'relationship_state' == 'broken'
            if current and self.parameters[
                    'state'] == 'present' and self.parameters[
                        'relationship_state'] == 'broken':
                if current['mirror_state'] == 'uninitialized':
                    self.module.fail_json(
                        msg=
                        'SnapMirror relationship cannot be broken if mirror state is uninitialized'
                    )
                elif current['relationship'] in ['load_sharing', 'vault']:
                    self.module.fail_json(
                        msg=
                        'SnapMirror break is not allowed in a load_sharing or vault relationship'
                    )
                elif current['mirror_state'] != 'broken-off':
                    self.snapmirror_break()
                    self.na_helper.changed = True
            # check for initialize
            elif current and self.parameters['initialize'] and self.parameters['relationship_state'] == 'active'\
                    and current['mirror_state'] == 'uninitialized':
                self.snapmirror_initialize()
                # set changed explicitly for initialize
                self.na_helper.changed = True
            if self.parameters['state'] == 'present' and self.parameters[
                    'relationship_state'] == 'active':
                # resync when state is broken-off
                if current['mirror_state'] == 'broken-off':
                    self.snapmirror_resync()
                    # set changed explicitly for resync
                    self.na_helper.changed = True
                # Update when create is called again, or modify is being called
                elif self.parameters['update']:
                    current = self.snapmirror_get()
                    if current['mirror_state'] == 'snapmirrored':
                        self.snapmirror_update()
                        self.na_helper.changed = True
        self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapQosPolicyGroup(object):
    """
    Create, delete, modify and rename a policy group.
    """
    def __init__(self):
        """
        Initialize the Ontap qos policy group class.
        """
        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'),
                 vserver=dict(required=True, type='str'),
                 max_throughput=dict(required=False, type='str'),
                 min_throughput=dict(required=False, type='str'),
                 is_shared=dict(required=False, type='bool'),
                 force=dict(required=False, type='bool', default=False)))

        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)

        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)

    def get_policy_group(self, policy_group_name=None):
        """
        Return details of a policy group.
        :param policy_group_name: policy group name
        :return: policy group details.
        :rtype: dict.
        """
        if policy_group_name is None:
            policy_group_name = self.parameters['name']
        policy_group_get_iter = netapp_utils.zapi.NaElement(
            'qos-policy-group-get-iter')
        policy_group_info = netapp_utils.zapi.NaElement(
            'qos-policy-group-info')
        policy_group_info.add_new_child('policy-group', policy_group_name)
        policy_group_info.add_new_child('vserver', self.parameters['vserver'])
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(policy_group_info)
        policy_group_get_iter.add_child_elem(query)
        result = self.server.invoke_successfully(policy_group_get_iter, True)
        policy_group_detail = None

        if result.get_child_by_name('num-records') and int(
                result.get_child_content('num-records')) == 1:
            policy_info = result.get_child_by_name(
                'attributes-list').get_child_by_name('qos-policy-group-info')

            policy_group_detail = {
                'name':
                policy_info.get_child_content('policy-group'),
                'vserver':
                policy_info.get_child_content('vserver'),
                'max_throughput':
                policy_info.get_child_content('max-throughput'),
                'min_throughput':
                policy_info.get_child_content('min-throughput'),
                'is_shared':
                self.na_helper.get_value_for_bool(
                    True, policy_info.get_child_content('is-shared'))
            }
        return policy_group_detail

    def create_policy_group(self):
        """
        create a policy group name.
        """
        policy_group = netapp_utils.zapi.NaElement('qos-policy-group-create')
        policy_group.add_new_child('policy-group', self.parameters['name'])
        policy_group.add_new_child('vserver', self.parameters['vserver'])
        if self.parameters.get('max_throughput'):
            policy_group.add_new_child('max-throughput',
                                       self.parameters['max_throughput'])
        if self.parameters.get('min_throughput'):
            policy_group.add_new_child('min-throughput',
                                       self.parameters['min_throughput'])
        if self.parameters.get('is_shared') is not None:
            policy_group.add_new_child(
                'is-shared',
                self.na_helper.get_value_for_bool(
                    False, self.parameters['is_shared']))
        try:
            self.server.invoke_successfully(policy_group, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error creating qos policy group %s: %s' %
                (self.parameters['name'], to_native(error)),
                exception=traceback.format_exc())

    def delete_policy_group(self, policy_group=None):
        """
        delete an existing policy group.
        :param policy_group: policy group name.
        """
        if policy_group is None:
            policy_group = self.parameters['name']
        policy_group_obj = netapp_utils.zapi.NaElement(
            'qos-policy-group-delete')
        policy_group_obj.add_new_child('policy-group', policy_group)
        if self.parameters.get('force'):
            policy_group_obj.add_new_child('force',
                                           str(self.parameters['force']))
        try:
            self.server.invoke_successfully(policy_group_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error deleting qos policy group %s: %s' %
                (policy_group, to_native(error)),
                exception=traceback.format_exc())

    def modify_policy_group(self):
        """
        Modify policy group.
        """
        policy_group_obj = netapp_utils.zapi.NaElement(
            'qos-policy-group-modify')
        policy_group_obj.add_new_child('policy-group', self.parameters['name'])
        if self.parameters.get('max_throughput'):
            policy_group_obj.add_new_child('max-throughput',
                                           self.parameters['max_throughput'])
        if self.parameters.get('min_throughput'):
            policy_group_obj.add_new_child('min-throughput',
                                           self.parameters['min_throughput'])
        try:
            self.server.invoke_successfully(policy_group_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error modifying qos policy group %s: %s' %
                (self.parameters['name'], to_native(error)),
                exception=traceback.format_exc())

    def rename_policy_group(self):
        """
        Rename policy group name.
        """
        rename_obj = netapp_utils.zapi.NaElement('qos-policy-group-rename')
        rename_obj.add_new_child('new-name', self.parameters['name'])
        rename_obj.add_new_child('policy-group-name',
                                 self.parameters['from_name'])
        try:
            self.server.invoke_successfully(rename_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error renaming qos policy group %s: %s' %
                (self.parameters['from_name'], to_native(error)),
                exception=traceback.format_exc())

    def modify_helper(self, modify):
        """
        helper method to modify policy group.
        :param modify: modified attributes.
        """
        if 'is_shared' in modify:
            self.module.fail_json(
                msg='Error cannot modify is_shared attribute.')
        if any([
                attribute in modify
                for attribute in ['max_throughput', 'min_throughput']
        ]):
            self.modify_policy_group()

    def apply(self):
        """
        Run module based on playbook
        """
        self.asup_log_for_cserver("na_ontap_qos_policy_group")
        current = self.get_policy_group()
        rename, cd_action = None, None
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if cd_action == 'create' and self.parameters.get('from_name'):
            # create policy by renaming an existing one
            old_policy = self.get_policy_group(self.parameters['from_name'])
            rename = self.na_helper.is_rename_action(old_policy, current)
            if rename:
                current = old_policy
                cd_action = None
            if rename is None:
                self.module.fail_json(
                    msg='Error renaming qos policy group: cannot find %s' %
                    self.parameters['from_name'])
        modify = self.na_helper.get_modified_attributes(
            current, self.parameters)
        if self.na_helper.changed and not self.module.check_mode:
            if rename:
                self.rename_policy_group()
            if cd_action == 'create':
                self.create_policy_group()
            elif cd_action == 'delete':
                self.delete_policy_group()
            elif modify:
                self.modify_helper(modify)
        self.module.exit_json(changed=self.na_helper.changed)

    def asup_log_for_cserver(self, event_name):
        """
        Fetch admin vserver for the given cluster
        Create and Autosupport log event with the given module name
        :param event_name: Name of the event log
        :return: None
        """
        results = netapp_utils.get_cserver(self.server)
        cserver = netapp_utils.setup_na_ontap_zapi(module=self.module,
                                                   vserver=results)
        netapp_utils.ems_log_event(event_name, cserver)
class NetAppOntapLDAPClient(object):
    '''
    LDAP Client definition class
    '''
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(ad_domain=dict(required=False, default=None, type='str'),
                 base_dn=dict(required=False, type='str'),
                 base_scope=dict(required=False,
                                 default=None,
                                 choices=['subtree', 'onelevel', 'base']),
                 bind_as_cifs_server=dict(required=False, type='bool'),
                 bind_dn=dict(required=False, default=None, type='str'),
                 bind_password=dict(type='str',
                                    required=False,
                                    default=None,
                                    no_log=True),
                 name=dict(required=True, type='str'),
                 ldap_servers=dict(required=False, type='list',
                                   elements='str'),
                 min_bind_level=dict(required=False,
                                     default=None,
                                     choices=['anonymous', 'simple', 'sasl']),
                 preferred_ad_servers=dict(required=False,
                                           type='list',
                                           elements='str'),
                 port=dict(required=False, default=None, type='int'),
                 query_timeout=dict(required=False, default=None, type='int'),
                 referral_enabled=dict(required=False, type='bool'),
                 schema=dict(
                     required=False,
                     default=None,
                     choices=['AD-IDMU', 'AD-SFU', 'MS-AD-BIS', 'RFC-2307']),
                 session_security=dict(required=False,
                                       default=None,
                                       choices=['none', 'sign', 'seal']),
                 state=dict(required=False,
                            choices=['present', 'absent'],
                            default='present'),
                 use_start_tls=dict(required=False, type='bool'),
                 vserver=dict(required=True, type='str')))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True,
            required_if=[
                ('state', 'present', ['schema']),
            ],
            mutually_exclusive=[['ldap_servers', 'ad_domain'],
                                ['ldap_servers', 'preferred_ad_servers']],
        )
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)

        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'])

        self.simple_attributes = [
            'ad_domain', 'base_dn', 'base_scope', 'bind_as_cifs_server',
            'bind_dn', 'bind_password', 'min_bind_level', 'port',
            'query_timeout', 'referral_enabled', 'session_security',
            'use_start_tls'
        ]

    def get_ldap_client(self, client_config_name=None, vserver_name=None):
        '''
        Checks if LDAP client config exists.

        :return:
            ldap client config object if found
            None if not found
        :rtype: object/None
        '''
        # Make query
        client_config_info = netapp_utils.zapi.NaElement(
            'ldap-client-get-iter')

        if client_config_name is None:
            client_config_name = self.parameters['name']

        if vserver_name is None:
            vserver_name = '*'

        query_details = netapp_utils.zapi.NaElement.create_node_with_children(
            'ldap-client', **{
                'ldap-client-config': client_config_name,
                'vserver': vserver_name
            })

        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(query_details)
        client_config_info.add_child_elem(query)

        result = self.server.invoke_successfully(client_config_info,
                                                 enable_tunneling=False)

        # Get LDAP client configuration details
        client_config_details = None
        if result.get_child_by_name('num-records') and int(
                result.get_child_content('num-records')) >= 1:
            attributes_list = result.get_child_by_name('attributes-list')
            client_config_info = attributes_list.get_child_by_name(
                'ldap-client')
            # Get LDAP servers list
            ldap_server_list = list()
            get_list = client_config_info.get_child_by_name('ldap-servers')
            if get_list is not None:
                ldap_server_list = [
                    x.get_content() for x in get_list.get_children()
                ]

            preferred_ad_servers_list = list()
            get_pref_ad_server_list = client_config_info.get_child_by_name(
                'preferred-ad-servers')
            if get_pref_ad_server_list is not None:
                preferred_ad_servers_list = [
                    x.get_content()
                    for x in get_pref_ad_server_list.get_children()
                ]

            # Define config details structure
            client_config_details = {
                'name':
                client_config_info.get_child_content('ldap-client-config'),
                'ldap_servers':
                ldap_server_list,
                'ad_domain':
                client_config_info.get_child_content('ad-domain'),
                'base_dn':
                client_config_info.get_child_content('base-dn'),
                'base_scope':
                client_config_info.get_child_content('base-scope'),
                'bind_as_cifs_server':
                self.na_helper.get_value_for_bool(
                    from_zapi=True,
                    value=client_config_info.get_child_content(
                        'bind-as-cifs-server')),
                'bind_dn':
                client_config_info.get_child_content('bind-dn'),
                'bind_password':
                client_config_info.get_child_content('bind-password'),
                'min_bind_level':
                client_config_info.get_child_content('min-bind-level'),
                'port':
                self.na_helper.get_value_for_int(
                    from_zapi=True,
                    value=client_config_info.get_child_content('port')),
                'preferred_ad_servers':
                preferred_ad_servers_list,
                'query_timeout':
                self.na_helper.get_value_for_int(
                    from_zapi=True,
                    value=client_config_info.get_child_content(
                        'query-timeout')),
                'referral_enabled':
                self.na_helper.get_value_for_bool(
                    from_zapi=True,
                    value=client_config_info.get_child_content(
                        'referral-enabled')),
                'schema':
                client_config_info.get_child_content('schema'),
                'session_security':
                client_config_info.get_child_content('session-security'),
                'use_start_tls':
                self.na_helper.get_value_for_bool(
                    from_zapi=True,
                    value=client_config_info.get_child_content(
                        'use-start-tls'))
            }
        return client_config_details

    def create_ldap_client(self):
        '''
        Create LDAP client configuration
        '''

        options = {
            'ldap-client-config': self.parameters['name'],
            'schema': self.parameters['schema'],
        }

        # Other options/attributes
        for attribute in self.simple_attributes:
            if self.parameters.get(attribute) is not None:
                options[str(attribute).replace('_', '-')] = str(
                    self.parameters[attribute])

        # Initialize NaElement
        ldap_client_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'ldap-client-create', **options)

        # LDAP servers NaElement
        if self.parameters.get('ldap_servers') is not None:
            ldap_servers_element = netapp_utils.zapi.NaElement('ldap-servers')
            for ldap_server_name in self.parameters['ldap_servers']:
                ldap_servers_element.add_new_child('string', ldap_server_name)
            ldap_client_create.add_child_elem(ldap_servers_element)

        # preferred_ad_servers
        if self.parameters.get('preferred_ad_servers') is not None:
            preferred_ad_servers_element = netapp_utils.zapi.NaElement(
                'preferred-ad-servers')
            for pref_ad_server in self.parameters['preferred_ad_servers']:
                preferred_ad_servers_element.add_new_child(
                    'ip-address', pref_ad_server)
            ldap_client_create.add_child_elem(preferred_ad_servers_element)

        # Try to create LDAP configuration
        try:
            self.server.invoke_successfully(ldap_client_create,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as errcatch:
            self.module.fail_json(
                msg='Error creating LDAP client %s: %s' %
                (self.parameters['name'], to_native(errcatch)),
                exception=traceback.format_exc())

    def delete_ldap_client(self):
        '''
        Delete LDAP client configuration
        '''
        ldap_client_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'ldap-client-delete',
            **{'ldap-client-config': self.parameters['name']})

        try:
            self.server.invoke_successfully(ldap_client_delete,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as errcatch:
            self.module.fail_json(
                msg='Error deleting LDAP client configuration %s: %s' %
                (self.parameters['name'], to_native(errcatch)),
                exception=traceback.format_exc())

    def modify_ldap_client(self, modify):
        '''
        Modify LDAP client
        :param modify: list of modify attributes
        '''
        ldap_client_modify = netapp_utils.zapi.NaElement('ldap-client-modify')
        ldap_client_modify.add_new_child('ldap-client-config',
                                         self.parameters['name'])

        for attribute in modify:
            # LDAP_servers
            if attribute == 'ldap_servers':
                ldap_servers_element = netapp_utils.zapi.NaElement(
                    'ldap-servers')
                for ldap_server_name in self.parameters['ldap_servers']:
                    ldap_servers_element.add_new_child('string',
                                                       ldap_server_name)
                ldap_client_modify.add_child_elem(ldap_servers_element)
            # preferred_ad_servers
            if attribute == 'preferred_ad_servers':
                preferred_ad_servers_element = netapp_utils.zapi.NaElement(
                    'preferred-ad-servers')
                ldap_client_modify.add_child_elem(preferred_ad_servers_element)
                for pref_ad_server in self.parameters['preferred_ad_servers']:
                    preferred_ad_servers_element.add_new_child(
                        'ip-address', pref_ad_server)
            # Simple attributes
            if attribute in self.simple_attributes:
                ldap_client_modify.add_new_child(
                    str(attribute).replace('_', '-'),
                    str(self.parameters[attribute]))

        # Try to modify LDAP client
        try:
            self.server.invoke_successfully(ldap_client_modify,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as errcatch:
            self.module.fail_json(
                msg='Error modifying LDAP client %s: %s' %
                (self.parameters['name'], to_native(errcatch)),
                exception=traceback.format_exc())

    def apply(self):
        '''Call create/modify/delete operations.'''
        current = self.get_ldap_client()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)

        # state is present, either ldap_servers or ad_domain is required
        if self.parameters['state'] == 'present' and not self.parameters.get('ldap_servers') \
                and self.parameters.get('ad_domain') is None:
            self.module.fail_json(
                msg='Required one of ldap_servers or ad_domain')

        if self.parameters['state'] == 'present' and cd_action is None:
            modify = self.na_helper.get_modified_attributes(
                current, self.parameters)

        # create an ems log event for users with auto support turned on
        netapp_utils.ems_log_event("na_ontap_ldap_client", self.server)

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_ldap_client()
                elif cd_action == 'delete':
                    self.delete_ldap_client()
                elif modify:
                    self.modify_ldap_client(modify)
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 9
0
class NetAppOntapVolumeEfficiency(object):
    """
        Creates, Modifies and Disables a Volume Efficiency
    """
    def __init__(self):
        """
            Initialize the ONTAP Volume Efficiency class
        """
        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'),
            path=dict(required=True, type='str'),
            schedule=dict(required=False, type='str'),
            policy=dict(required=False, choices=['auto', 'default', 'inline-only', '-'], type='str'),
            enable_inline_compression=dict(required=False, type='bool'),
            enable_compression=dict(required=False, type='bool'),
            enable_inline_dedupe=dict(required=False, type='bool'),
            enable_data_compaction=dict(required=False, type='bool'),
            enable_cross_volume_inline_dedupe=dict(required=False, type='bool'),
            enable_cross_volume_background_dedupe=dict(required=False, type='bool')
        ))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True,
            mutually_exclusive=[('policy', 'schedule')]
        )

        # set up variables
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        if self.parameters['state'] == 'present':
            self.parameters['enabled'] = 'enabled'
        else:
            self.parameters['enabled'] = 'disabled'

        self.rest_api = OntapRestAPI(self.module)
        self.use_rest = self.rest_api.is_rest()

        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_volume_efficiency(self):
        """
        get the storage efficiency for a given path
        :return: dict of sis if exist, None if not
        """

        return_value = None

        if self.use_rest:
            api = 'private/cli/volume/efficiency'
            query = {
                'fields': 'path,volume,state,schedule,compression,inline_compression,inline_dedupe,policy,data_compaction,'
                          'cross_volume_inline_dedupe,cross_volume_background_dedupe',
                'path': self.parameters['path'],
                'vserver': self.parameters['vserver']
            }
            message, error = self.rest_api.get(api, query)

            if error:
                self.module.fail_json(msg=error)
            if len(message.keys()) == 0:
                return None
            if 'records' in message and len(message['records']) == 0:
                return None
            if 'records' not in message:
                error = "Unexpected response in api call from %s: %s" % (api, repr(message))
                self.module.fail_json(msg=error)
            return_value = {
                'path': message['records'][0]['path'],
                'enabled': message['records'][0]['state'],
                'schedule': message['records'][0]['schedule'],
                'enable_inline_compression': message['records'][0]['inline_compression'],
                'enable_compression': message['records'][0]['compression'],
                'enable_inline_dedupe': message['records'][0]['inline_dedupe'],
                'enable_data_compaction': message['records'][0]['data_compaction'],
                'enable_cross_volume_inline_dedupe': message['records'][0]['cross_volume_inline_dedupe'],
                'enable_cross_volume_background_dedupe': message['records'][0]['cross_volume_background_dedupe']
            }

            if 'policy' in message['records'][0]:
                return_value['policy'] = message['records'][0]['policy']
            else:
                return_value['policy'] = '-'
            return return_value

        else:

            sis_get_iter = netapp_utils.zapi.NaElement('sis-get-iter')
            sis_status_info = netapp_utils.zapi.NaElement('sis-status-info')
            sis_status_info.add_new_child('path', self.parameters['path'])
            query = netapp_utils.zapi.NaElement('query')
            query.add_child_elem(sis_status_info)
            sis_get_iter.add_child_elem(query)
            result = self.server.invoke_successfully(sis_get_iter, True)

            try:

                if result.get_child_by_name('attributes-list'):
                    sis_status_attributes = result['attributes-list']['sis-status-info']
                    return_value = {
                        'path': sis_status_attributes['path'],
                        'enabled': sis_status_attributes['state'],
                        'schedule': sis_status_attributes['schedule'],
                        'enable_inline_compression': self.na_helper.get_value_for_bool(
                            True, sis_status_attributes.get_child_content('is-inline-compression-enabled')
                        ),
                        'enable_compression': self.na_helper.get_value_for_bool(True, sis_status_attributes.get_child_content('is-compression-enabled')),
                        'enable_inline_dedupe': self.na_helper.get_value_for_bool(True, sis_status_attributes.get_child_content('is-inline-dedupe-enabled')),
                        'enable_data_compaction': self.na_helper.get_value_for_bool(
                            True, sis_status_attributes.get_child_content('is-data-compaction-enabled')
                        ),
                        'enable_cross_volume_inline_dedupe': self.na_helper.get_value_for_bool(
                            True, sis_status_attributes.get_child_content('is-cross-volume-inline-dedupe-enabled')
                        ),
                        'enable_cross_volume_background_dedupe': self.na_helper.get_value_for_bool(
                            True, sis_status_attributes.get_child_content('is-cross-volume-background-dedupe-enabled')
                        )
                    }

                    if sis_status_attributes.get_child_by_name('policy'):
                        return_value['policy'] = sis_status_attributes['policy']
                    else:
                        return_value['policy'] = '-'

            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error getting volume efficiency for path %s on vserver %s: %s' % (
                    self.parameters['path'], self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()
                )

            return return_value

    def enable_volume_efficiency(self):
        """
        Enables Volume efficiency for a given volume by path
        """

        if self.use_rest:
            api = 'private/cli/volume/efficiency/on'
            body = dict()
            query = {
                'path': self.parameters['path'],
                'vserver': self.parameters['vserver']
            }
            message, error = self.rest_api.patch(api, body, query)

            if error:
                self.module.fail_json(msg=error)
            elif message['num_records'] == 0:
                error = 'Error enabling storage efficiency for path %s on vserver %s as the path provided does not exist.' % (self.parameters['path'],
                                                                                                                              self.parameters['vserver'])
                self.module.fail_json(msg=error)

        else:
            sis_enable = netapp_utils.zapi.NaElement("sis-enable")
            sis_enable.add_new_child("path", self.parameters['path'])

            try:
                self.server.invoke_successfully(sis_enable, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error enabling storage efficiency for path %s on vserver %s: %s' % (self.parameters['path'],
                                      self.parameters['vserver'], to_native(error)), exception=traceback.format_exc())

    def disable_volume_efficiency(self):
        """
        Disables Volume efficiency for a given volume by path
        """
        if self.use_rest:
            api = 'private/cli/volume/efficiency/off'
            body = dict()
            query = {
                'path': self.parameters['path'],
                'vserver': self.parameters['vserver']
            }
            dummy, error = self.rest_api.patch(api, body, query)
            if error:
                self.module.fail_json(msg=error)

        else:

            sis_disable = netapp_utils.zapi.NaElement("sis-disable")
            sis_disable.add_new_child("path", self.parameters['path'])

            try:
                self.server.invoke_successfully(sis_disable, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error disabling storage efficiency for path %s: %s' % (self.parameters['path'], to_native(error)),
                                      exception=traceback.format_exc())

    def modify_volume_efficiency(self):
        """
        Modifies volume efficiency settings for a given volume by path
        """

        if self.use_rest:
            api = 'private/cli/volume/efficiency'
            body = dict()
            query = {
                'path': self.parameters['path'],
                'vserver': self.parameters['vserver']
            }

            if 'schedule' in self.parameters:
                body['schedule'] = self.parameters['schedule']
            if 'policy' in self.parameters:
                body['policy'] = self.parameters['policy']
            if 'enable_compression' in self.parameters:
                body['compression'] = self.parameters['enable_compression']
            if 'enable_inline_compression' in self.parameters:
                body['inline_compression'] = self.parameters['enable_inline_compression']
            if 'enable_inline_dedupe' in self.parameters:
                body['inline_dedupe'] = self.parameters['enable_inline_dedupe']
            if 'enable_data_compaction' in self.parameters:
                body['data_compaction'] = self.parameters['enable_data_compaction']
            if 'enable_cross_volume_inline_dedupe' in self.parameters:
                body['cross_volume_inline_dedupe'] = self.parameters['enable_cross_volume_inline_dedupe']
            if 'enable_cross_volume_background_dedupe' in self.parameters:
                body['cross_volume_background_dedupe'] = self.parameters['enable_cross_volume_background_dedupe']

            dummy, error = self.rest_api.patch(api, body, query)
            if error:
                self.module.fail_json(msg=error)

        else:

            sis_config_obj = netapp_utils.zapi.NaElement("sis-set-config")
            sis_config_obj.add_new_child('path', self.parameters['path'])
            if 'schedule' in self.parameters:
                sis_config_obj.add_new_child('schedule', self.parameters['schedule'])
            if 'policy' in self.parameters:
                sis_config_obj.add_new_child('policy-name', self.parameters['policy'])
            if 'enable_compression' in self.parameters:
                sis_config_obj.add_new_child('enable-compression', self.na_helper.get_value_for_bool(False, self.parameters['enable_compression']))
            if 'enable_inline_compression' in self.parameters:
                sis_config_obj.add_new_child('enable-inline-compression', self.na_helper.get_value_for_bool(
                    False, self.parameters['enable_inline_compression'])
                )
            if 'enable_inline_dedupe' in self.parameters:
                sis_config_obj.add_new_child('enable-inline-dedupe', self.na_helper.get_value_for_bool(
                    False, self.parameters['enable_inline_dedupe'])
                )
            if 'enable_data_compaction' in self.parameters:
                sis_config_obj.add_new_child('enable-data-compaction', self.na_helper.get_value_for_bool(
                    False, self.parameters['enable_data_compaction'])
                )
            if 'enable_cross_volume_inline_dedupe' in self.parameters:
                sis_config_obj.add_new_child('enable-cross-volume-inline-dedupe', self.na_helper.get_value_for_bool(
                    False, self.parameters['enable_cross_volume_inline_dedupe'])
                )
            if 'enable_cross_volume_background_dedupe' in self.parameters:
                sis_config_obj.add_new_child('enable-cross-volume-background-dedupe', self.na_helper.get_value_for_bool(
                    False, self.parameters['enable_cross_volume_background_dedupe'])
                )

            try:
                self.server.invoke_successfully(sis_config_obj, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error modifying storage efficiency for path %s: %s' % (self.parameters['path'], to_native(error)),
                                      exception=traceback.format_exc())

    def apply(self):
        if not self.use_rest:
            netapp_utils.ems_log_event("na_ontap_volume_efficiency", self.server)

        current = self.get_volume_efficiency()

        # If the volume efficiency does not exist for a given path to create this current is set to disabled
        # this is for ONTAP systems that do not enable efficiency by default.
        if current is None:
            current = {'enabled': 'disabled'}

        modify = self.na_helper.get_modified_attributes(current, self.parameters)

        if self.na_helper.changed:
            if not self.module.check_mode:
                if self.parameters['state'] == 'present' and current['enabled'] == 'disabled':
                    self.enable_volume_efficiency()
                    # Checking to see if there are any additional parameters that need to be set after enabling volume efficiency required for Non-AFF systems
                    current = self.get_volume_efficiency()
                    modify = self.na_helper.get_modified_attributes(current, self.parameters)
                elif self.parameters['state'] == 'absent' and current['enabled'] == 'enabled':
                    self.disable_volume_efficiency()

                if 'enabled' in modify:
                    del modify['enabled']
                # Removed the enabled key if there is anything remaining in the modify dict we need to modify.
                if modify:
                    self.modify_volume_efficiency()

        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 10
0
class NetAppOntapUnixGroup(object):
    """
    Common operations to manage UNIX groups
    """
    def __init__(self):
        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'),
                 id=dict(required=False, type='int'),
                 skip_name_validation=dict(required=False, type='bool'),
                 vserver=dict(required=True, type='str'),
                 users=dict(required=False, type='list', elements='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.set_playbook_zapi_key_map()

        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 set_playbook_zapi_key_map(self):
        self.na_helper.zapi_string_keys = {'name': 'group-name'}
        self.na_helper.zapi_int_keys = {'id': 'group-id'}
        self.na_helper.zapi_bool_keys = {
            'skip_name_validation': 'skip-name-validation'
        }

    def get_unix_group(self):
        """
        Checks if the UNIX group exists.

        :return:
            dict() if group found
            None if group is not found
        """

        get_unix_group = netapp_utils.zapi.NaElement(
            'name-mapping-unix-group-get-iter')
        attributes = {
            'query': {
                'unix-group-info': {
                    'group-name': self.parameters['name'],
                    'vserver': self.parameters['vserver'],
                }
            }
        }
        get_unix_group.translate_struct(attributes)
        try:
            result = self.server.invoke_successfully(get_unix_group,
                                                     enable_tunneling=True)
            if result.get_child_by_name('num-records') and int(
                    result.get_child_content('num-records')) >= 1:
                group_info = result['attributes-list']['unix-group-info']
                group_details = dict()
            else:
                return None
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error getting UNIX group %s: %s' %
                                  (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())
        for item_key, zapi_key in self.na_helper.zapi_string_keys.items():
            group_details[item_key] = group_info[zapi_key]
        for item_key, zapi_key in self.na_helper.zapi_int_keys.items():
            group_details[item_key] = self.na_helper.get_value_for_int(
                from_zapi=True, value=group_info[zapi_key])
        if group_info.get_child_by_name('users') is not None:
            group_details['users'] = [
                user.get_child_content('user-name') for user in
                group_info.get_child_by_name('users').get_children()
            ]
        else:
            group_details['users'] = None
        return group_details

    def create_unix_group(self):
        """
        Creates an UNIX group in the specified Vserver

        :return: None
        """
        if self.parameters.get('id') is None:
            self.module.fail_json(
                msg='Error: Missing a required parameter for create: (id)')

        group_create = netapp_utils.zapi.NaElement(
            'name-mapping-unix-group-create')
        group_details = {}
        for item in self.parameters:
            if item in self.na_helper.zapi_string_keys:
                zapi_key = self.na_helper.zapi_string_keys.get(item)
                group_details[zapi_key] = self.parameters[item]
            elif item in self.na_helper.zapi_bool_keys:
                zapi_key = self.na_helper.zapi_bool_keys.get(item)
                group_details[zapi_key] = self.na_helper.get_value_for_bool(
                    from_zapi=False, value=self.parameters[item])
            elif item in self.na_helper.zapi_int_keys:
                zapi_key = self.na_helper.zapi_int_keys.get(item)
                group_details[zapi_key] = self.na_helper.get_value_for_int(
                    from_zapi=True, value=self.parameters[item])
        group_create.translate_struct(group_details)
        try:
            self.server.invoke_successfully(group_create,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error creating UNIX group %s: %s' %
                                  (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())
        if self.parameters.get('users') is not None:
            self.modify_users_in_group()

    def delete_unix_group(self):
        """
        Deletes an UNIX group from a vserver

        :return: None
        """
        group_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'name-mapping-unix-group-destroy',
            **{'group-name': self.parameters['name']})

        try:
            self.server.invoke_successfully(group_delete,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error removing UNIX group %s: %s' %
                                  (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())

    def modify_unix_group(self, params):
        """
        Modify an UNIX group from a vserver
        :param params: modify parameters
        :return: None
        """
        # modify users requires separate zapi.
        if 'users' in params:
            self.modify_users_in_group()
            if len(params) == 1:
                return

        group_modify = netapp_utils.zapi.NaElement(
            'name-mapping-unix-group-modify')
        group_details = {'group-name': self.parameters['name']}
        for key in params:
            if key in self.na_helper.zapi_int_keys:
                zapi_key = self.na_helper.zapi_int_keys.get(key)
                group_details[zapi_key] = self.na_helper.get_value_for_int(
                    from_zapi=True, value=params[key])
        group_modify.translate_struct(group_details)

        try:
            self.server.invoke_successfully(group_modify,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error modifying UNIX group %s: %s' %
                                  (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())

    def modify_users_in_group(self):
        """
        Add/delete one or many users in a UNIX group

        :return: None
        """
        current_users = self.get_unix_group().get('users')
        expect_users = self.parameters.get('users')

        if current_users is None:
            current_users = []
        if expect_users[0] == '' and len(expect_users) == 1:
            expect_users = []

        users_to_remove = list(set(current_users) - set(expect_users))
        users_to_add = list(set(expect_users) - set(current_users))

        if len(users_to_add) > 0:
            for user in users_to_add:
                add_user = netapp_utils.zapi.NaElement(
                    'name-mapping-unix-group-add-user')
                group_details = {
                    'group-name': self.parameters['name'],
                    'user-name': user
                }
                add_user.translate_struct(group_details)
                try:
                    self.server.invoke_successfully(add_user,
                                                    enable_tunneling=True)
                except netapp_utils.zapi.NaApiError as error:
                    self.module.fail_json(
                        msg='Error adding user %s to UNIX group %s: %s' %
                        (user, self.parameters['name'], to_native(error)),
                        exception=traceback.format_exc())

        if len(users_to_remove) > 0:
            for user in users_to_remove:
                delete_user = netapp_utils.zapi.NaElement(
                    'name-mapping-unix-group-delete-user')
                group_details = {
                    'group-name': self.parameters['name'],
                    'user-name': user
                }
                delete_user.translate_struct(group_details)
                try:
                    self.server.invoke_successfully(delete_user,
                                                    enable_tunneling=True)
                except netapp_utils.zapi.NaApiError as error:
                    self.module.fail_json(
                        msg='Error deleting user %s from UNIX group %s: %s' %
                        (user, self.parameters['name'], to_native(error)),
                        exception=traceback.format_exc())

    def autosupport_log(self):
        """
          Autosupport log for unix_group
          :return: None
          """
        netapp_utils.ems_log_event("na_ontap_unix_group", self.server)

    def apply(self):
        """
        Invoke appropriate action based on playbook parameters

        :return: None
        """
        self.autosupport_log()
        current = self.get_unix_group()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if self.parameters['state'] == 'present' and cd_action is None:
            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_unix_group()
                elif cd_action == 'delete':
                    self.delete_unix_group()
                else:
                    self.modify_unix_group(modify)
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 11
0
class NetAppOntapSecurityConfig(object):
    """
        Modifies SSL Security Config
    """
    def __init__(self):
        """
            Initialize the ONTAP Security Config class
        """
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(name=dict(required=False, type='str', default='ssl'),
                 is_fips_enabled=dict(required=False, type='bool'),
                 supported_ciphers=dict(required=False, type='str'),
                 supported_protocols=dict(
                     required=False,
                     type='list',
                     elements='str',
                     choices=['TLSv1.2', 'TLSv1.1', 'TLSv1'])))

        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)

        if 'is_fips_enabled' in self.parameters and 'supported_ciphers' in self.parameters:
            #  if fips is enabled, supported ciphers should not be specified.
            if self.parameters['is_fips_enabled']:
                self.module.fail_json(
                    msg=
                    'is_fips_enabled was specified as true and supported_ciphers was specified. \
                    If fips is enabled then supported ciphers should not be specified'
                )

        if 'is_fips_enabled' in self.parameters and 'supported_protocols' in self.parameters:
            #  if fips is enabled, TLSv1 is not a supported protocol.
            if self.parameters[
                    'is_fips_enabled'] and 'TLSv1' in self.parameters[
                        'supported_protocols']:
                self.module.fail_json(
                    msg=
                    'is_fips_enabled was specified as true and TLSv1 was specified as a supported protocol. \
                    If fips is enabled then TLSv1 is not a supported protocol')

        if 'supported_ciphers' in self.parameters:
            self.parameters['supported_ciphers'] = self.parameters[
                'supported_ciphers'].replace('\\', '')

        self.rest_api = OntapRestAPI(self.module)
        self.use_rest = self.rest_api.is_rest()

        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)

    def get_security_config(self):
        """
            Get the current security configuration
        """
        if self.use_rest:
            api = "private/cli/security/config"
            query = {
                'fields':
                'interface,is-fips-enabled,supported-protocols,supported-ciphers'
            }
            message, error = self.rest_api.get(api, query)
            if error:
                self.module.fail_json(msg=error)
            if not message:
                self.module.fail_json(
                    msg="get_security_config expected a message")

            return_value = {
                'name': message['records'][0]['interface'],
                'is_fips_enabled': message['records'][0]['is_fips_enabled'],
                'supported_ciphers':
                message['records'][0]['supported_ciphers'],
                'supported_protocols':
                message['records'][0]['supported_protocols']
            }
        else:
            return_value = None

            security_config_get_iter = netapp_utils.zapi.NaElement(
                'security-config-get')
            security_config_info = netapp_utils.zapi.NaElement(
                'desired-attributes')

            if 'is_fips_enabled' in self.parameters:
                security_config_info.add_new_child(
                    'is-fips-enabled',
                    self.na_helper.get_value_for_bool(
                        from_zapi=False,
                        value=self.parameters['is_fips_enabled']))
            if 'supported_ciphers' in self.parameters:
                security_config_info.add_new_child(
                    'supported-ciphers', self.parameters['supported_ciphers'])
            if 'supported_protocols' in self.parameters:
                security_config_info.add_new_child(
                    'supported-protocols',
                    ','.join(self.parameters['supported_protocols']))

            security_config_get_iter.add_child_elem(security_config_info)
            security_config_get_iter.add_new_child('interface',
                                                   self.parameters['name'])

            try:
                result = self.server.invoke_successfully(
                    security_config_get_iter, True)
                security_supported_protocols = []

                if result.get_child_by_name('attributes'):
                    attributes = result.get_child_by_name('attributes')
                    security_config_attributes = attributes.get_child_by_name(
                        'security-config-info')
                    supported_protocols = security_config_attributes.get_child_by_name(
                        'supported-protocols')
                    for supported_protocol in supported_protocols.get_children(
                    ):
                        security_supported_protocols.append(
                            supported_protocol.get_content())

                    return_value = {
                        'name':
                        security_config_attributes['interface'],
                        'is_fips_enabled':
                        self.na_helper.get_value_for_bool(
                            from_zapi=True,
                            value=security_config_attributes['is-fips-enabled']
                        ),
                        'supported_ciphers':
                        security_config_attributes['supported-ciphers'],
                        'supported_protocols':
                        security_supported_protocols,
                    }

            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg='Error getting security config for interface %s: %s' %
                    (self.parameters['name'], to_native(error)),
                    exception=traceback.format_exc())

        return return_value

    def modify_security_config(self):
        """
        Modifies the security configuration.
        """
        if self.use_rest:
            #  url contains the value for 'name' due to the interface not being supported through body using the api.
            api = "private/cli/security/config?interface=%s" % (
                self.parameters['name'])
            body = {}
            if 'is_fips_enabled' in self.parameters:
                body['is_fips_enabled'] = self.parameters['is_fips_enabled']
            if 'supported_ciphers' in self.parameters:
                body['supported_ciphers'] = self.parameters[
                    'supported_ciphers']
            if 'supported_protocols' in self.parameters:
                body['supported_protocols'] = self.parameters[
                    'supported_protocols']

            dummy, error = self.rest_api.patch(api, body)

            if error:
                self.module.fail_json(msg=error)
        else:
            security_config_obj = netapp_utils.zapi.NaElement(
                "security-config-modify")
            security_config_obj.add_new_child("interface",
                                              self.parameters['name'])
            if 'is_fips_enabled' in self.parameters:
                self.parameters[
                    'is_fips_enabled'] = self.na_helper.get_value_for_bool(
                        from_zapi=False,
                        value=self.parameters['is_fips_enabled'])
                security_config_obj.add_new_child(
                    'is-fips-enabled', self.parameters['is_fips_enabled'])
            if 'supported_ciphers' in self.parameters:
                security_config_obj.add_new_child(
                    'supported-ciphers', self.parameters['supported_ciphers'])
            if 'supported_protocols' in self.parameters:
                supported_protocol_obj = netapp_utils.zapi.NaElement(
                    "supported-protocols")
                for protocol in self.parameters['supported_protocols']:
                    supported_protocol_obj.add_new_child('string', protocol)
                security_config_obj.add_child_elem(supported_protocol_obj)
            try:
                self.server.invoke_successfully(security_config_obj, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg='Error modifying security config for interface %s: %s'
                    % (self.parameters['name'], to_native(error)),
                    exception=traceback.format_exc())

    def ems_log_event(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_security_config", cserver)

    def apply(self):
        if not self.use_rest:
            self.ems_log_event()
        current = self.get_security_config()
        modify = self.na_helper.get_modified_attributes(
            current, self.parameters)

        if self.na_helper.changed:
            if not self.module.check_mode:
                self.modify_security_config()

        self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapStorageAutoGiveback(object):
    """
        Enable or disable storage failover for a specified node
    """
    def __init__(self):
        """
            Initialize the ONTAP Storage auto giveback class
        """

        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(name=dict(required=True, type='str'),
                 auto_giveback_enabled=dict(required=True, type='bool'),
                 auto_giveback_after_panic_enabled=dict(required=False,
                                                        type='bool')))

        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.rest_api = OntapRestAPI(self.module)
        self.use_rest = self.rest_api.is_rest()

        if not self.use_rest:
            if not netapp_utils.has_netapp_lib():
                self.module.fail_json(
                    msg='The python NetApp-Lib module is required')
            else:
                self.server = netapp_utils.setup_na_ontap_zapi(
                    module=self.module)

    def get_storage_auto_giveback(self):
        """
        get the storage failover giveback options for a given node
        :return: dict for options
        """
        return_value = None

        if self.use_rest:

            api = "private/cli/storage/failover"
            query = {
                'fields': 'node,auto_giveback,auto_giveback_after_panic',
                'node': self.parameters['name'],
            }
            message, error = self.rest_api.get(api, query)
            records, error = rrh.check_for_0_or_1_records(api, message, error)

            if error is None and records is not None:
                return_value = {
                    'name':
                    message['records'][0]['node'],
                    'auto_giveback_enabled':
                    message['records'][0]['auto_giveback'],
                    'auto_giveback_after_panic_enabled':
                    message['records'][0]['auto_giveback_after_panic']
                }

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

            if not records:
                error = "REST API did not return failover options for node %s" % (
                    self.parameters['name'])
                self.module.fail_json(msg=error)

        else:

            storage_auto_giveback_get_iter = netapp_utils.zapi.NaElement(
                'cf-get-iter')

            try:
                result = self.server.invoke_successfully(
                    storage_auto_giveback_get_iter, True)

            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg='Error getting auto giveback info for node %s: %s' %
                    (self.parameters['name'], to_native(error)),
                    exception=traceback.format_exc())

            if result.get_child_by_name('attributes-list'):
                attributes_list = result.get_child_by_name('attributes-list')
                for storage_failover_info_attributes in attributes_list.get_children(
                ):

                    sfo_node_info = storage_failover_info_attributes.get_child_by_name(
                        'sfo-node-info')
                    node_related_info = sfo_node_info.get_child_by_name(
                        'node-related-info')

                    if node_related_info.get_child_content(
                            'node') == self.parameters['name']:

                        sfo_options_info = storage_failover_info_attributes.get_child_by_name(
                            'sfo-options-info')
                        options_related_info = sfo_options_info.get_child_by_name(
                            'options-related-info')
                        sfo_giveback_options_info = options_related_info.get_child_by_name(
                            'sfo-giveback-options-info')
                        giveback_options = sfo_giveback_options_info.get_child_by_name(
                            'giveback-options')

                        return_value = {
                            'name':
                            node_related_info.get_child_content('node'),
                            'auto_giveback_enabled':
                            self.na_helper.get_value_for_bool(
                                True,
                                options_related_info.get_child_content(
                                    'auto-giveback-enabled')),
                            'auto_giveback_after_panic_enabled':
                            self.na_helper.get_value_for_bool(
                                True,
                                giveback_options.get_child_content(
                                    'auto-giveback-after-panic-enabled')),
                        }
                        break

        return return_value

    def modify_storage_auto_giveback(self):
        """
        Modifies storage failover giveback options for a specified node
        """
        if self.use_rest:
            api = "private/cli/storage/failover"
            body = dict()
            query = {'node': self.parameters['name']}

            body['auto_giveback'] = self.parameters['auto_giveback_enabled']
            if 'auto_giveback_after_panic_enabled' in self.parameters:
                body['auto_giveback_after_panic'] = self.parameters[
                    'auto_giveback_after_panic_enabled']

            dummy, error = self.rest_api.patch(api, body, query)
            if error:
                self.module.fail_json(msg=error)

        else:

            storage_auto_giveback_enable = netapp_utils.zapi.NaElement(
                'cf-modify-iter')
            attributes_info = netapp_utils.zapi.NaElement(
                'options-related-info-modify')
            query_info = netapp_utils.zapi.NaElement(
                'options-related-info-modify')

            attributes_info.add_new_child('node', self.parameters['name'])
            attributes_info.add_new_child(
                'auto-giveback-enabled',
                self.na_helper.get_value_for_bool(
                    from_zapi=False,
                    value=self.parameters['auto_giveback_enabled']))

            if 'auto_giveback_after_panic_enabled' in self.parameters:
                sfo_give_back_options_info_modify = netapp_utils.zapi.NaElement(
                    'sfo-giveback-options-info-modify')
                give_back_options_modify = netapp_utils.zapi.NaElement(
                    'giveback-options-modify')
                give_back_options_modify.add_new_child(
                    'auto-giveback-after-panic-enabled',
                    self.na_helper.get_value_for_bool(
                        from_zapi=False,
                        value=self.
                        parameters['auto_giveback_after_panic_enabled']))
                sfo_give_back_options_info_modify.add_child_elem(
                    give_back_options_modify)
                attributes_info.add_child_elem(
                    sfo_give_back_options_info_modify)

            query = netapp_utils.zapi.NaElement('query')
            attributes = netapp_utils.zapi.NaElement("attributes")
            query.add_child_elem(query_info)
            attributes.add_child_elem(attributes_info)

            storage_auto_giveback_enable.add_child_elem(query)
            storage_auto_giveback_enable.add_child_elem(attributes)

            try:
                result = self.server.invoke_successfully(
                    storage_auto_giveback_enable, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg='Error modifying auto giveback for node %s: %s' %
                    (self.parameters['name'], to_native(error)),
                    exception=traceback.format_exc())

    def ems_log_event(self):
        results = netapp_utils.get_cserver(self.server)
        cserver = netapp_utils.setup_na_ontap_zapi(module=self.module,
                                                   vserver=results)
        return netapp_utils.ems_log_event("na_ontap_storage_auto_giveback",
                                          cserver)

    def apply(self):
        if not self.use_rest:
            self.ems_log_event()

        current = self.get_storage_auto_giveback()
        modify = self.na_helper.get_modified_attributes(
            current, self.parameters)

        if self.na_helper.changed:
            if not self.module.check_mode:
                self.modify_storage_auto_giveback()
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 13
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)
Exemplo n.º 14
0
class NetAppONTAPQuotas(object):
    '''Class with quotas methods'''

    def __init__(self):

        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'),
            volume=dict(required=True, type='str'),
            quota_target=dict(required=False, type='str'),
            qtree=dict(required=False, type='str', default=""),
            type=dict(required=False, type='str', choices=['user', 'group', 'tree']),
            policy=dict(required=False, type='str'),
            set_quota_status=dict(required=False, type='bool'),
            perform_user_mapping=dict(required=False, type='bool'),
            file_limit=dict(required=False, type='str'),
            disk_limit=dict(required=False, type='str'),
            soft_file_limit=dict(required=False, type='str'),
            soft_disk_limit=dict(required=False, type='str'),
            threshold=dict(required=False, type='str'),
            activate_quota_on_change=dict(required=False, type='str', choices=['resize', 'reinitialize', 'none'], default='resize')
        ))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            required_by={
                'policy': ['quota_target', 'type'],
                'perform_user_mapping': ['quota_target', 'type'],
                'file_limit': ['quota_target', 'type'],
                'disk_limit': ['quota_target', 'type'],
                'soft_file_limit': ['quota_target', 'type'],
                'soft_disk_limit': ['quota_target', 'type'],
                'threshold': ['quota_target', 'type'],
            },
            required_together=[['quota_target', 'type']],
            supports_check_mode=True
        )

        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)

        # converted blank parameter to * as shown in vsim
        if self.parameters.get('quota_target') == "":
            self.parameters['quota_target'] = '*'

        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_quota_status(self):
        """
        Return details about the quota status
        :param:
            name : volume name
        :return: status of the quota. None if not found.
        :rtype: dict
        """
        quota_status_get = netapp_utils.zapi.NaElement('quota-status')
        quota_status_get.translate_struct({
            'volume': self.parameters['volume']
        })
        try:
            result = self.server.invoke_successfully(quota_status_get, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error fetching quotas status info: %s' % to_native(error),
                                  exception=traceback.format_exc())
        if result:
            return result['status']
        return None

    def get_quotas(self):
        """
        Get quota details
        :return: name of volume if quota exists, None otherwise
        """
        if self.parameters.get('type') is None:
            return None
        quota_get = netapp_utils.zapi.NaElement('quota-list-entries-iter')
        query = {
            'query': {
                'quota-entry': {
                    'volume': self.parameters['volume'],
                    'quota-target': self.parameters['quota_target'],
                    'quota-type': self.parameters['type'],
                    'vserver': self.parameters['vserver']
                }
            }
        }
        quota_get.translate_struct(query)
        if self.parameters.get('policy'):
            quota_get['query']['quota-entry'].add_new_child('policy', self.parameters['policy'])
        try:
            result = self.server.invoke_successfully(quota_get, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error fetching quotas info: %s' % to_native(error),
                                  exception=traceback.format_exc())
        if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
            # if quota-target is '*', the query treats it as a wildcard. But a blank entry is represented as '*'.
            # Hence the need to loop through all records to find a match.
            for quota_entry in result.get_child_by_name('attributes-list').get_children():
                quota_target = quota_entry.get_child_content('quota-target')
                if quota_target == self.parameters['quota_target']:
                    return_values = {'volume': quota_entry.get_child_content('volume'),
                                     'file_limit': quota_entry.get_child_content('file-limit'),
                                     'disk_limit': quota_entry.get_child_content('disk-limit'),
                                     'soft_file_limit': quota_entry.get_child_content('soft-file-limit'),
                                     'soft_disk_limit': quota_entry.get_child_content('soft-disk-limit'),
                                     'threshold': quota_entry.get_child_content('threshold')}
                    value = self.na_helper.safe_get(quota_entry, ['perform-user-mapping'])
                    if value is not None:
                        return_values['perform_user_mapping'] = self.na_helper.get_value_for_bool(True, value)
                    return return_values
        return None

    def quota_entry_set(self):
        """
        Adds a quota entry
        """
        options = {'volume': self.parameters['volume'],
                   'quota-target': self.parameters['quota_target'],
                   'quota-type': self.parameters['type'],
                   'qtree': self.parameters['qtree']}

        if self.parameters.get('file_limit'):
            options['file-limit'] = self.parameters['file_limit']
        if self.parameters.get('disk_limit'):
            options['disk-limit'] = self.parameters['disk_limit']
        if self.parameters.get('perform_user_mapping') is not None:
            options['perform-user-mapping'] = str(self.parameters['perform_user_mapping'])
        if self.parameters.get('soft_file_limit'):
            options['soft-file-limit'] = self.parameters['soft_file_limit']
        if self.parameters.get('soft_disk_limit'):
            options['soft-disk-limit'] = self.parameters['soft_disk_limit']
        if self.parameters.get('threshold'):
            options['threshold'] = self.parameters['threshold']
        if self.parameters.get('policy'):
            options['policy'] = self.parameters['policy']
        set_entry = netapp_utils.zapi.NaElement.create_node_with_children(
            'quota-set-entry', **options)
        try:
            self.server.invoke_successfully(set_entry, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error adding/modifying quota entry %s: %s'
                                  % (self.parameters['volume'], to_native(error)),
                                  exception=traceback.format_exc())

    def quota_entry_delete(self):
        """
        Deletes a quota entry
        """
        options = {'volume': self.parameters['volume'],
                   'quota-target': self.parameters['quota_target'],
                   'quota-type': self.parameters['type'],
                   'qtree': self.parameters['qtree']}
        set_entry = netapp_utils.zapi.NaElement.create_node_with_children(
            'quota-delete-entry', **options)
        if self.parameters.get('policy'):
            set_entry.add_new_child('policy', self.parameters['policy'])
        try:
            self.server.invoke_successfully(set_entry, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error deleting quota entry %s: %s'
                                  % (self.parameters['volume'], to_native(error)),
                                  exception=traceback.format_exc())

    def quota_entry_modify(self, modify_attrs):
        """
        Modifies a quota entry
        """
        options = {'volume': self.parameters['volume'],
                   'quota-target': self.parameters['quota_target'],
                   'quota-type': self.parameters['type'],
                   'qtree': self.parameters['qtree']}
        options.update(modify_attrs)
        if self.parameters.get('file_limit'):
            options['file-limit'] = self.parameters['file_limit']
        if self.parameters.get('disk_limit'):
            options['disk-limit'] = self.parameters['disk_limit']
        if self.parameters.get('perform_user_mapping') is not None:
            options['perform-user-mapping'] = str(self.parameters['perform_user_mapping'])
        if self.parameters.get('soft_file_limit'):
            options['soft-file-limit'] = self.parameters['soft_file_limit']
        if self.parameters.get('soft_disk_limit'):
            options['soft-disk-limit'] = self.parameters['soft_disk_limit']
        if self.parameters.get('threshold'):
            options['threshold'] = self.parameters['threshold']
        if self.parameters.get('policy'):
            options['policy'] = str(self.parameters['policy'])
        modify_entry = netapp_utils.zapi.NaElement.create_node_with_children(
            'quota-modify-entry', **options)
        try:
            self.server.invoke_successfully(modify_entry, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error modifying quota entry %s: %s'
                                  % (self.parameters['volume'], to_native(error)),
                                  exception=traceback.format_exc())

    def on_or_off_quota(self, status):
        """
        on or off quota
        """
        quota = netapp_utils.zapi.NaElement.create_node_with_children(
            status, **{'volume': self.parameters['volume']})
        try:
            self.server.invoke_successfully(quota,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error setting %s for %s: %s'
                                  % (status, self.parameters['volume'], to_native(error)),
                                  exception=traceback.format_exc())

    def resize_quota(self):
        """
        resize quota
        """
        quota = netapp_utils.zapi.NaElement.create_node_with_children(
            'quota-resize', **{'volume': self.parameters['volume']})
        try:
            self.server.invoke_successfully(quota,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error setting %s for %s: %s'
                                  % ('quota-resize', self.parameters['volume'], to_native(error)),
                                  exception=traceback.format_exc())

    def apply(self):
        """
        Apply action to quotas
        """
        netapp_utils.ems_log_event("na_ontap_quotas", self.server)
        cd_action = None
        modify_quota_status = None
        modify_quota = None
        quota_status = None
        current = self.get_quotas()
        if self.parameters.get('type') is not None:
            cd_action = self.na_helper.get_cd_action(current, self.parameters)
            if cd_action is None:
                modify_quota = self.na_helper.get_modified_attributes(current, self.parameters)
        if 'set_quota_status' in self.parameters or modify_quota:
            quota_status = self.get_quota_status()
        if 'set_quota_status' in self.parameters and quota_status is not None:
            quota_status_action = self.na_helper.get_modified_attributes(
                {'set_quota_status': True if quota_status == 'on' else False}, self.parameters)
            if quota_status_action:
                modify_quota_status = 'quota-on' if quota_status_action['set_quota_status'] else 'quota-off'
        if modify_quota is not None and modify_quota_status is None and quota_status == 'on':
            # do we need to resize or reinitialize:
            if self.parameters['activate_quota_on_change'] in ['resize', 'reinitialize']:
                modify_quota_status = self.parameters['activate_quota_on_change']
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.quota_entry_set()
                elif cd_action == 'delete':
                    self.quota_entry_delete()
                elif modify_quota is not None:
                    for key in list(modify_quota):
                        modify_quota[key.replace("_", "-")] = modify_quota.pop(key)
                    self.quota_entry_modify(modify_quota)
                if modify_quota_status in ['quota-off', 'quota-on']:
                    self.on_or_off_quota(modify_quota_status)
                elif modify_quota_status == 'resize':
                    self.resize_quota()
                elif modify_quota_status == 'reinitialize':
                    self.on_or_off_quota('quota-off')
                    time.sleep(10)  # status switch interval
                    self.on_or_off_quota('quota-on')

        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 15
0
class NetAppOntapLUN(object):
    ''' create, modify, delete LUN '''
    def __init__(self):

        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'),
                size=dict(type='int'),
                size_unit=dict(default='gb',
                               choices=[
                                   'bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb',
                                   'eb', 'zb', 'yb'
                               ],
                               type='str'),
                force_resize=dict(default=False, type='bool'),
                force_remove=dict(default=False, type='bool'),
                force_remove_fenced=dict(default=False, type='bool'),
                flexvol_name=dict(type='str'),
                vserver=dict(required=True, type='str'),
                os_type=dict(required=False, type='str', aliases=['ostype']),
                qos_policy_group=dict(required=False, type='str'),
                space_reserve=dict(required=False, type='bool', default=True),
                space_allocation=dict(required=False,
                                      type='bool',
                                      default=False),
                use_exact_size=dict(required=False, type='bool', default=True),
                san_application_template=dict(
                    type='dict',
                    options=dict(
                        use_san_application=dict(type='bool', default=True),
                        name=dict(required=True, type='str'),
                        igroup_name=dict(type='str'),
                        lun_count=dict(type='int'),
                        protection_type=dict(
                            type='dict',
                            options=dict(local_policy=dict(type='str'), )),
                        storage_service=dict(
                            type='str',
                            choices=['value', 'performance', 'extreme']),
                        tiering=dict(
                            type='dict',
                            options=dict(
                                control=dict(type='str',
                                             choices=[
                                                 'required', 'best_effort',
                                                 'disallowed'
                                             ]),
                                policy=dict(type='str',
                                            choices=[
                                                'all', 'auto', 'none',
                                                'snapshot-only'
                                            ]),
                                object_stores=dict(
                                    type='list', elements='str')  # create only
                            )),
                    ))))

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

        # set up state variables
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        if self.parameters.get('size') is not None:
            self.parameters['size'] *= netapp_utils.POW2_BYTE_MAP[
                self.parameters['size_unit']]

        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'])

        # REST API for application/applications if needed
        self.rest_api, self.rest_app = self.setup_rest_application()

    def setup_rest_application(self):
        use_application_template = self.na_helper.safe_get(
            self.parameters,
            ['san_application_template', 'use_san_application'])
        rest_api, rest_app = None, None
        if use_application_template:
            if self.parameters.get('flexvol_name') is not None:
                self.module.fail_json(
                    msg=
                    "'flexvol_name' option is not supported when san_application_template is present"
                )
            rest_api = netapp_utils.OntapRestAPI(self.module)
            name = self.na_helper.safe_get(
                self.parameters, ['san_application_template', 'name'],
                allow_sparse_dict=False)
            rest_app = RestApplication(rest_api, self.parameters['vserver'],
                                       name)
        elif self.parameters.get('flexvol_name') is None:
            self.module.fail_json(
                msg=
                "flexvol_name option is required when san_application_template is not present"
            )
        return rest_api, rest_app

    def get_luns(self, lun_path=None):
        """
        Return list of LUNs matching vserver and volume names.

        :return: list of LUNs in XML format.
        :rtype: list
        """
        luns = []
        tag = None
        if lun_path is None and self.parameters.get('flexvol_name') is None:
            return luns

        query_details = netapp_utils.zapi.NaElement('lun-info')
        query_details.add_new_child('vserver', self.parameters['vserver'])
        if lun_path is not None:
            query_details.add_new_child('lun_path', lun_path)
        else:
            query_details.add_new_child('volume',
                                        self.parameters['flexvol_name'])
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(query_details)

        while True:
            lun_info = netapp_utils.zapi.NaElement('lun-get-iter')
            lun_info.add_child_elem(query)
            if tag:
                lun_info.add_new_child('tag', tag, True)

            result = self.server.invoke_successfully(lun_info, True)
            if result.get_child_by_name('num-records') and int(
                    result.get_child_content('num-records')) >= 1:
                attr_list = result.get_child_by_name('attributes-list')
                luns.extend(attr_list.get_children())
            tag = result.get_child_content('next-tag')
            if tag is None:
                break
        return luns

    def get_lun_details(self, lun):
        """
        Extract LUN details, from XML to python dict

        :return: Details about the lun
        :rtype: dict
        """
        return_value = dict()
        return_value['size'] = int(lun.get_child_content('size'))
        bool_attr_map = {
            'is-space-alloc-enabled': 'space_allocation',
            'is-space-reservation-enabled': 'space_reserve'
        }
        for attr in bool_attr_map:
            value = lun.get_child_content(attr)
            if value is not None:
                return_value[
                    bool_attr_map[attr]] = self.na_helper.get_value_for_bool(
                        True, value)
        str_attr_map = {
            'name': 'name',
            'path': 'path',
            'qos-policy-group': 'qos_policy_group',
            'multiprotocol-type': 'os_type'
        }
        for attr in str_attr_map:
            value = lun.get_child_content(attr)
            if value is not None:
                return_value[str_attr_map[attr]] = value

        # Find out if the lun is attached
        attached_to = None
        lun_id = None
        if lun.get_child_content('mapped') == 'true':
            lun_map_list = netapp_utils.zapi.NaElement.create_node_with_children(
                'lun-map-list-info', **{'path': lun.get_child_content('path')})
            result = self.server.invoke_successfully(lun_map_list,
                                                     enable_tunneling=True)
            igroups = result.get_child_by_name('initiator-groups')
            if igroups:
                for igroup_info in igroups.get_children():
                    igroup = igroup_info.get_child_content(
                        'initiator-group-name')
                    attached_to = igroup
                    lun_id = igroup_info.get_child_content('lun-id')

        return_value.update({'attached_to': attached_to, 'lun_id': lun_id})
        return return_value

    def find_lun(self, luns, name, lun_path=None):
        """
        Return lun record matching name or path

        :return: lun record
        :rtype: XML or None if not found
        """
        for lun in luns:
            path = lun.get_child_content('path')
            if lun_path is not None:
                if lun_path == path:
                    return lun
            else:
                if name == path:
                    return lun
                _rest, _splitter, found_name = path.rpartition('/')
                if found_name == name:
                    return lun
        return None

    def get_lun(self, name, lun_path=None):
        """
        Return details about the LUN

        :return: Details about the lun
        :rtype: dict
        """
        luns = self.get_luns(lun_path)
        lun = self.find_lun(luns, name, lun_path)
        if lun is not None:
            return self.get_lun_details(lun)
        return None

    def get_luns_from_app(self):
        app_details, error = self.rest_app.get_application_details()
        self.fail_on_error(error)
        if app_details is not None:
            app_details['paths'] = self.get_lun_paths_from_app()
        return app_details

    def get_lun_paths_from_app(self):
        """Get luns path for SAN application"""
        backing_storage, error = self.rest_app.get_application_component_backing_storage(
        )
        self.fail_on_error(error)
        # {'luns': [{'path': '/vol/ansibleLUN/ansibleLUN_1', ...
        if backing_storage is not None:
            return [lun['path'] for lun in backing_storage.get('luns', [])]
        return None

    def get_lun_path_from_backend(self, name):
        """returns lun path matching name if found in backing_storage
           retruns None if not found
        """
        lun_paths = self.get_lun_paths_from_app()
        match = "/%s" % name
        for path in lun_paths:
            if path.endswith(match):
                return path
        return None

    def create_san_app_component(self):
        '''Create SAN application component'''
        required_options = ('name', 'size')
        for option in required_options:
            if self.parameters.get(option) is None:
                self.module.fail_json(
                    msg='Error: "%s" is required to create san application.' %
                    option)

        application_component = dict(
            name=self.parameters['name'],
            total_size=self.parameters['size'],
            lun_count=1  # default value, may be overriden below
        )
        for attr in ('igroup_name', 'lun_count', 'storage_service'):
            value = self.na_helper.safe_get(self.parameters,
                                            ['san_application_template', attr])
            if value is not None:
                application_component[attr] = value
        for attr in ('os_type', 'qos_policy_group'):
            value = self.na_helper.safe_get(self.parameters, [attr])
            if value is not None:
                if attr == 'qos_policy_group':
                    attr = 'qos'
                    value = dict(policy=dict(name=value))
                application_component[attr] = value
        tiering = self.na_helper.safe_get(
            self.parameters, ['nas_application_template', 'tiering'])
        if tiering is not None:
            application_component['tiering'] = dict()
            for attr in ('control', 'policy', 'object_stores'):
                value = tiering.get(attr)
                if attr == 'object_stores' and value is not None:
                    value = [dict(name=x) for x in value]
                if value is not None:
                    application_component['tiering'][attr] = value
        return application_component

    def create_san_app_body(self):
        '''Create body for san template'''
        # TODO:
        # Should we support new_igroups?
        # It may raise idempotency issues if the REST call fails if the igroup already exists.
        # And we already have na_ontap_igroups.
        san = {
            'application_components': [self.create_san_app_component()],
        }
        for attr in ('protection_type', ):
            value = self.na_helper.safe_get(self.parameters,
                                            ['san_application_template', attr])
            if value is not None:
                # we expect value to be a dict, but maybe an empty dict
                value = self.na_helper.filter_out_none_entries(value)
                if value:
                    san[attr] = value
        for attr in ('os_type', ):
            value = self.na_helper.safe_get(self.parameters, [attr])
            if value is not None:
                san[attr] = value
        body, error = self.rest_app.create_application_body('san', san)
        return body, error

    def create_san_application(self):
        '''Use REST application/applications san template to create one or more LUNs'''
        body, error = self.create_san_app_body()
        self.fail_on_error(error)
        dummy, error = self.rest_app.create_application(body)
        self.fail_on_error(error)

    def delete_san_application(self):
        '''Use REST application/applications san template to delete one or more LUNs'''
        dummy, error = self.rest_app.delete_application()
        self.fail_on_error(error)

    def create_lun(self):
        """
        Create LUN with requested name and size
        """
        path = '/vol/%s/%s' % (self.parameters['flexvol_name'],
                               self.parameters['name'])
        options = {
            'path': path,
            'size': str(self.parameters['size']),
            'space-reservation-enabled': str(self.parameters['space_reserve']),
            'space-allocation-enabled':
            str(self.parameters['space_allocation']),
            'use-exact-size': str(self.parameters['use_exact_size'])
        }
        if self.parameters.get('os_type') is not None:
            options['ostype'] = self.parameters['os_type']
        if self.parameters.get('qos_policy_group') is not None:
            options['qos-policy-group'] = self.parameters['qos_policy_group']
        lun_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'lun-create-by-size', **options)

        try:
            self.server.invoke_successfully(lun_create, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            self.module.fail_json(
                msg="Error provisioning lun %s of size %s: %s" %
                (self.parameters['name'], self.parameters['size'],
                 to_native(exc)),
                exception=traceback.format_exc())

    def delete_lun(self, path):
        """
        Delete requested LUN
        """
        lun_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'lun-destroy', **{
                'path': path,
                'force': str(self.parameters['force_remove']),
                'destroy-fenced-lun':
                str(self.parameters['force_remove_fenced'])
            })

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

    def resize_lun(self, path):
        """
        Resize requested LUN.

        :return: True if LUN was actually re-sized, false otherwise.
        :rtype: bool
        """
        lun_resize = netapp_utils.zapi.NaElement.create_node_with_children(
            'lun-resize', **{
                'path': path,
                'size': str(self.parameters['size']),
                'force': str(self.parameters['force_resize'])
            })
        try:
            self.server.invoke_successfully(lun_resize, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            if to_native(exc.code) == "9042":
                # Error 9042 denotes the new LUN size being the same as the
                # old LUN size. This happens when there's barely any difference
                # in the two sizes. For example, from 8388608 bytes to
                # 8194304 bytes. This should go away if/when the default size
                # requested/reported to/from the controller is changed to a
                # larger unit (MB/GB/TB).
                return False
            else:
                self.module.fail_json(msg="Error resizing lun %s: %s" %
                                      (path, to_native(exc)),
                                      exception=traceback.format_exc())

        return True

    def set_lun_value(self, path, key, value):
        key_to_zapi = dict(qos_policy_group=('lun-set-qos-policy-group',
                                             'qos-policy-group'),
                           space_allocation=('lun-set-space-alloc', 'enable'),
                           space_reserve=('lun-set-space-reservation-info',
                                          'enable'))
        if key in key_to_zapi:
            zapi, option = key_to_zapi[key]
        else:
            self.module.fail_json(msg="option %s cannot be modified to %s" %
                                  (key, value))
        options = dict(path=path)
        if option == 'enable':
            options[option] = self.na_helper.get_value_for_bool(False, value)
        else:
            options[option] = value

        lun_set = netapp_utils.zapi.NaElement.create_node_with_children(
            zapi, **options)
        try:
            self.server.invoke_successfully(lun_set, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            self.module.fail_json(msg="Error setting lun option %s: %s" %
                                  (key, to_native(exc)),
                                  exception=traceback.format_exc())
        return

    def modify_lun(self, path, modify):
        """
        update LUN properties (except size or name)
        """
        for key, value in modify.items():
            self.set_lun_value(path, key, value)

    def rename_lun(self, path, new_path):
        """
        rename LUN
        """
        lun_move = netapp_utils.zapi.NaElement.create_node_with_children(
            'lun-move', **{
                'path': path,
                'new-path': new_path
            })
        try:
            self.server.invoke_successfully(lun_move, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            self.module.fail_json(msg="Error moving lun %s: %s" %
                                  (path, to_native(exc)),
                                  exception=traceback.format_exc())

    def fail_on_error(self, error, stack=False):
        if error is None:
            return
        elements = dict(msg="Error: %s" % error)
        if stack:
            elements['stack'] = traceback.format_stack()
        self.module.fail_json(**elements)

    def apply(self):
        results = dict()
        warnings = list()
        netapp_utils.ems_log_event("na_ontap_lun", self.server)
        app_cd_action = None
        if self.rest_app:
            app_current, error = self.rest_app.get_application_uuid()
            self.fail_on_error(error)
            app_cd_action = self.na_helper.get_cd_action(
                app_current, self.parameters)
            if app_cd_action == 'create' and self.parameters.get(
                    'size') is None:
                self.module.fail_json(
                    msg="size is a required parameter for create.")

        # For LUNs created using a SAN application, we're getting lun paths from the backing storage
        lun_path, from_lun_path = None, None
        from_name = self.parameters.get('from_name')
        if self.rest_app and app_cd_action is None and app_current:
            lun_path = self.get_lun_path_from_backend(self.parameters['name'])
            if from_name is not None:
                from_lun_path = self.get_lun_path_from_backend(from_name)

        if app_cd_action is None:
            # actions at LUN level
            current = self.get_lun(self.parameters['name'], lun_path)
            if current is not None and lun_path is None:
                lun_path = current['path']
            cd_action = self.na_helper.get_cd_action(current, self.parameters)
            modify, rename = None, None
            if cd_action == 'create' and from_name is not None:
                # create by renaming existing LUN, if it really exists
                old_lun = self.get_lun(from_name, from_lun_path)
                rename = self.na_helper.is_rename_action(old_lun, current)
                if rename is None:
                    self.module.fail_json(
                        msg="Error renaming lun: %s does not exist" %
                        from_name)
                if rename:
                    current = old_lun
                    if from_lun_path is None:
                        from_lun_path = current['path']
                    head, _sep, tail = from_lun_path.rpartition(from_name)
                    if tail:
                        self.module.fail_json(
                            msg=
                            "Error renaming lun: %s does not match lun_path %s"
                            % (from_name, from_lun_path))
                    lun_path = head + self.parameters['name']
                    results['renamed'] = True
                    cd_action = None
            if cd_action == 'create' and self.parameters.get('size') is None:
                self.module.fail_json(
                    msg="size is a required parameter for create.")
            if cd_action is None and self.parameters['state'] == 'present':
                # we already handled rename if required
                current.pop('name', None)
                modify = self.na_helper.get_modified_attributes(
                    current, self.parameters)
                results['modify'] = dict(modify)
            if cd_action and self.rest_app and app_cd_action is None and app_current:
                msg = 'This module does not support %s a LUN by name %s a SAN application.' %\
                      ('adding', 'to') if cd_action == 'create' else ('removing', 'from')
                warnings.append(msg)
                cd_action = None
                self.na_helper.changed = False

        if self.na_helper.changed and not self.module.check_mode:
            if app_cd_action == 'create':
                self.create_san_application()
            elif app_cd_action == 'delete':
                self.rest_app.delete_application()
            elif cd_action == 'create':
                self.create_lun()
            elif cd_action == 'delete':
                self.delete_lun(lun_path)
            else:
                if rename:
                    self.rename_lun(from_lun_path, lun_path)
                size_changed = False
                if modify and 'size' in modify:
                    # Ensure that size was actually changed. Please
                    # read notes in 'resize_lun' function for details.
                    size_changed = self.resize_lun(lun_path)
                    modify.pop('size')
                if modify:
                    self.modify_lun(lun_path, modify)
                if not modify and not rename:
                    # size may not have changed
                    self.na_helper.changed = size_changed

        results['changed'] = self.na_helper.changed
        self.module.exit_json(**results)
class NetAppOntapEfficiencyPolicy(object):
    """
    Create, delete and modify efficiency policy
    """
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(required=False,
                            choices=['present', 'absent'],
                            default='present'),
                 policy_name=dict(required=True, type='str'),
                 comment=dict(required=False, type='str'),
                 duration=dict(required=False, type='int'),
                 enabled=dict(required=False, type='bool'),
                 policy_type=dict(required=False,
                                  choices=['threshold', 'scheduled']),
                 qos_policy=dict(required=False,
                                 choices=['background', 'best_effort']),
                 schedule=dict(reuired=False, type='str'),
                 vserver=dict(required=True, type='str'),
                 changelog_threshold_percent=dict(required=False, type='int')))
        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True,
            mutually_exclusive=[('changelog_threshold_percent', 'duration'),
                                ('changelog_threshold_percent', 'schedule')])
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        self.set_playbook_zapi_key_map()
        if self.parameters.get('policy_type'):
            if self.parameters['policy_type'] == 'threshold':
                if self.parameters.get('duration'):
                    self.module.fail_json(
                        msg="duration cannot be set if policy_type is threshold"
                    )
                if self.parameters.get('schedule'):
                    self.module.fail_json(
                        msg='schedule cannot be set if policy_type is threshold'
                    )
            # if policy_type is 'scheduled'
            else:
                if self.parameters.get('changelog_threshold_percent'):
                    self.module.fail_json(
                        msg=
                        'changelog_threshold_percent cannot be set if policy_type is scheduled'
                    )
        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 set_playbook_zapi_key_map(self):

        self.na_helper.zapi_int_keys = {
            'changelog_threshold_percent': 'changelog-threshold-percent',
            'duration': 'duration',
        }
        self.na_helper.zapi_str_keys = {
            'policy_name': 'policy-name',
            'comment': 'comment',
            'policy_type': 'policy-type',
            'qos_policy': 'qos-policy',
            'schedule': 'schedule'
        }
        self.na_helper.zapi_bool_keys = {'enabled': 'enabled'}

    def get_efficiency_policy(self):
        """
        Get a efficiency policy
        :return: a efficiency-policy info
        """
        sis_policy_obj = netapp_utils.zapi.NaElement("sis-policy-get-iter")
        query = netapp_utils.zapi.NaElement("query")
        sis_policy_info = netapp_utils.zapi.NaElement("sis-policy-info")
        sis_policy_info.add_new_child("policy-name",
                                      self.parameters['policy_name'])
        sis_policy_info.add_new_child("vserver", self.parameters['vserver'])
        query.add_child_elem(sis_policy_info)
        sis_policy_obj.add_child_elem(query)
        try:
            results = self.server.invoke_successfully(sis_policy_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg="Error searching for efficiency policy %s: %s" %
                (self.parameters['policy_name'], to_native(error)),
                exception=traceback.format_exc())
        return_value = {}
        if results.get_child_by_name('num-records') and int(
                results.get_child_content('num-records')) == 1:
            attributes_list = results.get_child_by_name('attributes-list')
            sis_info = attributes_list.get_child_by_name('sis-policy-info')
            for option, zapi_key in self.na_helper.zapi_int_keys.items():
                return_value[option] = self.na_helper.get_value_for_int(
                    from_zapi=True, value=sis_info.get_child_content(zapi_key))
            for option, zapi_key in self.na_helper.zapi_bool_keys.items():
                return_value[option] = self.na_helper.get_value_for_bool(
                    from_zapi=True, value=sis_info.get_child_content(zapi_key))
            for option, zapi_key in self.na_helper.zapi_str_keys.items():
                return_value[option] = sis_info.get_child_content(zapi_key)
            return return_value
        return None

    def create_efficiency_policy(self):
        """
        Creates a efficiency policy
        :return: None
        """
        sis_policy_obj = netapp_utils.zapi.NaElement("sis-policy-create")
        for option, zapi_key in self.na_helper.zapi_int_keys.items():
            if self.parameters.get(option):
                sis_policy_obj.add_new_child(
                    zapi_key,
                    self.na_helper.get_value_for_int(
                        from_zapi=False, value=self.parameters[option]))
        for option, zapi_key in self.na_helper.zapi_bool_keys.items():
            if self.parameters.get(option):
                sis_policy_obj.add_new_child(
                    zapi_key,
                    self.na_helper.get_value_for_bool(
                        from_zapi=False, value=self.parameters[option]))
        for option, zapi_key in self.na_helper.zapi_str_keys.items():
            if self.parameters.get(option):
                sis_policy_obj.add_new_child(zapi_key,
                                             str(self.parameters[option]))
        try:
            self.server.invoke_successfully(sis_policy_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg="Error creating efficiency policy %s: %s" %
                (self.parameters["policy_name"], to_native(error)),
                exception=traceback.format_exc())

    def delete_efficiency_policy(self):
        """
        Delete a efficiency Policy
        :return: None
        """
        sis_policy_obj = netapp_utils.zapi.NaElement("sis-policy-delete")
        sis_policy_obj.add_new_child("policy-name",
                                     self.parameters['policy_name'])
        try:
            self.server.invoke_successfully(sis_policy_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg="Error deleting efficiency policy %s: %s" %
                (self.parameters["policy_name"], to_native(error)),
                exception=traceback.format_exc())

    def modify_efficiency_policy(self, current, modify):
        """
        Modify a efficiency policy
        :return: None
        """
        sis_policy_obj = netapp_utils.zapi.NaElement("sis-policy-modify")
        sis_policy_obj.add_new_child("policy-name",
                                     self.parameters['policy_name'])
        # sis-policy-create zapi pre-checks the options and fails if it's not supported.
        # sis-policy-modify pre-checks one of the options, but tries to modify the others even it's not supported. And it will mess up the vsim.
        # Do the checks before sending to the zapi.
        if current['policy_type'] == 'scheduled' and self.parameters.get(
                'policy_type') != 'threshold':
            if modify.get('changelog_threshold_percent'):
                self.module.fail_json(
                    msg=
                    "changelog_threshold_percent cannot be set if policy_type is scheduled"
                )
        elif current['policy_type'] == 'threshold' and self.parameters.get(
                'policy_type') != 'scheduled':
            if modify.get('duration'):
                self.module.fail_json(
                    msg="duration cannot be set if policy_type is threshold")
            elif modify.get('schedule'):
                self.module.fail_json(
                    msg="schedule cannot be set if policy_type is threshold")
        for attribute in modify:
            sis_policy_obj.add_new_child(self.attribute_to_name(attribute),
                                         str(self.parameters[attribute]))
        try:
            self.server.invoke_successfully(sis_policy_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg="Error modifying efficiency policy %s: %s" %
                (self.parameters["policy_name"], to_native(error)),
                exception=traceback.format_exc())

    @staticmethod
    def attribute_to_name(attribute):
        return str.replace(attribute, '_', '-')

    def apply(self):
        netapp_utils.ems_log_event("na_ontap_efficiency_policy", self.server)
        current = self.get_efficiency_policy()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if 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_efficiency_policy()
                elif cd_action == 'delete':
                    self.delete_efficiency_policy()
                elif modify:
                    self.modify_efficiency_policy(current, modify)
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 17
0
class NetAppOntapUser(object):
    """
    Common operations to manage users and roles.
    """
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(
                state=dict(required=False,
                           choices=['present', 'absent'],
                           default='present'),
                name=dict(required=True, type='str'),
                applications=dict(
                    required=True,
                    type='list',
                    aliases=['application'],
                    choices=[
                        'console', 'http', 'ontapi', 'rsh', 'snmp', 'sp',
                        'service-processor', 'ssh', 'telnet'
                    ],
                ),
                authentication_method=dict(required=True,
                                           type='str',
                                           choices=[
                                               'community', 'password',
                                               'publickey', 'domain',
                                               'nsswitch', 'usm', 'cert'
                                           ]),
                set_password=dict(required=False, type='str', no_log=True),
                role_name=dict(required=False, type='str'),
                lock_user=dict(required=False, type='bool'),
                vserver=dict(required=True, type='str'),
            ))

        self.module = AnsibleModule(argument_spec=self.argument_spec,
                                    required_if=[('state', 'present',
                                                  ['role_name'])],
                                    supports_check_mode=True)

        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)

        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_user(self, application=None):
        """
        Checks if the user exists.
        :param: application: application to grant access to
        :return:
            Dictionary if user found
            None if user is not found
        """
        security_login_get_iter = netapp_utils.zapi.NaElement(
            'security-login-get-iter')
        query_details = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-account-info', **{
                'vserver': self.parameters['vserver'],
                'user-name': self.parameters['name'],
                'authentication-method':
                self.parameters['authentication_method']
            })
        if application is not None:
            query_details.add_new_child('application', application)
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(query_details)
        security_login_get_iter.add_child_elem(query)
        try:
            result = self.server.invoke_successfully(security_login_get_iter,
                                                     enable_tunneling=False)
            if result.get_child_by_name('num-records') and \
                    int(result.get_child_content('num-records')) >= 1:
                interface_attributes = result.get_child_by_name('attributes-list').\
                    get_child_by_name('security-login-account-info')
                return_value = {
                    'lock_user':
                    interface_attributes.get_child_content('is-locked'),
                    'role_name':
                    interface_attributes.get_child_content('role-name')
                }
                return return_value
            return None
        except netapp_utils.zapi.NaApiError as error:
            # Error 16034 denotes a user not being found.
            if to_native(error.code) == "16034":
                return None
            # Error 16043 denotes the user existing, but the application missing
            elif to_native(error.code) == "16043":
                return None
            else:
                self.module.fail_json(
                    msg='Error getting user %s: %s' %
                    (self.parameters['name'], to_native(error)),
                    exception=traceback.format_exc())

    def create_user(self, application):
        """
        creates the user for the given application and authentication_method
        :param: application: application to grant access to
        """
        user_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-create', **{
                'vserver': self.parameters['vserver'],
                'user-name': self.parameters['name'],
                'application': application,
                'authentication-method':
                self.parameters['authentication_method'],
                'role-name': self.parameters.get('role_name')
            })
        if self.parameters.get('set_password') is not None:
            user_create.add_new_child('password',
                                      self.parameters.get('set_password'))

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

    def lock_given_user(self):
        """
        locks the user

        :return:
            True if user locked
            False if lock user is not performed
        :rtype: bool
        """
        user_lock = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-lock', **{
                'vserver': self.parameters['vserver'],
                'user-name': self.parameters['name']
            })

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

    def unlock_given_user(self):
        """
        unlocks the user

        :return:
            True if user unlocked
            False if unlock user is not performed
        :rtype: bool
        """
        user_unlock = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-unlock', **{
                'vserver': self.parameters['vserver'],
                'user-name': self.parameters['name']
            })

        try:
            self.server.invoke_successfully(user_unlock,
                                            enable_tunneling=False)
        except netapp_utils.zapi.NaApiError as error:
            if to_native(error.code) == '13114':
                return False
            else:
                self.module.fail_json(
                    msg='Error unlocking user %s: %s' %
                    (self.parameters['name'], to_native(error)),
                    exception=traceback.format_exc())
        return True

    def delete_user(self, application):
        """
        deletes the user for the given application and authentication_method
        :param: application: application to grant access to
        """
        user_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-delete', **{
                'vserver': self.parameters['vserver'],
                'user-name': self.parameters['name'],
                'application': application,
                'authentication-method':
                self.parameters['authentication_method']
            })

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

    def change_password(self):
        """
        Changes the password

        :return:
            True if password updated
            False if password is not updated
        :rtype: bool
        """
        # self.server.set_vserver(self.parameters['vserver'])
        modify_password = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-modify-password', **{
                'new-password': str(self.parameters.get('set_password')),
                'user-name': self.parameters['name']
            })
        try:
            self.server.invoke_successfully(modify_password,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            if to_native(error.code) == '13114':
                return False
            # if the user give the same password, instead of returning an error, return ok
            if to_native(error.code) == '13214' and \
                    (error.message.startswith('New password must be different than last 6 passwords.')
                     or error.message.startswith('New password must be different than the old password.')):
                return False
            self.module.fail_json(
                msg='Error setting password for user %s: %s' %
                (self.parameters['name'], to_native(error)),
                exception=traceback.format_exc())

        self.server.set_vserver(None)
        return True

    def modify_user(self, application):
        """
        Modify user
        """
        user_modify = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-modify', **{
                'vserver': self.parameters['vserver'],
                'user-name': self.parameters['name'],
                'application': application,
                'authentication-method':
                self.parameters['authentication_method'],
                'role-name': self.parameters.get('role_name')
            })

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

    def apply(self):
        create_delete_decision = {}
        modify_decision = {}

        netapp_utils.ems_log_event("na_ontap_user", self.server)
        for application in self.parameters['applications']:
            current = self.get_user(application)
            if current is not None:
                current['lock_user'] = self.na_helper.get_value_for_bool(
                    True, current['lock_user'])

            cd_action = self.na_helper.get_cd_action(current, self.parameters)

            if cd_action is not None:
                create_delete_decision[application] = cd_action
            else:
                modify_decision[
                    application] = self.na_helper.get_modified_attributes(
                        current, self.parameters)

        if not create_delete_decision and self.parameters.get(
                'state') == 'present':
            if self.parameters.get('set_password') is not None:
                self.na_helper.changed = True

        if self.na_helper.changed:

            if self.module.check_mode:
                pass
            else:
                for application in create_delete_decision:
                    if create_delete_decision[application] == 'create':
                        self.create_user(application)
                    elif create_delete_decision[application] == 'delete':
                        self.delete_user(application)
                lock_user = False
                for application in modify_decision:
                    if 'role_name' in modify_decision[application]:
                        self.modify_user(application)
                    if 'lock_user' in modify_decision[application]:
                        lock_user = True

                if lock_user:
                    if self.parameters.get('lock_user'):
                        self.lock_given_user()
                    else:
                        self.unlock_given_user()
                if not create_delete_decision and self.parameters.get(
                        'set_password') is not None:
                    # if change password return false nothing has changed so we need to set changed to False
                    self.na_helper.changed = self.change_password()
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 18
0
class NetAppOntapLogForward(object):
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(choices=['present', 'absent'], default='present'),
                 destination=dict(required=True, type='str'),
                 port=dict(required=True, type='int'),
                 facility=dict(required=False,
                               type='str',
                               choices=[
                                   'kern', 'user', 'local0', 'local1',
                                   'local2', 'local3', 'local4', 'local5',
                                   'local6', 'local7'
                               ]),
                 force=dict(required=False, type='bool'),
                 protocol=dict(required=False,
                               type='str',
                               choices=[
                                   'udp_unencrypted', 'tcp_unencrypted',
                                   'tcp_encrypted'
                               ]),
                 verify_server=dict(required=False, type='bool')))
        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.rest_api = OntapRestAPI(self.module)
        self.use_rest = self.rest_api.is_rest()

        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)

    def get_log_forward_config(self):
        """
        gets log forward configuration
        :return: dict of log forward properties if exist, None if not
        """

        if self.use_rest:
            log_forward_config = None
            api = "security/audit/destinations"
            query = {
                'fields': 'port,protocol,facility,address,verify_server',
                'address': self.parameters['destination'],
                'port': self.parameters['port']
            }

            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_security_key_manager from %s: %s" % (
                    api, repr(message))
                self.module.fail_json(msg=error)
            log_forward_config = {
                'destination': message['records'][0]['address'],
                'facility': message['records'][0]['facility'],
                'port': message['records'][0]['port'],
                'protocol': message['records'][0]['protocol'],
                'verify_server': message['records'][0]['verify_server']
            }

            return log_forward_config

        else:
            log_forward_config = None

            log_forward_get = netapp_utils.zapi.NaElement(
                'cluster-log-forward-get')
            log_forward_get.add_new_child('destination',
                                          self.parameters['destination'])
            log_forward_get.add_new_child(
                'port',
                self.na_helper.get_value_for_int(False,
                                                 self.parameters['port']))

            try:
                result = self.server.invoke_successfully(log_forward_get, True)
            except netapp_utils.zapi.NaApiError as error:
                if to_native(error.code) == "15661":
                    # config doesnt exist
                    return None
                else:
                    self.module.fail_json(
                        msg=
                        'Error getting log forward configuration for destination %s on port %s: %s'
                        % (self.parameters['destination'],
                           self.na_helper.get_value_for_int(
                               False,
                               self.parameters['port']), to_native(error)),
                        exception=traceback.format_exc())

            if result.get_child_by_name('attributes'):
                log_forward_attributes = result.get_child_by_name('attributes')
                cluster_log_forward_info = log_forward_attributes.get_child_by_name(
                    'cluster-log-forward-info')
                log_forward_config = {
                    'destination':
                    cluster_log_forward_info.get_child_content('destination'),
                    'facility':
                    cluster_log_forward_info.get_child_content('facility'),
                    'port':
                    self.na_helper.get_value_for_int(
                        True,
                        cluster_log_forward_info.get_child_content('port')),
                    'protocol':
                    cluster_log_forward_info.get_child_content('protocol'),
                    'verify_server':
                    self.na_helper.get_value_for_bool(
                        True,
                        cluster_log_forward_info.get_child_content(
                            'verify-server'))
                }

            return log_forward_config

    def create_log_forward_config(self):
        """
        Creates a log forward config
        :return: nothing
        """

        if self.use_rest:
            api = "security/audit/destinations"
            body = dict()
            body['address'] = self.parameters['destination']
            body['port'] = self.parameters['port']

            for attr in ('protocol', 'facility', 'verify_server', 'force'):
                if attr in self.parameters:
                    body[attr] = self.parameters[attr]

            dummy, error = self.rest_api.post(api, body)
            if error:
                self.module.fail_json(msg=error)

        else:
            log_forward_config_obj = netapp_utils.zapi.NaElement(
                'cluster-log-forward-create')
            log_forward_config_obj.add_new_child(
                'destination', self.parameters['destination'])
            log_forward_config_obj.add_new_child(
                'port',
                self.na_helper.get_value_for_int(False,
                                                 self.parameters['port']))

            if 'facility' in self.parameters:
                log_forward_config_obj.add_new_child(
                    'facility', self.parameters['facility'])

            if 'force' in self.parameters:
                log_forward_config_obj.add_new_child(
                    'force',
                    self.na_helper.get_value_for_bool(
                        False, self.parameters['force']))

            if 'protocol' in self.parameters:
                log_forward_config_obj.add_new_child(
                    'protocol', self.parameters['protocol'])

            if 'verify_server' in self.parameters:
                log_forward_config_obj.add_new_child(
                    'verify-server',
                    self.na_helper.get_value_for_bool(
                        False, self.parameters['verify_server']))

            try:
                self.server.invoke_successfully(log_forward_config_obj, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg=
                    'Error creating log forward config with destination %s on port %s: %s'
                    % (self.parameters['destination'],
                       self.na_helper.get_value_for_int(
                           False, self.parameters['port']), to_native(error)),
                    exception=traceback.format_exc())

    def modify_log_forward_config(self):
        # need to recreate as protocol can't be changed
        self.destroy_log_forward_config()
        self.create_log_forward_config()

    def destroy_log_forward_config(self):
        """
        Delete a log forward configuration
        :return: nothing
        """
        if self.use_rest:

            api = "security/audit/destinations/%s/%s" % (
                self.parameters['destination'], self.parameters['port'])
            body = None
            query = {'return_timeout': 3}
            dummy, error = self.rest_api.delete(api, body, query)
            if error:
                self.module.fail_json(msg=error)

        else:
            log_forward_config_obj = netapp_utils.zapi.NaElement(
                'cluster-log-forward-destroy')
            log_forward_config_obj.add_new_child(
                'destination', self.parameters['destination'])
            log_forward_config_obj.add_new_child(
                'port',
                self.na_helper.get_value_for_int(False,
                                                 self.parameters['port']))

            try:
                self.server.invoke_successfully(log_forward_config_obj, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg=
                    'Error destroying log forward destination %s on port %s: %s'
                    % (self.parameters['destination'],
                       self.na_helper.get_value_for_int(
                           False, self.parameters['port']), to_native(error)),
                    exception=traceback.format_exc())

    def ems_log_event(self):
        results = netapp_utils.get_cserver(self.server)
        cserver = netapp_utils.setup_na_ontap_zapi(module=self.module,
                                                   vserver=results)
        return netapp_utils.ems_log_event("na_ontap_log_forward", cserver)

    def apply(self):
        if not self.use_rest:
            self.ems_log_event()

        current = self.get_log_forward_config()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)

        if 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 not self.module.check_mode:
                if cd_action == 'create':
                    self.create_log_forward_config()
                elif cd_action == 'delete':
                    self.destroy_log_forward_config()
                elif modify:
                    self.modify_log_forward_config()

        self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapLUN(object):
    ''' create, modify, delete LUN '''
    def __init__(self):

        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'),
            size=dict(type='int'),
            size_unit=dict(default='gb',
                           choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb',
                                    'pb', 'eb', 'zb', 'yb'], type='str'),
            comment=dict(required=False, type='str'),
            force_resize=dict(default=False, type='bool'),
            force_remove=dict(default=False, type='bool'),
            force_remove_fenced=dict(default=False, type='bool'),
            flexvol_name=dict(type='str'),
            vserver=dict(required=True, type='str'),
            os_type=dict(required=False, type='str', aliases=['ostype']),
            qos_policy_group=dict(required=False, type='str'),
            qos_adaptive_policy_group=dict(required=False, type='str'),
            space_reserve=dict(required=False, type='bool', default=True),
            space_allocation=dict(required=False, type='bool', default=False),
            use_exact_size=dict(required=False, type='bool', default=True),
            san_application_template=dict(type='dict', options=dict(
                use_san_application=dict(type='bool', default=True),
                name=dict(required=True, type='str'),
                igroup_name=dict(type='str'),
                lun_count=dict(type='int'),
                protection_type=dict(type='dict', options=dict(
                    local_policy=dict(type='str'),
                )),
                storage_service=dict(type='str', choices=['value', 'performance', 'extreme']),
                tiering=dict(type='dict', options=dict(
                    control=dict(type='str', choices=['required', 'best_effort', 'disallowed']),
                    policy=dict(type='str', choices=['all', 'auto', 'none', 'snapshot-only']),
                    object_stores=dict(type='list', elements='str')     # create only
                )),
                total_size=dict(type='int'),
                total_size_unit=dict(choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb',
                                              'pb', 'eb', 'zb', 'yb'], type='str'),
                scope=dict(type='str', choices=['application', 'auto', 'lun'], default='auto'),
            ))
        ))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True,
            mutually_exclusive=[('qos_policy_group', 'qos_adaptive_policy_group')]
        )

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

        if self.parameters.get('size') is not None:
            self.parameters['size'] *= netapp_utils.POW2_BYTE_MAP[self.parameters['size_unit']]
        if self.na_helper.safe_get(self.parameters, ['san_application_template', 'total_size']) is not None:
            unit = self.na_helper.safe_get(self.parameters, ['san_application_template', 'total_size_unit'])
            if unit is None:
                unit = self.parameters['size_unit']
            self.parameters['san_application_template']['total_size'] *= netapp_utils.POW2_BYTE_MAP[unit]

        self.warnings = list()
        self.debug = dict()
        # self.debug['got'] = 'empty'     # uncomment to enable collecting data

        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'])

        # REST API for application/applications if needed
        self.rest_api, self.rest_app = self.setup_rest_application()

    def setup_rest_application(self):
        use_application_template = self.na_helper.safe_get(self.parameters, ['san_application_template', 'use_san_application'])
        rest_api, rest_app = None, None
        if use_application_template:
            if self.parameters.get('flexvol_name') is not None:
                self.module.fail_json(msg="'flexvol_name' option is not supported when san_application_template is present")
            rest_api = netapp_utils.OntapRestAPI(self.module)
            name = self.na_helper.safe_get(self.parameters, ['san_application_template', 'name'], allow_sparse_dict=False)
            rest_app = RestApplication(rest_api, self.parameters['vserver'], name)
        elif self.parameters.get('flexvol_name') is None:
            self.module.fail_json(msg="flexvol_name option is required when san_application_template is not present")
        return rest_api, rest_app

    def get_luns(self, lun_path=None):
        """
        Return list of LUNs matching vserver and volume names.

        :return: list of LUNs in XML format.
        :rtype: list
        """
        luns = []
        tag = None
        if lun_path is None and self.parameters.get('flexvol_name') is None:
            return luns

        query_details = netapp_utils.zapi.NaElement('lun-info')
        query_details.add_new_child('vserver', self.parameters['vserver'])
        if lun_path is not None:
            query_details.add_new_child('lun_path', lun_path)
        else:
            query_details.add_new_child('volume', self.parameters['flexvol_name'])
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(query_details)

        while True:
            lun_info = netapp_utils.zapi.NaElement('lun-get-iter')
            lun_info.add_child_elem(query)
            if tag:
                lun_info.add_new_child('tag', tag, True)

            result = self.server.invoke_successfully(lun_info, True)
            if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
                attr_list = result.get_child_by_name('attributes-list')
                luns.extend(attr_list.get_children())
            tag = result.get_child_content('next-tag')
            if tag is None:
                break
        return luns

    def get_lun_details(self, lun):
        """
        Extract LUN details, from XML to python dict

        :return: Details about the lun
        :rtype: dict
        """
        return_value = dict()
        return_value['size'] = int(lun.get_child_content('size'))
        bool_attr_map = {
            'is-space-alloc-enabled': 'space_allocation',
            'is-space-reservation-enabled': 'space_reserve'
        }
        for attr in bool_attr_map:
            value = lun.get_child_content(attr)
            if value is not None:
                return_value[bool_attr_map[attr]] = self.na_helper.get_value_for_bool(True, value)
        str_attr_map = {
            'comment': 'comment',
            'multiprotocol-type': 'os_type',
            'name': 'name',
            'path': 'path',
            'qos-policy-group': 'qos_policy_group',
            'qos-adaptive-policy-group': 'qos_adaptive_policy_group',
        }
        for attr in str_attr_map:
            value = lun.get_child_content(attr)
            if value is None and attr in ('comment', 'qos-policy-group', 'qos-adaptive-policy-group'):
                value = ''
            if value is not None:
                return_value[str_attr_map[attr]] = value

        # Find out if the lun is attached
        attached_to = None
        lun_id = None
        if lun.get_child_content('mapped') == 'true':
            lun_map_list = netapp_utils.zapi.NaElement.create_node_with_children(
                'lun-map-list-info', **{'path': lun.get_child_content('path')})
            result = self.server.invoke_successfully(
                lun_map_list, enable_tunneling=True)
            igroups = result.get_child_by_name('initiator-groups')
            if igroups:
                for igroup_info in igroups.get_children():
                    igroup = igroup_info.get_child_content(
                        'initiator-group-name')
                    attached_to = igroup
                    lun_id = igroup_info.get_child_content('lun-id')

        return_value.update({
            'attached_to': attached_to,
            'lun_id': lun_id
        })
        return return_value

    def find_lun(self, luns, name, lun_path=None):
        """
        Return lun record matching name or path

        :return: lun record
        :rtype: XML or None if not found
        """
        for lun in luns:
            path = lun.get_child_content('path')
            if lun_path is not None:
                if lun_path == path:
                    return lun
            else:
                if name == path:
                    return lun
                _rest, _splitter, found_name = path.rpartition('/')
                if found_name == name:
                    return lun
        return None

    def get_lun(self, name, lun_path=None):
        """
        Return details about the LUN

        :return: Details about the lun
        :rtype: dict
        """
        luns = self.get_luns(lun_path)
        lun = self.find_lun(luns, name, lun_path)
        if lun is not None:
            return self.get_lun_details(lun)
        return None

    def get_luns_from_app(self):
        app_details, error = self.rest_app.get_application_details()
        self.fail_on_error(error)
        if app_details is not None:
            app_details['paths'] = self.get_lun_paths_from_app()
        return app_details

    def get_lun_paths_from_app(self):
        """Get luns path for SAN application"""
        backing_storage, error = self.rest_app.get_application_component_backing_storage()
        self.fail_on_error(error)
        # {'luns': [{'path': '/vol/ansibleLUN/ansibleLUN_1', ...
        if backing_storage is not None:
            return [lun['path'] for lun in backing_storage.get('luns', [])]
        return None

    def get_lun_path_from_backend(self, name):
        """returns lun path matching name if found in backing_storage
           retruns None if not found
        """
        lun_paths = self.get_lun_paths_from_app()
        match = "/%s" % name
        for path in lun_paths:
            if path.endswith(match):
                return path
        return None

    def create_san_app_component(self, modify):
        '''Create SAN application component'''
        if modify:
            required_options = ['name']
            action = 'modify'
            if 'lun_count' in modify:
                required_options.append('total_size')
        else:
            required_options = ('name', 'total_size')
            action = 'create'
        for option in required_options:
            if self.parameters.get(option) is None:
                self.module.fail_json(msg="Error: '%s' is required to %s a san application." % (option, action))

        application_component = dict(name=self.parameters['name'])
        if not modify:
            application_component['lun_count'] = 1      # default value for create, may be overriden below

        for attr in ('igroup_name', 'lun_count', 'storage_service'):
            if not modify or attr in modify:
                value = self.na_helper.safe_get(self.parameters, ['san_application_template', attr])
                if value is not None:
                    application_component[attr] = value
        for attr in ('os_type', 'qos_policy_group', 'qos_adaptive_policy_group', 'total_size'):
            if not modify or attr in modify:
                value = self.na_helper.safe_get(self.parameters, [attr])
                if value is not None:
                    # only one of them can be present at most
                    if attr in ('qos_policy_group', 'qos_adaptive_policy_group'):
                        attr = 'qos'
                        value = dict(policy=dict(name=value))
                    application_component[attr] = value
        tiering = self.na_helper.safe_get(self.parameters, ['nas_application_template', 'tiering'])
        if tiering is not None and not modify:
            application_component['tiering'] = dict()
            for attr in ('control', 'policy', 'object_stores'):
                value = tiering.get(attr)
                if attr == 'object_stores' and value is not None:
                    value = [dict(name=x) for x in value]
                if value is not None:
                    application_component['tiering'][attr] = value
        return application_component

    def create_san_app_body(self, modify=None):
        '''Create body for san template'''
        # TODO:
        # Should we support new_igroups?
        # It may raise idempotency issues if the REST call fails if the igroup already exists.
        # And we already have na_ontap_igroups.
        san = {
            'application_components': [self.create_san_app_component(modify)],
        }
        for attr in ('protection_type',):
            if not modify or attr in modify:
                value = self.na_helper.safe_get(self.parameters, ['san_application_template', attr])
                if value is not None:
                    # we expect value to be a dict, but maybe an empty dict
                    value = self.na_helper.filter_out_none_entries(value)
                    if value:
                        san[attr] = value
        for attr in ('os_type',):
            if not modify:      # not supported for modify operation, but required at applicaiton component level
                value = self.na_helper.safe_get(self.parameters, [attr])
                if value is not None:
                    san[attr] = value
        body, error = self.rest_app.create_application_body('san', san)
        return body, error

    def create_san_application(self):
        '''Use REST application/applications san template to create one or more LUNs'''
        body, error = self.create_san_app_body()
        self.fail_on_error(error)
        dummy, error = self.rest_app.create_application(body)
        self.fail_on_error(error)

    def modify_san_application(self, modify):
        '''Use REST application/applications san template to add one or more LUNs'''
        body, error = self.create_san_app_body(modify)
        self.fail_on_error(error)
        # these cannot be present when using PATCH
        body.pop('name')
        body.pop('svm')
        body.pop('smart_container')
        dummy, error = self.rest_app.patch_application(body)
        self.fail_on_error(error)

    def convert_to_san_application(self, scope):
        '''First convert volume to smart container using POST
           Second modify app to add new luns using PATCH
        '''
        # dummy modify, so that we don't fill in the body
        modify = dict(dummy='dummy')
        body, error = self.create_san_app_body(modify)
        self.fail_on_error(error)
        dummy, error = self.rest_app.create_application(body)
        self.fail_on_error(error)
        app_current, error = self.rest_app.get_application_uuid()
        self.fail_on_error(error)
        if app_current is None:
            self.module.fail_json('Error: failed to create smart container for %s' % self.parameters['name'])
        app_modify, app_modify_warning = self.app_changes(scope)
        if app_modify_warning is not None:
            self.warnings.append(app_modify_warning)
        if app_modify:
            self.modify_san_application(app_modify)

    def delete_san_application(self):
        '''Use REST application/applications san template to delete one or more LUNs'''
        dummy, error = self.rest_app.delete_application()
        self.fail_on_error(error)

    def create_lun(self):
        """
        Create LUN with requested name and size
        """
        path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name'])
        options = {'path': path,
                   'size': str(self.parameters['size']),
                   'space-reservation-enabled': str(self.parameters['space_reserve']),
                   'space-allocation-enabled': str(self.parameters['space_allocation']),
                   'use-exact-size': str(self.parameters['use_exact_size'])}
        if self.parameters.get('comment') is not None:
            options['comment'] = self.parameters['comment']
        if self.parameters.get('os_type') is not None:
            options['ostype'] = self.parameters['os_type']
        if self.parameters.get('qos_policy_group') is not None:
            options['qos-policy-group'] = self.parameters['qos_policy_group']
        if self.parameters.get('qos_adaptive_policy_group') is not None:
            options['qos-adaptive-policy-group'] = self.parameters['qos_adaptive_policy_group']
        lun_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'lun-create-by-size', **options)

        try:
            self.server.invoke_successfully(lun_create, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            self.module.fail_json(msg="Error provisioning lun %s of size %s: %s"
                                  % (self.parameters['name'], self.parameters['size'], to_native(exc)),
                                  exception=traceback.format_exc())

    def delete_lun(self, path):
        """
        Delete requested LUN
        """
        lun_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'lun-destroy', **{'path': path,
                              'force': str(self.parameters['force_remove']),
                              'destroy-fenced-lun':
                                  str(self.parameters['force_remove_fenced'])})

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

    def resize_lun(self, path):
        """
        Resize requested LUN.

        :return: True if LUN was actually re-sized, false otherwise.
        :rtype: bool
        """
        lun_resize = netapp_utils.zapi.NaElement.create_node_with_children(
            'lun-resize', **{'path': path,
                             'size': str(self.parameters['size']),
                             'force': str(self.parameters['force_resize'])})
        try:
            self.server.invoke_successfully(lun_resize, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            if to_native(exc.code) == "9042":
                # Error 9042 denotes the new LUN size being the same as the
                # old LUN size. This happens when there's barely any difference
                # in the two sizes. For example, from 8388608 bytes to
                # 8194304 bytes. This should go away if/when the default size
                # requested/reported to/from the controller is changed to a
                # larger unit (MB/GB/TB).
                return False
            else:
                self.module.fail_json(msg="Error resizing lun %s: %s" % (path, to_native(exc)),
                                      exception=traceback.format_exc())

        return True

    def set_lun_value(self, path, key, value):
        key_to_zapi = dict(
            comment=('lun-set-comment', 'comment'),
            # The same ZAPI is used for both QOS attributes
            qos_policy_group=('lun-set-qos-policy-group', 'qos-policy-group'),
            qos_adaptive_policy_group=('lun-set-qos-policy-group', 'qos-adaptive-policy-group'),
            space_allocation=('lun-set-space-alloc', 'enable'),
            space_reserve=('lun-set-space-reservation-info', 'enable')
        )
        if key in key_to_zapi:
            zapi, option = key_to_zapi[key]
        else:
            self.module.fail_json(msg="option %s cannot be modified to %s" % (key, value))
        options = dict(path=path)
        if option == 'enable':
            options[option] = self.na_helper.get_value_for_bool(False, value)
        else:
            options[option] = value

        lun_set = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options)
        try:
            self.server.invoke_successfully(lun_set, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            self.module.fail_json(msg="Error setting lun option %s: %s" % (key, to_native(exc)),
                                  exception=traceback.format_exc())
        return

    def modify_lun(self, path, modify):
        """
        update LUN properties (except size or name)
        """
        for key, value in modify.items():
            self.set_lun_value(path, key, value)

    def rename_lun(self, path, new_path):
        """
        rename LUN
        """
        lun_move = netapp_utils.zapi.NaElement.create_node_with_children(
            'lun-move', **{'path': path,
                           'new-path': new_path})
        try:
            self.server.invoke_successfully(lun_move, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            self.module.fail_json(msg="Error moving lun %s: %s" % (path, to_native(exc)),
                                  exception=traceback.format_exc())

    def fail_on_error(self, error, stack=False):
        if error is None:
            return
        elements = dict(msg="Error: %s" % error)
        if stack:
            elements['stack'] = traceback.format_stack()
        self.module.fail_json(**elements)

    def set_total_size(self, validate):
        # fix total_size attribute, report error if total_size is missing (or size is missing)
        attr = 'total_size'
        value = self.na_helper.safe_get(self.parameters, ['san_application_template', attr])
        if value is not None or not validate:
            self.parameters[attr] = value
            return
        lun_count = self.na_helper.safe_get(self.parameters, ['san_application_template', 'lun_count'])
        value = self.parameters.get('size')
        if value is not None and (lun_count is None or lun_count == 1):
            self.parameters[attr] = value
            return
        self.module.fail_json("Error: 'total_size' is a required SAN application template attribute when creating a LUN application")

    def validate_app_create(self):
        # fix total_size attribute
        self.set_total_size(validate=True)

    def validate_app_changes(self, modify, warning):
        errors = list()
        for key in modify:
            if key not in ('lun_count', 'total_size'):
                errors.append("Error: the following application parameter cannot be modified: %s: %s."
                              % (key, modify[key]))
        if 'lun_count' in modify:
            for attr in ('total_size', 'os_type', 'igroup_name'):
                value = self.parameters.get(attr)
                if value is None:
                    value = self.na_helper.safe_get(self.parameters['san_application_template'], [attr])
                if value is None:
                    errors.append('Error: %s is a required parameter when increasing lun_count.' % attr)
                else:
                    modify[attr] = value
            if warning:
                errors.append('Error: %s' % warning)
        if errors:
            self.module.fail_json(msg='\n'.join(errors))
        if 'total_size' in modify:
            self.set_total_size(validate=False)
            if warning:
                # can't change total_size, let's ignore it
                self.warnings.append(warning)
                modify.pop('total_size')

    def app_changes(self, scope):
        # find and validate app changes
        app_current, error = self.rest_app.get_application_details('san')
        self.fail_on_error(error)
        # save application name, as it is overriden in the flattening operation
        app_name = app_current['name']
        # there is an issue with total_size not reflecting the real total_size, and some additional overhead
        provisioned_size = self.na_helper.safe_get(app_current, ['statistics', 'space', 'provisioned'])
        if provisioned_size is None:
            provisioned_size = 0
        if self.debug:
            self.debug['app_current'] = app_current             # will be updated below as it is mutable
            self.debug['got'] = copy.deepcopy(app_current)      # fixed copy
        # flatten
        app_current = app_current['san']                                # app template
        app_current.update(app_current['application_components'][0])    # app component
        del app_current['application_components']
        # if component name does not match, assume a change at LUN level
        comp_name = app_current['name']
        if comp_name != self.parameters['name']:
            msg = "desired component/volume name: %s does not match existing component name: %s" % (self.parameters['name'], comp_name)
            if scope == 'application':
                self.module.fail_json(msg='Error: ' + msg + ".  scope=%s" % scope)
            return None, msg + ".  scope=%s, assuming 'lun' scope." % scope
        # restore app name
        app_current['name'] = app_name

        # ready to compare, except for a quirk in size handling
        total_size = app_current['total_size']
        desired = dict(self.parameters['san_application_template'])
        desired_size = desired.get('total_size')

        warning = None
        if desired_size is not None:
            if desired_size < total_size:
                self.module.fail_json("Error: can't reduce size: total_size=%d, provisioned=%d, requested=%d"
                                      % (total_size, provisioned_size, desired_size))
            elif desired_size > total_size and desired_size < provisioned_size:
                # we can't increase, but we can't say it is a problem, as the size is already bigger!
                warning = "requested size is too small: total_size=%d, provisioned=%d, requested=%d" % (total_size, provisioned_size, desired_size)

        # preserve change state before calling modify in case an ignorable total_size change is the only change
        changed = self.na_helper.changed
        app_modify = self.na_helper.get_modified_attributes(app_current, desired)
        self.validate_app_changes(app_modify, warning)
        if not app_modify:
            self.na_helper.changed = changed
            app_modify = None
        return app_modify, None

    def apply(self):
        results = dict()
        netapp_utils.ems_log_event("na_ontap_lun", self.server)
        app_cd_action, app_modify, lun_cd_action, lun_modify, lun_rename = None, None, None, None, None
        app_modify_warning = None
        actions = list()
        if self.rest_app:
            scope = self.na_helper.safe_get(self.parameters, ['san_application_template', 'scope'])
            app_current, error = self.rest_app.get_application_uuid()
            self.fail_on_error(error)
            if scope == 'lun' and app_current is None:
                self.module.fail_json('Application not found: %s.  scope=%s.' %
                                      (self.na_helper.safe_get(self.parameters, ['san_application_template', 'name']), scope))
        else:
            # no application template, fall back to LUN only
            scope = 'lun'

        if self.rest_app and scope != 'lun':
            app_cd_action = self.na_helper.get_cd_action(app_current, self.parameters)
            if app_cd_action == 'create':
                # check if target volume already exists
                cp_volume_name = self.parameters['name']
                volume, error = rest_volume.get_volume(self.rest_api, self.parameters['vserver'], cp_volume_name)
                self.fail_on_error(error)
                if volume is not None:
                    if scope == 'application':
                        # volume already exists, but not as part of this application
                        app_cd_action = 'convert'
                    else:
                        # default name already in use, ask user to clarify intent
                        msg = "Error: volume '%s' already exists.  Please use a different group name, or use 'application' scope.  scope=%s"
                        self.module.fail_json(msg=msg % (cp_volume_name, scope))
            if app_cd_action is not None:
                actions.append('app_%s' % app_cd_action)
            if app_cd_action == 'create':
                self.validate_app_create()
            if app_cd_action is None and app_current is not None:
                app_modify, app_modify_warning = self.app_changes(scope)
                if app_modify:
                    actions.append('app_modify')
                    results['app_modify'] = dict(app_modify)

        if app_cd_action is None and scope != 'application':
            # actions at LUN level
            lun_path, from_lun_path = None, None
            from_name = self.parameters.get('from_name')
            if self.rest_app and app_current:
                # For LUNs created using a SAN application, we're getting lun paths from the backing storage
                lun_path = self.get_lun_path_from_backend(self.parameters['name'])
                if from_name is not None:
                    from_lun_path = self.get_lun_path_from_backend(from_name)
            current = self.get_lun(self.parameters['name'], lun_path)
            if current is not None and lun_path is None:
                lun_path = current['path']
            lun_cd_action = self.na_helper.get_cd_action(current, self.parameters)
            if lun_cd_action == 'create' and from_name is not None:
                # create by renaming existing LUN, if it really exists
                old_lun = self.get_lun(from_name, from_lun_path)
                lun_rename = self.na_helper.is_rename_action(old_lun, current)
                if lun_rename is None:
                    self.module.fail_json(msg="Error renaming lun: %s does not exist" % from_name)
                if lun_rename:
                    current = old_lun
                    if from_lun_path is None:
                        from_lun_path = current['path']
                    head, _sep, tail = from_lun_path.rpartition(from_name)
                    if tail:
                        self.module.fail_json(msg="Error renaming lun: %s does not match lun_path %s" % (from_name, from_lun_path))
                    lun_path = head + self.parameters['name']
                    lun_cd_action = None
                    actions.append('lun_rename')
                    app_modify_warning = None       # reset warning as we found a match
            if lun_cd_action is not None:
                actions.append('lun_%s' % lun_cd_action)
            if lun_cd_action is None and self.parameters['state'] == 'present':
                # we already handled rename if required
                current.pop('name', None)
                lun_modify = self.na_helper.get_modified_attributes(current, self.parameters)
                if lun_modify:
                    actions.append('lun_modify')
                    results['lun_modify'] = dict(lun_modify)
                    app_modify_warning = None       # reset warning as we found a match
            if lun_cd_action and self.rest_app and app_current:
                msg = 'This module does not support %s a LUN by name %s a SAN application.' %\
                    ('adding', 'to') if lun_cd_action == 'create' else ('removing', 'from')
                if scope == 'auto':
                    # ignore LUN not found, as name can be a group name
                    self.warnings.append(msg + ".  scope=%s, assuming 'application'" % scope)
                    if not app_modify:
                        self.na_helper.changed = False
                elif scope == 'lun':
                    self.module.fail_json(msg=msg + ".  scope=%s." % scope)
                lun_cd_action = None
            if lun_cd_action == 'create' and self.parameters.get('size') is None:
                self.module.fail_json(msg="size is a required parameter for create.")

        if self.na_helper.changed and not self.module.check_mode:
            if app_cd_action == 'create':
                self.create_san_application()
            elif app_cd_action == 'convert':
                self.convert_to_san_application(scope)
            elif app_cd_action == 'delete':
                self.rest_app.delete_application()
            elif lun_cd_action == 'create':
                self.create_lun()
            elif lun_cd_action == 'delete':
                self.delete_lun(lun_path)
            else:
                if app_modify:
                    self.modify_san_application(app_modify)
                if lun_rename:
                    self.rename_lun(from_lun_path, lun_path)
                size_changed = False
                if lun_modify and 'size' in lun_modify:
                    # Ensure that size was actually changed. Please
                    # read notes in 'resize_lun' function for details.
                    size_changed = self.resize_lun(lun_path)
                    lun_modify.pop('size')
                if lun_modify:
                    self.modify_lun(lun_path, lun_modify)
                if not lun_modify and not lun_rename and not app_modify:
                    # size may not have changed
                    self.na_helper.changed = size_changed

        if app_modify_warning:
            self.warnings.append(app_modify_warning)
        results['changed'] = self.na_helper.changed
        results['actions'] = actions
        if self.warnings:
            results['warnings'] = self.warnings
        results.update(self.debug)
        self.module.exit_json(**results)
class NetAppONTAPCifsSecurity(object):
    '''
    modify vserver cifs security
    '''
    def __init__(self):

        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            vserver=dict(required=True, type='str'),
            kerberos_clock_skew=dict(required=False, type='int'),
            kerberos_ticket_age=dict(required=False, type='int'),
            kerberos_renew_age=dict(required=False, type='int'),
            kerberos_kdc_timeout=dict(required=False, type='int'),
            is_signing_required=dict(required=False, type='bool'),
            is_password_complexity_required=dict(required=False, type='bool'),
            is_aes_encryption_enabled=dict(required=False, type='bool'),
            is_smb_encryption_required=dict(required=False, type='bool'),
            lm_compatibility_level=dict(required=False, choices=['lm_ntlm_ntlmv2_krb', 'ntlm_ntlmv2_krb', 'ntlmv2_krb', 'krb']),
            referral_enabled_for_ad_ldap=dict(required=False, type='bool'),
            session_security_for_ad_ldap=dict(required=False, choices=['none', 'sign', 'seal']),
            smb1_enabled_for_dc_connections=dict(required=False, choices=['false', 'true', 'system_default']),
            smb2_enabled_for_dc_connections=dict(required=False, choices=['false', 'true', 'system_default']),
            use_start_tls_for_ad_ldap=dict(required=False, type='bool')
        ))

        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.set_playbook_zapi_key_map()

        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 set_playbook_zapi_key_map(self):

        self.na_helper.zapi_int_keys = {
            'kerberos_clock_skew': 'kerberos-clock-skew',
            'kerberos_ticket_age': 'kerberos-ticket-age',
            'kerberos_renew_age': 'kerberos-renew-age',
            'kerberos_kdc_timeout': 'kerberos-kdc-timeout'
        }
        self.na_helper.zapi_bool_keys = {
            'is_signing_required': 'is-signing-required',
            'is_password_complexity_required': 'is-password-complexity-required',
            'is_aes_encryption_enabled': 'is-aes-encryption-enabled',
            'is_smb_encryption_required': 'is-smb-encryption-required',
            'referral_enabled_for_ad_ldap': 'referral-enabled-for-ad-ldap',
            'use_start_tls_for_ad_ldap': 'use-start-tls-for-ad-ldap'
        }
        self.na_helper.zapi_str_keys = {
            'lm_compatibility_level': 'lm-compatibility-level',
            'session_security_for_ad_ldap': 'session-security-for-ad-ldap',
            'smb1_enabled_for_dc_connections': 'smb1-enabled-for-dc-connections',
            'smb2_enabled_for_dc_connections': 'smb2-enabled-for-dc-connections'
        }

    def cifs_security_get_iter(self):
        """
        get current vserver cifs security.
        :return: a dict of vserver cifs security
        """
        cifs_security_get = netapp_utils.zapi.NaElement('cifs-security-get-iter')
        query = netapp_utils.zapi.NaElement('query')
        cifs_security = netapp_utils.zapi.NaElement('cifs-security')
        cifs_security.add_new_child('vserver', self.parameters['vserver'])
        query.add_child_elem(cifs_security)
        cifs_security_get.add_child_elem(query)
        cifs_security_details = dict()
        try:
            result = self.server.invoke_successfully(cifs_security_get, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error fetching cifs security from %s: %s'
                                      % (self.parameters['vserver'], to_native(error)),
                                  exception=traceback.format_exc())
        if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0:
            cifs_security_info = result.get_child_by_name('attributes-list').get_child_by_name('cifs-security')
            for option, zapi_key in self.na_helper.zapi_int_keys.items():
                cifs_security_details[option] = self.na_helper.get_value_for_int(from_zapi=True, value=cifs_security_info.get_child_content(zapi_key))
            for option, zapi_key in self.na_helper.zapi_bool_keys.items():
                cifs_security_details[option] = self.na_helper.get_value_for_bool(from_zapi=True, value=cifs_security_info.get_child_content(zapi_key))
            for option, zapi_key in self.na_helper.zapi_str_keys.items():
                if cifs_security_info.get_child_content(zapi_key) is None:
                    cifs_security_details[option] = None
                else:
                    cifs_security_details[option] = cifs_security_info.get_child_content(zapi_key)
            return cifs_security_details
        return None

    def cifs_security_modify(self, modify):
        """
        :param modify: A list of attributes to modify
        :return: None
        """
        cifs_security_modify = netapp_utils.zapi.NaElement('cifs-security-modify')
        for attribute in modify:
            cifs_security_modify.add_new_child(self.attribute_to_name(attribute), str(self.parameters[attribute]))
        try:
            self.server.invoke_successfully(cifs_security_modify, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as e:
            self.module.fail_json(msg='Error modifying cifs security on %s: %s'
                                  % (self.parameters['vserver'], to_native(e)),
                                  exception=traceback.format_exc())

    @staticmethod
    def attribute_to_name(attribute):
        return str.replace(attribute, '_', '-')

    def apply(self):
        """Call modify operations."""
        self.asup_log_for_cserver("na_ontap_vserver_cifs_security")
        current = self.cifs_security_get_iter()
        modify = self.na_helper.get_modified_attributes(current, self.parameters)
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if modify:
                    self.cifs_security_modify(modify)
        self.module.exit_json(changed=self.na_helper.changed)

    def asup_log_for_cserver(self, event_name):
        """
        Fetch admin vserver for the given cluster
        Create and Autosupport log event with the given module name
        :param event_name: Name of the event log
        :return: None
        """
        results = netapp_utils.get_cserver(self.server)
        cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
        netapp_utils.ems_log_event(event_name, cserver)
Exemplo n.º 21
0
class NetAppOntapUser(object):
    """
    Common operations to manage users and roles.
    """

    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'),

            applications=dict(required=True, type='list', elements='str', aliases=['application'],
                              choices=['console', 'http', 'ontapi', 'rsh', 'snmp',
                                       'sp', 'service-processor', 'ssh', 'telnet'],),
            authentication_method=dict(required=True, type='str',
                                       choices=['community', 'password', 'publickey', 'domain', 'nsswitch', 'usm', 'cert']),
            set_password=dict(required=False, type='str', no_log=True),
            role_name=dict(required=False, type='str'),
            lock_user=dict(required=False, type='bool'),
            vserver=dict(required=True, type='str', aliases=['svm']),
            authentication_protocol=dict(required=False, type='str', choices=['none', 'md5', 'sha', 'sha2-256']),
            authentication_password=dict(required=False, type='str', no_log=True),
            engine_id=dict(required=False, type='str'),
            privacy_protocol=dict(required=False, type='str', choices=['none', 'des', 'aes128']),
            privacy_password=dict(required=False, type='str', no_log=True),
            remote_switch_ipaddress=dict(required=False, type='str')
        ))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            required_if=[
                ('state', 'present', ['role_name'])
            ],
            supports_check_mode=True
        )

        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)

        # REST API should be used for ONTAP 9.6 or higher
        self.restApi = OntapRestAPI(self.module)
        # some attributes are not supported in earlier REST implementation
        unsupported_rest_properties = ['authentication_password', 'authentication_protocol', 'engine_id',
                                       'privacy_password', 'privacy_protocol']
        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 is not None:
            self.module.fail_json(msg=error)
        if not self.use_rest:
            if not HAS_NETAPP_LIB:
                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'])
        else:
            if 'snmp' in self.parameters['applications']:
                self.module.fail_json(msg="Snmp as application is not supported in REST.")

    def get_user_rest(self):
        api = 'security/accounts'
        params = {
            'name': self.parameters['name']
        }
        if self.parameters.get('vserver') is None:
            # vserser is empty for cluster
            params['scope'] = 'cluster'
        else:
            params['owner.name'] = self.parameters['vserver']

        message, error = self.restApi.get(api, params)
        if error:
            self.module.fail_json(msg='Error while fetching user info: %s' % error)
        if message['num_records'] == 1:
            return message['records'][0]['owner']['uuid'], message['records'][0]['name']
        if message['num_records'] > 1:
            self.module.fail_json(msg='Error while fetching user info, found multiple entries: %s' % repr(message))

        return None

    def get_user_details_rest(self, name, uuid):
        params = {
            'fields': 'role,applications,locked'
        }
        api = "security/accounts/%s/%s" % (uuid, name)
        message, error = self.restApi.get(api, params)
        if error:
            self.module.fail_json(msg='Error while fetching user details: %s' % error)
        if message:
            return_value = {
                'role_name': message['role']['name'],
                'applications': [app['application'] for app in message['applications']]
            }
            if "locked" in message:
                return_value['lock_user'] = message['locked']
        return return_value

    def get_user(self, application=None):
        """
        Checks if the user exists.
        :param: application: application to grant access to
        :return:
            Dictionary if user found
            None if user is not found
        """
        security_login_get_iter = netapp_utils.zapi.NaElement('security-login-get-iter')
        query_details = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-account-info', **{'vserver': self.parameters['vserver'],
                                              'user-name': self.parameters['name'],
                                              'authentication-method': self.parameters['authentication_method']})
        if application is not None:
            query_details.add_new_child('application', application)
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(query_details)
        security_login_get_iter.add_child_elem(query)
        try:
            result = self.server.invoke_successfully(security_login_get_iter,
                                                     enable_tunneling=False)
            if result.get_child_by_name('num-records') and \
                    int(result.get_child_content('num-records')) >= 1:
                interface_attributes = result.get_child_by_name('attributes-list').\
                    get_child_by_name('security-login-account-info')
                return_value = {
                    'lock_user': interface_attributes.get_child_content('is-locked'),
                    'role_name': interface_attributes.get_child_content('role-name')
                }
                return return_value
            return None
        except netapp_utils.zapi.NaApiError as error:
            # Error 16034 denotes a user not being found.
            if to_native(error.code) == "16034":
                return None
            # Error 16043 denotes the user existing, but the application missing
            elif to_native(error.code) == "16043":
                return None
            else:
                self.module.fail_json(msg='Error getting user %s: %s' % (self.parameters['name'], to_native(error)),
                                      exception=traceback.format_exc())

    def create_user_rest(self, apps=None):
        app_list = list()
        if apps is not None:
            for app in apps:
                mydict = {
                    "application": app,
                    "authentication_methods": self.parameters['authentication_method'].split(),
                }
                app_list.append(mydict)
            api = 'security/accounts'
            params = {
                'name': self.parameters['name'],
                'role.name': self.parameters['role_name'],
                'applications': app_list
            }
            if self.parameters.get('vserver') is not None:
                # vserser is empty for cluster
                params['owner.name'] = self.parameters['vserver']
            if 'set_password' in self.parameters:
                params['password'] = self.parameters['set_password']
            if 'lock_user' in self.parameters:
                params['locked'] = self.parameters['lock_user']
            dummy, error = self.restApi.post(api, params)
            if error:
                self.module.fail_json(msg='Error while creating user: %s' % error)

    def create_user(self, application):
        """
        creates the user for the given application and authentication_method
        :param: application: application to grant access to
        """
        user_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-create', **{'vserver': self.parameters['vserver'],
                                        'user-name': self.parameters['name'],
                                        'application': application,
                                        'authentication-method': self.parameters['authentication_method'],
                                        'role-name': self.parameters.get('role_name')})
        if self.parameters.get('set_password') is not None:
            user_create.add_new_child('password', self.parameters.get('set_password'))
        if self.parameters.get('authentication_method') == 'usm':
            if self.parameters.get('remote_switch_ipaddress') is not None:
                user_create.add_new_child('remote-switch-ipaddress', self.parameters.get('remote_switch_ipaddress'))
            snmpv3_login_info = netapp_utils.zapi.NaElement('snmpv3-login-info')
            if self.parameters.get('authentication_password') is not None:
                snmpv3_login_info.add_new_child('authentication-password', self.parameters['authentication_password'])
            if self.parameters.get('authentication_protocol') is not None:
                snmpv3_login_info.add_new_child('authentication-protocol', self.parameters['authentication_protocol'])
            if self.parameters.get('engine_id') is not None:
                snmpv3_login_info.add_new_child('engine-id', self.parameters['engine_id'])
            if self.parameters.get('privacy_password') is not None:
                snmpv3_login_info.add_new_child('privacy-password', self.parameters['privacy_password'])
            if self.parameters.get('privacy_protocol') is not None:
                snmpv3_login_info.add_new_child('privacy-protocol', self.parameters['privacy_protocol'])
            user_create.add_child_elem(snmpv3_login_info)

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

    def lock_unlock_user_rest(self, useruuid, username, value=None):
        data = {
            'locked': value
        }
        params = {
            'name': self.parameters['name'],
            'owner.uuid': useruuid,
        }
        api = "security/accounts/%s/%s" % (useruuid, username)
        dummy, error = self.restApi.patch(api, data, params)
        if error:
            self.module.fail_json(msg='Error while locking/unlocking user: %s' % error)

    def lock_given_user(self):
        """
        locks the user

        :return:
            True if user locked
            False if lock user is not performed
        :rtype: bool
        """
        user_lock = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-lock', **{'vserver': self.parameters['vserver'],
                                      'user-name': self.parameters['name']})

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

    def unlock_given_user(self):
        """
        unlocks the user

        :return:
            True if user unlocked
            False if unlock user is not performed
        :rtype: bool
        """
        user_unlock = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-unlock', **{'vserver': self.parameters['vserver'],
                                        'user-name': self.parameters['name']})

        try:
            self.server.invoke_successfully(user_unlock,
                                            enable_tunneling=False)
        except netapp_utils.zapi.NaApiError as error:
            if to_native(error.code) == '13114':
                return False
            else:
                self.module.fail_json(msg='Error unlocking user %s: %s' % (self.parameters['name'], to_native(error)),
                                      exception=traceback.format_exc())
        return True

    def delete_user_rest(self):
        uuid, username = self.get_user_rest()
        data = {}
        params = {
            'name': username,
            'owner.uuid': uuid,
        }
        api = "security/accounts/%s/%s" % (uuid, username)
        dummy, error = self.restApi.delete(api, data, params)
        if error:
            self.module.fail_json(msg='Error while deleting user : %s' % error)

    def delete_user(self, application):
        """
        deletes the user for the given application and authentication_method
        :param: application: application to grant access to
        """
        user_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-delete', **{'vserver': self.parameters['vserver'],
                                        'user-name': self.parameters['name'],
                                        'application': application,
                                        'authentication-method': self.parameters['authentication_method']})

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

    @staticmethod
    def is_repeated_password(message):
        return message.startswith('New password must be different than last 6 passwords.') \
            or message.startswith('New password must be different from last 6 passwords.') \
            or message.startswith('New password must be different than the old password.') \
            or message.startswith('New password must be different from the old password.')

    def change_password_rest(self, useruuid, username):
        data = {
            'password': self.parameters['set_password'],
        }
        params = {
            'name': self.parameters['name'],
            'owner.uuid': useruuid,
        }
        api = "security/accounts/%s/%s" % (useruuid, username)
        dummy, error = self.restApi.patch(api, data, params)
        if error:
            if 'message' in error and self.is_repeated_password(error['message']):
                # if the password is reused, assume idempotency
                return False
            else:
                self.module.fail_json(msg='Error while updating user password: %s' % error)
        return True

    def change_password(self):
        """
        Changes the password

        :return:
            True if password updated
            False if password is not updated
        :rtype: bool
        """
        # self.server.set_vserver(self.parameters['vserver'])
        modify_password = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-modify-password', **{
                'new-password': str(self.parameters.get('set_password')),
                'user-name': self.parameters['name']})
        try:
            self.server.invoke_successfully(modify_password,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            if to_native(error.code) == '13114':
                return False
            # if the user give the same password, instead of returning an error, return ok
            if to_native(error.code) == '13214' and self.is_repeated_password(error.message):
                return False
            self.module.fail_json(msg='Error setting password for user %s: %s' % (self.parameters['name'], to_native(error)),
                                      exception=traceback.format_exc())

        self.server.set_vserver(None)
        return True

    def modify_apps_rest(self, useruuid, username, apps=None):
        app_list = list()
        if apps is not None:
            for app in apps:
                mydict = {
                    "application": app,
                    "authentication_methods": self.parameters['authentication_method'].split(),
                }
                app_list.append(mydict)
        data = {
            'role.name': self.parameters['role_name'],
            'applications': app_list
        }
        params = {
            'name': self.parameters['name'],
            'owner.uuid': useruuid,
        }
        api = "security/accounts/%s/%s" % (useruuid, username)
        dummy, error = self.restApi.patch(api, data, params)
        if error:
            self.module.fail_json(msg='Error while modifying user details: %s' % error)

    def modify_user(self, application):
        """
        Modify user
        """
        user_modify = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-modify', **{'vserver': self.parameters['vserver'],
                                        'user-name': self.parameters['name'],
                                        'application': application,
                                        'authentication-method': self.parameters['authentication_method'],
                                        'role-name': self.parameters.get('role_name')})

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

    def apply_for_rest(self):
        current = self.get_user_rest()

        if current is not None:
            uuid, name = current
            current = self.get_user_details_rest(name, uuid)

        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        modify_decision = self.na_helper.get_modified_attributes(current, self.parameters)

        if current and 'lock_user' not in current:
            # REST does not return locked if password is not set
            if cd_action is None and self.parameters.get('lock_user') is not None:
                if self.parameters.get('set_password') is None:
                    self.module.fail_json(msg='Error: cannot modify lock state if password is not set.')
                modify_decision['lock_user'] = self.parameters['lock_user']
                self.na_helper.changed = True

        if self.na_helper.changed and not self.module.check_mode:
            if cd_action == 'create':
                self.create_user_rest(self.parameters['applications'])
            elif cd_action == 'delete':
                self.delete_user_rest()
            elif modify_decision:
                if 'role_name' in modify_decision or 'applications' in modify_decision:
                    self.modify_apps_rest(uuid, name, self.parameters['applications'])
        if cd_action is None and self.parameters.get('set_password') is not None:
            # if check_mode, don't attempt to change the password, but assume it would be changed
            if self.module.check_mode or self.change_password_rest(uuid, name):
                self.na_helper.changed = True
        if cd_action is None and self.na_helper.changed and not self.module.check_mode:
            # lock/unlock actions require password to be set
            if modify_decision and 'lock_user' in modify_decision:
                self.lock_unlock_user_rest(uuid, name, self.parameters['lock_user'])

        self.module.exit_json(changed=self.na_helper.changed)

    def apply(self):
        if self.use_rest:
            self.apply_for_rest()
        else:
            create_delete_decision = {}
            modify_decision = {}
            netapp_utils.ems_log_event("na_ontap_user", self.server)
            for application in self.parameters['applications']:
                current = self.get_user(application)

                if current is not None:
                    current['lock_user'] = self.na_helper.get_value_for_bool(True, current['lock_user'])

                cd_action = self.na_helper.get_cd_action(current, self.parameters)

                if cd_action is not None:
                    create_delete_decision[application] = cd_action
                else:
                    modify_decision[application] = self.na_helper.get_modified_attributes(current, self.parameters)

            if not create_delete_decision and self.parameters.get('state') == 'present':
                if self.parameters.get('set_password') is not None:
                    self.na_helper.changed = True

            if self.na_helper.changed:

                if self.module.check_mode:
                    pass
                else:
                    for application in create_delete_decision:
                        if create_delete_decision[application] == 'create':
                            self.create_user(application)
                        elif create_delete_decision[application] == 'delete':
                            self.delete_user(application)
                    lock_user = False
                    for application in modify_decision:
                        if 'role_name' in modify_decision[application]:
                            self.modify_user(application)
                        if 'lock_user' in modify_decision[application]:
                            lock_user = True
                    if not create_delete_decision and self.parameters.get('set_password') is not None:
                        # if change password return false nothing has changed so we need to set changed to False
                        self.na_helper.changed = self.change_password()
                    # NOTE: unlock has to be performed after setting a password
                    if lock_user:
                        if self.parameters.get('lock_user'):
                            self.lock_given_user()
                        else:
                            self.unlock_given_user()
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 22
0
class NetAppOntapStorageFailover(object):
    """
        Enable or disable storage failover for a specified node
    """
    def __init__(self):
        """
            Initialize the Ontap Storage failover class
        """

        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            state=dict(required=False, choices=['present', 'absent'], default='present'),
            node_name=dict(required=True, type='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)

        if self.parameters['state'] == 'present':
            self.parameters['is_enabled'] = True
        else:
            self.parameters['is_enabled'] = False

        self.rest_api = OntapRestAPI(self.module)
        self.use_rest = self.rest_api.is_rest()

        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)

    def get_storage_failover(self):
        """
        get the storage failover for a given node
        :return: dict of is-enabled: true if enabled is true None if not
        """

        if self.use_rest:
            return_value = None
            api = "cluster/nodes"
            query = {
                'fields': 'uuid,ha.enabled',
                'name': self.parameters['node_name']
            }
            message, error = self.rest_api.get(api, query)
            records, error = rrh.check_for_0_or_1_records(api, message, error)

            if error is None and records is not None:
                return_value = {
                    'uuid': message['records'][0]['uuid'],
                    'is_enabled': message['records'][0]['ha']['enabled']
                }

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

            if not records:
                error = "REST API did not return failover details for node %s" % (self.parameters['node_name'])
                self.module.fail_json(msg=error)

            return return_value

        else:
            storage_failover_get_iter = netapp_utils.zapi.NaElement('cf-status')
            storage_failover_get_iter.add_new_child('node', self.parameters['node_name'])

            try:
                result = self.server.invoke_successfully(storage_failover_get_iter, True)
                return_value = {'is_enabled': self.na_helper.get_value_for_bool(True, result.get_child_content('is-enabled'))}

            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error getting storage failover info for node %s: %s' % (
                    self.parameters['node_name'], to_native(error)), exception=traceback.format_exc())

            return return_value

    def modify_storage_failover(self, current):
        """
        Modifies storage failover for a specified node
        """

        if self.use_rest:
            api = "cluster/nodes"
            query = {
                'uuid': current['uuid'],
                'return_timeout': 60  # Timeout added to allow for return messages
            }
            body = {'ha': {'enabled': self.parameters['is_enabled']}}
            message, error = self.rest_api.patch(api, body, query)
            if error:
                self.module.fail_json(msg=error)

        else:

            if self.parameters['state'] == 'present':
                cf_service = 'cf-service-enable'
            else:
                cf_service = 'cf-service-disable'

            storage_failover_modify = netapp_utils.zapi.NaElement(cf_service)
            storage_failover_modify.add_new_child('node', self.parameters['node_name'])

            try:
                result = self.server.invoke_successfully(storage_failover_modify, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error modifying storage failover for node %s: %s' % (
                    self.parameters['node_name'], to_native(error)), exception=traceback.format_exc())

    def ems_log_event(self):
        results = netapp_utils.get_cserver(self.server)
        cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
        return netapp_utils.ems_log_event("na_ontap_storage_failover", cserver)

    def apply(self):
        if not self.use_rest:
            self.ems_log_event()

        current = self.get_storage_failover()
        modify = self.na_helper.get_modified_attributes(current, self.parameters)

        if self.na_helper.changed:
            if not self.module.check_mode:
                self.modify_storage_failover(current)
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 23
0
class NetAppOntapInterface(object):
    ''' object to describe  interface info '''
    def __init__(self):

        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(required=False,
                            choices=['present', 'absent'],
                            default='present'),
                 interface_name=dict(required=True, type='str'),
                 home_node=dict(required=False, type='str', default=None),
                 home_port=dict(required=False, type='str'),
                 role=dict(required=False, type='str'),
                 is_ipv4_link_local=dict(required=False,
                                         type='bool',
                                         default=None),
                 address=dict(required=False, type='str'),
                 netmask=dict(required=False, type='str'),
                 vserver=dict(required=True, type='str'),
                 firewall_policy=dict(required=False, type='str',
                                      default=None),
                 failover_policy=dict(required=False,
                                      type='str',
                                      default=None,
                                      choices=[
                                          'disabled', 'system-defined',
                                          'local-only', 'sfo-partner-only',
                                          'broadcast-domain-wide'
                                      ]),
                 failover_group=dict(required=False, type='str'),
                 admin_status=dict(required=False, choices=['up', 'down']),
                 subnet_name=dict(required=False, type='str'),
                 is_auto_revert=dict(required=False, type='bool',
                                     default=None),
                 protocols=dict(required=False, type='list'),
                 force_subnet_association=dict(required=False,
                                               type='bool',
                                               default=None),
                 dns_domain_name=dict(required=False, type='str'),
                 listen_for_dns_query=dict(required=False, type='bool'),
                 is_dns_update_enabled=dict(required=False, type='bool'),
                 service_policy=dict(required=False, type='str',
                                     default=None)))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            mutually_exclusive=[['subnet_name', 'address'],
                                ['subnet_name', 'netmask'],
                                ['is_ipv4_link_local', 'address'],
                                ['is_ipv4_link_local', 'netmask'],
                                ['is_ipv4_link_local', 'subnet_name']],
            supports_check_mode=True)
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        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)

    def get_interface(self):
        """
        Return details about the interface
        :param:
            name : Name of the interface

        :return: Details about the interface. None if not found.
        :rtype: dict
        """
        interface_info = netapp_utils.zapi.NaElement('net-interface-get-iter')
        interface_attributes = netapp_utils.zapi.NaElement(
            'net-interface-info')
        interface_attributes.add_new_child('interface-name',
                                           self.parameters['interface_name'])
        interface_attributes.add_new_child('vserver',
                                           self.parameters['vserver'])
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(interface_attributes)
        interface_info.add_child_elem(query)
        result = self.server.invoke_successfully(interface_info, True)
        return_value = None
        if result.get_child_by_name('num-records') and \
                int(result.get_child_content('num-records')) >= 1:

            interface_attributes = result.get_child_by_name('attributes-list'). \
                get_child_by_name('net-interface-info')
            return_value = {
                'interface_name':
                self.parameters['interface_name'],
                'admin_status':
                interface_attributes['administrative-status'],
                'home_port':
                interface_attributes['home-port'],
                'home_node':
                interface_attributes['home-node'],
                'failover_policy':
                interface_attributes['failover-policy'].replace('_', '-'),
            }
            if interface_attributes.get_child_by_name('is-auto-revert'):
                return_value['is_auto_revert'] = True if interface_attributes[
                    'is-auto-revert'] == 'true' else False
            if interface_attributes.get_child_by_name('failover-group'):
                return_value['failover_group'] = interface_attributes[
                    'failover-group']
            if interface_attributes.get_child_by_name('address'):
                return_value['address'] = interface_attributes['address']
            if interface_attributes.get_child_by_name('netmask'):
                return_value['netmask'] = interface_attributes['netmask']
            if interface_attributes.get_child_by_name('firewall-policy'):
                return_value['firewall_policy'] = interface_attributes[
                    'firewall-policy']
            if interface_attributes.get_child_by_name(
                    'dns-domain-name') != 'none':
                return_value['dns_domain_name'] = interface_attributes[
                    'dns-domain-name']
            else:
                return_value['dns_domain_name'] = None
            if interface_attributes.get_child_by_name('listen-for-dns-query'):
                return_value[
                    'listen_for_dns_query'] = self.na_helper.get_value_for_bool(
                        True, interface_attributes['listen-for-dns-query'])
            if interface_attributes.get_child_by_name('is-dns-update-enabled'):
                return_value[
                    'is_dns_update_enabled'] = self.na_helper.get_value_for_bool(
                        True, interface_attributes['is-dns-update-enabled'])
            if interface_attributes.get_child_by_name('service-policy'):
                return_value['service_policy'] = interface_attributes[
                    'service-policy']
        return return_value

    @staticmethod
    def set_options(options, parameters):
        """ set attributes for create or modify """
        if parameters.get('home_port') is not None:
            options['home-port'] = parameters['home_port']
        if parameters.get('subnet_name') is not None:
            options['subnet-name'] = parameters['subnet_name']
        if parameters.get('address') is not None:
            options['address'] = parameters['address']
        if parameters.get('netmask') is not None:
            options['netmask'] = parameters['netmask']
        if parameters.get('failover_policy') is not None:
            options['failover-policy'] = parameters['failover_policy']
        if parameters.get('failover_group') is not None:
            options['failover-group'] = parameters['failover_group']
        if parameters.get('firewall_policy') is not None:
            options['firewall-policy'] = parameters['firewall_policy']
        if parameters.get('is_auto_revert') is not None:
            options['is-auto-revert'] = 'true' if parameters[
                'is_auto_revert'] is True else 'false'
        if parameters.get('admin_status') is not None:
            options['administrative-status'] = parameters['admin_status']
        if parameters.get('force_subnet_association') is not None:
            options['force-subnet-association'] = 'true' if parameters[
                'force_subnet_association'] else 'false'
        if parameters.get('dns_domain_name') is not None:
            options['dns-domain-name'] = parameters['dns_domain_name']
        if parameters.get('listen_for_dns_query') is not None:
            options['listen-for-dns-query'] = str(
                parameters['listen_for_dns_query'])
        if parameters.get('is_dns_update_enabled') is not None:
            options['is-dns-update-enabled'] = str(
                parameters['is_dns_update_enabled'])
        if parameters.get('is_ipv4_link_local') is not None:
            options['is-ipv4-link-local'] = 'true' if parameters[
                'is_ipv4_link_local'] else 'false'
        if parameters.get('service_policy') is not None:
            options['service-policy'] = parameters['service_policy']

    def set_protocol_option(self, required_keys):
        """ set protocols for create """
        if self.parameters.get('protocols') is not None:
            data_protocols_obj = netapp_utils.zapi.NaElement('data-protocols')
            for protocol in self.parameters.get('protocols'):
                if protocol.lower() in ['fc-nvme', 'fcp']:
                    if 'address' in required_keys:
                        required_keys.remove('address')
                    if 'home_port' in required_keys:
                        required_keys.remove('home_port')
                    if 'netmask' in required_keys:
                        required_keys.remove('netmask')
                    not_required_params = set(
                        ['address', 'netmask', 'firewall_policy'])
                    if not not_required_params.isdisjoint(
                            set(self.parameters.keys())):
                        self.module.fail_json(
                            msg=
                            'Error: Following parameters for creating interface are not supported'
                            ' for data-protocol fc-nvme: %s' %
                            ', '.join(not_required_params))
                data_protocols_obj.add_new_child('data-protocol', protocol)
            return data_protocols_obj
        return None

    def get_home_node_for_cluster(self):
        ''' get the first node name from this cluster '''
        get_node = netapp_utils.zapi.NaElement('cluster-node-get-iter')
        attributes = {'query': {'cluster-node-info': {}}}
        get_node.translate_struct(attributes)
        try:
            result = self.server.invoke_successfully(get_node,
                                                     enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            self.module.fail_json(
                msg='Error fetching node for interface %s: %s' %
                (self.parameters['interface_name'], to_native(exc)),
                exception=traceback.format_exc())
        if result.get_child_by_name('num-records') and int(
                result.get_child_content('num-records')) >= 1:
            attributes = result.get_child_by_name('attributes-list')
            return attributes.get_child_by_name(
                'cluster-node-info').get_child_content('node-name')
        return None

    def validate_create_parameters(self, keys):
        '''
            Validate if required parameters for create are present.
            Parameter requirement might vary based on given data-protocol.
            :return: None
        '''
        if self.parameters.get('home_node') is None:
            node = self.get_home_node_for_cluster()
            if node is not None:
                self.parameters['home_node'] = node
        # validate if mandatory parameters are present for create
        if not keys.issubset(set(self.parameters.keys())
                             ) and self.parameters.get('subnet_name') is None:
            self.module.fail_json(
                msg=
                'Error: Missing one or more required parameters for creating interface: %s'
                % ', '.join(keys))
        # if role is intercluster, protocol cannot be specified
        if self.parameters['role'] == "intercluster" and self.parameters.get(
                'protocols') is not None:
            self.module.fail_json(
                msg='Error: Protocol cannot be specified for intercluster role,'
                'failed to create interface')

    def create_interface(self):
        ''' calling zapi to create interface '''
        required_keys = set(['role', 'home_port'])
        data_protocols_obj = None
        if self.parameters.get('subnet_name') is None:
            if self.parameters.get('is_ipv4_link_local') is not None:
                if not self.parameters.get('is_ipv4_link_local'):
                    required_keys.add('address')
                    required_keys.add('netmask')
        data_protocols_obj = self.set_protocol_option(required_keys)
        self.validate_create_parameters(required_keys)

        options = {
            'interface-name': self.parameters['interface_name'],
            'role': self.parameters['role'],
            'home-node': self.parameters.get('home_node'),
            'vserver': self.parameters['vserver']
        }
        NetAppOntapInterface.set_options(options, self.parameters)
        interface_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'net-interface-create', **options)
        if data_protocols_obj is not None:
            interface_create.add_child_elem(data_protocols_obj)
        try:
            self.server.invoke_successfully(interface_create,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            # msg: "Error Creating interface ansible_interface: NetApp API failed. Reason - 17:A LIF with the same name already exists"
            if to_native(exc.code) == "17":
                self.na_helper.changed = False
            else:
                self.module.fail_json(
                    msg='Error Creating interface %s: %s' %
                    (self.parameters['interface_name'], to_native(exc)),
                    exception=traceback.format_exc())

    def delete_interface(self, current_status):
        ''' calling zapi to delete interface '''
        if current_status == 'up':
            self.parameters['admin_status'] = 'down'
            self.modify_interface({'admin_status': 'down'})

        interface_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'net-interface-delete', **{
                'interface-name': self.parameters['interface_name'],
                'vserver': self.parameters['vserver']
            })
        try:
            self.server.invoke_successfully(interface_delete,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            self.module.fail_json(
                msg='Error deleting interface %s: %s' %
                (self.parameters['interface_name'], to_native(exc)),
                exception=traceback.format_exc())

    def modify_interface(self, modify):
        """
        Modify the interface.
        """
        options = {
            'interface-name': self.parameters['interface_name'],
            'vserver': self.parameters['vserver']
        }
        NetAppOntapInterface.set_options(options, modify)
        interface_modify = netapp_utils.zapi.NaElement.create_node_with_children(
            'net-interface-modify', **options)
        try:
            self.server.invoke_successfully(interface_modify,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as err:
            self.module.fail_json(
                msg='Error modifying interface %s: %s' %
                (self.parameters['interface_name'], to_native(err)),
                exception=traceback.format_exc())

    def autosupport_log(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_interface", cserver)

    def apply(self):
        ''' calling all interface features '''

        # Checking to see if autosupport_log() can be ran as this is a post cluster setup request.
        try:
            self.autosupport_log()
        except netapp_utils.zapi.NaApiError as error:
            # Error 13003 denotes cluster does not exist. It happens when running operations on a node not in cluster.
            if to_native(error.code) == "13003":
                pass
            else:
                self.module.fail_json(
                    msg='Error calling autosupport_log(): %s' %
                    (to_native(error)),
                    exception=traceback.format_exc())
        current = self.get_interface()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        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_interface()
                elif cd_action == 'delete':
                    self.delete_interface(current['admin_status'])
                elif modify:
                    self.modify_interface(modify)
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 24
0
class NetAppOntapLUN(object):
    ''' create, modify, delete LUN '''
    def __init__(self):

        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'),
            size=dict(type='int'),
            size_unit=dict(default='gb',
                           choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb',
                                    'pb', 'eb', 'zb', 'yb'], type='str'),
            force_resize=dict(default=False, type='bool'),
            force_remove=dict(default=False, type='bool'),
            force_remove_fenced=dict(default=False, type='bool'),
            flexvol_name=dict(required=True, type='str'),
            vserver=dict(required=True, type='str'),
            ostype=dict(required=False, type='str', default='image'),
            qos_policy_group=dict(required=False, type='str'),
            space_reserve=dict(required=False, type='bool', default=True),
            space_allocation=dict(required=False, type='bool', default=False),
            use_exact_size=dict(required=False, type='bool', default=True),
        ))

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

        # set up state variables
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        if self.parameters.get('size') is not None:
            self.parameters['size'] *= netapp_utils.POW2_BYTE_MAP[self.parameters['size_unit']]

        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_luns(self):
        """
        Return list of LUNs matching vserver and volume names.

        :return: list of LUNs in XML format.
        :rtype: list
        """
        luns = []
        tag = None
        while True:
            lun_info = netapp_utils.zapi.NaElement('lun-get-iter')
            if tag:
                lun_info.add_new_child('tag', tag, True)

            query_details = netapp_utils.zapi.NaElement('lun-info')
            query_details.add_new_child('vserver', self.parameters['vserver'])
            query_details.add_new_child('volume', self.parameters['flexvol_name'])

            query = netapp_utils.zapi.NaElement('query')
            query.add_child_elem(query_details)

            lun_info.add_child_elem(query)

            result = self.server.invoke_successfully(lun_info, True)
            if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
                attr_list = result.get_child_by_name('attributes-list')
                luns.extend(attr_list.get_children())

            tag = result.get_child_content('next-tag')

            if tag is None:
                break
        return luns

    def get_lun_details(self, name, lun):
        """
        Extract LUN details, from XML to python dict

        :return: Details about the lun
        :rtype: dict
        """
        return_value = dict(name=name)
        return_value['size'] = int(lun.get_child_content('size'))
        bool_attr_map = {
            'is-space-alloc-enabled': 'space_allocation',
            'is-space-reservation-enabled': 'space_reserve'
        }
        for attr in bool_attr_map:
            value = lun.get_child_content(attr)
            if value is not None:
                return_value[bool_attr_map[attr]] = self.na_helper.get_value_for_bool(True, value)
        str_attr_map = {
            'qos-policy-group': 'qos_policy_group',
            'multiprotocol-type': 'ostype'
        }
        for attr in str_attr_map:
            value = lun.get_child_content(attr)
            if value is not None:
                return_value[str_attr_map[attr]] = value

        # Find out if the lun is attached
        attached_to = None
        lun_id = None
        if lun.get_child_content('mapped') == 'true':
            lun_map_list = netapp_utils.zapi.NaElement.create_node_with_children(
                'lun-map-list-info', **{'path': lun.get_child_content('path')})
            result = self.server.invoke_successfully(
                lun_map_list, enable_tunneling=True)
            igroups = result.get_child_by_name('initiator-groups')
            if igroups:
                for igroup_info in igroups.get_children():
                    igroup = igroup_info.get_child_content(
                        'initiator-group-name')
                    attached_to = igroup
                    lun_id = igroup_info.get_child_content('lun-id')

        return_value.update({
            'attached_to': attached_to,
            'lun_id': lun_id
        })
        return return_value

    def get_lun(self, name):
        """
        Return details about the LUN

        :return: Details about the lun
        :rtype: dict
        """
        return_value = dict()
        luns = self.get_luns()
        # The LUNs have been extracted.
        # Find the specified lun and extract details.
        for lun in luns:
            path = lun.get_child_content('path')
            _rest, _splitter, found_name = path.rpartition('/')

            if found_name == name:
                return_value = self.get_lun_details(found_name, lun)
                break

        return return_value if return_value else None

    def create_lun(self):
        """
        Create LUN with requested name and size
        """
        path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name'])
        options = {'path': path,
                   'size': str(self.parameters['size']),
                   'ostype': self.parameters['ostype'],
                   'space-reservation-enabled': str(self.parameters['space_reserve']),
                   'space-allocation-enabled': str(self.parameters['space_allocation']),
                   'use-exact-size': str(self.parameters['use_exact_size'])}
        if self.parameters.get('qos_policy_group') is not None:
            options['qos-policy-group'] = self.parameters['qos_policy_group']
        lun_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'lun-create-by-size', **options)

        try:
            self.server.invoke_successfully(lun_create, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            self.module.fail_json(msg="Error provisioning lun %s of size %s: %s"
                                  % (self.parameters['name'], self.parameters['size'], to_native(exc)),
                                  exception=traceback.format_exc())

    def delete_lun(self):
        """
        Delete requested LUN
        """
        path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name'])

        lun_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'lun-destroy', **{'path': path,
                              'force': str(self.parameters['force_remove']),
                              'destroy-fenced-lun':
                                  str(self.parameters['force_remove_fenced'])})

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

    def resize_lun(self):
        """
        Resize requested LUN.

        :return: True if LUN was actually re-sized, false otherwise.
        :rtype: bool
        """
        path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name'])

        lun_resize = netapp_utils.zapi.NaElement.create_node_with_children(
            'lun-resize', **{'path': path,
                             'size': str(self.parameters['size']),
                             'force': str(self.parameters['force_resize'])})
        try:
            self.server.invoke_successfully(lun_resize, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            if to_native(exc.code) == "9042":
                # Error 9042 denotes the new LUN size being the same as the
                # old LUN size. This happens when there's barely any difference
                # in the two sizes. For example, from 8388608 bytes to
                # 8194304 bytes. This should go away if/when the default size
                # requested/reported to/from the controller is changed to a
                # larger unit (MB/GB/TB).
                return False
            else:
                self.module.fail_json(msg="Error resizing lun %s: %s" % (path, to_native(exc)),
                                      exception=traceback.format_exc())

        return True

    def set_lun_value(self, key, value):
        key_to_zapi = dict(
            qos_policy_group=('lun-set-qos-policy-group', 'qos-policy-group'),
            space_allocation=('lun-set-space-alloc', 'enable'),
            space_reserve=('lun-set-space-reservation-info', 'enable')
        )
        if key in key_to_zapi:
            zapi, option = key_to_zapi[key]
        else:
            self.module.fail_json(msg="option %s cannot be modified to %s" % (key, value))

        path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name'])
        options = dict(path=path)
        if option == 'enable':
            options[option] = self.na_helper.get_value_for_bool(False, value)
        else:
            options[option] = value

        lun_set = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options)
        try:
            self.server.invoke_successfully(lun_set, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            self.module.fail_json(msg="Error setting lun option %s: %s" % (key, to_native(exc)),
                                  exception=traceback.format_exc())
        return

    def modify_lun(self, modify):
        """
        update LUN properties (except size or name)
        """
        for key, value in modify.items():
            self.set_lun_value(key, value)

    def rename_lun(self):
        """
        rename LUN
        """
        path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['from_name'])
        new_path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name'])

        lun_move = netapp_utils.zapi.NaElement.create_node_with_children(
            'lun-move', **{'path': path,
                           'new-path': new_path})
        try:
            self.server.invoke_successfully(lun_move, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as exc:
            self.module.fail_json(msg="Error moving lun %s: %s" % (path, to_native(exc)),
                                  exception=traceback.format_exc())

    def apply(self):
        results = dict()
        netapp_utils.ems_log_event("na_ontap_lun", self.server)
        current = self.get_lun(self.parameters['name'])
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        modify, rename = None, None
        from_name = self.parameters.get('from_name')
        if cd_action == 'create' and from_name is not None:
            # create by renaming existing LUN, if it really exists
            old_lun = self.get_lun(from_name)
            rename = self.na_helper.is_rename_action(old_lun, current)
            if rename is None:
                self.module.fail_json(msg="Error renaming lun: %s does not exist" % self.parameters['from_name'])
            if rename:
                current = old_lun
                cd_action = None
        if cd_action is None and self.parameters['state'] == 'present':
            modify = self.na_helper.get_modified_attributes(current, self.parameters)
            results['modify'] = dict(modify)
        if cd_action == 'create' and self.parameters.get('size') is None:
            self.module.fail_json(msg="size is a required parameter for create.")
        if self.na_helper.changed and not self.module.check_mode:
            if cd_action == 'create':
                self.create_lun()
            elif cd_action == 'delete':
                self.delete_lun()
            else:
                if rename:
                    self.rename_lun()
                size_changed = False
                if 'size' in modify:
                    # Ensure that size was actually changed. Please
                    # read notes in 'resize_lun' function for details.
                    size_changed = self.resize_lun()
                    modify.pop('size')
                if modify:
                    # we already handled rename if required
                    modify.pop('name', None)
                    self.modify_lun(modify)
                if not modify and not rename:
                    # size may not have changed
                    self.na_helper.changed = size_changed

        results['changed'] = self.na_helper.changed
        self.module.exit_json(**results)
Exemplo n.º 25
0
class NetAppONTAPNVMe(object):
    """
    Class with NVMe service methods
    """
    def __init__(self):

        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'),
                 vserver=dict(required=True, type='str'),
                 status_admin=dict(required=False, type='bool')))

        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)

        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_nvme(self):
        """
        Get current nvme details
        :return: dict if nvme exists, None otherwise
        """
        nvme_get = netapp_utils.zapi.NaElement('nvme-get-iter')
        query = {
            'query': {
                'nvme-target-service-info': {
                    'vserver': self.parameters['vserver']
                }
            }
        }
        nvme_get.translate_struct(query)
        try:
            result = self.server.invoke_successfully(nvme_get,
                                                     enable_tunneling=False)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error fetching nvme info: %s' %
                                  to_native(error),
                                  exception=traceback.format_exc())
        if result.get_child_by_name('num-records') and int(
                result.get_child_content('num-records')) >= 1:
            attributes_list = result.get_child_by_name('attributes-list')
            nvme_info = attributes_list.get_child_by_name(
                'nvme-target-service-info')
            return_value = {
                'status_admin': nvme_info.get_child_content('is-available')
            }
            return return_value
        return None

    def create_nvme(self):
        """
        Create NVMe service
        """
        nvme_create = netapp_utils.zapi.NaElement('nvme-create')
        if self.parameters.get('status_admin') is not None:
            options = {'is-available': self.parameters['status_admin']}
            nvme_create.translate_struct(options)
        try:
            self.server.invoke_successfully(nvme_create, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error creating nvme for vserver %s: %s' %
                (self.parameters['vserver'], to_native(error)),
                exception=traceback.format_exc())

    def delete_nvme(self):
        """
        Delete NVMe service
        """
        nvme_delete = netapp_utils.zapi.NaElement('nvme-delete')
        try:
            self.server.invoke_successfully(nvme_delete, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error deleting nvme for vserver %s: %s' %
                (self.parameters['vserver'], to_native(error)),
                exception=traceback.format_exc())

    def modify_nvme(self, status=None):
        """
        Modify NVMe service
        """
        if status is None:
            status = self.parameters['status_admin']
        options = {'is-available': status}
        nvme_modify = netapp_utils.zapi.NaElement('nvme-modify')
        nvme_modify.translate_struct(options)
        try:
            self.server.invoke_successfully(nvme_modify, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error modifying nvme for vserver %s: %s' %
                (self.parameters['vserver'], to_native(error)),
                exception=traceback.format_exc())

    def apply(self):
        """
        Apply action to NVMe service
        """
        netapp_utils.ems_log_event("na_ontap_nvme", self.server)
        current = self.get_nvme()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if self.parameters.get('status_admin') is not None:
            self.parameters[
                'status_admin'] = self.na_helper.get_value_for_bool(
                    False, self.parameters['status_admin'])
            if 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_nvme()
                elif cd_action == 'delete':
                    # NVMe status_admin needs to be down before deleting it
                    self.modify_nvme('false')
                    self.delete_nvme()
                elif modify:
                    self.modify_nvme()

        self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapVolumeSnaplock(object):
    '''Class with volume operations'''
    def __init__(self):
        '''Initialize module parameters'''

        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(
                name=dict(required=True, type='str'),
                vserver=dict(required=True, type='str'),
                default_retention_period=dict(required=False, type='str'),
                maximum_retention_period=dict(required=False, type='str'),
                minimum_retention_period=dict(required=False, type='str'),
                autocommit_period=dict(required=False, type='str'),
                is_volume_append_mode_enabled=dict(required=False,
                                                   type='bool'),
            ))
        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)

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

    def get_volume_snaplock_attrs(self):
        """
        Return volume-get-snaplock-attrs query results
        :param vol_name: name of the volume
        :return: dict of the volume snaplock attrs
        """
        volume_snaplock = netapp_utils.zapi.NaElement(
            'volume-get-snaplock-attrs')
        volume_snaplock.add_new_child('volume', self.parameters['name'])

        try:
            result = self.server.invoke_successfully(volume_snaplock, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error fetching snaplock attributes for volume %s : %s' %
                (self.parameters['name'], to_native(error)),
                exception=traceback.format_exc())

        return_value = None

        if result.get_child_by_name('snaplock-attrs'):
            volume_snaplock_attributes = result['snaplock-attrs'][
                'snaplock-attrs-info']
            return_value = {
                'autocommit_period':
                volume_snaplock_attributes['autocommit-period'],
                'default_retention_period':
                volume_snaplock_attributes['default-retention-period'],
                'is_volume_append_mode_enabled':
                self.na_helper.get_value_for_bool(
                    True,
                    volume_snaplock_attributes['is-volume-append-mode-enabled']
                ),
                'maximum_retention_period':
                volume_snaplock_attributes['maximum-retention-period'],
                'minimum_retention_period':
                volume_snaplock_attributes['minimum-retention-period'],
            }
        return return_value

    def set_volume_snaplock_attrs(self, modify):
        '''Set ONTAP volume snaplock attributes'''
        volume_snaplock_obj = netapp_utils.zapi.NaElement(
            'volume-set-snaplock-attrs')
        volume_snaplock_obj.add_new_child('volume', self.parameters['name'])
        if modify.get('autocommit_period') is not None:
            volume_snaplock_obj.add_new_child(
                'autocommit-period', self.parameters['autocommit_period'])
        if modify.get('default_retention_period') is not None:
            volume_snaplock_obj.add_new_child(
                'default-retention-period',
                self.parameters['default_retention_period'])
        if modify.get('is_volume_append_mode_enabled') is not None:
            volume_snaplock_obj.add_new_child(
                'is-volume-append-mode-enabled',
                self.na_helper.get_value_for_bool(
                    False, self.parameters['is_volume_append_mode_enabled']))
        if modify.get('maximum_retention_period') is not None:
            volume_snaplock_obj.add_new_child(
                'maximum-retention-period',
                self.parameters['maximum_retention_period'])
        if modify.get('minimum_retention_period') is not None:
            volume_snaplock_obj.add_new_child(
                'minimum-retention-period',
                self.parameters['minimum_retention_period'])
        try:
            self.server.invoke_successfully(volume_snaplock_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error setting snaplock attributes for volume %s : %s' %
                (self.parameters['name'], to_native(error)),
                exception=traceback.format_exc())

    def apply(self):
        netapp_utils.ems_log_event("na_ontap_volume_snaplock", self.server)
        current, modify = self.get_volume_snaplock_attrs(), None
        modify = self.na_helper.get_modified_attributes(
            current, self.parameters)

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                self.set_volume_snaplock_attrs(modify)
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 27
0
class NetAppONTAPasup(object):
    """Class with autosupport methods"""

    def __init__(self):

        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            state=dict(required=False, choices=['present', 'absent'], default='present'),
            node_name=dict(required=True, type='str'),
            transport=dict(required=False, type='str', choices=['smtp', 'http', 'https']),
            noteto=dict(required=False, type='list'),
            post_url=dict(reuired=False, type='str'),
            support=dict(required=False, type='bool'),
            mail_hosts=dict(required=False, type='list'),
            from_address=dict(required=False, type='str'),
            partner_addresses=dict(required=False, type='list'),
            to_addresses=dict(required=False, type='list'),
            proxy_url=dict(required=False, type='str'),
            hostname_in_subject=dict(required=False, type='bool'),
        ))

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

        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        # present or absent requires modifying state to enabled or disabled
        self.parameters['service_state'] = 'started' if self.parameters['state'] == 'present' else 'stopped'
        self.set_playbook_zapi_key_map()

        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)

    def set_playbook_zapi_key_map(self):
        self.na_helper.zapi_string_keys = {
            'node_name': 'node-name',
            'transport': 'transport',
            'post_url': 'post-url',
            'from_address': 'from',
            'proxy_url': 'proxy-url'
        }
        self.na_helper.zapi_list_keys = {
            'noteto': ('noteto', 'mail-address'),
            'mail_hosts': ('mail-hosts', 'string'),
            'partner_addresses': ('partner-address', 'mail-address'),
            'to_addresses': ('to', 'mail-address'),
        }
        self.na_helper.zapi_bool_keys = {
            'support': 'is-support-enabled',
            'hostname_in_subject': 'is-node-in-subject'
        }

    def get_autosupport_config(self):
        """
        Invoke zapi - get current autosupport details
        :return: dict()
        """
        asup_details = netapp_utils.zapi.NaElement('autosupport-config-get')
        asup_details.add_new_child('node-name', self.parameters['node_name'])
        asup_info = dict()
        try:
            result = self.server.invoke_successfully(asup_details, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='%s' % to_native(error),
                                  exception=traceback.format_exc())
        # zapi invoke successful
        asup_attr_info = result.get_child_by_name('attributes').get_child_by_name('autosupport-config-info')
        asup_info['service_state'] = 'started' if asup_attr_info['is-enabled'] == 'true' else 'stopped'
        for item_key, zapi_key in self.na_helper.zapi_string_keys.items():
            asup_info[item_key] = asup_attr_info[zapi_key]
        for item_key, zapi_key in self.na_helper.zapi_bool_keys.items():
            asup_info[item_key] = self.na_helper.get_value_for_bool(from_zapi=True,
                                                                    value=asup_attr_info[zapi_key])
        for item_key, zapi_key in self.na_helper.zapi_list_keys.items():
            parent, dummy = zapi_key
            asup_info[item_key] = self.na_helper.get_value_for_list(from_zapi=True,
                                                                    zapi_parent=asup_attr_info.get_child_by_name(parent)
                                                                    )
        return asup_info

    def modify_autosupport_config(self, modify):
        """
        Invoke zapi - modify autosupport config
        @return: NaElement object / FAILURE with an error_message
        """
        asup_details = {'node-name': self.parameters['node_name']}
        if modify.get('service_state'):
            asup_details['is-enabled'] = 'true' if modify.get('service_state') == 'started' else 'false'
        asup_config = netapp_utils.zapi.NaElement('autosupport-config-modify')
        for item_key in modify:
            if item_key in self.na_helper.zapi_string_keys:
                zapi_key = self.na_helper.zapi_string_keys.get(item_key)
                asup_details[zapi_key] = modify[item_key]
            elif item_key in self.na_helper.zapi_bool_keys:
                zapi_key = self.na_helper.zapi_bool_keys.get(item_key)
                asup_details[zapi_key] = self.na_helper.get_value_for_bool(from_zapi=False,
                                                                           value=modify[item_key])
            elif item_key in self.na_helper.zapi_list_keys:
                parent_key, child_key = self.na_helper.zapi_list_keys.get(item_key)
                asup_config.add_child_elem(self.na_helper.get_value_for_list(from_zapi=False,
                                                                             zapi_parent=parent_key,
                                                                             zapi_child=child_key,
                                                                             data=modify.get(item_key)))
        asup_config.translate_struct(asup_details)
        try:
            return self.server.invoke_successfully(asup_config, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='%s' % to_native(error), exception=traceback.format_exc())

    def autosupport_log(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_autosupport", cserver)

    def apply(self):
        """
        Apply action to autosupport
        """
        current = self.get_autosupport_config()
        modify = self.na_helper.get_modified_attributes(current, self.parameters)
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                self.modify_autosupport_config(modify)
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 28
0
class NetAppOntapFpolicyPolicy():

    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            state=dict(choices=['present', 'absent'], default='present'),
            vserver=dict(required=True, type='str'),
            name=dict(required=True, type='str'),
            allow_privileged_access=dict(required=False, type='bool'),
            engine=dict(required=False, type='str'),
            events=dict(required=True, type='list', elements='str'),
            is_mandatory=dict(required=False, type='bool'),
            is_passthrough_read_enabled=dict(required=False, type='bool'),
            privileged_user_name=dict(required=False, 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.rest_api = OntapRestAPI(self.module)
        self.use_rest = self.rest_api.is_rest()

        if not self.use_rest:
            if not netapp_utils.has_netapp_lib():
                self.module.fail_json(msg=netapp_utils.netapp_lib_is_required())
            else:
                self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])

    def get_fpolicy_policy(self):
        """
       Check if FPolicy policy exists, if it exists get the current state of the policy.
        """
        if self.use_rest:
            api = "/private/cli/vserver/fpolicy/policy"
            query = {
                'vserver': self.parameters['vserver'],
                'policy-name': self.parameters['name'],
                'fields': 'events,engine,allow-privileged-access,is-mandatory,is-passthrough-read-enabled,privileged-user-name'
            }

            message, error = self.rest_api.get(api, query)
            if error:
                self.module.fail_json(msg=error)
            if len(message.keys()) == 0:
                return None
            if 'records' in message and len(message['records']) == 0:
                return None
            if 'records' not in message:
                error = "Unexpected response in get_fpolicy_policy from %s: %s" % (api, repr(message))
                self.module.fail_json(msg=error)
            return_value = {
                'vserver': message['records'][0]['vserver'],
                'name': message['records'][0]['policy_name'],
                'events': message['records'][0]['events'],
                'allow_privileged_access': message['records'][0]['allow_privileged_access'],
                'engine': message['records'][0]['engine'],
                'is_mandatory': message['records'][0]['is_mandatory'],
                'is_passthrough_read_enabled': message['records'][0]['is_passthrough_read_enabled']
            }
            if 'privileged_user_name' in message['records'][0]:
                return_value['privileged_user_name'] = message['records'][0]['privileged_user_name']

            return return_value

        else:
            return_value = None

            fpolicy_policy_obj = netapp_utils.zapi.NaElement('fpolicy-policy-get-iter')
            fpolicy_policy_config = netapp_utils.zapi.NaElement('fpolicy-policy-info')
            fpolicy_policy_config.add_new_child('policy-name', self.parameters['name'])
            fpolicy_policy_config.add_new_child('vserver', self.parameters['vserver'])
            query = netapp_utils.zapi.NaElement('query')
            query.add_child_elem(fpolicy_policy_config)
            fpolicy_policy_obj.add_child_elem(query)

            try:
                result = self.server.invoke_successfully(fpolicy_policy_obj, True)

            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg='Error searching for fPolicy policy %s on vserver %s: %s' % (self.parameters['name'], self.parameters['vserver'], to_native(error)),
                    exception=traceback.format_exc())
            if result.get_child_by_name('attributes-list'):
                fpolicy_policy_attributes = result['attributes-list']['fpolicy-policy-info']
                events = []
                if fpolicy_policy_attributes.get_child_by_name('events'):
                    for event in fpolicy_policy_attributes.get_child_by_name('events').get_children():
                        events.append(event.get_content())

                return_value = {
                    'vserver': fpolicy_policy_attributes.get_child_content('vserver'),
                    'name': fpolicy_policy_attributes.get_child_content('policy-name'),
                    'events': events,
                    'allow_privileged_access': self.na_helper.get_value_for_bool(
                        from_zapi=True, value=fpolicy_policy_attributes.get_child_content('allow-privileged-access')),
                    'engine': fpolicy_policy_attributes.get_child_content('engine-name'),
                    'is_mandatory': self.na_helper.get_value_for_bool(
                        from_zapi=True, value=fpolicy_policy_attributes.get_child_content('is-mandatory')),
                    'is_passthrough_read_enabled': self.na_helper.get_value_for_bool(
                        from_zapi=True, value=fpolicy_policy_attributes.get_child_content('is-passthrough-read-enabled')),
                    'privileged_user_name': fpolicy_policy_attributes.get_child_content('privileged-user-name')
                }

            return return_value

    def create_fpolicy_policy(self):
        """
        Create an FPolicy policy.
        """
        if self.use_rest:
            api = "/private/cli/vserver/fpolicy/policy"
            body = {
                'vserver': self.parameters['vserver'],
                'policy-name': self.parameters['name'],
                'events': self.parameters['events']
            }
            for parameter in ('engine', 'allow_privileged_access', 'is_mandatory', 'is_passthrough_read_enabled', 'privileged_user_name'):
                if parameter in self.parameters:
                    body[parameter.replace('_', '-')] = self.parameters[parameter]

            dummy, error = self.rest_api.post(api, body)
            if error:
                self.module.fail_json(msg=error)

        else:
            fpolicy_policy_obj = netapp_utils.zapi.NaElement('fpolicy-policy-create')
            fpolicy_policy_obj.add_new_child('policy-name', self.parameters['name'])
            if 'is_mandatory' in self.parameters:
                fpolicy_policy_obj.add_new_child('is-mandatory', self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters['is_mandatory']))
            if 'engine' in self.parameters:
                fpolicy_policy_obj.add_new_child('engine-name', self.parameters['engine'])
            if 'allow_privileged_access' in self.parameters:
                fpolicy_policy_obj.add_new_child(
                    'allow-privileged-access', self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters['allow_privileged_access'])
                )
            if 'is_passthrough_read_enabled' in self.parameters:
                fpolicy_policy_obj.add_new_child(
                    'is-passthrough-read-enabled', self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters['is_passthrough_read_enabled'])
                )
            events_obj = netapp_utils.zapi.NaElement('events')
            for event in self.parameters['events']:
                events_obj.add_new_child('event-name', event)
            fpolicy_policy_obj.add_child_elem(events_obj)

            if 'privileged_user_name' in self.parameters:
                fpolicy_policy_obj.add_new_child('privileged-user-name', self.parameters['privileged_user_name'])
            try:
                self.server.invoke_successfully(fpolicy_policy_obj, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg='Error creating fPolicy policy %s on vserver %s: %s' % (self.parameters['name'], self.parameters['vserver'], to_native(error)),
                    exception=traceback.format_exc()
                )

    def modify_fpolicy_policy(self, modify):
        """
        Modify an FPolicy policy.
        """
        if self.use_rest:
            api = "/private/cli/vserver/fpolicy/policy"
            query = {'vserver': self.parameters['vserver']}
            query['policy-name'] = self.parameters['name']
            dummy, error = self.rest_api.patch(api, modify, query)
            if error:
                self.module.fail_json(msg=error)
        else:
            fpolicy_policy_obj = netapp_utils.zapi.NaElement('fpolicy-policy-modify')
            fpolicy_policy_obj.add_new_child('policy-name', self.parameters['name'])
            if 'is_mandatory' in self.parameters:
                fpolicy_policy_obj.add_new_child('is-mandatory', self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters['is_mandatory']))
            if 'engine' in self.parameters:
                fpolicy_policy_obj.add_new_child('engine-name', self.parameters['engine'])
            if 'allow_privileged_access' in self.parameters:
                fpolicy_policy_obj.add_new_child(
                    'allow-privileged-access', self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters['allow_privileged_access'])
                )
            if 'is_passthrough_read_enabled' in self.parameters:
                fpolicy_policy_obj.add_new_child(
                    'is-passthrough-read-enabled', self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters['is_passthrough_read_enabled'])
                )
            events_obj = netapp_utils.zapi.NaElement('events')
            for event in self.parameters['events']:
                events_obj.add_new_child('event-name', event)
            fpolicy_policy_obj.add_child_elem(events_obj)

            if 'privileged_user_name' in self.parameters:
                fpolicy_policy_obj.add_new_child('privileged-user-name', self.parameters['privileged_user_name'])
            try:
                self.server.invoke_successfully(fpolicy_policy_obj, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg='Error modifying fPolicy policy %s on vserver %s: %s' %
                    (self.parameters['name'], self.parameters['vserver'], to_native(error)), exception=traceback.format_exc()
                )

    def delete_fpolicy_policy(self):
        """
        Delete an FPolicy policy.
        """
        if self.use_rest:
            api = "/private/cli/vserver/fpolicy/policy"
            body = {
                'vserver': self.parameters['vserver'],
                'policy-name': self.parameters['name']
            }
            dummy, error = self.rest_api.delete(api, body)
            if error:
                self.module.fail_json(msg=error)

        else:
            fpolicy_policy_obj = netapp_utils.zapi.NaElement('fpolicy-policy-delete')
            fpolicy_policy_obj.add_new_child('policy-name', self.parameters['name'])

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

    def apply(self):
        if not self.use_rest:
            netapp_utils.ems_log_event("na_ontap_fpolicy_policy", self.server)

        current = self.get_fpolicy_policy()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if 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 not self.module.check_mode:
                if cd_action == 'create':
                    self.create_fpolicy_policy()
                elif cd_action == 'delete':
                    self.delete_fpolicy_policy()
                elif modify:
                    self.modify_fpolicy_policy(modify)

        self.module.exit_json(changed=self.na_helper.changed)