Beispiel #1
0
class NetAppONTAPCommandREST(object):
    ''' calls a CLI command '''

    def __init__(self):
        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            command=dict(required=True, type='str'),
            verb=dict(required=True, type='str', choices=['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS']),
            params=dict(required=False, type='dict', default={}),
            body=dict(required=False, type='dict', default={})
        ))
        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True
        )
        self.restApi = OntapRestAPI(self.module)
        parameters = self.module.params
        # set up state variables
        self.command = parameters['command']
        self.verb = parameters['verb']
        self.params = parameters['params']
        self.body = parameters['body']

        if self.restApi.is_rest():
            self.use_rest = True
        else:
            self.module.fail_json(msg="use na_ontap_command for non-rest cli")

    def run_command(self):
        api = "private/cli/" + self.command

        if self.verb == 'POST':
            message, error = self.restApi.post(api, self.body, self.params)
        elif self.verb == 'GET':
            message, error = self.restApi.get(api, self.params)
        elif self.verb == 'PATCH':
            message, error = self.restApi.patch(api, self.body, self.params)
        elif self.verb == 'DELETE':
            message, error = self.restApi.delete(api, self.body, self.params)
        elif self.verb == 'OPTIONS':
            message, error = self.restApi.options(api, self.params)
        else:
            self.module.fail_json(msg='Error running command %s:' % self.command,
                                  exception=traceback.format_exc())

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

    def apply(self):
        ''' calls the command and returns raw output '''
        changed = True
        output = self.run_command()
        self.module.exit_json(changed=changed, msg=output)
Beispiel #2
0
class NetAppOntapNetRoutes(object):
    """
    Create, Modifies and Destroys a Net Route
    """
    def __init__(self):
        """
        Initialize the Ontap Net Route class
        """
        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'),
                vserver=dict(required=True, type='str'),
                destination=dict(required=True, type='str'),
                gateway=dict(required=True, type='str'),
                metric=dict(required=False, type='int'),
                from_destination=dict(required=False, type='str',
                                      default=None),
                from_gateway=dict(required=False, type='str', default=None),
                from_metric=dict(required=False, type='int', default=None),
            ))

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

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

        self.restApi = OntapRestAPI(self.module)
        # some attributes are not supported in earlier REST implementation
        unsupported_rest_properties = ['metric', 'from_metric']
        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 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'])
        return

    def create_net_route(self, current_metric=None):
        """
        Creates a new Route
        """
        if self.use_rest:
            api = "network/ip/routes"
            params = {
                'gateway': self.parameters['gateway'],
                'svm': self.parameters['vserver']
            }
            if self.parameters.get('destination') is not None:
                d = self.parameters['destination'].split('/')
                params['destination'] = {'address': d[0], 'netmask': d[1]}
            __, error = self.restApi.post(api, params)
            if error:
                self.module.fail_json(msg=error)
        else:
            route_obj = netapp_utils.zapi.NaElement('net-routes-create')
            route_obj.add_new_child("destination",
                                    self.parameters['destination'])
            route_obj.add_new_child("gateway", self.parameters['gateway'])
            if current_metric is None and self.parameters.get(
                    'metric') is not None:
                metric = self.parameters['metric']
            else:
                metric = current_metric
            # Metric can be None, Can't set metric to none
            if metric is not None:
                route_obj.add_new_child("metric", str(metric))
            try:
                self.server.invoke_successfully(route_obj, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error creating net route: %s' %
                                      (to_native(error)),
                                      exception=traceback.format_exc())

    def delete_net_route(self, params):
        """
        Deletes a given Route
        """
        if self.use_rest:
            uuid = params['uuid']
            api = "network/ip/routes/" + uuid
            data = None
            message, error = self.restApi.delete(api, data)
            if error:
                self.module.fail_json(msg=error)
        else:
            route_obj = netapp_utils.zapi.NaElement('net-routes-destroy')
            if params is None:
                params = self.parameters
            route_obj.add_new_child("destination", params['destination'])
            route_obj.add_new_child("gateway", params['gateway'])
            try:
                self.server.invoke_successfully(route_obj, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error deleting net route: %s' %
                                      (to_native(error)),
                                      exception=traceback.format_exc())

    def modify_net_route(self, current, desired):
        """
        Modify a net route
        Since we cannot modify a route, we are deleting the existing route, and creating a new one.
        """
        if self.use_rest:
            if desired.get('destination') is not None:
                d = desired['destination'].split('/')
                if d[0] != current['destination']['address'] or d[
                        1] != current['destination']['netmask']:
                    self.na_helper.changed = True
                self.parameters['destination'] = desired['destination']
            else:
                self.parameters['destination'] = '%s/%s' % (
                    current['destination']['address'],
                    current['destination']['netmask'])
            if desired.get('gateway') is not None:
                if desired['gateway'] != current['gateway']:
                    self.na_helper.changed = True
                self.parameters['gateway'] = desired['gateway']
            else:
                self.parameters['gateway'] = current['gateway']
            if not self.na_helper.changed or self.module.check_mode:
                return
            params = {
                'destination':
                '%s/%s' % (current['destination']['address'],
                           current['destination']['netmask']),
                'gateway':
                current['gateway']
            }
            target = self.get_net_route(params)
            self.delete_net_route(target)
            self.create_net_route()
            return

        else:
            # return if there is nothing to change
            for key, val in desired.items():
                if val != current[key]:
                    self.na_helper.changed = True
                    break
            if not self.na_helper.changed or self.module.check_mode:
                return
            # delete and re-create with new params
            self.delete_net_route(current)
            route_obj = netapp_utils.zapi.NaElement('net-routes-create')
            for attribute in ['metric', 'destination', 'gateway']:
                if desired.get(attribute) is not None:
                    value = desired[attribute]
                else:
                    value = current[attribute]
                route_obj.add_new_child(attribute, str(value))
            try:
                result = self.server.invoke_successfully(route_obj, True)
            except netapp_utils.zapi.NaApiError as error:
                # restore the old route, create the route with the existing metric
                self.create_net_route(current['metric'])
                # return if desired route already exists
                if to_native(error.code) == '13001':
                    return
                # Invalid value specified for any of the attributes
                self.module.fail_json(msg='Error modifying net route: %s' %
                                      (to_native(error)),
                                      exception=traceback.format_exc())

    def get_net_route(self, params=None):
        """
        Checks to see if a route exist or not
        :return: NaElement object if a route exists, None otherwise
        """
        if params is not None:
            # we need either destination or gateway to fetch desired route
            if params.get('destination') is None and params.get(
                    'gateway') is None:
                return None
        if self.use_rest:
            api = "network/ip/routes"
            data = {'fields': 'destination,gateway,svm'}
            message, error = self.restApi.get(api, data)
            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_net_route from %s: %s" % (
                    api, repr(message))
                self.module.fail_json(msg=error)
            if params is None:
                params = self.parameters
            else:
                if params.get('destination') is None:
                    params['destination'] = self.parameters['destination']
                if params.get('gateway') is None:
                    params['gateway'] = self.parameters['gateway']
                params['vserver'] = self.parameters['vserver']
            for record in message['records']:
                if record['gateway'] == params['gateway'] and \
                        record['destination']['address'] == params['destination'].split('/')[0] and \
                        record.get('svm') and record['svm']['name'] == params['vserver']:
                    return record
            return None
        else:
            current = None
            route_obj = netapp_utils.zapi.NaElement('net-routes-get')
            for attr in ['destination', 'gateway']:
                if params and params.get(attr) is not None:
                    value = params[attr]
                else:
                    value = self.parameters[attr]
                route_obj.add_new_child(attr, value)
            try:
                result = self.server.invoke_successfully(route_obj, True)
                if result.get_child_by_name('attributes') is not None:
                    route_info = result.get_child_by_name(
                        'attributes').get_child_by_name('net-vs-routes-info')
                    current = {
                        'destination':
                        route_info.get_child_content('destination'),
                        'gateway': route_info.get_child_content('gateway'),
                        'metric': int(route_info.get_child_content('metric'))
                    }

            except netapp_utils.zapi.NaApiError as error:
                # Error 13040 denotes a route doesn't exist.
                if to_native(error.code) == "15661":
                    return None
                self.module.fail_json(msg='Error fetching net route: %s' %
                                      (to_native(error)),
                                      exception=traceback.format_exc())
            return current

    @staticmethod
    def is_modify_action(current, desired):
        """
        Get desired action to be applied for net routes
        Destination and gateway are unique params for a route and cannot be duplicated
        So if a route with desired destination or gateway exists already, we don't try to modify
        :param current: current details
        :param desired: desired details
        :return: create / delete / modify / None
        """
        if current is None and desired is None:
            # this is invalid
            # cannot modify a non existent resource
            return None
        if current is None and desired is not None:
            # idempotency or duplication
            # we need not create
            return False
        if current is not None and desired is not None:
            # we can't modify an ambiguous route (idempotency/duplication)
            return False
        return True

    def get_params_to_be_modified(self, current):
        """
        Get parameters and values that need to be modified
        :param current: current details
        :return: dict(), None
        """
        if current is None:
            return None
        desired = dict()
        if self.parameters.get('new_destination') is not None and \
                self.parameters['new_destination'] != current['destination']:
            desired['destination'] = self.parameters['new_destination']
        if self.parameters.get('new_gateway') is not None and \
                self.parameters['new_gateway'] != current['gateway']:
            desired['gateway'] = self.parameters['new_gateway']
        if self.parameters.get('new_metric') is not None and \
                self.parameters['new_metric'] != current['metric']:
            desired['metric'] = self.parameters['new_metric']
        return desired

    def apply(self):
        """
        Run Module based on play book
        """
        if not self.use_rest:
            netapp_utils.ems_log_event("na_ontap_net_routes", self.server)
        current = self.get_net_route()
        modify, cd_action = None, None
        if self.use_rest:
            modify_params = {
                'gateway': self.parameters.get('from_gateway'),
                'destination': self.parameters.get('from_destination')
            }
            if any(modify_params.values()):
                # destination and gateway combination is unique, and is considered like a id. so modify destination
                # or gateway is considered a rename action.
                old_params = self.get_net_route(modify_params)
                modify = self.na_helper.is_rename_action(old_params, current)
                if modify is None:
                    self.module.fail_json(
                        msg="Error modifying: route %s does not exist" %
                        self.parameters['from_destination'])
            else:
                cd_action = self.na_helper.get_cd_action(
                    current, self.parameters)
        else:
            modify_params = {
                'destination': self.parameters.get('from_destination'),
                'gateway': self.parameters.get('from_gateway'),
                'metric': self.parameters.get('from_metric')
            }
            # if any from_* param is present in playbook, check for modify action
            if any(modify_params.values()):
                # destination and gateway combination is unique, and is considered like a id. so modify destination
                # or gateway is considered a rename action. metric is considered an attribute of the route so it is
                # considered as modify.
                if modify_params.get('metric') is not None:
                    modify = True
                    old_params = current
                else:
                    # get parameters that are eligible for modify
                    old_params = self.get_net_route(modify_params)
                    modify = self.na_helper.is_rename_action(
                        old_params, current)
                if modify is None:
                    self.module.fail_json(
                        msg="Error modifying: route %s does not exist" %
                        self.parameters['from_destination'])
            else:
                cd_action = self.na_helper.get_cd_action(
                    current, self.parameters)
        if cd_action == 'create':
            if not self.module.check_mode:
                self.create_net_route()
        elif cd_action == 'delete':
            if not self.module.check_mode:
                self.delete_net_route(current)
        elif modify:
            desired = {}
            for key, value in old_params.items():
                desired[key] = value
            for key, value in modify_params.items():
                if value is not None:
                    desired[key] = self.parameters.get(key)
            self.modify_net_route(old_params, desired)
        self.module.exit_json(changed=self.na_helper.changed)
