class NetAppOntapLUN(object):
    ''' create, modify, delete LUN '''
    def __init__(self):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return True

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        if app_modify_warning:
            self.warnings.append(app_modify_warning)
        results['changed'] = self.na_helper.changed
        results['actions'] = actions
        if self.warnings:
            results['warnings'] = self.warnings
        results.update(self.debug)
        self.module.exit_json(**results)
Exemplo n.º 2
0
class NetAppONTAPNsswitch(object):
    """
    Class with NVMe service methods
    """

    def __init__(self):

        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
            vserver=dict(required=True, type='str'),
            database_type=dict(required=True, type='str', choices=['hosts', 'group', 'passwd', 'netgroup', 'namemap']),
            sources=dict(required=False, type='list')
        ))

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

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

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

    def get_name_service_switch(self):
        """
        get current name service switch config
        :return: dict of current name service switch
        """
        nss_iter = netapp_utils.zapi.NaElement('nameservice-nsswitch-get-iter')
        nss_info = netapp_utils.zapi.NaElement('namservice-nsswitch-config-info')
        db_type = netapp_utils.zapi.NaElement('nameservice-database')
        db_type.set_content(self.parameters['database_type'])
        query = netapp_utils.zapi.NaElement('query')
        nss_info.add_child_elem(db_type)
        query.add_child_elem(nss_info)
        nss_iter.add_child_elem(query)
        result = self.server.invoke_successfully(nss_iter, True)
        return_value = None
        if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) == 1:
            nss_sources = result.get_child_by_name('attributes-list').get_child_by_name(
                'namservice-nsswitch-config-info').get_child_by_name('nameservice-sources')
            sources = [sources.get_content() for sources in nss_sources.get_children()]
            return_value = {
                'sources': sources
            }
        return return_value

    def create_name_service_switch(self):
        """
        create name service switch config
        :return: None
        """
        nss_create = netapp_utils.zapi.NaElement('nameservice-nsswitch-create')
        nss_create.add_new_child('nameservice-database', self.parameters['database_type'])
        nss_sources = netapp_utils.zapi.NaElement('nameservice-sources')
        nss_create.add_child_elem(nss_sources)
        for source in self.parameters['sources']:
            nss_sources.add_new_child('nss-source-type', source.strip())
        try:
            self.server.invoke_successfully(nss_create,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error on creating name service switch config on vserver %s: %s'
                                      % (self.parameters['vserver'], to_native(error)),
                                  exception=traceback.format_exc())

    def delete_name_service_switch(self):
        """
        delete name service switch
        :return: None
        """
        nss_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'nameservice-nsswitch-destroy', **{'nameservice-database': self.parameters['database_type']})
        try:
            self.server.invoke_successfully(nss_delete,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error on deleting name service switch config on vserver %s: %s'
                                      % (self.parameters['vserver'], to_native(error)),
                                  exception=traceback.format_exc())

    def modify_name_service_switch(self, modify):
        """
        modify name service switch
        :param modify: dict of modify attributes
        :return: None
        """
        nss_modify = netapp_utils.zapi.NaElement('nameservice-nsswitch-modify')
        nss_modify.add_new_child('nameservice-database', self.parameters['database_type'])
        nss_sources = netapp_utils.zapi.NaElement('nameservice-sources')
        nss_modify.add_child_elem(nss_sources)
        if 'sources' in modify:
            for source in self.parameters['sources']:
                nss_sources.add_new_child('nss-source-type', source.strip())
        try:
            self.server.invoke_successfully(nss_modify, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error on modifying name service switch config on vserver %s: %s'
                                  % (self.parameters['vserver'], to_native(error)),
                                  exception=traceback.format_exc())

    def apply(self):
        netapp_utils.ems_log_event("na_ontap_name_service_switch", self.server)
        current = self.get_name_service_switch()
        cd_action, modify = None, None
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        modify = self.na_helper.get_modified_attributes(current, self.parameters)

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_name_service_switch()
                elif cd_action == 'delete':
                    self.delete_name_service_switch()
                elif modify:
                    self.modify_name_service_switch(modify)
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 3
0
class NetAppONTAPSnapmirror(object):
    """
    Class with Snapmirror methods
    """
    def __init__(self):

        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(required=False,
                            type='str',
                            choices=['present', 'absent'],
                            default='present'),
                 source_vserver=dict(required=False, type='str'),
                 destination_vserver=dict(required=False, type='str'),
                 source_volume=dict(required=False, type='str'),
                 destination_volume=dict(required=False, type='str'),
                 source_path=dict(required=False, type='str'),
                 destination_path=dict(required=False, type='str'),
                 schedule=dict(required=False, type='str'),
                 policy=dict(required=False, type='str'),
                 relationship_type=dict(required=False,
                                        type='str',
                                        choices=[
                                            'data_protection', 'load_sharing',
                                            'vault', 'restore',
                                            'transition_data_protection',
                                            'extended_data_protection'
                                        ]),
                 source_hostname=dict(required=False, type='str'),
                 connection_type=dict(required=False,
                                      type='str',
                                      choices=[
                                          'ontap_ontap', 'elementsw_ontap',
                                          'ontap_elementsw'
                                      ],
                                      default='ontap_ontap'),
                 source_username=dict(required=False, type='str'),
                 source_password=dict(required=False, type='str', no_log=True),
                 max_transfer_rate=dict(required=False, type='int'),
                 initialize=dict(required=False, type='bool', default=True),
                 update=dict(required=False, type='bool', default=True),
                 identity_preserve=dict(required=False, type='bool'),
                 relationship_state=dict(required=False,
                                         type='str',
                                         choices=['active', 'broken'],
                                         default='active'),
                 relationship_info_only=dict(required=False,
                                             type='bool',
                                             default=False)))

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def apply(self):
        """
        Apply action to SnapMirror
        """
        self.asup_log_for_cserver("na_ontap_snapmirror")
        # source is ElementSW
        if self.parameters['state'] == 'present' and self.parameters.get(
                'connection_type') == 'elementsw_ontap':
            self.check_elementsw_parameters()
        elif self.parameters.get('connection_type') == 'ontap_elementsw':
            self.check_elementsw_parameters('destination')
        else:
            self.check_parameters()
        if self.parameters['state'] == 'present' and self.parameters.get(
                'connection_type') == 'ontap_elementsw':
            current_elementsw_ontap = self.snapmirror_get(
                self.parameters['source_path'])
            if current_elementsw_ontap is None:
                self.module.fail_json(
                    msg=
                    'Error: creating an ONTAP to ElementSW snapmirror relationship requires an '
                    'established SnapMirror relation from ElementSW to ONTAP cluster'
                )
        current = self.snapmirror_get()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        modify = self.na_helper.get_modified_attributes(
            current, self.parameters)
        element_snapmirror = False
        if cd_action == 'create':
            self.snapmirror_create()
        elif cd_action == 'delete':
            if current['status'] == 'transferring':
                self.snapmirror_abort()
            else:
                if self.parameters.get('connection_type') == 'elementsw_ontap':
                    element_snapmirror = True
                self.delete_snapmirror(element_snapmirror,
                                       current['relationship'],
                                       current['mirror_state'])
        else:
            if modify:
                self.snapmirror_modify(modify)
            # break relationship when 'relationship_state' == 'broken'
            if current and self.parameters[
                    'state'] == 'present' and self.parameters[
                        'relationship_state'] == 'broken':
                if current['mirror_state'] == 'uninitialized':
                    self.module.fail_json(
                        msg=
                        'SnapMirror relationship cannot be broken if mirror state is uninitialized'
                    )
                elif current['relationship'] in ['load_sharing', 'vault']:
                    self.module.fail_json(
                        msg=
                        'SnapMirror break is not allowed in a load_sharing or vault relationship'
                    )
                elif current['mirror_state'] != 'broken-off':
                    self.snapmirror_break()
                    self.na_helper.changed = True
            # check for initialize
            elif current and self.parameters['initialize'] and self.parameters['relationship_state'] == 'active'\
                    and current['mirror_state'] == 'uninitialized':
                self.snapmirror_initialize()
                # set changed explicitly for initialize
                self.na_helper.changed = True
            if self.parameters['state'] == 'present' and self.parameters[
                    'relationship_state'] == 'active':
                # resync when state is broken-off
                if current['mirror_state'] == 'broken-off':
                    self.snapmirror_resync()
                    # set changed explicitly for resync
                    self.na_helper.changed = True
                # Update when create is called again, or modify is being called
                elif self.parameters['update']:
                    current = self.snapmirror_get()
                    if current['mirror_state'] == 'snapmirrored':
                        self.snapmirror_update()
                        self.na_helper.changed = True
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 4
0
class NetAppOntapUserRole(object):

    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'),
            command_directory_name=dict(required=True, type='str'),
            access_level=dict(required=False, type='str', default='all',
                              choices=['none', 'readonly', 'all']),
            vserver=dict(required=True, type='str'),
            query=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)

        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_role(self):
        """
        Checks if the role exists for specific command-directory-name.

        :return:
            True if role found
            False if role is not found
        :rtype: bool
        """
        options = {'vserver': self.parameters['vserver'],
                   'role-name': self.parameters['name'],
                   'command-directory-name': self.parameters['command_directory_name']}

        security_login_role_get_iter = netapp_utils.zapi.NaElement(
            'security-login-role-get-iter')
        query_details = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-role-info', **options)
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(query_details)
        security_login_role_get_iter.add_child_elem(query)

        try:
            result = self.server.invoke_successfully(
                security_login_role_get_iter, enable_tunneling=False)
        except netapp_utils.zapi.NaApiError as e:
            # Error 16031 denotes a role not being found.
            if to_native(e.code) == "16031":
                return None
            # Error 16039 denotes command directory not found.
            elif to_native(e.code) == "16039":
                return None
            else:
                self.module.fail_json(msg='Error getting role %s: %s' % (self.name, to_native(e)),
                                      exception=traceback.format_exc())
        if (result.get_child_by_name('num-records') and
                int(result.get_child_content('num-records')) >= 1):
            role_info = result.get_child_by_name('attributes-list').get_child_by_name('security-login-role-info')
            result = {
                'name': role_info['role-name'],
                'access_level': role_info['access-level'],
                'command_directory_name': role_info['command-directory-name'],
                'query': role_info['role-query']
            }
            return result

        return None

    def create_role(self):
        options = {'vserver': self.parameters['vserver'],
                   'role-name': self.parameters['name'],
                   'command-directory-name': self.parameters['command_directory_name'],
                   'access-level': self.parameters['access_level']}
        if self.parameters.get('query'):
            options['role-query'] = self.parameters['query']
        role_create = netapp_utils.zapi.NaElement.create_node_with_children('security-login-role-create', **options)

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

    def delete_role(self):
        role_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'security-login-role-delete', **{'vserver': self.parameters['vserver'],
                                             'role-name': self.parameters['name'],
                                             'command-directory-name':
                                                 self.parameters['command_directory_name']})

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

    def modify_role(self, modify):
        options = {'vserver': self.parameters['vserver'],
                   'role-name': self.parameters['name'],
                   'command-directory-name': self.parameters['command_directory_name']}
        if 'access_level' in modify.keys():
            options['access-level'] = self.parameters['access_level']
        if 'query' in modify.keys():
            options['role-query'] = self.parameters['query']

        role_modify = netapp_utils.zapi.NaElement.create_node_with_children('security-login-role-modify', **options)

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

    def apply(self):
        self.asup_log_for_cserver('na_ontap_user_role')
        current = self.get_role()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)

        # if desired state specify empty quote query and current query is None, set desired query to None.
        # otherwise na_helper.get_modified_attributes will detect a change.
        if self.parameters.get('query') == '' and current is not None:
            if current['query'] is None:
                self.parameters['query'] = None

        modify = self.na_helper.get_modified_attributes(current, self.parameters)
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_role()
                elif cd_action == 'delete':
                    self.delete_role()
                elif modify:
                    self.modify_role(modify)
        self.module.exit_json(changed=self.na_helper.changed)

    def asup_log_for_cserver(self, event_name):
        """
        Fetch admin vserver for the given cluster
        Create and Autosupport log event with the given module name
        :param event_name: Name of the event log
        :return: None
        """
        netapp_utils.ems_log_event(event_name, self.server)