Beispiel #3
0
class NetAppOntapIpspace(object):
    '''Class with ipspace operations'''
    def __init__(self):
        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(
                state=dict(required=False,
                           type='str',
                           choices=['present', 'absent'],
                           default='present'),
                name=dict(required=True, type='str'),
                from_name=dict(required=False, type='str'),
            ))
        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)
        if self.rest_api.is_rest():
            self.use_rest = True
        else:
            if HAS_NETAPP_LIB is False:
                self.module.fail_json(
                    msg="the python NetApp-Lib module is required")
            else:
                self.server = netapp_utils.setup_na_ontap_zapi(
                    module=self.module)
        return

    def ipspace_get_iter(self, name):
        """
        Return net-ipspaces-get-iter query results
        :param name: Name of the ipspace
        :return: NaElement if ipspace found, None otherwise
        """
        ipspace_get_iter = netapp_utils.zapi.NaElement('net-ipspaces-get-iter')
        query_details = netapp_utils.zapi.NaElement.create_node_with_children(
            'net-ipspaces-info', **{'ipspace': name})
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(query_details)
        ipspace_get_iter.add_child_elem(query)
        try:
            result = self.server.invoke_successfully(ipspace_get_iter,
                                                     enable_tunneling=False)
        except netapp_utils.zapi.NaApiError as error:
            # Error 14636 denotes an ipspace does not exist
            # Error 13073 denotes an ipspace not found
            if to_native(error.code) == "14636" or to_native(
                    error.code) == "13073":
                return None
            else:
                self.module.fail_json(msg=to_native(error),
                                      exception=traceback.format_exc())
        return result

    def get_ipspace(self, name=None):
        """
        Fetch details if ipspace exists
        :param name: Name of the ipspace to be fetched
        :return:
            Dictionary of current details if ipspace found
            None if ipspace is not found
        """
        if name is None:
            name = self.parameters['name']
        if self.use_rest:
            api = 'network/ipspaces'
            params = None
            message, error = self.rest_api.get(api, params)
            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 from %s: %s" % (api,
                                                             repr(message))
                self.module.fail_json(msg=error)
            for record in message['records']:
                if record['name'] == name:
                    return record
            return None
        else:
            ipspace_get = self.ipspace_get_iter(name)
            if (ipspace_get and ipspace_get.get_child_by_name('num-records')
                    and
                    int(ipspace_get.get_child_content('num-records')) >= 1):
                current_ipspace = dict()
                attr_list = ipspace_get.get_child_by_name('attributes-list')
                attr = attr_list.get_child_by_name('net-ipspaces-info')
                current_ipspace['name'] = attr.get_child_content('ipspace')
                return current_ipspace
            return None

    def create_ipspace(self):
        """
        Create ipspace
        :return: None
        """
        if self.use_rest:
            api = 'network/ipspaces'
            params = {'name': self.parameters['name']}
            dummy, error = self.rest_api.post(api, params)
            if error:
                self.module.fail_json(msg=error)
        else:
            ipspace_create = netapp_utils.zapi.NaElement.create_node_with_children(
                'net-ipspaces-create', **{'ipspace': self.parameters['name']})
            try:
                self.server.invoke_successfully(ipspace_create,
                                                enable_tunneling=False)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg="Error provisioning ipspace %s: %s" %
                    (self.parameters['name'], to_native(error)),
                    exception=traceback.format_exc())

    def delete_ipspace(self):
        """
        Destroy ipspace
        :return: None
        """
        if self.use_rest:
            current = self.get_ipspace()
            if current is not None:
                uuid = current['uuid']
                api = 'network/ipspaces/' + uuid
                dummy, error = self.rest_api.delete(api)
                if error:
                    self.module.fail_json(msg=error)
        else:
            ipspace_destroy = netapp_utils.zapi.NaElement.create_node_with_children(
                'net-ipspaces-destroy', **{'ipspace': self.parameters['name']})
            try:
                self.server.invoke_successfully(ipspace_destroy,
                                                enable_tunneling=False)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg="Error removing ipspace %s: %s" %
                    (self.parameters['name'], to_native(error)),
                    exception=traceback.format_exc())

    def rename_ipspace(self):
        """
        Rename an ipspace
        :return: Nothing
        """
        if self.use_rest:
            current = self.get_ipspace(self.parameters['from_name'])
            if current is None:
                self.module.fail_json(msg="Error renaming ipspace %s" %
                                      (self.parameters['from_name']))
            uuid = current['uuid']
            api = 'network/ipspaces/' + uuid
            params = {'name': self.parameters['name']}
            dummy, error = self.rest_api.patch(api, params)
            if error:
                self.module.fail_json(msg=error)
        else:
            ipspace_rename = netapp_utils.zapi.NaElement.create_node_with_children(
                'net-ipspaces-rename', **{
                    'ipspace': self.parameters['from_name'],
                    'new-name': self.parameters['name']
                })
            try:
                self.server.invoke_successfully(ipspace_rename,
                                                enable_tunneling=False)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg="Error renaming ipspace %s: %s" %
                    (self.parameters['from_name'], to_native(error)),
                    exception=traceback.format_exc())

    def apply(self):
        """
        Apply action to the ipspace
        :return: Nothing
        """
        current = self.get_ipspace()
        # rename and create are mutually exclusive
        rename, cd_action = None, None
        if self.parameters.get('from_name'):
            rename = self.na_helper.is_rename_action(
                self.get_ipspace(self.parameters['from_name']), current)
            if rename is None:
                self.module.fail_json(
                    msg="Error renaming: ipspace %s does not exist" %
                    self.parameters['from_name'])
        else:
            cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if rename:
                    self.rename_ipspace()
                elif cd_action == 'create':
                    self.create_ipspace()
                elif cd_action == 'delete':
                    self.delete_ipspace()
        self.module.exit_json(changed=self.na_helper.changed)
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)
Beispiel #5
0
class NetAppONTAPExportPolicy(object):
    """
    Class with export policy methods
    """

    def __init__(self):
        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
            name=dict(required=True, type='str'),
            from_name=dict(required=False, type='str', default=None),
            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.restApi = OntapRestAPI(self.module)
        if self.restApi.is_rest():
            self.use_rest = True
        else:
            if HAS_NETAPP_LIB is False:
                self.module.fail_json(msg="the python NetApp-Lib module is required")
            else:
                self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])

    def get_export_policy(self, name=None, uuid=None):
        """
        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
        """
        if name is None:
            name = self.parameters['name']
        if self.use_rest:
            params = {'fields': 'name',
                      'name': name,
                      'svm.uuid': uuid}
            api = 'protocols/nfs/export-policies/'
            message, error = self.restApi.get(api, params)
            if error is not None:
                self.module.fail_json(msg="Error on fetching export policy: %s" % error)
            if message['num_records'] > 0:
                return {'policy-name': message['records'][0]['name']}
            else:
                return None

        else:
            export_policy_iter = netapp_utils.zapi.NaElement('export-policy-get-iter')
            export_policy_info = netapp_utils.zapi.NaElement('export-policy-info')
            export_policy_info.add_new_child('policy-name', name)
            export_policy_info.add_new_child('vserver', self.parameters['vserver'])
            query = netapp_utils.zapi.NaElement('query')
            query.add_child_elem(export_policy_info)
            export_policy_iter.add_child_elem(query)
            result = self.server.invoke_successfully(export_policy_iter, True)
            return_value = None
            # check if query returns the expected export-policy
            if result.get_child_by_name('num-records') and \
                    int(result.get_child_content('num-records')) == 1:

                export_policy = result.get_child_by_name('attributes-list').get_child_by_name('export-policy-info').get_child_by_name('policy-name')
                return_value = {
                    'policy-name': export_policy
                }
            return return_value

    def create_export_policy(self, uuid=None):
        """
        Creates an export policy
        """
        if self.use_rest:
            params = {'name': self.parameters['name'],
                      'svm.uuid': uuid}
            api = 'protocols/nfs/export-policies'
            message, error = self.restApi.post(api, params)
            if error is not None:
                self.module.fail_json(msg="Error on creating export policy: %s" % error)
        else:
            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 on creating export-policy %s: %s'
                                      % (self.parameters['name'], to_native(error)),
                                      exception=traceback.format_exc())

    def delete_export_policy(self, policy_id=None):
        """
        Delete export-policy
        """
        if self.use_rest:
            params = {}
            api = 'protocols/nfs/export-policies/' + str(policy_id)
            message, error = self.restApi.delete(api, params)
            if error is not None:
                self.module.fail_json(msg=" Error on deleting export policy: %s" % error)
        else:
            export_policy_delete = netapp_utils.zapi.NaElement.create_node_with_children(
                'export-policy-destroy', **{'policy-name': self.parameters['name'], })
            try:
                self.server.invoke_successfully(export_policy_delete,
                                                enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error on deleting export-policy %s: %s'
                                      % (self.parameters['name'],
                                         to_native(error)), exception=traceback.format_exc())

    def rename_export_policy(self, policy_id=None):
        """
        Rename the export-policy.
        """
        if self.use_rest:
            params = {'name': self.parameters['name']}
            api = 'protocols/nfs/export-policies/' + str(policy_id)
            message, error = self.restApi.patch(api, params)
            if error is not None:
                self.module.fail_json(msg="Error on renaming export policy: %s" % error)
        else:
            export_policy_rename = netapp_utils.zapi.NaElement.create_node_with_children(
                'export-policy-rename', **{'policy-name': self.parameters['from_name'],
                                           'new-policy-name': self.parameters['name']})
            try:
                self.server.invoke_successfully(export_policy_rename,
                                                enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error on renaming export-policy %s:%s'
                                      % (self.parameters['name'], to_native(error)),
                                      exception=traceback.format_exc())

    def get_export_policy_id(self, name=None):
        """
        Get a export policy's id
        :return: id of the export policy
        """
        if name is None:
            name = self.parameters['name']

        params = {'fields': 'id',
                  'svm.name': self.parameters['vserver'],
                  'name': name
                  }
        api = 'protocols/nfs/export-policies'
        message, error = self.restApi.get(api, params)
        if error is not None:
            self.module.fail_json(msg="%s" % error)
        if message['num_records'] == 0:
            return None
        else:
            return message['records'][0]['id']

    def get_export_policy_svm_uuid(self):
        """
        Get a svm's uuid
        :return: uuid of the svm
        """
        params = {'svm.name': self.parameters['vserver']}
        api = 'protocols/nfs/export-policies'
        message, error = self.restApi.get(api, params)
        if error is not None:
            self.module.fail_json(msg="%s" % error)
        return message['records'][0]['svm']['uuid']

    def apply(self):
        """
        Apply action to export-policy
        """
        policy_id, uuid = None, None
        cd_action, rename = None, None

        if not self.use_rest:
            netapp_utils.ems_log_event("na_ontap_export_policy", self.server)
        if self.use_rest:
            uuid = self.get_export_policy_svm_uuid()
            if self.parameters.get('from_name'):
                policy_id = self.get_export_policy_id(self.parameters['from_name'])
            else:
                policy_id = self.get_export_policy_id()

        current = self.get_export_policy(uuid=uuid)

        if self.parameters.get('from_name'):
            rename = self.na_helper.is_rename_action(self.get_export_policy(self.parameters['from_name']), current)
            if rename is None:
                self.module.fail_json(msg="Error renaming: export policy %s does not exist" % self.parameters['from_name'])
        else:
            cd_action = self.na_helper.get_cd_action(current, self.parameters)

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if rename:
                    self.rename_export_policy(policy_id=policy_id)
                elif cd_action == 'create':
                    self.create_export_policy(uuid=uuid)
                elif cd_action == 'delete':
                    self.delete_export_policy(policy_id=policy_id)
        self.module.exit_json(changed=self.na_helper.changed)