Exemplo n.º 5
0
class NetAppOntapQTree(object):
    '''Class with qtree operations'''

    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'),
            from_name=dict(required=False, type='str'),
            flexvol_name=dict(type='str'),
            vserver=dict(required=True, type='str'),
            export_policy=dict(required=False, type='str'),
            security_style=dict(required=False, choices=['unix', 'ntfs', 'mixed']),
            oplocks=dict(required=False, choices=['enabled', 'disabled']),
            unix_permissions=dict(required=False, type='str'),
        ))

        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)

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

        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'],
                        'unix_permissions': result['attributes-list']['qtree-info']['mode'],
                        'oplocks': result['attributes-list']['qtree-info']['oplocks'],
                        'security_style': result['attributes-list']['qtree-info']['security-style']}

        return return_q

    def create_qtree(self):
        """
        Create a qtree
        """
        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):
        """
        Delete a qtree
        """
        path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name'])
        qtree_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'qtree-delete', **{'qtree': path})

        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):
        """
        Rename a qtree
        """
        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):
        """
        Modify a qtree
        """
        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'''
        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'):
            rename = self.na_helper.is_rename_action(self.get_qtree(self.parameters['from_name']), current)
        else:
            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 rename:
                    self.rename_qtree()
                if cd_action == 'create':
                    self.create_qtree()
                elif cd_action == 'delete':
                    self.delete_qtree()
                elif modify:
                    self.modify_qtree()
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 6
0
class NetAppOntapUser(object):
    """
    Common operations to manage users and roles.
    """
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(
                state=dict(required=False,
                           choices=['present', 'absent'],
                           default='present'),
                name=dict(required=True, type='str'),
                applications=dict(
                    required=True,
                    type='list',
                    aliases=['application'],
                    choices=[
                        'console', 'http', 'ontapi', 'rsh', 'snmp', 'sp',
                        'service-processor', 'ssh', 'telnet'
                    ],
                ),
                authentication_method=dict(required=True,
                                           type='str',
                                           choices=[
                                               'community', 'password',
                                               'publickey', 'domain',
                                               'nsswitch', 'usm', 'cert'
                                           ]),
                set_password=dict(required=False, type='str', no_log=True),
                role_name=dict(required=False, type='str'),
                lock_user=dict(required=False, type='bool'),
                vserver=dict(required=True, type='str'),
            ))

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self.server.set_vserver(None)
        return True

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

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

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

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

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

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

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

        if self.na_helper.changed:

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

                if lock_user:
                    if self.parameters.get('lock_user'):
                        self.lock_given_user()
                    else:
                        self.unlock_given_user()
                if not create_delete_decision and self.parameters.get(
                        'set_password') is not None:
                    # if change password return false nothing has changed so we need to set changed to False
                    self.na_helper.changed = self.change_password()
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 7
0
class NetAppONTAPCluster(object):
    """
    object initialize and class methods
    """
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(required=False,
                            type='str',
                            choices=['present', 'absent'],
                            default='present'),
                 cluster_name=dict(required=False, type='str'),
                 cluster_ip_address=dict(required=False, type='str'),
                 cluster_location=dict(required=False, type='str'),
                 cluster_contact=dict(required=False, type='str'),
                 single_node_cluster=dict(required=False,
                                          type='bool',
                                          default=False),
                 node_name=dict(required=False, type='str')))

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return is_complete

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

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

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

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

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

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

    def autosupport_log(self):
        """
        Autosupport log for cluster
        :return:
        """
        results = netapp_utils.get_cserver(self.server)
        cserver = netapp_utils.setup_na_ontap_zapi(module=self.module,
                                                   vserver=results)
        netapp_utils.ems_log_event("na_ontap_cluster", cserver)

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

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

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

        if not self.module.check_mode:
            if cluster_action == 'create':
                if self.create_cluster():
                    self.cluster_create_wait()
            if node_action == 'add_node':
                if self.add_node():
                    self.node_add_wait()
            elif node_action == 'remove_node':
                self.remove_node()
                self.node_remove_wait()
            if modify:
                self.modify_cluster_identity(modify)
        self.autosupport_log()
        self.module.exit_json(changed=self.na_helper.changed,
                              warnings=self.warnings)
Exemplo n.º 8
0
class NetAppONTAPMotd(object):
    def __init__(self):
        argument_spec = netapp_utils.na_ontap_host_argument_spec()
        argument_spec.update(
            dict(state=dict(required=False,
                            default='present',
                            choices=['present', 'absent']),
                 vserver=dict(required=True, type='str'),
                 motd_message=dict(default='', type='str',
                                   aliases=['message']),
                 show_cluster_motd=dict(default=True, type='bool')))

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

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

        if HAS_NETAPP_LIB is False:
            self.module.fail_json(
                msg="the python NetApp-Lib module is required")

        self.server = netapp_utils.setup_na_ontap_zapi(
            module=self.module, vserver=self.parameters['vserver'])

    def motd_get_iter(self):
        """
        Compose NaElement object to query current motd
        :return: NaElement object for vserver-motd-get-iter
        """
        motd_get_iter = netapp_utils.zapi.NaElement('vserver-motd-get-iter')
        query = netapp_utils.zapi.NaElement('query')
        motd_info = netapp_utils.zapi.NaElement('vserver-motd-info')
        motd_info.add_new_child('is-cluster-message-enabled',
                                str(self.parameters['show_cluster_motd']))
        motd_info.add_new_child('vserver', self.parameters['vserver'])
        query.add_child_elem(motd_info)
        motd_get_iter.add_child_elem(query)
        return motd_get_iter

    def motd_get(self):
        """
        Get current motd
        :return: Dictionary of current motd details if query successful, else None
        """
        motd_get_iter = self.motd_get_iter()
        motd_result = dict()
        try:
            result = self.server.invoke_successfully(motd_get_iter,
                                                     enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error fetching motd info: %s' %
                                  to_native(error),
                                  exception=traceback.format_exc())
        if result.get_child_by_name('num-records') and \
                int(result.get_child_content('num-records')) > 0:
            motd_info = result.get_child_by_name(
                'attributes-list').get_child_by_name('vserver-motd-info')
            motd_result['motd_message'] = motd_info.get_child_content(
                'message')
            motd_result['motd_message'] = str(
                motd_result['motd_message']).rstrip()
            motd_result[
                'show_cluster_motd'] = True if motd_info.get_child_content(
                    'is-cluster-message-enabled') == 'true' else False
            motd_result['vserver'] = motd_info.get_child_content('vserver')
            return motd_result
        return None

    def modify_motd(self):
        motd_create = netapp_utils.zapi.NaElement('vserver-motd-modify-iter')
        motd_create.add_new_child('message', self.parameters['motd_message'])
        motd_create.add_new_child(
            'is-cluster-message-enabled', 'true'
            if self.parameters['show_cluster_motd'] is True else 'false')
        query = netapp_utils.zapi.NaElement('query')
        motd_info = netapp_utils.zapi.NaElement('vserver-motd-info')
        motd_info.add_new_child('vserver', self.parameters['vserver'])
        query.add_child_elem(motd_info)
        motd_create.add_child_elem(query)
        try:
            self.server.invoke_successfully(motd_create,
                                            enable_tunneling=False)
        except netapp_utils.zapi.NaApiError as err:
            self.module.fail_json(msg="Error creating motd: %s" %
                                  (to_native(err)),
                                  exception=traceback.format_exc())
        return motd_create

    def apply(self):
        """
        Applies action from playbook
        """
        netapp_utils.ems_log_event("na_ontap_motd", self.server)
        current = self.motd_get()
        if self.parameters['state'] == 'absent':
            # Just make sure it is empty
            self.parameters['motd_message'] = ''
            if current and current['motd_message'] == 'None':
                current = None
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if cd_action is None and self.parameters['state'] == 'present':
            self.na_helper.get_modified_attributes(current, self.parameters)

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                self.modify_motd()
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 9
0
class NetAppOntapLDAPClient(object):
    '''
    LDAP Client definition class
    '''
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(base_dn=dict(required=False, type='str'),
                 base_scope=dict(required=False,
                                 default=None,
                                 choices=['subtree', 'onelevel', 'base']),
                 bind_dn=dict(required=False, default=None, type='str'),
                 bind_password=dict(type='str',
                                    required=False,
                                    default=None,
                                    no_log=True),
                 name=dict(required=True, type='str'),
                 ldap_servers=dict(required_if=[["state", "present"]],
                                   type='list'),
                 min_bind_level=dict(required=False,
                                     default=None,
                                     choices=['anonymous', 'simple', 'sasl']),
                 port=dict(required=False, default=None, type='int'),
                 query_timeout=dict(required=False, default=None, type='int'),
                 referral_enabled=dict(required=False,
                                       default=None,
                                       choices=['true', 'false']),
                 schema=dict(
                     required_if=[["state", "present"]],
                     default=None,
                     type='str',
                     choices=['AD-IDMU', 'AD-SFU', 'MS-AD-BIS', 'RFC-2307']),
                 session_security=dict(required=False,
                                       default=None,
                                       choices=['true', 'false']),
                 state=dict(required=False,
                            choices=['present', 'absent'],
                            default='present'),
                 use_start_tls=dict(required=False,
                                    default=None,
                                    choices=['true', 'false']),
                 vserver=dict(required=True, type='str')))

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

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

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

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

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

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

        if vserver_name is None:
            vserver_name = '*'

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

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

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

        # Get LDAP client configuration details
        client_config_details = None
        if (result.get_child_by_name('num-records')
                and int(result.get_child_content('num-records')) >= 1):
            attributes_list = result.get_child_by_name('attributes-list')
            client_config_info = attributes_list.get_child_by_name(
                'ldap-client')

            # Get LDAP servers list
            ldap_server_list = list()
            get_list = client_config_info.get_child_by_name('ldap-servers')
            if get_list is not None:
                ldap_servers = get_list.get_children()
                for ldap_server in ldap_servers:
                    ldap_server_list.append(ldap_server.get_content())

            # Define config details structure
            client_config_details = {
                'name':
                client_config_info.get_child_content('ldap-client-config'),
                'ldap_servers':
                client_config_info.get_child_content('ldap-servers'),
                'base_dn':
                client_config_info.get_child_content('base-dn'),
                'base_scope':
                client_config_info.get_child_content('base-scope'),
                'bind_dn':
                client_config_info.get_child_content('bind-dn'),
                'bind_password':
                client_config_info.get_child_content('bind-password'),
                'min_bind_level':
                client_config_info.get_child_content('min-bind-level'),
                'port':
                client_config_info.get_child_content('port'),
                'query_timeout':
                client_config_info.get_child_content('query-timeout'),
                'referral_enabled':
                client_config_info.get_child_content('referral-enabled'),
                'schema':
                client_config_info.get_child_content('schema'),
                'session_security':
                client_config_info.get_child_content('session-security'),
                'use_start_tls':
                client_config_info.get_child_content('use-start-tls'),
                'vserver':
                client_config_info.get_child_content('vserver')
            }

        return client_config_details

    def create_ldap_client(self):
        '''
        Create LDAP client configuration
        '''
        # LDAP servers NaElement
        ldap_servers_element = netapp_utils.zapi.NaElement('ldap-servers')

        # Mandatory options
        for ldap_server_name in self.parameters['ldap_servers']:
            ldap_servers_element.add_new_child('string', ldap_server_name)

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

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

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

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

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

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

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

        for attribute in modify:
            # LDAP_servers
            if attribute == 'ldap_servers':
                ldap_servers_element = netapp_utils.zapi.NaElement(
                    'ldap-servers')
                for ldap_server_name in self.parameters['ldap_servers']:
                    ldap_servers_element.add_new_child('string',
                                                       ldap_server_name)
                ldap_client_modify.add_child_elem(ldap_servers_element)

            # Simple attributes
            if attribute in self.simple_attributes:
                ldap_client_modify.add_new_child(
                    str(attribute).replace('_', '-'),
                    self.parameters[attribute])

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

    def apply(self):
        '''Call create/modify/delete operations.'''
        current = self.get_ldap_client()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        modify = self.na_helper.get_modified_attributes(
            current, self.parameters)
        # create an ems log event for users with auto support turned on
        netapp_utils.ems_log_event("na_ontap_ldap_client", self.server)

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_ldap_client()
                elif cd_action == 'delete':
                    self.delete_ldap_client()
                elif modify:
                    self.modify_ldap_client(modify)
        self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapVolumeSnaplock(object):
    '''Class with volume operations'''
    def __init__(self):
        '''Initialize module parameters'''

        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(
                name=dict(required=True, type='str'),
                vserver=dict(required=True, type='str'),
                default_retention_period=dict(required=False, type='str'),
                maximum_retention_period=dict(required=False, type='str'),
                minimum_retention_period=dict(required=False, type='str'),
                autocommit_period=dict(required=False, type='str'),
                is_volume_append_mode_enabled=dict(required=False,
                                                   type='bool'),
            ))
        self.module = AnsibleModule(argument_spec=self.argument_spec,
                                    supports_check_mode=True)
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)

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

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

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

        return_value = None

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

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

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

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                self.set_volume_snaplock_attrs(modify)
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 11
0
class NetAppOntapLUN(object):
    ''' create, modify, delete LUN '''
    def __init__(self):

        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
            name=dict(required=True, type='str'),
            from_name=dict(required=False, type='str'),
            size=dict(type='int'),
            size_unit=dict(default='gb',
                           choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb',
                                    'pb', 'eb', 'zb', 'yb'], type='str'),
            force_resize=dict(default=False, type='bool'),
            force_remove=dict(default=False, type='bool'),
            force_remove_fenced=dict(default=False, type='bool'),
            flexvol_name=dict(required=True, type='str'),
            vserver=dict(required=True, type='str'),
            ostype=dict(required=False, type='str', default='image'),
            qos_policy_group=dict(required=False, type='str'),
            space_reserve=dict(required=False, type='bool', default=True),
            space_allocation=dict(required=False, type='bool', default=False),
            use_exact_size=dict(required=False, type='bool', default=True),
        ))

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

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

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

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

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

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

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

            lun_info.add_child_elem(query)

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

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

            if tag is None:
                break
        return luns

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

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

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

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

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

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

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

        return return_value if return_value else None

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

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

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

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

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

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

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

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

        return True

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

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

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

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

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

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

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

        results['changed'] = self.na_helper.changed
        self.module.exit_json(**results)
Exemplo n.º 12
0
class NetAppOntapIgroup(object):
    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),
                 ostype=dict(required=False, type='str'),
                 initiator_group_type=dict(required=False,
                                           type='str',
                                           choices=['fcp', 'iscsi', 'mixed']),
                 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),
                 bind_portset=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)
        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']
            ]

        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_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
        """
        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)
        result, current = None, 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 = result.get_child_by_name(
                'attributes-list').get_child_by_name('initiator-group-info')
            initiators = []
            if igroup.get_child_by_name('initiators'):
                current_initiators = igroup['initiators'].get_children()
                for initiator in current_initiators:
                    initiators.append(initiator['initiator-name'])
            current = {'initiators': initiators}

        return current

    def add_initiators(self):
        """
        Add the list of initiators to igroup
        :return: None
        """
        # don't add if initiators is empty string
        if self.parameters.get('initiators') == [
                ''
        ] or self.parameters.get('initiators') is None:
            return
        for initiator in self.parameters['initiators']:
            self.modify_initiator(initiator, 'igroup-add')

    def remove_initiators(self, initiators):
        """
        Removes all existing initiators from igroup
        :return: None
        """
        for initiator in initiators:
            self.modify_initiator(initiator, '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(self):
        """
        Create the igroup.
        """
        options = {'initiator-group-name': self.parameters['name']}
        if self.parameters.get('ostype') is not None:
            options['os-type'] = self.parameters['ostype']
        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()

    def delete_igroup(self):
        """
        Delete the igroup.
        """
        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.
        """
        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 autosupport_log(self):
        netapp_utils.ems_log_event("na_ontap_igroup", self.server)

    def apply(self):
        self.autosupport_log()
        current = self.get_igroup(self.parameters['name'])
        # rename and create are mutually exclusive
        rename, cd_action, modify = None, None, None
        if self.parameters.get('from_name'):
            rename = self.na_helper.is_rename_action(
                self.get_igroup(self.parameters['from_name']), current)
        else:
            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 rename:
                    self.rename_igroup()
                elif cd_action == 'create':
                    self.create_igroup()
                elif cd_action == 'delete':
                    self.delete_igroup()
                if modify:
                    self.remove_initiators(current['initiators'])
                    self.add_initiators()
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 13
0
class NetAppONTAPCifsShare(object):
    """
    Methods to create/delete/modify(path) CIFS share
    """
    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'),
                 share_name=dict(required=True, type='str'),
                 path=dict(required=False, type='str'),
                 vserver=dict(required=True, type='str'),
                 share_properties=dict(required=False,
                                       type='list',
                                       elements='str'),
                 symlink_properties=dict(required=False,
                                         type='list',
                                         elements='str'),
                 vscan_fileop_profile=dict(
                     required=False,
                     type='str',
                     choices=['no_scan', 'standard', 'strict',
                              'writes_only'])))

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

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

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

    def get_cifs_share(self):
        """
        Return details about the cifs-share
        :param:
            name : Name of the cifs-share
        :return: Details about the cifs-share. None if not found.
        :rtype: dict
        """
        cifs_iter = netapp_utils.zapi.NaElement('cifs-share-get-iter')
        cifs_info = netapp_utils.zapi.NaElement('cifs-share')
        cifs_info.add_new_child('share-name',
                                self.parameters.get('share_name'))
        cifs_info.add_new_child('vserver', self.parameters.get('vserver'))

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

        cifs_iter.add_child_elem(query)

        result = self.server.invoke_successfully(cifs_iter, True)

        return_value = None
        # check if query returns the expected cifs-share
        if result.get_child_by_name('num-records') and \
                int(result.get_child_content('num-records')) == 1:
            properties_list = []
            symlink_list = []
            cifs_attrs = result.get_child_by_name('attributes-list').\
                get_child_by_name('cifs-share')
            if cifs_attrs.get_child_by_name('share-properties'):
                properties_attrs = cifs_attrs['share-properties']
                if properties_attrs is not None:
                    properties_list = [
                        property.get_content()
                        for property in properties_attrs.get_children()
                    ]
            if cifs_attrs.get_child_by_name('symlink-properties'):
                symlink_attrs = cifs_attrs['symlink-properties']
                if symlink_attrs is not None:
                    symlink_list = [
                        symlink.get_content()
                        for symlink in symlink_attrs.get_children()
                    ]
            return_value = {
                'share': cifs_attrs.get_child_content('share-name'),
                'path': cifs_attrs.get_child_content('path'),
                'share_properties': properties_list,
                'symlink_properties': symlink_list
            }
            if cifs_attrs.get_child_by_name('vscan-fileop-profile'):
                return_value['vscan_fileop_profile'] = cifs_attrs[
                    'vscan-fileop-profile']

        return return_value

    def create_cifs_share(self):
        """
        Create CIFS share
        """
        options = {
            'share-name': self.parameters.get('share_name'),
            'path': self.parameters.get('path')
        }
        cifs_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'cifs-share-create', **options)
        if self.parameters.get('share_properties'):
            property_attrs = netapp_utils.zapi.NaElement('share-properties')
            cifs_create.add_child_elem(property_attrs)
            for aproperty in self.parameters.get('share_properties'):
                property_attrs.add_new_child('cifs-share-properties',
                                             aproperty)
        if self.parameters.get('symlink_properties'):
            symlink_attrs = netapp_utils.zapi.NaElement('symlink-properties')
            cifs_create.add_child_elem(symlink_attrs)
            for symlink in self.parameters.get('symlink_properties'):
                symlink_attrs.add_new_child('cifs-share-symlink-properties',
                                            symlink)
        if self.parameters.get('vscan_fileop_profile'):
            fileop_attrs = netapp_utils.zapi.NaElement('vscan-fileop-profile')
            fileop_attrs.set_content(self.parameters['vscan_fileop_profile'])
            cifs_create.add_child_elem(fileop_attrs)

        try:
            self.server.invoke_successfully(cifs_create, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:

            self.module.fail_json(
                msg='Error creating cifs-share %s: %s' %
                (self.parameters.get('share_name'), to_native(error)),
                exception=traceback.format_exc())

    def delete_cifs_share(self):
        """
        Delete CIFS share
        """
        cifs_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'cifs-share-delete',
            **{'share-name': self.parameters.get('share_name')})

        try:
            self.server.invoke_successfully(cifs_delete, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error deleting cifs-share %s: %s' %
                (self.parameters.get('share_name'), to_native(error)),
                exception=traceback.format_exc())

    def modify_cifs_share(self):
        """
        modilfy path for the given CIFS share
        """
        options = {'share-name': self.parameters.get('share_name')}
        cifs_modify = netapp_utils.zapi.NaElement.create_node_with_children(
            'cifs-share-modify', **options)
        if self.parameters.get('path'):
            cifs_modify.add_new_child('path', self.parameters.get('path'))
        if self.parameters.get('share_properties'):
            property_attrs = netapp_utils.zapi.NaElement('share-properties')
            cifs_modify.add_child_elem(property_attrs)
            for aproperty in self.parameters.get('share_properties'):
                property_attrs.add_new_child('cifs-share-properties',
                                             aproperty)
        if self.parameters.get('symlink_properties'):
            symlink_attrs = netapp_utils.zapi.NaElement('symlink-properties')
            cifs_modify.add_child_elem(symlink_attrs)
            for aproperty in self.parameters.get('symlink_properties'):
                symlink_attrs.add_new_child('cifs-share-symlink-properties',
                                            aproperty)
        if self.parameters.get('vscan_fileop_profile'):
            fileop_attrs = netapp_utils.zapi.NaElement('vscan-fileop-profile')
            fileop_attrs.set_content(self.parameters['vscan_fileop_profile'])
            cifs_modify.add_child_elem(fileop_attrs)
        try:
            self.server.invoke_successfully(cifs_modify, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error modifying cifs-share %s:%s' %
                (self.parameters.get('share_name'), to_native(error)),
                exception=traceback.format_exc())

    def apply(self):
        '''Apply action to cifs share'''
        netapp_utils.ems_log_event("na_ontap_cifs", self.server)
        current = self.get_cifs_share()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        modify = None
        if cd_action is None:
            # ZAPI accepts both 'show-previous-versions' and 'show_previous_versions', but only returns the latter
            if 'show-previous-versions' in self.parameters.get('share_properties', []) and\
               current and 'show_previous_versions' in current.get('share_properties', []):
                self.parameters['share_properties'].remove(
                    'show-previous-versions')
                self.parameters['share_properties'].append(
                    'show_previous_versions')
            modify = self.na_helper.get_modified_attributes(
                current, self.parameters)
        if self.na_helper.changed and not self.module.check_mode:
            if cd_action == 'create':
                self.create_cifs_share()
            elif cd_action == 'delete':
                self.delete_cifs_share()
            elif modify:
                self.modify_cifs_share()
        results = dict(changed=self.na_helper.changed)
        if modify and netapp_utils.has_feature(self.module, 'show_modified'):
            results['modify'] = str(modify)
        self.module.exit_json(**results)
class NetAppOntapQosPolicyGroup(object):
    """
    Create, delete, modify and rename a policy group.
    """
    def __init__(self):
        """
        Initialize the Ontap qos policy group class.
        """
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(required=False,
                            type='str',
                            choices=['present', 'absent'],
                            default='present'),
                 name=dict(required=True, type='str'),
                 from_name=dict(required=False, type='str'),
                 vserver=dict(required=True, type='str'),
                 max_throughput=dict(required=False, type='str'),
                 min_throughput=dict(required=False, type='str'),
                 is_shared=dict(required=False, type='bool'),
                 force=dict(required=False, type='bool', default=False)))

        self.module = AnsibleModule(argument_spec=self.argument_spec,
                                    supports_check_mode=True)
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)

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

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

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

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

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

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

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

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

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

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

    def asup_log_for_cserver(self, event_name):
        """
        Fetch admin vserver for the given cluster
        Create and Autosupport log event with the given module name
        :param event_name: Name of the event log
        :return: None
        """
        results = netapp_utils.get_cserver(self.server)
        cserver = netapp_utils.setup_na_ontap_zapi(module=self.module,
                                                   vserver=results)
        netapp_utils.ems_log_event(event_name, cserver)
Exemplo n.º 15
0
class NetAppOntapLUN(object):
    ''' create, modify, delete LUN '''
    def __init__(self):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return True

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

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

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

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

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

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

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

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

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

        results['changed'] = self.na_helper.changed
        self.module.exit_json(**results)
class NetAppONTAPPortset(object):
    """
    Methods to create or delete portset
    """
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(required=False, type='str', default='present'),
                 vserver=dict(required=True, type='str'),
                 name=dict(required=True, type='str'),
                 type=dict(required=False,
                           type='str',
                           choices=['fcp', 'iscsi', 'mixed']),
                 force=dict(required=False, type='bool', default=False),
                 ports=dict(required=False, type='list', elements='str')))

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

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

        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 portset_get_iter(self):
        """
        Compose NaElement object to query current portset using vserver, portset-name and portset-type parameters
        :return: NaElement object for portset-get-iter with query
        """
        portset_get = netapp_utils.zapi.NaElement('portset-get-iter')
        query = netapp_utils.zapi.NaElement('query')
        portset_info = netapp_utils.zapi.NaElement('portset-info')
        portset_info.add_new_child('vserver', self.parameters['vserver'])
        portset_info.add_new_child('portset-name', self.parameters['name'])
        query.add_child_elem(portset_info)
        portset_get.add_child_elem(query)
        return portset_get

    def portset_get(self):
        """
        Get current portset info
        :return: Dictionary of current portset details if query successful, else return None
        """
        portset_get_iter = self.portset_get_iter()
        result, portset_info = None, dict()
        try:
            result = self.server.invoke_successfully(portset_get_iter,
                                                     enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error fetching portset %s: %s' %
                                  (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())
        # return portset details
        if result.get_child_by_name('num-records') and int(
                result.get_child_content('num-records')) > 0:
            portset_get_info = result.get_child_by_name(
                'attributes-list').get_child_by_name('portset-info')
            portset_info['type'] = portset_get_info.get_child_content(
                'portset-type')
            if int(portset_get_info.get_child_content(
                    'portset-port-total')) > 0:
                ports = portset_get_info.get_child_by_name('portset-port-info')
                portset_info['ports'] = [
                    port.get_content() for port in ports.get_children()
                ]
            else:
                portset_info['ports'] = []
            return portset_info
        return None

    def create_portset(self):
        """
        Create a portset
        """
        if self.parameters.get('type') is None:
            self.module.fail_json(
                msg='Error: Missing required parameter for create (type)')
        portset_info = netapp_utils.zapi.NaElement("portset-create")
        portset_info.add_new_child("portset-name", self.parameters['name'])
        portset_info.add_new_child("portset-type", self.parameters['type'])
        try:
            self.server.invoke_successfully(portset_info,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg="Error creating portset %s: %s" %
                                  (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())

    def delete_portset(self):
        """
        Delete a portset
        """
        portset_info = netapp_utils.zapi.NaElement("portset-destroy")
        portset_info.add_new_child("portset-name", self.parameters['name'])
        if self.parameters.get('force'):
            portset_info.add_new_child("force", str(self.parameters['force']))
        try:
            self.server.invoke_successfully(portset_info,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg="Error deleting portset %s: %s" %
                                  (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())

    def remove_ports(self, ports):
        """
        Removes all existing ports from portset
        :return: None
        """
        for port in ports:
            self.modify_port(port, 'portset-remove', 'removing')

    def add_ports(self):
        """
        Add the list of ports to portset
        :return: None
        """
        # don't add if ports is empty string
        if self.parameters.get('ports') == [
                ''
        ] or self.parameters.get('ports') is None:
            return
        for port in self.parameters['ports']:
            self.modify_port(port, 'portset-add', 'adding')

    def modify_port(self, port, zapi, action):
        """
        Add or remove an port to/from a portset
        """
        port.strip(
        )  # remove leading spaces if any (eg: if user types a space after comma in initiators list)
        options = {
            'portset-name': self.parameters['name'],
            'portset-port-name': port
        }

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

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

    def apply(self):
        """
        Applies action from playbook
        """
        netapp_utils.ems_log_event("na_ontap_autosupport", self.server)
        current, modify = self.portset_get(), None
        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(
                    'type') and self.parameters['type'] != current['type']:
                self.module.fail_json(
                    msg=
                    "modify protocol(type) not supported and %s already exists in vserver %s under different type"
                    % (self.parameters['name'], self.parameters['vserver']))
            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_portset()
                    self.add_ports()
                elif cd_action == 'delete':
                    self.delete_portset()
                elif modify:
                    self.remove_ports(current['ports'])
                    self.add_ports()
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 17
0
class NetAppONTAPCifsSecurity(object):
    '''
    modify vserver cifs security
    '''
    def __init__(self):

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

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

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

        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 cifs_security_get_iter(self):
        """
        get current vserver cifs security.
        :return: a dict of vserver cifs security
        """
        cifs_security_get = netapp_utils.zapi.NaElement('cifs-security-get-iter')
        query = netapp_utils.zapi.NaElement('query')
        cifs_security = netapp_utils.zapi.NaElement('cifs-security')
        cifs_security.add_new_child('vserver', self.parameters['vserver'])
        query.add_child_elem(cifs_security)
        cifs_security_get.add_child_elem(query)
        cifs_security_details = dict()
        try:
            result = self.server.invoke_successfully(cifs_security_get, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error fetching cifs security from %s: %s'
                                      % (self.parameters['vserver'], to_native(error)),
                                  exception=traceback.format_exc())
        if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0:
            cifs_security_info = result.get_child_by_name('attributes-list').get_child_by_name('cifs-security')
            cifs_security_details['kerberos_clock_skew'] = cifs_security_info.get_child_content('kerberos-clock-skew')
            cifs_security_details['kerberos_ticket_age'] = cifs_security_info.get_child_content('kerberos-ticket-age')
            cifs_security_details['kerberos_renew_age'] = cifs_security_info.get_child_content('kerberos-renew-age')
            cifs_security_details['kerberos_kdc_timeout'] = cifs_security_info.get_child_content('kerberos-kdc-timeout')
            cifs_security_details['is_signing_required'] = bool(cifs_security_info.get_child_content('is-signing-required'))
            cifs_security_details['is_password_complexity_required'] = bool(cifs_security_info.get_child_content('is-password-complexity-required'))
            cifs_security_details['is_aes_encryption_enabled'] = bool(cifs_security_info.get_child_content('is-aes-encryption-enabled'))
            cifs_security_details['is_smb_encryption_required'] = bool(cifs_security_info.get_child_content('is-smb-encryption-required'))
            cifs_security_details['lm_compatibility_level'] = cifs_security_info.get_child_content('lm-compatibility-level')
            cifs_security_details['referral_enabled_for_ad_ldap'] = bool(cifs_security_info.get_child_content('referral-enabled-for-ad-ldap'))
            cifs_security_details['session_security_for_ad_ldap'] = cifs_security_info.get_child_content('session-security-for-ad-ldap')
            cifs_security_details['smb1_enabled_for_dc_connections'] = cifs_security_info.get_child_content('smb1-enabled-for-dc-connections')
            cifs_security_details['smb2_enabled_for_dc_connections'] = cifs_security_info.get_child_content('smb2-enabled-for-dc-connections')
            cifs_security_details['use_start_tls_for_ad_ldap'] = bool(cifs_security_info.get_child_content('use-start-tls-for-ad-ldap'))
            return cifs_security_details
        return None

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

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

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

    def asup_log_for_cserver(self, event_name):
        """
        Fetch admin vserver for the given cluster
        Create and Autosupport log event with the given module name
        :param event_name: Name of the event log
        :return: None
        """
        results = netapp_utils.get_cserver(self.server)
        cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
        netapp_utils.ems_log_event(event_name, cserver)
Exemplo n.º 18
0
class NetAppOntapNtfsSd(object):
    """
        Creates, Modifies and Destroys a NTFS security descriptor
    """
    def __init__(self):
        """
            Initialize the Ontap NTFS Security Descriptor 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'),
                name=dict(required=True, type='str'),
                owner=dict(required=False, type='str'),
                group=dict(required=False, type='str'),
                control_flags_raw=dict(required=False, type='int'),
            ))

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

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

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

        ntfs_sd_entry, result = None, None

        ntfs_sd_get_iter = netapp_utils.zapi.NaElement(
            'file-directory-security-ntfs-get-iter')
        ntfs_sd_info = netapp_utils.zapi.NaElement(
            'file-directory-security-ntfs')
        ntfs_sd_info.add_new_child('vserver', self.parameters['vserver'])
        ntfs_sd_info.add_new_child('ntfs-sd', self.parameters['name'])
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(ntfs_sd_info)
        ntfs_sd_get_iter.add_child_elem(query)
        try:
            result = self.server.invoke_successfully(ntfs_sd_get_iter, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error fetching NTFS security descriptor %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:
            attributes_list = result.get_child_by_name('attributes-list')
            ntfs_sd = attributes_list.get_child_by_name(
                'file-directory-security-ntfs')
            ntfs_sd_entry = {
                'vserver':
                ntfs_sd.get_child_content('vserver'),
                'name':
                ntfs_sd.get_child_content('ntfs-sd'),
                'owner':
                ntfs_sd.get_child_content('owner'),
                'group':
                ntfs_sd.get_child_content('group'),
                'control_flags_raw':
                ntfs_sd.get_child_content('control-flags-raw'),
            }
            if ntfs_sd_entry.get('control_flags_raw'):
                ntfs_sd_entry['control_flags_raw'] = int(
                    ntfs_sd_entry['control_flags_raw'])
            return ntfs_sd_entry
        return None

    def add_ntfs_sd(self):
        """
        Adds a new NTFS security descriptor
        """

        ntfs_sd_obj = netapp_utils.zapi.NaElement(
            "file-directory-security-ntfs-create")
        ntfs_sd_obj.add_new_child("ntfs-sd", self.parameters['name'])

        if self.parameters.get('control_flags_raw'):
            ntfs_sd_obj.add_new_child(
                "control-flags-raw", str(self.parameters['control_flags_raw']))

        if self.parameters.get('owner'):
            ntfs_sd_obj.add_new_child("owner", self.parameters['owner'])

        if self.parameters.get('group'):
            ntfs_sd_obj.add_new_child("group", self.parameters['group'])

        if 'owner' not in self.parameters.keys(
        ) and 'group' not in self.parameters.keys():
            self.module.fail_json(
                msg=
                'Either owner or group must be specified when creating NTFS security descriptor.'
            )

        try:
            self.server.invoke_successfully(ntfs_sd_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error creating NTFS security descriptor %s: %s' %
                (self.parameters['name'], to_native(error)),
                exception=traceback.format_exc())

    def remove_ntfs_sd(self):
        """
        Deletes a NTFS security descriptor
        """
        ntfs_sd_obj = netapp_utils.zapi.NaElement(
            "file-directory-security-ntfs-delete")
        ntfs_sd_obj.add_new_child("ntfs-sd", self.parameters['name'])
        try:
            self.server.invoke_successfully(ntfs_sd_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error deleting NTFS security descriptor %s: %s' %
                (self.parameters['name'], to_native(error)),
                exception=traceback.format_exc())

    def modify_ntfs_sd(self):
        """
        Modifies a NTFS security descriptor
        """

        ntfs_sd_obj = netapp_utils.zapi.NaElement(
            "file-directory-security-ntfs-modify")
        ntfs_sd_obj.add_new_child("ntfs-sd", self.parameters['name'])

        if self.parameters.get('control_flags_raw'):
            ntfs_sd_obj.add_new_child(
                'control-flags-raw', str(self.parameters['control_flags_raw']))

        if self.parameters.get('owner'):
            ntfs_sd_obj.add_new_child('owner', self.parameters['owner'])

        if self.parameters.get('group'):
            ntfs_sd_obj.add_new_child('group', self.parameters['group'])

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

    def apply(self):
        netapp_utils.ems_log_event("na_ontap_ntfs_sd", self.server)
        current, modify = self.get_ntfs_sd(), None
        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.add_ntfs_sd()
                elif cd_action == 'delete':
                    self.remove_ntfs_sd()
                elif modify:
                    self.modify_ntfs_sd()
        self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapLoginMessages(object):
    """
    modify and delete login banner and motd
    """
    def __init__(self):
        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(vserver=dict(required=True, type='str'),
                 banner=dict(required=False, type='str'),
                 motd_message=dict(required=False,
                                   type='str',
                                   aliases=['message']),
                 show_cluster_motd=dict(default=True, type='bool')))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True,
            required_one_of=[['show_cluster_motd', 'banner', 'motd_message']])

        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_banner_motd(self, uuid=None):
        if self.use_rest:
            api = 'security/login/messages/' + uuid
            params = {'fields': '*'}
            message, error = self.rest_api.get(api, params)
            if error:
                self.module.fail_json(
                    msg='Error when fetching login_banner info: %s' % error)
            return_result = dict()
            return_result['banner'] = message['banner'].rstrip(
            ) if message.get('banner') else ''
            return_result['motd_message'] = message['message'].rstrip(
            ) if message.get('message') else ''
            if message.get('show_cluster_message'):
                return_result['show_cluster_message'] = message[
                    'show_cluster_message']
            return return_result
        else:
            login_banner_get_iter = netapp_utils.zapi.NaElement(
                'vserver-login-banner-get-iter')
            query = netapp_utils.zapi.NaElement('query')
            login_banner_info = netapp_utils.zapi.NaElement(
                'vserver-login-banner-info')
            login_banner_info.add_new_child('vserver',
                                            self.parameters['vserver'])
            query.add_child_elem(login_banner_info)
            login_banner_get_iter.add_child_elem(query)
            return_result = dict()
            try:
                result = self.server.invoke_successfully(login_banner_get_iter,
                                                         enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(
                    msg='Error fetching login_banner info: %s' %
                    to_native(error),
                    exception=traceback.format_exc())
            if result.get_child_by_name('num-records') and int(
                    result.get_child_content('num-records')) > 0:
                login_banner_info = result.get_child_by_name(
                    'attributes-list').get_child_by_name(
                        'vserver-login-banner-info')
                return_result['banner'] = login_banner_info.get_child_content(
                    'message')
                return_result['banner'] = str(return_result['banner']).rstrip()
                # if the message is '-' that means the banner doesn't exist.
                if return_result['banner'] == '-' or return_result[
                        'banner'] == 'None':
                    return_result['banner'] = ''

            motd_get_iter = netapp_utils.zapi.NaElement(
                'vserver-motd-get-iter')
            query = netapp_utils.zapi.NaElement('query')
            motd_info = netapp_utils.zapi.NaElement('vserver-motd-info')
            motd_info.add_new_child('vserver', self.parameters['vserver'])
            query.add_child_elem(motd_info)
            motd_get_iter.add_child_elem(query)
            try:
                result = self.server.invoke_successfully(motd_get_iter,
                                                         enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error fetching motd info: %s' %
                                      to_native(error),
                                      exception=traceback.format_exc())
            if result.get_child_by_name('num-records') and \
                    int(result.get_child_content('num-records')) > 0:
                motd_info = result.get_child_by_name(
                    'attributes-list').get_child_by_name('vserver-motd-info')
                return_result['motd_message'] = motd_info.get_child_content(
                    'message')
                return_result['motd_message'] = str(
                    return_result['motd_message']).rstrip()
                return_result[
                    'show_cluster_motd'] = True if motd_info.get_child_content(
                        'is-cluster-message-enabled') == 'true' else False
                if return_result['motd_message'] == 'None':
                    return_result['motd_message'] = ''
            return return_result

    def modify_banner(self, modify, uuid):
        if self.use_rest:
            api = 'security/login/messages/' + uuid
            params = {"banner": modify['banner']}
            dummy, error = self.rest_api.patch(api, params)
            if error:
                self.module.fail_json(msg='Error when modifying banner: %s' %
                                      error)
        else:
            login_banner_modify = netapp_utils.zapi.NaElement(
                'vserver-login-banner-modify-iter')
            login_banner_modify.add_new_child('message', modify['banner'])
            query = netapp_utils.zapi.NaElement('query')
            login_banner_info = netapp_utils.zapi.NaElement(
                'vserver-login-banner-info')
            login_banner_info.add_new_child('vserver',
                                            self.parameters['vserver'])
            query.add_child_elem(login_banner_info)
            login_banner_modify.add_child_elem(query)
            try:
                self.server.invoke_successfully(login_banner_modify,
                                                enable_tunneling=False)
            except netapp_utils.zapi.NaApiError as err:
                self.module.fail_json(msg="Error modifying login_banner: %s" %
                                      (to_native(err)),
                                      exception=traceback.format_exc())

    def modify_motd(self, modify, uuid):
        if self.use_rest:
            api = 'security/login/messages/' + uuid
            params = {
                'message': modify['motd_message'],
            }
            if modify.get('show_cluster_motd'):
                params['show_cluster_message'] = modify['show_cluster_motd']
            dummy, error = self.rest_api.patch(api, params)
            if error:
                self.module.fail_json(msg='Error when modifying motd: %s' %
                                      error)
        else:
            motd_create = netapp_utils.zapi.NaElement(
                'vserver-motd-modify-iter')
            if modify.get('motd_message') is not None:
                motd_create.add_new_child('message', modify['motd_message'])
            if modify.get('show_cluster_motd') is not None:
                motd_create.add_new_child(
                    'is-cluster-message-enabled',
                    'true' if modify['show_cluster_motd'] is True else 'false')
            query = netapp_utils.zapi.NaElement('query')
            motd_info = netapp_utils.zapi.NaElement('vserver-motd-info')
            motd_info.add_new_child('vserver', self.parameters['vserver'])
            query.add_child_elem(motd_info)
            motd_create.add_child_elem(query)
            try:
                self.server.invoke_successfully(motd_create,
                                                enable_tunneling=False)
            except netapp_utils.zapi.NaApiError as err:
                self.module.fail_json(msg="Error modifying motd: %s" %
                                      (to_native(err)),
                                      exception=traceback.format_exc())

    def get_svm_uuid(self):
        """
        Get a svm's uuid
        :return: uuid of the svm
        """
        params = {'name': self.parameters['vserver'], 'fields': 'uuid'}
        api = 'svm/svms'
        message, error = self.rest_api.get(api, params)
        if error is not None:
            self.module.fail_json(msg="%s" % error)
        if message['num_records'] == 0:
            self.module.fail_json(
                msg=
                "Error fetching specified vserver. Please make sure vserver name is correct. For cluster vserver, Please use ZAPI."
            )
        return message['records'][0]['uuid']

    def apply(self):
        uuid = None
        modify = None
        if self.use_rest:
            uuid = self.get_svm_uuid()
        else:
            netapp_utils.ems_log_event("na_ontap_login_banner", self.server)

        current = self.get_banner_motd(uuid=uuid)
        modify = self.na_helper.get_modified_attributes(
            current, self.parameters)
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if modify.get('banner') is not None:
                    self.modify_banner(modify, uuid=uuid)
                if modify.get('show_cluster_motd') is not None or modify.get(
                        'motd_message') is not None:
                    self.modify_motd(modify, uuid=uuid)

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