Beispiel #6
0
class NetAppOntapSVM(object):
    def __init__(self):
        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(required=False,
                            type='str',
                            choices=['present', 'absent'],
                            default='present'),
                 name=dict(required=True, type='str'),
                 from_name=dict(required=False, type='str'),
                 root_volume=dict(type='str'),
                 root_volume_aggregate=dict(type='str'),
                 root_volume_security_style=dict(
                     type='str', choices=['unix', 'ntfs', 'mixed', 'unified']),
                 allowed_protocols=dict(type='list', elements='str'),
                 aggr_list=dict(type='list', elements='str'),
                 ipspace=dict(type='str', required=False),
                 snapshot_policy=dict(type='str', required=False),
                 language=dict(type='str', required=False),
                 subtype=dict(type='str',
                              choices=[
                                  'default', 'dp_destination', 'sync_source',
                                  'sync_destination'
                              ]),
                 comment=dict(type="str", required=False)))

        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)
        # Ontap documentation uses C.UTF-8, but actually stores as c.utf_8.
        if 'language' in self.parameters and self.parameters['language'].lower(
        ) == 'c.utf-8':
            self.parameters['language'] = 'c.utf_8'

        self.restApi = OntapRestAPI(self.module)
        # with REST, to force synchronous operations
        self.timeout = self.restApi.timeout
        # root volume not supported with rest api
        unsupported_rest_properties = [
            'root_volume', 'root_volume_aggregate',
            'root_volume_security_style'
        ]
        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 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)

    @staticmethod
    def clean_up_output(vserver_details):
        vserver_details['root_volume'] = None
        vserver_details['root_volume_aggregate'] = None
        vserver_details['root_volume_security_style'] = None
        vserver_details['aggr_list'] = []
        for aggr in vserver_details['aggregates']:
            vserver_details['aggr_list'].append(aggr['name'])
        vserver_details.pop('aggregates')
        vserver_details['ipspace'] = vserver_details['ipspace']['name']
        vserver_details['snapshot_policy'] = vserver_details[
            'snapshot_policy']['name']
        vserver_details['allowed_protocols'] = []
        if 'cifs' in vserver_details:
            if vserver_details['cifs']['enabled']:
                vserver_details['allowed_protocols'].append('cifs')
            vserver_details.pop('cifs')
        if 'fcp' in vserver_details:
            if vserver_details['fcp']['enabled']:
                vserver_details['allowed_protocols'].append('fcp')
            vserver_details.pop('fcp')
        if 'issi' in vserver_details:
            if vserver_details['iscsi']['enabled']:
                vserver_details['allowed_protocols'].append('iscsi')
            vserver_details.pop('iscsi')
        if 'nvme' in vserver_details:
            if vserver_details['nvme']['enabled']:
                vserver_details['allowed_protocols'].append('nvme')
            vserver_details.pop('nvme')
        if 'nfs' in vserver_details:
            if vserver_details['nfs']['enabled']:
                vserver_details['allowed_protocols'].append('nfs')
            vserver_details.pop('nfs')
        return vserver_details

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

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

        if self.use_rest:
            api = 'svm/svms'
            params = {
                'fields':
                'subtype,aggregates,language,snapshot_policy,ipspace,comment,nfs,cifs,fcp,iscsi,nvme'
            }
            message, error = self.restApi.get(api, params)
            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_net_route from %s: %s" % (
                    api, repr(message))
                self.module.fail_json(msg=error)
            vserver_details = None
            for record in message['records']:
                if record['name'] == vserver_name:
                    vserver_details = copy.deepcopy(record)
                    break
            if vserver_details is None:
                return None
            return self.clean_up_output(vserver_details)

        else:
            vserver_info = netapp_utils.zapi.NaElement('vserver-get-iter')
            query_details = netapp_utils.zapi.NaElement.create_node_with_children(
                'vserver-info', **{'vserver-name': vserver_name})

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

            result = self.server.invoke_successfully(vserver_info,
                                                     enable_tunneling=False)
            vserver_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')
                vserver_info = attributes_list.get_child_by_name(
                    'vserver-info')
                aggr_list = list()
                ''' vserver aggr-list can be empty by default'''
                get_list = vserver_info.get_child_by_name('aggr-list')
                if get_list is not None:
                    aggregates = get_list.get_children()
                    for aggr in aggregates:
                        aggr_list.append(aggr.get_content())

                protocols = list()
                '''allowed-protocols is not empty for data SVM, but is for node SVM'''
                allowed_protocols = vserver_info.get_child_by_name(
                    'allowed-protocols')
                if allowed_protocols is not None:
                    get_protocols = allowed_protocols.get_children()
                    for protocol in get_protocols:
                        protocols.append(protocol.get_content())
                vserver_details = {
                    'name':
                    vserver_info.get_child_content('vserver-name'),
                    'root_volume':
                    vserver_info.get_child_content('root-volume'),
                    'root_volume_aggregate':
                    vserver_info.get_child_content('root-volume-aggregate'),
                    'root_volume_security_style':
                    vserver_info.get_child_content(
                        'root-volume-security-style'),
                    'subtype':
                    vserver_info.get_child_content('vserver-subtype'),
                    'aggr_list':
                    aggr_list,
                    'language':
                    vserver_info.get_child_content('language'),
                    'snapshot_policy':
                    vserver_info.get_child_content('snapshot-policy'),
                    'allowed_protocols':
                    protocols,
                    'ipspace':
                    vserver_info.get_child_content('ipspace'),
                    'comment':
                    vserver_info.get_child_content('comment')
                }
            return vserver_details

    def create_vserver(self):
        if self.use_rest:
            api = 'svm/svms'
            params = {'name': self.parameters['name']}
            if self.parameters.get('language'):
                params['language'] = self.parameters['language']
            if self.parameters.get('ipspace'):
                params['ipspace'] = self.parameters['ipspace']
            if self.parameters.get('snapshot_policy'):
                params['snapshot_policy'] = self.parameters['snapshot_policy']
            if self.parameters.get('subtype'):
                params['subtype'] = self.parameters['subtype']
            if self.parameters.get('comment'):
                params['comment'] = self.parameters['comment']
            if self.parameters.get('aggr_list'):
                params['aggregates'] = []
                for aggr in self.parameters['aggr_list']:
                    params['aggregates'].append({'name': aggr})
            if self.parameters.get('allowed_protocols'):
                for protocol in self.parameters['allowed_protocols']:
                    params[protocol] = {'enabled': 'true'}
            # for a sync operation
            data = {'return_timeout': self.timeout}
            __, error = self.restApi.post(api, params, data)
            if error:
                self.module.fail_json(msg=error)
        else:
            options = {'vserver-name': self.parameters['name']}
            self.add_parameter_to_dict(options, 'root_volume', 'root-volume')
            self.add_parameter_to_dict(options, 'root_volume_aggregate',
                                       'root-volume-aggregate')
            self.add_parameter_to_dict(options, 'root_volume_security_style',
                                       'root-volume-security-style')
            self.add_parameter_to_dict(options, 'language', 'language')
            self.add_parameter_to_dict(options, 'ipspace', 'ipspace')
            self.add_parameter_to_dict(options, 'snapshot_policy',
                                       'snapshot-policy')
            self.add_parameter_to_dict(options, 'subtype', 'vserver-subtype')
            self.add_parameter_to_dict(options, 'comment', 'comment')
            vserver_create = netapp_utils.zapi.NaElement.create_node_with_children(
                'vserver-create', **options)
            try:
                self.server.invoke_successfully(vserver_create,
                                                enable_tunneling=False)
            except netapp_utils.zapi.NaApiError as e:
                self.module.fail_json(msg='Error provisioning SVM %s: %s' %
                                      (self.parameters['name'], to_native(e)),
                                      exception=traceback.format_exc())
            # add allowed-protocols, aggr-list after creation,
            # since vserver-create doesn't allow these attributes during creation
            options = dict()
            for key in ('allowed_protocols', 'aggr_list'):
                if self.parameters.get(key):
                    options[key] = self.parameters[key]
            if options:
                self.modify_vserver(options)

    def delete_vserver(self, current=None):
        if self.use_rest:
            if current is None:
                self.module.fail_json(
                    msg='Internal error, expecting SVM object in delete')
            api = 'svm/svms/%s' % current['uuid']
            params = {}
            # for a sync operation
            data = {'return_timeout': self.timeout}
            __, error = self.restApi.delete(api, params, data)
            if error:
                self.module.fail_json(msg=error)
        else:
            vserver_delete = netapp_utils.zapi.NaElement.create_node_with_children(
                'vserver-destroy', **{'vserver-name': self.parameters['name']})

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

    def rename_vserver(self, current=None):
        if self.use_rest:
            if current is None:
                self.module.fail_json(
                    msg='Internal error, expecting SVM object in rename')
            api = 'svm/svms/%s' % current['uuid']
            params = {'name': self.parameters['name']}
            # for a sync operation
            data = {'return_timeout': self.timeout}
            __, error = self.restApi.patch(api, params, data)
            if error:
                self.module.fail_json(msg=error)
        else:
            vserver_rename = netapp_utils.zapi.NaElement.create_node_with_children(
                'vserver-rename', **{
                    'vserver-name': self.parameters['from_name'],
                    'new-name': self.parameters['name']
                })

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

    def modify_vserver(self, modify, current=None):
        '''
        Modify vserver.
        :param modify: list of modify attributes
        :param current: with rest, SVM object to modify
        '''
        if self.use_rest:
            if current is None:
                self.module.fail_json(
                    msg='Internal error, expecting SVM object in modify')
            api = 'svm/svms/%s' % current['uuid']
            for attribute in modify:
                if attribute == 'snapshot_policy' or attribute == 'allowed_protocols' or attribute == 'aggr_list':
                    self.module.fail_json(
                        msg='REST API does not support modify of %s' %
                        attribute)
            # for a sync operation
            data = {'return_timeout': self.timeout}
            __, error = self.restApi.patch(api, modify, data)
            if error:
                self.module.fail_json(msg=error)
        else:
            vserver_modify = netapp_utils.zapi.NaElement('vserver-modify')
            vserver_modify.add_new_child('vserver-name',
                                         self.parameters['name'])
            for attribute in modify:
                if attribute == 'language':
                    vserver_modify.add_new_child('language',
                                                 self.parameters['language'])
                if attribute == 'snapshot_policy':
                    vserver_modify.add_new_child(
                        'snapshot-policy', self.parameters['snapshot_policy'])
                if attribute == 'comment':
                    vserver_modify.add_new_child('comment',
                                                 self.parameters['comment'])
                if attribute == 'allowed_protocols':
                    allowed_protocols = netapp_utils.zapi.NaElement(
                        'allowed-protocols')
                    for protocol in self.parameters['allowed_protocols']:
                        allowed_protocols.add_new_child('protocol', protocol)
                    vserver_modify.add_child_elem(allowed_protocols)
                if attribute == 'aggr_list':
                    aggregates = netapp_utils.zapi.NaElement('aggr-list')
                    for aggr in self.parameters['aggr_list']:
                        aggregates.add_new_child('aggr-name', aggr)
                    vserver_modify.add_child_elem(aggregates)
            try:
                self.server.invoke_successfully(vserver_modify,
                                                enable_tunneling=False)
            except netapp_utils.zapi.NaApiError as e:
                self.module.fail_json(msg='Error modifying SVM %s: %s' %
                                      (self.parameters['name'], to_native(e)),
                                      exception=traceback.format_exc())

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

    def apply(self):
        '''Call create/modify/delete operations.'''
        if not self.use_rest:
            self.asup_log_for_cserver("na_ontap_svm")
        current = self.get_vserver()
        cd_action, rename = None, None
        if self.parameters.get('from_name'):
            old_svm = self.get_vserver(self.parameters['from_name'])
            rename = self.na_helper.is_rename_action(old_svm, current)
            if rename is None:
                self.module.fail_json(
                    msg='Error renaming SVM %s: no SVM with from_name %s.' %
                    (self.parameters['name'], self.parameters['from_name']))
        else:
            cd_action = self.na_helper.get_cd_action(current, self.parameters)
        modify = self.na_helper.get_modified_attributes(
            current, self.parameters)
        for attribute in modify:
            if attribute in [
                    'root_volume', 'root_volume_aggregate',
                    'root_volume_security_style', 'subtype', 'ipspace'
            ]:
                self.module.fail_json(
                    msg='Error modifying SVM %s: can not modify %s.' %
                    (self.parameters['name'], attribute))
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if rename:
                    self.rename_vserver(old_svm)
                # If rename is True, cd_action is None, but modify could be true or false.
                if cd_action == 'create':
                    self.create_vserver()
                elif cd_action == 'delete':
                    self.delete_vserver(current)
                elif modify:
                    self.modify_vserver(modify, current)
        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 NetAppOntapCifsLocalGroupMember(object):
    """
        Add or remove CIFS local group members
    """
    def __init__(self):
        """
            Initialize the Ontap CifsLocalGroupMember 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'),
                 group=dict(required=True, type='str'),
                 member=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)

        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_cifs_local_group_member(self):
        return_value = None

        if self.use_rest:
            api = "private/cli/vserver/cifs/users-and-groups/local-group/members"
            query = {
                'group-name': self.parameters['group'],
                'fields': 'member',
                'vserver': self.parameters['vserver'],
                'member': self.parameters['member']
            }
            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_cifs_local_group_member from %s: %s" % (
                    api, repr(message))
                self.module.fail_json(msg=error)
            return_value = {
                'group': message['records'][0]['group_name'],
                'member': message['records'][0]['member'],
                'vserver': message['records'][0]['vserver']
            }
            return return_value

        else:
            group_members_get_iter = netapp_utils.zapi.NaElement(
                'cifs-local-group-members-get-iter')
            group_members_info = netapp_utils.zapi.NaElement(
                'cifs-local-group-members')
            group_members_info.add_new_child('group-name',
                                             self.parameters['group'])
            group_members_info.add_new_child('vserver',
                                             self.parameters['vserver'])
            group_members_info.add_new_child('member',
                                             self.parameters['member'])
            query = netapp_utils.zapi.NaElement('query')
            query.add_child_elem(group_members_info)
            group_members_get_iter.add_child_elem(query)

            try:
                result = self.server.invoke_successfully(
                    group_members_get_iter, True)
                if result.get_child_by_name('attributes-list'):
                    group_member_policy_attributes = result['attributes-list'][
                        'cifs-local-group-members']

                    return_value = {
                        'group': group_member_policy_attributes['group-name'],
                        'member': group_member_policy_attributes['member'],
                        'vserver': group_member_policy_attributes['vserver']
                    }

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

            return return_value

    def add_cifs_local_group_member(self):
        """
        Adds a member to a CIFS local group
        """
        if self.use_rest:
            api = "private/cli/vserver/cifs/users-and-groups/local-group/add-members"
            body = {
                "vserver": self.parameters['vserver'],
                "group-name": self.parameters['group'],
                "member-names": [self.parameters['member']]
            }
            dummy, error = self.rest_api.post(api, body)
            if error:
                self.module.fail_json(msg=error)

        else:
            group_members_obj = netapp_utils.zapi.NaElement(
                "cifs-local-group-members-add-members")
            group_members_obj.add_new_child("group-name",
                                            self.parameters['group'])
            member_names = netapp_utils.zapi.NaElement("member-names")
            member_names.add_new_child('cifs-name', self.parameters['member'])
            group_members_obj.add_child_elem(member_names)

            try:
                self.server.invoke_successfully(group_members_obj, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg=
                    'Error adding member %s to cifs local group %s on vserver %s: %s'
                    % (self.parameters['member'], self.parameters['group'],
                       self.parameters['vserver'], to_native(error)),
                    exception=traceback.format_exc())

    def remove_cifs_local_group_member(self):
        """
        Removes a member from a CIFS local group
        """
        if self.use_rest:
            api = "private/cli/vserver/cifs/users-and-groups/local-group/remove-members"
            body = {
                "vserver": self.parameters['vserver'],
                "group-name": self.parameters['group'],
                "member-names": [self.parameters['member']]
            }

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

        else:
            group_members_obj = netapp_utils.zapi.NaElement(
                "cifs-local-group-members-remove-members")
            group_members_obj.add_new_child("group-name",
                                            self.parameters['group'])
            member_names = netapp_utils.zapi.NaElement("member-names")
            member_names.add_new_child('cifs-name', self.parameters['member'])
            group_members_obj.add_child_elem(member_names)

            try:
                self.server.invoke_successfully(group_members_obj, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg=
                    'Error removing member %s from cifs local group %s on vserver %s: %s'
                    % (self.parameters['member'], self.parameters['group'],
                       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_cifs_local_group_member",
                                       self.server)

        current = self.get_cifs_local_group_member()

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

        if self.na_helper.changed:
            if not self.module.check_mode:
                if cd_action == 'create':
                    self.add_cifs_local_group_member()
                elif cd_action == 'delete':
                    self.remove_cifs_local_group_member()
        self.module.exit_json(changed=self.na_helper.changed)
Beispiel #8
0
class NetAppOntapDns(object):
    """
    Enable and Disable dns
    """
    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,
                            choices=['present', 'absent'],
                            default='present'),
                 vserver=dict(required=True, type='str'),
                 domains=dict(required=False, type='list'),
                 nameservers=dict(required=False, type='list'),
                 skip_validation=dict(required=False, type='bool')))

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

        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        # Cluster vserver and data vserver use different REST API.
        self.is_cluster = False

        # REST 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 = ['skip_validation']
        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 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'])
        return

    def create_dns(self):
        """
        Create DNS server
        :return: none
        """
        if self.use_rest:
            if self.is_cluster:
                api = 'cluster'
                params = {
                    'dns_domains': self.parameters['domains'],
                    'name_servers': self.parameters['nameservers']
                }
                message, error = self.restApi.patch(api, params)
                if error:
                    self.module.fail_json(msg=error)
            else:
                api = 'name-services/dns'
                params = {
                    'domains': self.parameters['domains'],
                    'servers': self.parameters['nameservers'],
                    'svm': {
                        'name': self.parameters['vserver']
                    }
                }
                message, error = self.restApi.post(api, params)
                if error:
                    self.module.fail_json(msg=error)
        else:
            dns = netapp_utils.zapi.NaElement('net-dns-create')
            nameservers = netapp_utils.zapi.NaElement('name-servers')
            domains = netapp_utils.zapi.NaElement('domains')
            for each in self.parameters['nameservers']:
                ip_address = netapp_utils.zapi.NaElement('ip-address')
                ip_address.set_content(each)
                nameservers.add_child_elem(ip_address)
            dns.add_child_elem(nameservers)
            for each in self.parameters['domains']:
                domain = netapp_utils.zapi.NaElement('string')
                domain.set_content(each)
                domains.add_child_elem(domain)
            dns.add_child_elem(domains)
            if self.parameters.get('skip_validation'):
                validation = netapp_utils.zapi.NaElement(
                    'skip-config-validation')
                validation.set_content(str(self.parameters['skip_validation']))
                dns.add_child_elem(validation)
            try:
                self.server.invoke_successfully(dns, True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error creating dns: %s' %
                                      (to_native(error)),
                                      exception=traceback.format_exc())

    def destroy_dns(self, dns_attrs):
        """
        Destroys an already created dns
        :return:
        """
        if self.use_rest:
            if self.is_cluster:
                error = 'cluster operation for deleting DNS is not supported with REST.'
                self.module.fail_json(msg=error)
            api = 'name-services/dns/' + dns_attrs['uuid']
            data = None
            message, error = self.restApi.delete(api, data)
            if error:
                self.module.fail_json(msg=error)
        else:
            try:
                self.server.invoke_successfully(
                    netapp_utils.zapi.NaElement('net-dns-destroy'), True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error destroying dns %s' %
                                      (to_native(error)),
                                      exception=traceback.format_exc())

    def get_cluster(self):
        api = "cluster"
        message, error = self.restApi.get(api, None)
        if error:
            self.module.fail_json(msg=error)
        if len(message.keys()) == 0:
            self.module.fail_json(msg="no data from cluster %s" % str(message))
        return message

    def get_cluster_dns(self):
        cluster_attrs = self.get_cluster()
        dns_attrs = None
        if self.parameters['vserver'] == cluster_attrs['name']:
            dns_attrs = {
                'domains': cluster_attrs.get('dns_domains'),
                'nameservers': cluster_attrs.get('name_servers'),
                'uuid': cluster_attrs['uuid'],
            }
            self.is_cluster = True
            if dns_attrs['domains'] is None and dns_attrs[
                    'nameservers'] is None:
                dns_attrs = None
        return dns_attrs

    def get_dns(self):
        if self.use_rest:
            api = "name-services/dns"
            params = {
                'fields': 'domains,servers,svm',
                "svm.name": self.parameters['vserver']
            }
            message, error = self.restApi.get(api, params)
            if error:
                self.module.fail_json(msg=error)
            if len(message.keys()) == 0:
                message = None
            elif 'records' in message and len(message['records']) == 0:
                message = None
            elif 'records' not in message or len(message['records']) != 1:
                error = "Unexpected response from %s: %s" % (api,
                                                             repr(message))
                self.module.fail_json(msg=error)
            if message is not None:
                record = message['records'][0]
                attrs = {
                    'domains': record['domains'],
                    'nameservers': record['servers'],
                    'uuid': record['svm']['uuid']
                }
                return attrs
            return None
        else:
            dns_obj = netapp_utils.zapi.NaElement('net-dns-get')
            try:
                result = self.server.invoke_successfully(dns_obj, True)
            except netapp_utils.zapi.NaApiError as error:
                if to_native(error.code) == "15661":
                    # 15661 is object not found
                    return None
                else:
                    self.module.fail_json(msg=to_native(error),
                                          exception=traceback.format_exc())

            # read data for modify
            attrs = dict()
            attributes = result.get_child_by_name('attributes')
            dns_info = attributes.get_child_by_name('net-dns-info')
            nameservers = dns_info.get_child_by_name('name-servers')
            attrs['nameservers'] = [
                each.get_content() for each in nameservers.get_children()
            ]
            domains = dns_info.get_child_by_name('domains')
            attrs['domains'] = [
                each.get_content() for each in domains.get_children()
            ]
            attrs['skip_validation'] = dns_info.get_child_by_name(
                'skip-config-validation')
            return attrs

    def modify_dns(self, dns_attrs):
        if self.use_rest:
            changed = False
            params = {}
            if dns_attrs['nameservers'] != self.parameters['nameservers']:
                changed = True
                params['servers'] = self.parameters['nameservers']
            if dns_attrs['domains'] != self.parameters['domains']:
                changed = True
                params['domains'] = self.parameters['domains']
            if changed:
                uuid = dns_attrs['uuid']
                api = "name-services/dns/" + uuid
                if self.is_cluster:
                    api = 'cluster'
                    params = {
                        'dns_domains': self.parameters['domains'],
                        'name_servers': self.parameters['nameservers']
                    }
                message, error = self.restApi.patch(api, params)
                if error:
                    self.module.fail_json(msg=error)

        else:
            changed = False
            dns = netapp_utils.zapi.NaElement('net-dns-modify')
            if dns_attrs['nameservers'] != self.parameters['nameservers']:
                changed = True
                nameservers = netapp_utils.zapi.NaElement('name-servers')
                for each in self.parameters['nameservers']:
                    ip_address = netapp_utils.zapi.NaElement('ip-address')
                    ip_address.set_content(each)
                    nameservers.add_child_elem(ip_address)
                dns.add_child_elem(nameservers)
            if dns_attrs['domains'] != self.parameters['domains']:
                changed = True
                domains = netapp_utils.zapi.NaElement('domains')
                for each in self.parameters['domains']:
                    domain = netapp_utils.zapi.NaElement('string')
                    domain.set_content(each)
                    domains.add_child_elem(domain)
                dns.add_child_elem(domains)
            if changed:
                if self.parameters.get('skip_validation'):
                    validation = netapp_utils.zapi.NaElement(
                        'skip-config-validation')
                    validation.set_content(
                        str(self.parameters['skip_validation']))
                    dns.add_child_elem(validation)
                try:
                    self.server.invoke_successfully(dns, True)
                except netapp_utils.zapi.NaApiError as error:
                    self.module.fail_json(msg='Error modifying dns %s' %
                                          (to_native(error)),
                                          exception=traceback.format_exc())
        return changed

    def apply(self):
        # asup logging
        if not self.use_rest:
            netapp_utils.ems_log_event("na_ontap_dns", self.server)
        dns_attrs = self.get_dns()
        if self.use_rest and dns_attrs is None:
            # There is a chance we are working at the cluster level
            dns_attrs = self.get_cluster_dns()
        changed = False
        if self.parameters['state'] == 'present':
            if dns_attrs is not None:
                changed = self.modify_dns(dns_attrs)
            else:
                self.create_dns()
                changed = True
        else:
            if dns_attrs is not None:
                self.destroy_dns(dns_attrs)
                changed = True
        self.module.exit_json(changed=changed)
class NetAppOntapDomainTunnel(object):
    def __init__(self):
        """
            Initialize the ONTAP domain tunnel class
        """
        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')))
        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:
            self.module.fail_json(msg=self.rest_api.requires_ontap_version(
                'na_ontap_domain_tunnel', '9.7'))

    def get_domain_tunnel(self):
        """
            Get the current domain tunnel info
        """
        api = "/security/authentication/cluster/ad-proxy"
        message, error = self.rest_api.get(api)

        if error:
            if int(error['code']) != 4:  # error code 4 is empty table
                self.module.fail_json(msg=error)
        if message:
            message = {'vserver': message['svm']['name']}
            return message
        else:
            return None

    def create_domain_tunnel(self):
        """
            Creates the domain tunnel on the specified vserver
        """
        api = "/security/authentication/cluster/ad-proxy"
        body = {"svm": {"name": self.parameters['vserver']}}
        dummy, error = self.rest_api.post(api, body)
        if error:
            self.module.fail_json(msg=error)

    def modify_domain_tunnel(self):
        """
            Modifies the domain tunnel on the specified vserver
        """
        api = "/security/authentication/cluster/ad-proxy"
        body = {"svm": {"name": self.parameters['vserver']}}
        dummy, error = self.rest_api.patch(api, body)
        if error:
            self.module.fail_json(msg=error)

    def delete_domain_tunnel(self):
        """
            Deletes the current domain tunnel
        """
        api = "/security/authentication/cluster/ad-proxy"

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

    def apply(self):
        current = self.get_domain_tunnel()
        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 not self.module.check_mode:
                if cd_action == 'create':
                    self.create_domain_tunnel()
                elif cd_action == 'delete':
                    self.delete_domain_tunnel()
                elif modify:
                    self.modify_domain_tunnel()

        self.module.exit_json(changed=self.na_helper.changed)
Beispiel #10
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)
Beispiel #11
0
class NetAppONTAPSnmpTraphosts(object):
    """Class with SNMP methods"""
    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'),
                ip_address=dict(required=True, type='str'),
            ))
        self.module = AnsibleModule(argument_spec=self.argument_spec,
                                    supports_check_mode=True)
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        self.restApi = OntapRestAPI(self.module)
        if not self.restApi.is_rest():
            self.module.fail_json(
                msg="na_ontap_snmp_traphosts only support Rest and ONTAP 9.6+")

    def get_snmp_traphosts(self):
        params = {'ip_address': self.parameters['ip_address']}
        api = 'support/snmp/traphosts'
        message, error = self.restApi.get(api, params)
        if error:
            self.module.fail_json(msg=error)
        if not message['records']:
            return None
        return message['records']

    def create_snmp_traphost(self):
        api = '/support/snmp/traphosts'
        params = {'host': self.parameters['ip_address']}
        message, error = self.restApi.post(api, params)
        if error:
            self.module.fail_json(msg=error)

    def delete_snmp_traphost(self):
        api = '/support/snmp/traphosts/' + self.parameters['ip_address']
        params = None
        message, error = self.restApi.delete(api, params)
        if error is not None:
            self.module.fail_json(msg="Error deleting traphost: %s" % error)

    def apply(self):
        """
        Apply action to SNMP traphost
        """
        current = self.get_snmp_traphosts()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_snmp_traphost()
                elif cd_action == 'delete':
                    self.delete_snmp_traphost()

        self.module.exit_json(changed=self.na_helper.changed)
Beispiel #12
0
class NetAppOntapIgroup(object):
    """Create/Delete/Rename Igroups and Modify initiators list"""
    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', default=None),
            os_type=dict(required=False, type='str', aliases=['ostype']),
            igroups=dict(required=False, type='list', elements='str'),
            initiator_group_type=dict(required=False, type='str',
                                      choices=['fcp', 'iscsi', 'mixed'],
                                      aliases=['protocol']),
            initiators=dict(required=False, type='list', elements='str', aliases=['initiator']),
            vserver=dict(required=True, type='str'),
            force_remove_initiator=dict(required=False, type='bool', default=False, aliases=['allow_delete_while_mapped']),
            bind_portset=dict(required=False, type='str')
        ))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True,
            mutually_exclusive=[('igroups', 'initiators')]
        )

        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        self.rest_modify_zapi_to_rest = dict(
            # initiator_group_type (protocol) cannot be changed after create
            bind_portset='portset',
            name='name',
            os_type='os_type'
        )

        if self.module.params.get('initiators') is not None:
            self.parameters['initiators'] = [self.na_helper.sanitize_wwn(initiator)
                                             for initiator in self.module.params['initiators']]

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

        def too_old_for_rest(minimum_generation, minimum_major):
            return self.use_rest and self.rest_api.get_ontap_version() < (minimum_generation, minimum_major)

        ontap_99_options = ['bind_portset']
        if too_old_for_rest(9, 9) and any(x in self.parameters for x in ontap_99_options):
            self.module.warn('Warning: falling back to ZAPI: %s' % self.rest_api.options_require_ontap_version(ontap_99_options, version='9.9'))
            self.use_rest = False

        ontap_99_options = ['igroups']
        if not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9) and any(x in self.parameters for x in ontap_99_options):
            self.module.fail_json(msg='Error: %s' % self.rest_api.options_require_ontap_version(ontap_99_options, version='9.9'))

        if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9):
            if 'igroups' in self.parameters:
                # we may need to remove existing initiators
                self.parameters['initiators'] = list()
            elif 'initiators' in self.parameters:
                # we may need to remove existing igroups
                self.parameters['igroups'] = list()

        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 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 get_igroup_rest(self, name):
        api = "protocols/san/igroups"
        fields = 'name,uuid,svm,initiators,os_type,protocol'
        if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9):
            fields += ',igroups'
        query = dict(name=name, fields=fields)
        query['svm.name'] = self.parameters['vserver']
        response, error = self.rest_api.get(api, query)
        igroup, error = rrh.check_for_0_or_1_records(api, response, error)
        self.fail_on_error(error)
        if igroup:
            try:
                igroup_details = dict(
                    name=igroup['name'],
                    uuid=igroup['uuid'],
                    vserver=igroup['svm']['name'],
                    os_type=igroup['os_type'],
                    initiator_group_type=igroup['protocol'],
                    name_to_uuid=dict()
                )
            except KeyError as exc:
                self.module.fail_json(msg='Error: unexpected igroup body: %s, KeyError on %s' % (str(igroup), str(exc)))
            igroup_details['name_to_key'] = dict()
            for attr in ('igroups', 'initiators'):
                if attr in igroup:
                    igroup_details[attr] = [item['name'] for item in igroup[attr]]
                    # for initiators, there is no uuid, so we're using name as the key
                    igroup_details['name_to_uuid'][attr] = dict((item['name'], item.get('uuid', item['name'])) for item in igroup[attr])
                else:
                    igroup_details[attr] = []
                    igroup_details['name_to_uuid'][attr] = dict()
            return igroup_details
        return None

    def get_igroup(self, name):
        """
        Return details about the igroup
        :param:
            name : Name of the igroup

        :return: Details about the igroup. None if not found.
        :rtype: dict
        """
        if self.use_rest:
            return self.get_igroup_rest(name)

        igroup_info = netapp_utils.zapi.NaElement('igroup-get-iter')
        attributes = dict(query={'initiator-group-info': {'initiator-group-name': name,
                                                          'vserver': self.parameters['vserver']}})
        igroup_info.translate_struct(attributes)
        current = None

        try:
            result = self.server.invoke_successfully(igroup_info, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error fetching igroup info %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:
            igroup_info = result.get_child_by_name('attributes-list')
            initiator_group_info = igroup_info.get_child_by_name('initiator-group-info')
            initiators = []
            if initiator_group_info.get_child_by_name('initiators'):
                current_initiators = initiator_group_info['initiators'].get_children()
                for initiator in current_initiators:
                    initiators.append(initiator['initiator-name'])
            current = {
                'initiators': initiators,
                # place holder, not used for ZAPI
                'name_to_uuid': dict(initiators=dict())
            }
            zapi_to_params = {
                'vserver': 'vserver',
                'initiator-group-os-type': 'os_type',
                'initiator-group-portset-name': 'bind_portset',
                'initiator-group-type': 'initiator_group_type'
            }
            for attr in zapi_to_params:
                value = igroup_info.get_child_content(attr)
                if value is not None:
                    current[zapi_to_params[attr]] = value
        return current

    def check_what_is_valid(self, what):
        if self.use_rest and what in ('igroups', 'initiators'):
            return
        if what == 'initiators':
            return
        raise KeyError('what=%s' % what)

    def add_initiators_or_igroups_rest(self, uuid, what, names):
        self.check_what_is_valid(what)
        api = "protocols/san/igroups/%s/%s" % (uuid, what)
        records = [dict(name=name) for name in names]
        body = dict(records=records)
        dummy, error = self.rest_api.post(api, body)
        self.fail_on_error(error)

    def add_initiators_or_igroups(self, uuid, what, current_names):
        """
        Add the list of desired initiators to igroup unless they are already set
        :return: None
        """
        self.check_what_is_valid(what)
        # don't add if initiators/igroups is empty string
        if self.parameters.get(what) == [''] or self.parameters.get(what) is None:
            return
        names_to_add = [name for name in self.parameters[what] if name not in current_names]
        if self.use_rest and uuid is not None and names_to_add:
            self.add_initiators_or_igroups_rest(uuid, what, names_to_add)
        else:
            for name in names_to_add:
                self.modify_initiator(name, 'igroup-add')

    def delete_initiator_or_igroup_rest(self, uuid, what, name):
        self.check_what_is_valid(what)
        api = "protocols/san/igroups/%s/%s/%s" % (uuid, what, name)
        dummy, error = self.rest_api.delete(api)
        self.fail_on_error(error)

    def remove_initiators_or_igroups(self, uuid, what, current_names, mapping):
        """
        Removes current names from igroup unless they are still desired
        :return: None
        """
        self.check_what_is_valid(what)
        for name in current_names:
            if name not in self.parameters.get(what, list()):
                if self.use_rest:
                    self.delete_initiator_or_igroup_rest(uuid, what, mapping[name])
                else:
                    self.modify_initiator(name, 'igroup-remove')

    def modify_initiator(self, initiator, zapi):
        """
        Add or remove an initiator to/from an igroup
        """
        options = {'initiator-group-name': self.parameters['name'],
                   'initiator': initiator}

        igroup_modify = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options)

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

    def create_igroup_rest(self):
        api = "protocols/san/igroups"
        body = dict(
            name=self.parameters['name'],
            os_type=self.parameters['os_type'])
        body['svm'] = dict(name=self.parameters['vserver'])
        mapping = dict(
            initiator_group_type='protocol',
            bind_portset='portset',
            igroups='igroups',
            initiators='initiators'
        )
        for option in mapping:
            value = self.parameters.get(option)
            if value is not None:
                if option in ('igroups', 'initiators'):
                    # we may have an empty list, ignore it
                    value = [dict(name=name) for name in value] if value else None
                if value is not None:
                    body[mapping[option]] = value
        dummy, error = self.rest_api.post(api, body)
        self.fail_on_error(error)

    def create_igroup(self):
        """
        Create the igroup.
        """
        if self.use_rest:
            self.create_igroup_rest()
            return

        options = {'initiator-group-name': self.parameters['name']}
        if self.parameters.get('os_type') is not None:
            options['os-type'] = self.parameters['os_type']
        if self.parameters.get('initiator_group_type') is not None:
            options['initiator-group-type'] = self.parameters['initiator_group_type']
        if self.parameters.get('bind_portset') is not None:
            options['bind-portset'] = self.parameters['bind_portset']

        igroup_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'igroup-create', **options)

        try:
            self.server.invoke_successfully(igroup_create,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error provisioning igroup %s: %s' % (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())
        self.add_initiators_or_igroups(None, 'initiators', [])

    def modify_igroup_rest(self, uuid, modify):
        api = "protocols/san/igroups/%s" % uuid
        body = dict()
        for option in modify:
            if option not in self.rest_modify_zapi_to_rest:
                self.module.fail_json(msg='Error: modifying %s is not supported in REST' % option)
            body[self.rest_modify_zapi_to_rest[option]] = modify[option]
        if body:
            dummy, error = self.rest_api.patch(api, body)
            self.fail_on_error(error)

    def delete_igroup_rest(self, uuid):
        api = "protocols/san/igroups/%s" % uuid
        if self.parameters['force_remove_initiator']:
            query = dict(allow_delete_while_mapped=True)
        else:
            query = None
        dummy, error = self.rest_api.delete(api, params=query)
        self.fail_on_error(error)

    def delete_igroup(self, uuid):
        """
        Delete the igroup.
        """
        if self.use_rest:
            self.delete_igroup_rest(uuid)
            return

        igroup_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'igroup-destroy', **{'initiator-group-name': self.parameters['name'],
                                 'force': 'true' if self.parameters['force_remove_initiator'] else 'false'})

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

    def rename_igroup(self):
        """
        Rename the igroup.
        """
        if self.use_rest:
            self.module.fail_json('Internal error, should not call rename, but use modify')

        igroup_rename = netapp_utils.zapi.NaElement.create_node_with_children(
            'igroup-rename', **{'initiator-group-name': self.parameters['from_name'],
                                'initiator-group-new-name': str(self.parameters['name'])})
        try:
            self.server.invoke_successfully(igroup_rename,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error renaming igroup %s: %s' % (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())

    def report_error_in_modify(self, modify, context):
        if modify:
            if len(modify) > 1:
                tag = 'any of '
            else:
                tag = ''
            self.module.fail_json(msg='Error: modifying %s %s is not supported in %s' % (tag, str(modify), context))

    def validate_modify(self, modify):
        """Identify options that cannot be modified for REST or ZAPI
        """
        if not modify:
            return
        modify_local = dict(modify)
        modify_local.pop('igroups', None)
        modify_local.pop('initiators', None)
        if not self.use_rest:
            self.report_error_in_modify(modify_local, 'ZAPI')
            return
        for option in modify:
            if option in self.rest_modify_zapi_to_rest:
                modify_local.pop(option)
        self.report_error_in_modify(modify_local, 'REST')

    def autosupport_log(self):
        if not self.use_rest:
            netapp_utils.ems_log_event("na_ontap_igroup", self.server)

    def is_rename_action(self, cd_action, current):
        old = self.get_igroup(self.parameters['from_name'])
        rename = self.na_helper.is_rename_action(old, current)
        if rename is None:
            self.module.fail_json(msg='Error: igroup with from_name=%s not found' % self.parameters.get('from_name'))
        if rename:
            current = old
            cd_action = None
        return cd_action, rename, current

    def apply(self):
        self.autosupport_log()
        uuid = None
        rename, modify = None, None
        current = self.get_igroup(self.parameters['name'])
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if cd_action == 'create' and self.parameters.get('from_name'):
            cd_action, rename, current = self.is_rename_action(cd_action, current)
        if cd_action is None and self.parameters['state'] == 'present':
            modify = self.na_helper.get_modified_attributes(current, self.parameters)
            # a change in name is handled in rename for ZAPI, but REST can use modify
            if self.use_rest:
                rename = False
            else:
                modify.pop('name', None)
        if current and self.use_rest:
            uuid = current['uuid']
        if cd_action == 'create' and self.use_rest and 'os_type' not in self.parameters:
            self.module.fail_json(msg='Error: os_type is a required parameter when creating an igroup with REST')
        self.validate_modify(modify)

        if self.na_helper.changed and not self.module.check_mode:
            if rename:
                self.rename_igroup()
            elif cd_action == 'create':
                self.create_igroup()
            elif cd_action == 'delete':
                self.delete_igroup(uuid)
            if modify:
                for attr in ('igroups', 'initiators'):
                    if attr in current:
                        # we need to remove everything first
                        self.remove_initiators_or_igroups(uuid, attr, current[attr], current['name_to_uuid'][attr])
                for attr in ('igroups', 'initiators'):
                    if attr in current:
                        self.add_initiators_or_igroups(uuid, attr, current[attr])
                    modify.pop(attr, None)
                if modify:
                    self.modify_igroup_rest(uuid, modify)
        self.module.exit_json(changed=self.na_helper.changed, current=current, modify=modify)
Beispiel #13
0
class NetAppOntapWwpnAlias(object):
    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'),
                 wwpn=dict(required=False, type='str'),
                 vserver=dict(required=True, type='str')))

        self.module = AnsibleModule(argument_spec=self.argument_spec,
                                    required_if=[('state', 'present', ['wwpn'])
                                                 ],
                                    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)
        if self.restApi.is_rest():
            self.use_rest = True
        else:
            self.module.fail_json(msg="This module only supports REST API.")

    def get_alias(self, uuid):
        params = {
            'fields': 'alias,wwpn',
            'alias': self.parameters['name'],
            'svm.uuid': uuid
        }
        api = 'network/fc/wwpn-aliases'
        message, error = self.restApi.get(api, params)
        if error is not None:
            self.module.fail_json(msg="Error on fetching wwpn alias: %s" %
                                  error)
        if message['num_records'] > 0:
            return {
                'name': message['records'][0]['alias'],
                'wwpn': message['records'][0]['wwpn'],
            }
        else:
            return None

    def create_alias(self, uuid, is_modify=False):
        params = {
            'alias': self.parameters['name'],
            'wwpn': self.parameters['wwpn'],
            'svm.uuid': uuid
        }
        api = 'network/fc/wwpn-aliases'
        message, error = self.restApi.post(api, params)
        if error is not None:
            if is_modify:
                self.module.fail_json(
                    msg=
                    "Error on modifying wwpn alias when trying to re-create alias: %s."
                    % error)
            else:
                self.module.fail_json(msg="Error on creating wwpn alias: %s." %
                                      error)

    def delete_alias(self, uuid, is_modify=False):
        params = {'alias': self.parameters['name'], 'svm.uuid': uuid}
        api = 'network/fc/wwpn-aliases/'
        message, error = self.restApi.delete(api, params)
        if error is not None:
            if is_modify:
                self.module.fail_json(
                    msg=
                    "Error on modifying wwpn alias when trying to delete alias: %s."
                    % error)
            else:
                self.module.fail_json(msg="Error on deleting wwpn alias: %s." %
                                      error)

    def get_svm_uuid(self):
        """
        Get a svm's UUID
        :return: uuid of the svm.
        """
        params = {'fields': 'uuid', 'name': self.parameters['vserver']}
        api = "svm/svms"
        message, error = self.restApi.get(api, params)
        if error is not None:
            self.module.fail_json(msg="Error on fetching svm uuid: %s" % error)
        return message['records'][0]['uuid']

    def apply(self):
        cd_action, uuid, modify = None, None, None
        uuid = self.get_svm_uuid()
        current = self.get_alias(uuid)
        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_alias(uuid)
                elif cd_action == 'delete':
                    self.delete_alias(uuid)
                elif modify:
                    self.delete_alias(uuid, is_modify=True)
                    self.create_alias(uuid, is_modify=True)
        self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapObjectStoreConfig(object):
    ''' object initialize and class methods '''
    def __init__(self):
        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(name=dict(required=True, type='str'),
                 state=dict(required=False,
                            choices=['present', 'absent'],
                            default='present'),
                 provider_type=dict(required=False, type='str'),
                 server=dict(required=False, type='str'),
                 container=dict(required=False, type='str'),
                 access_key=dict(required=False, type='str'),
                 secret_password=dict(required=False, type='str',
                                      no_log=True)))

        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)

        # API should be used for ONTAP 9.6 or higher, Zapi for lower version
        self.restApi = OntapRestAPI(self.module)
        if self.restApi.is_rest():
            self.use_rest = True
        else:
            if HAS_NETAPP_LIB is False:
                self.module.fail_json(
                    msg="the python NetApp-Lib module is required")
            else:
                self.server = netapp_utils.setup_na_ontap_zapi(
                    module=self.module)

    def get_aggr_object_store(self):
        """
        Fetch details if object store config exists.
        :return:
            Dictionary of current details if object store config found
            None if object store config is not found
        """
        if self.use_rest:
            data = {'fields': 'uuid,name', 'name': self.parameters['name']}
            api = "cloud/targets"
            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:
            aggr_object_store_get_iter = netapp_utils.zapi.NaElement.create_node_with_children(
                'aggr-object-store-config-get',
                **{'object-store-name': self.parameters['name']})
            result = None
            try:
                result = self.server.invoke_successfully(
                    aggr_object_store_get_iter, enable_tunneling=False)
            except netapp_utils.zapi.NaApiError as error:
                # Error 15661 denotes an object store not being found.
                if to_native(error.code) == "15661":
                    pass
                else:
                    self.module.fail_json(msg=to_native(error),
                                          exception=traceback.format_exc())
            return result

    def create_aggr_object_store(self):
        """
        Create aggregate object store config
        :return: None
        """
        required_keys = set(
            ['provider_type', 'server', 'container', 'access_key'])
        if not required_keys.issubset(set(self.parameters.keys())):
            self.module.fail_json(
                msg=
                'Error provisioning object store %s: one of the following parameters are missing '
                '%s' % (self.parameters['name'], ', '.join(required_keys)))
        if self.use_rest:
            data = {
                'name': self.parameters['name'],
                'provider_type': self.parameters['provider_type'],
                'server': self.parameters['server'],
                'container': self.parameters['container'],
                'access_key': self.parameters['access_key'],
                'owner': 'fabricpool'
            }
            if self.parameters.get('secret_password'):
                data['secret_password'] = self.parameters['secret_password']
            api = "cloud/targets"
            message, error = self.restApi.post(api, data)
            if error:
                self.module.fail_json(msg=error)
        else:
            options = {
                'object-store-name': self.parameters['name'],
                'provider-type': self.parameters['provider_type'],
                'server': self.parameters['server'],
                's3-name': self.parameters['container'],
                'access-key': self.parameters['access_key']
            }
            if self.parameters.get('secret_password'):
                options['secret-password'] = self.parameters['secret_password']
            object_store_create = netapp_utils.zapi.NaElement.create_node_with_children(
                'aggr-object-store-config-create', **options)

            try:
                self.server.invoke_successfully(object_store_create,
                                                enable_tunneling=False)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg="Error provisioning object store config %s: %s" %
                    (self.parameters['name'], to_native(error)),
                    exception=traceback.format_exc())

    def delete_aggr_object_store(self, uuid=None):
        """
        Delete aggregate object store config
        :return: None
        """
        if self.use_rest:
            api = "cloud/targets/"
            data = {'uuid': uuid}
            message, error = self.restApi.delete(api, data)
            if error:
                self.module.fail_json(msg=error)
        else:
            object_store_destroy = netapp_utils.zapi.NaElement.create_node_with_children(
                'aggr-object-store-config-delete',
                **{'object-store-name': self.parameters['name']})

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

    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)

    def apply(self):
        """
        Apply action to the object store config
        :return: None
        """
        uuid = None
        if not self.use_rest:
            self.asup_log_for_cserver("na_ontap_object_store_config")
        current = self.get_aggr_object_store()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_aggr_object_store()
                elif cd_action == 'delete':
                    if self.use_rest:
                        uuid = current['uuid']
                    self.delete_aggr_object_store(uuid)
        self.module.exit_json(changed=self.na_helper.changed)
Beispiel #15
0
class NetAppONTAPJob(object):
    '''Class with job schedule cron methods'''
    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'),
                 job_minutes=dict(required=False, type='list', elements='str'),
                 job_months=dict(required=False, type='list', elements='str'),
                 job_hours=dict(required=False, type='list', elements='str'),
                 job_days_of_month=dict(required=False,
                                        type='list',
                                        elements='str'),
                 job_days_of_week=dict(required=False,
                                       type='list',
                                       elements='str')))

        self.uuid = None
        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()
        self.set_playbook_api_key_map()

        self.restApi = OntapRestAPI(self.module)
        if self.restApi.is_rest():
            self.use_rest = True
        else:
            if HAS_NETAPP_LIB is False:
                self.module.fail_json(
                    msg="the python NetApp-Lib module is required")
            else:
                self.server = netapp_utils.setup_na_ontap_zapi(
                    module=self.module)

    def set_playbook_zapi_key_map(self):
        self.na_helper.zapi_string_keys = {
            'name': 'job-schedule-name',
        }
        self.na_helper.zapi_list_keys = {
            'job_minutes': ('job-schedule-cron-minute', 'cron-minute'),
            'job_months': ('job-schedule-cron-month', 'cron-month'),
            'job_hours': ('job-schedule-cron-hour', 'cron-hour'),
            'job_days_of_month':
            ('job-schedule-cron-day', 'cron-day-of-month'),
            'job_days_of_week':
            ('job-schedule-cron-day-of-week', 'cron-day-of-week')
        }

    def set_playbook_api_key_map(self):
        self.na_helper.api_list_keys = {
            'job_minutes': 'minutes',
            'job_months': 'months',
            'job_hours': 'hours',
            'job_days_of_month': 'days',
            'job_days_of_week': 'weekdays'
        }

    def get_job_schedule(self):
        """
        Return details about the job
        :param:
            name : Job name
        :return: Details about the Job. None if not found.
        :rtype: dict
        """
        if self.use_rest:
            params = {'name': self.parameters['name']}
            api = '/cluster/schedules'
            message, error = self.restApi.get(api, params)
            if error is not None:
                self.module.fail_json(
                    msg="Error on fetching job schedule: %s" % error)
            if message['num_records'] > 0:
                self.uuid = message['records'][0]['uuid']
                job_details = dict()
                job_details['name'] = message['records'][0]['name']
                for key, value in self.na_helper.api_list_keys.items():
                    if value in message['records'][0]['cron']:
                        job_details[key] = message['records'][0]['cron'][value]
                # convert list of int to list of string
                for key, value in job_details.items():
                    if isinstance(value, list):
                        for i in range(len(value)):
                            value[i] = str(value[i])
                return job_details

        else:
            job_get_iter = netapp_utils.zapi.NaElement(
                'job-schedule-cron-get-iter')
            job_get_iter.translate_struct({
                'query': {
                    'job-schedule-cron-info': {
                        'job-schedule-name': self.parameters['name']
                    }
                }
            })
            result = self.server.invoke_successfully(job_get_iter, True)
            job_details = None
            # check if job exists
            if result.get_child_by_name('num-records') and int(
                    result['num-records']) >= 1:
                job_info = result['attributes-list']['job-schedule-cron-info']
                job_details = dict()
                for item_key, zapi_key in self.na_helper.zapi_string_keys.items(
                ):
                    job_details[item_key] = job_info[zapi_key]
                for item_key, zapi_key in self.na_helper.zapi_list_keys.items(
                ):
                    parent, dummy = zapi_key
                    job_details[item_key] = self.na_helper.get_value_for_list(
                        from_zapi=True,
                        zapi_parent=job_info.get_child_by_name(parent))
                    # if any of the job_hours, job_minutes, job_months, job_days are empty:
                    # it means the value is -1 for ZAPI
                    if not job_details[item_key]:
                        job_details[item_key] = ['-1']
            return job_details

    def add_job_details(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 item_key in values:
            if item_key in self.na_helper.zapi_string_keys:
                zapi_key = self.na_helper.zapi_string_keys.get(item_key)
                na_element_object[zapi_key] = values[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)
                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.get(item_key)))

    def create_job_schedule(self):
        """
        Creates a job schedule
        """
        # job_minutes is mandatory for create
        if self.parameters.get('job_minutes') is None:
            self.module.fail_json(
                msg='Error: missing required parameter job_minutes for create')

        if self.use_rest:
            cron = dict()
            for key, value in self.na_helper.api_list_keys.items():
                # -1 means all in zapi, while empty means all in api.
                if self.parameters.get(key):
                    if len(self.parameters[key]) == 1 and int(
                            self.parameters[key][0]) == -1:
                        pass
                    else:
                        cron[value] = self.parameters[key]

            params = {'name': self.parameters['name'], 'cron': cron}
            api = '/cluster/schedules'
            message, error = self.restApi.post(api, params)
            if error is not None:
                self.module.fail_json(
                    msg="Error on creating job schedule: %s" % error)

        else:
            job_schedule_create = netapp_utils.zapi.NaElement(
                'job-schedule-cron-create')
            self.add_job_details(job_schedule_create, self.parameters)
            try:
                self.server.invoke_successfully(job_schedule_create,
                                                enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg='Error creating job schedule %s: %s' %
                    (self.parameters['name'], to_native(error)),
                    exception=traceback.format_exc())

    def delete_job_schedule(self):
        """
        Delete a job schedule
        """
        if self.use_rest:
            api = '/cluster/schedules/' + self.uuid
            message, error = self.restApi.delete(api, {})
            if error is not None:
                self.module.fail_json(
                    msg="Error on deleting job schedule: %s" % error)
        else:
            job_schedule_delete = netapp_utils.zapi.NaElement(
                'job-schedule-cron-destroy')
            self.add_job_details(job_schedule_delete, self.parameters)
            try:
                self.server.invoke_successfully(job_schedule_delete,
                                                enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg='Error deleting job schedule %s: %s' %
                    (self.parameters['name'], to_native(error)),
                    exception=traceback.format_exc())

    def modify_job_schedule(self, params, current):
        """
        modify a job schedule
        """
        if self.use_rest:
            cron = dict()
            for key, value in self.na_helper.api_list_keys.items():
                # -1 means all in zapi, while empty means all in api.
                if params.get(key):
                    if len(self.parameters[key]) == 1 and int(
                            self.parameters[key][0]) == -1:
                        pass
                    else:
                        cron[value] = self.parameters[key]
                # Usually only include modify attributes, but omitting an attribute means all in api.
                # Need to add the current attributes in params.
                elif current.get(key):
                    cron[value] = current[key]
            params = {'cron': cron}
            api = '/cluster/schedules/' + self.uuid
            message, error = self.restApi.patch(api, params)
            if error is not None:
                self.module.fail_json(
                    msg="Error on modifying job schedule: %s" % error)
        else:
            job_schedule_modify = netapp_utils.zapi.NaElement.create_node_with_children(
                'job-schedule-cron-modify',
                **{'job-schedule-name': self.parameters['name']})
            self.add_job_details(job_schedule_modify, params)
            try:
                self.server.invoke_successfully(job_schedule_modify,
                                                enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg='Error modifying job schedule %s: %s' %
                    (self.parameters['name'], to_native(error)),
                    exception=traceback.format_exc())

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

    def apply(self):
        """
        Apply action to job-schedule
        """
        if not self.use_rest:
            self.autosupport_log()
        current = self.get_job_schedule()
        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:
                if action == 'create':
                    self.create_job_schedule()
                elif action == 'delete':
                    self.delete_job_schedule()
                elif modify:
                    self.modify_job_schedule(modify, current)
        self.module.exit_json(changed=self.na_helper.changed)
class NetAppONTAPGatherInfo(object):
    '''Class with gather info methods'''
    def __init__(self):
        """
        Parse arguments, setup state variables,
        check paramenters and ensure request module is installed
        """
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(type='str',
                            choices=['info'],
                            default='info',
                            required=False),
                 gather_subset=dict(default=['all'],
                                    type='list',
                                    elements='str',
                                    required=False),
                 max_records=dict(type='int', default=1024, required=False),
                 fields=dict(type='list', elements='str', required=False),
                 parameters=dict(type='dict', required=False)))

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

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

        self.rest_api = OntapRestAPI(self.module)

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

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

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

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

        return ontap_version

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

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

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

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

        return None

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

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

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

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

        return gather_subset_info

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

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

        result_message = dict()

        # Validating ONTAP version
        self.validate_ontap_version()

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

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

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

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

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

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

            result_message[subset] = self.get_subset_info(specified_subset)

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

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

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

        self.module.exit_json(changed='False',
                              state=self.parameters['state'],
                              ontap_info=result_message)
Beispiel #17
0
class NetAppONTAPasupInvoke(object):
    ''' send ASUP message '''
    def __init__(self):
        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(name=dict(required=False, type='str'),
                 autosupport_message=dict(required=False,
                                          type='str',
                                          aliases=["message"]),
                 type=dict(required=False,
                           choices=['test', 'performance', 'all'],
                           default='all'),
                 uri=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)

        # REST API should be used for ONTAP 9.6 or higher.
        self.rest_api = OntapRestAPI(self.module)
        if self.rest_api.is_rest():
            self.use_rest = True
        else:
            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)

    def get_nodes(self):
        nodes = list()
        node_obj = netapp_utils.zapi.NaElement('system-node-get-iter')
        desired_attributes = netapp_utils.zapi.NaElement('desired-attributes')
        node_details_info = netapp_utils.zapi.NaElement('node-details-info')
        node_details_info.add_new_child('node', '')
        desired_attributes.add_child_elem(node_details_info)
        node_obj.add_child_elem(desired_attributes)
        try:
            result = self.server.invoke_successfully(node_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg=to_native(error),
                                  exception=traceback.format_exc())
        if result.get_child_by_name('num-records') and \
                int(result.get_child_content('num-records')) > 0:
            node_info = result.get_child_by_name('attributes-list')
            if node_info is not None:
                nodes = [
                    node_details.get_child_content('node')
                    for node_details in node_info.get_children()
                ]
        return nodes

    def send_zapi_message(self, params, node_name):
        params['node-name'] = node_name
        send_message = netapp_utils.zapi.NaElement.create_node_with_children(
            'autosupport-invoke', **params)
        try:
            self.server.invoke_successfully(send_message,
                                            enable_tunneling=False)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg="Error on sending autosupport message to node %s: %s." %
                (node_name, to_native(error)),
                exception=traceback.format_exc())

    def send_message(self):
        params = dict()
        if self.parameters.get('autosupport_message'):
            params['message'] = self.parameters['autosupport_message']
        if self.parameters.get('type'):
            params['type'] = self.parameters['type']
        if self.parameters.get('uri'):
            params['uri'] = self.parameters['uri']

        if self.use_rest:
            if self.parameters.get('name'):
                params['node.name'] = self.parameters['name']
                node_name = params['node.name']
            else:
                node_name = '*'
            api = 'support/autosupport/messages'
            dummy, error = self.rest_api.post(api, params)
            if error is not None:
                self.module.fail_json(
                    msg="Error on sending autosupport message to node %s: %s."
                    % (node_name, error))
        else:
            if self.parameters.get('name'):
                node_names = [self.parameters['name']]
            else:
                # simulate REST behavior by sending to all nodes in the cluster
                node_names = self.get_nodes()
            for name in node_names:
                self.send_zapi_message(params, name)

    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_autosupport_invoke",
                                          cserver)

    def apply(self):
        if not self.use_rest:
            self.ems_log_event()
        if self.module.check_mode:
            pass
        else:
            self.send_message()
        self.module.exit_json(changed=True)
Beispiel #18
0
class NetAppOntapMccipMediator(object):
    """
    Mediator object for Add/Remove/Display
    """
    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'),
                mediator_address=dict(required=True, type='str'),
                mediator_user=dict(required=True, type='str'),
                mediator_password=dict(required=True, type='str', no_log=True),
            ))

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

        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        self.restApi = OntapRestAPI(self.module)
        self.use_rest = self.restApi.is_rest()

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

    def add_mediator(self):
        """
        Adds an ONTAP Mediator to MCC configuration
        """
        api = 'cluster/mediators'
        params = {
            'ip_address': self.parameters['mediator_address'],
            'password': self.parameters['mediator_password'],
            'user': self.parameters['mediator_user']
        }
        message, error = self.restApi.post(api, params)
        if error:
            self.module.fail_json(msg=error)

    def remove_mediator(self, current_uuid):
        """
        Removes the ONTAP Mediator from MCC configuration
        """
        api = 'cluster/mediators'
        params = {
            'ip_address': self.parameters['mediator_address'],
            'password': self.parameters['mediator_password'],
            'user': self.parameters['mediator_user'],
            'uuid': current_uuid
        }
        message, error = self.restApi.delete(api, params)
        if error:
            self.module.fail_json(msg=error)

    def get_mediator(self):
        """
        Determine if the MCC configuration has added an ONTAP Mediator
        """
        api = "cluster/mediators"
        message, error = self.restApi.get(api, None)
        if error:
            self.module.fail_json(msg=error)
        if message['num_records'] > 0:
            return message['records'][0]['uuid']
        return None

    def apply(self):
        """
        Apply action to MCC Mediator
        """
        current = self.get_mediator()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.add_mediator()
                elif cd_action == 'delete':
                    self.remove_mediator(current)
        self.module.exit_json(changed=self.na_helper.changed)
Beispiel #19
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)
Beispiel #20
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)
class NetAppOntapQTree(object):
    '''Class with qtree operations'''

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

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

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

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

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

            return return_q

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

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

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

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

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

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

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

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

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

        if modify:
            self.na_helper.changed = True
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_qtree()
                elif cd_action == 'delete':
                    self.delete_qtree(current)
                else:
                    if rename:
                        self.rename_qtree(current)
                    if modify:
                        self.modify_qtree(current)
        self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapSecurityCertificates(object):
    ''' object initialize and class methods '''

    def __init__(self):
        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            common_name=dict(required=False, type='str'),
            name=dict(required=False, type='str'),
            state=dict(required=False, choices=['present', 'absent'], default='present'),
            type=dict(required=False, choices=['client', 'server', 'client_ca', 'server_ca', 'root_ca']),
            svm=dict(required=False, type='str', aliases=['vserver']),
            public_certificate=dict(required=False, type='str'),
            private_key=dict(required=False, type='str'),
            signing_request=dict(required=False, type='str'),
            expiry_time=dict(required=False, type='str'),
            key_size=dict(required=False, type='int'),
            hash_function=dict(required=False, type='str'),
            intermediate_certificates=dict(required=False, type='list', elements='str'),
            ignore_name_if_not_supported=dict(required=False, type='bool', default=True)
        ))

        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 self.parameters.get('name') is None:
            if self.parameters.get('common_name') is None or self.parameters.get('type') is None:
                error = "'name' or ('common_name' and 'type') are required parameters."
                self.module.fail_json(msg=error)

        # ONTAP 9.6 and 9.7 do not support name.  We'll change this to True if we detect an issue.
        self.ignore_name_param = False

        # API should be used for ONTAP 9.6 or higher
        self.rest_api = OntapRestAPI(self.module)
        if self.rest_api.is_rest():
            self.use_rest = True
        else:
            self.module.fail_json(msg=self.rest_api.requires_ontap_9_6('na_ontap_security_certificates'))

    def get_certificate(self):
        """
        Fetch uuid if certificate exists.
        NOTE: because of a bug in ONTAP 9.6 and 9.7, name is not supported. We are
        falling back to using common_name and type, but unicity is not guaranteed.
        :return:
            Dictionary if certificate with same name is found
            None if not found
        """
        error = "'name' or ('common_name', 'type') are required."
        for key in ('name', 'common_name'):
            if self.parameters.get(key) is None:
                continue
            data = {'fields': 'uuid',
                    key: self.parameters[key],
                    }
            if self.parameters.get('svm') is not None:
                data['svm.name'] = self.parameters['svm']
            else:
                data['scope'] = 'cluster'
            if key == 'common_name':
                if self.parameters.get('type') is not None:
                    data['type'] = self.parameters['type']
                else:
                    error = "When using 'common_name', 'type' is required."
                    break

            api = "security/certificates"
            message, error = self.rest_api.get(api, data)
            if error:
                try:
                    name_not_supported_error = (key == 'name') and (error['message'] == 'Unexpected argument "name".')
                except (KeyError, TypeError):
                    name_not_supported_error = False
                if name_not_supported_error:
                    if self.parameters['ignore_name_if_not_supported'] and self.parameters.get('common_name') is not None:
                        # let's attempt a retry using common_name
                        self.ignore_name_param = True
                        continue
                    error = "ONTAP 9.6 and 9.7 do not support 'name'.  Use 'common_name' and 'type' as a work-around."
            # report success, or any other error as is
            break

        if error:
            self.module.fail_json(msg='Error calling API: %s - %s' % (api, error))

        if len(message['records']) == 1:
            return message['records'][0]
        if len(message['records']) > 1:
            error = 'Duplicate records with same common_name are preventing safe operations: %s' % repr(message)
            self.module.fail_json(msg=error)
        return None

    def create_or_install_certificate(self):
        """
        Create or install certificate
        :return: message (should be empty dict)
        """
        required_keys = ['type', 'common_name']
        optional_keys = ['public_certificate', 'private_key', 'expiry_time', 'key_size', 'hash_function']
        if not self.ignore_name_param:
            optional_keys.append('name')
        # special key: svm

        if not set(required_keys).issubset(set(self.parameters.keys())):
            self.module.fail_json(msg='Error creating or installing certificate: one or more of the following options are missing: %s'
                                  % (', '.join(required_keys)))

        data = dict()
        if self.parameters.get('svm') is not None:
            data['svm'] = {'name': self.parameters['svm']}
        for key in required_keys + optional_keys:
            if self.parameters.get(key) is not None:
                data[key] = self.parameters[key]
        api = "security/certificates"
        message, error = self.rest_api.post(api, data)
        if error:
            if self.parameters.get('svm') is None and error.get('target') == 'uuid':
                error['target'] = 'cluster'
            if error.get('message') == 'duplicate entry':
                error['message'] += '.  Same certificate may already exist under a different name.'
            self.module.fail_json(msg="Error creating or installing certificate: %s" % error)
        return message

    def sign_certificate(self, uuid):
        """
        sign certificate
        :return: a dictionary with key "public_certificate"
        """
        api = "security/certificates/%s/sign" % uuid
        data = {'signing_request': self.parameters['signing_request']}
        optional_keys = ['expiry_time', 'hash_function']
        for key in optional_keys:
            if self.parameters.get(key) is not None:
                data[key] = self.parameters[key]
        message, error = self.rest_api.post(api, data)
        if error:
            self.module.fail_json(msg="Error signing certificate: %s" % error)
        return message

    def delete_certificate(self, uuid):
        """
        Delete certificate
        :return: message (should be empty dict)
        """
        api = "security/certificates/%s" % uuid
        message, error = self.rest_api.delete(api)
        if error:
            self.module.fail_json(msg="Error deleting certificate: %s" % error)
        return message

    def apply(self):
        """
        Apply action to create/install/sign/delete certificate
        :return: None
        """
        # TODO: add telemetry for REST

        current = self.get_certificate()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        message = None
        if self.parameters.get('signing_request') is not None:
            error = None
            if self.parameters['state'] == 'absent':
                error = "'signing_request' is not supported with 'state' set to 'absent'"
            elif current is None:
                scope = 'cluster' if self.parameters.get('svm') is None else "svm: %s" % self.parameters.get('svm')
                error = "signing certificate with name '%s' not found on %s" % (self.parameters.get('name'), scope)
            elif cd_action is not None:
                error = "'signing_request' is exclusive with other actions: create, install, delete"
            if error is not None:
                self.module.fail_json(msg=error)
            self.na_helper.changed = True

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    message = self.create_or_install_certificate()
                elif cd_action == 'delete':
                    message = self.delete_certificate(current['uuid'])
                elif self.parameters.get('signing_request') is not None:
                    message = self.sign_certificate(current['uuid'])

        results = {'changed': self.na_helper.changed}
        if message:
            results['ontap_info'] = message
        self.module.exit_json(**results)
class NetAppONTAPIscsiSecurity(object):
    """
    Class with iscsi security 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'),
                auth_type=dict(required=False,
                               type='str',
                               choices=['chap', 'none', 'deny']),
                inbound_password=dict(required=False, type='str', no_log=True),
                inbound_username=dict(required=False, type='str'),
                initiator=dict(required=True, type='str'),
                address_ranges=dict(required=False,
                                    type='list',
                                    elements='str'),
                outbound_password=dict(required=False, type='str',
                                       no_log=True),
                outbound_username=dict(required=False, type='str'),
            ))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True,
            required_if=[[
                'auth_type', 'chap', ['inbound_username', 'inbound_password']
            ]],
            required_together=[
                ['inbound_username', 'inbound_password'],
                ['outbound_username', 'outbound_password'],
            ],
        )

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

        self.restApi = OntapRestAPI(self.module)
        self.uuid = self.get_svm_uuid()

    def get_initiator(self):
        """
        Get current initiator.
        :return: dict of current initiator details.
        """
        params = {'fields': '*', 'initiator': self.parameters['initiator']}
        api = '/protocols/san/iscsi/credentials/'
        message, error = self.restApi.get(api, params)
        if error is not None:
            self.module.fail_json(msg="Error on fetching initiator: %s" %
                                  error)
        if message['num_records'] > 0:
            record = message['records'][0]
            initiator_details = dict()
            initiator_details['auth_type'] = record['authentication_type']
            if initiator_details['auth_type'] == 'chap':
                if record['chap'].get('inbound'):
                    initiator_details['inbound_username'] = record['chap'][
                        'inbound']['user']
                else:
                    initiator_details['inbound_username'] = None
                if record['chap'].get('outbound'):
                    initiator_details['outbound_username'] = record['chap'][
                        'outbound']['user']
                else:
                    initiator_details['outbound_username'] = None
            if record.get('initiator_address'):
                if record['initiator_address'].get('ranges'):
                    ranges = []
                    for address_range in record['initiator_address']['ranges']:
                        if address_range['start'] == address_range['end']:
                            ranges.append(address_range['start'])
                        else:
                            ranges.append(address_range['start'] + '-' +
                                          address_range['end'])
                    initiator_details['address_ranges'] = ranges
                else:
                    initiator_details['address_ranges'] = None
            return initiator_details

    def create_initiator(self):
        """
        Create initiator.
        :return: None.
        """
        params = dict()
        params['authentication_type'] = self.parameters['auth_type']
        params['initiator'] = self.parameters['initiator']
        if self.parameters['auth_type'] == 'chap':
            chap_info = dict()
            chap_info['inbound'] = {
                'user': self.parameters['inbound_username'],
                'password': self.parameters['inbound_password']
            }
            if self.parameters.get('outbound_username'):
                chap_info['outbound'] = {
                    'user': self.parameters['outbound_username'],
                    'password': self.parameters['outbound_password']
                }
            params['chap'] = chap_info
        address_info = self.get_address_info(
            self.parameters.get('address_ranges'))
        if address_info is not None:
            params['initiator_address'] = {'ranges': address_info}
        params['svm'] = {'uuid': self.uuid, 'name': self.parameters['vserver']}
        api = '/protocols/san/iscsi/credentials'
        message, error = self.restApi.post(api, params)
        if error is not None:
            self.module.fail_json(msg="Error on creating initiator: %s" %
                                  error)

    def delete_initiator(self):
        """
        Delete initiator.
        :return: None.
        """
        api = '/protocols/san/iscsi/credentials/{0}/{1}'.format(
            self.uuid, self.parameters['initiator'])
        message, error = self.restApi.delete(api, {})
        if error is not None:
            self.module.fail_json(msg="Error on deleting initiator: %s" %
                                  error)

    def modify_initiator(self, modify, current):
        """
        Modify initiator.
        :param modify: dict of modify attributes.
        :return: None.
        """
        params = dict()
        use_chap = False
        chap_update = False
        chap_update_inbound = False
        chap_update_outbound = False

        if modify.get('auth_type') and modify['auth_type'] == 'chap':
            # change in auth_type
            chap_update = True
            use_chap = True
        elif current.get('auth_type') == 'chap':
            # we're already using chap
            use_chap = True

        if use_chap and (modify.get('inbound_username')
                         or modify.get('inbound_password')):
            # change in chap inbound credentials
            chap_update = True
            chap_update_inbound = True

        if use_chap and (modify.get('outbound_username')
                         or modify.get('outbound_password')):
            # change in chap outbound credentials
            chap_update = True
            chap_update_outbound = True

        if chap_update:
            chap_info = dict()
            # set values from self.parameters as they may not show as modified
            if chap_update_inbound:
                chap_info['inbound'] = {
                    'user': self.parameters['inbound_username'],
                    'password': self.parameters['inbound_password']
                }
            else:
                # use current values as inbound username/password are required
                chap_info['inbound'] = {
                    'user': current.get('inbound_username'),
                    'password': current.get('inbound_password')
                }
            if chap_update_outbound:
                chap_info['outbound'] = {
                    'user': self.parameters['outbound_username'],
                    'password': self.parameters['outbound_password']
                }

            params['chap'] = chap_info
        address_info = self.get_address_info(modify.get('address_ranges'))
        if address_info is not None:
            params['initiator_address'] = {'ranges': address_info}
        api = '/protocols/san/iscsi/credentials/{0}/{1}'.format(
            self.uuid, self.parameters['initiator'])
        message, error = self.restApi.patch(api, params)
        if error is not None:
            self.module.fail_json(msg="Error on modifying initiator: %s" %
                                  error)

    def get_address_info(self, address_ranges):
        if address_ranges is None:
            return None
        else:
            address_info = []
            for address in address_ranges:
                address_range = {}
                if '-' in address:
                    address_range['end'] = address.split('-')[1]
                    address_range['start'] = address.split('-')[0]
                else:
                    address_range['end'] = address
                    address_range['start'] = address
                address_info.append(address_range)
            return address_info

    def apply(self):
        """
        check create/delete/modify operations if needed.
        :return: None.
        """
        current = self.get_initiator()
        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 action == 'create':
                    self.create_initiator()
                elif action == 'delete':
                    self.delete_initiator()
                elif modify:
                    self.modify_initiator(modify, current)
        self.module.exit_json(changed=self.na_helper.changed)

    def get_svm_uuid(self):
        """
        Get a svm's UUID
        :return: uuid of the svm.
        """
        params = {'fields': 'uuid', 'name': self.parameters['vserver']}
        api = "svm/svms"
        message, error = self.restApi.get(api, params)
        if error is not None:
            self.module.fail_json(msg="Error on fetching svm uuid: %s" % error)
        return message['records'][0]['uuid']
class NetAppONTAPMetroCluster(object):
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            state=dict(choices=['present'], default='present'),
            dr_pairs=dict(required=True, type='list', elements='dict', options=dict(
                node_name=dict(required=True, type='str'),
                partner_node_name=dict(required=True, type='str')
            )),
            partner_cluster_name=dict(required=True, type='str')
        ))
        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True
        )
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        self.restApi = OntapRestAPI(self.module)
        self.use_rest = self.restApi.is_rest()

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

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

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

    def apply(self):
        current = self.get_metrocluster()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_metrocluster()
                # Since there is no modify or delete, we will return no change
                else:
                    self.module.fail_json(msg="Modify and Delete currently not support in API")
        self.module.exit_json(changed=self.na_helper.changed)
Beispiel #25
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)