Exemplo n.º 1
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.º 2
0
class NetAppOntapNetRoutes(object):
    """
    Create, Modifies and Destroys a Net Route
    """
    def __init__(self):
        """
        Initialize the Ontap Net Route class
        """
        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(
                state=dict(required=False,
                           type='str',
                           choices=['present', 'absent'],
                           default='present'),
                vserver=dict(required=True, type='str'),
                destination=dict(required=True, type='str'),
                gateway=dict(required=True, type='str'),
                metric=dict(required=False, type='int'),
                from_destination=dict(required=False, type='str',
                                      default=None),
                from_gateway=dict(required=False, type='str', default=None),
                from_metric=dict(required=False, type='int', default=None),
            ))

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

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

        self.restApi = OntapRestAPI(self.module)
        # some attributes are not supported in earlier REST implementation
        unsupported_rest_properties = ['metric', 'from_metric']
        used_unsupported_rest_properties = [
            x for x in unsupported_rest_properties if x in self.parameters
        ]
        self.use_rest, error = self.restApi.is_rest(
            used_unsupported_rest_properties)

        if error is not None:
            self.module.fail_json(msg=error)

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

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

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

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

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

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

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

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

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

    def apply(self):
        """
        Run Module based on play book
        """
        if not self.use_rest:
            netapp_utils.ems_log_event("na_ontap_net_routes", self.server)
        current = self.get_net_route()
        modify, cd_action = None, None
        if self.use_rest:
            modify_params = {
                'gateway': self.parameters.get('from_gateway'),
                'destination': self.parameters.get('from_destination')
            }
            if any(modify_params.values()):
                # destination and gateway combination is unique, and is considered like a id. so modify destination
                # or gateway is considered a rename action.
                old_params = self.get_net_route(modify_params)
                modify = self.na_helper.is_rename_action(old_params, current)
                if modify is None:
                    self.module.fail_json(
                        msg="Error modifying: route %s does not exist" %
                        self.parameters['from_destination'])
            else:
                cd_action = self.na_helper.get_cd_action(
                    current, self.parameters)
        else:
            modify_params = {
                'destination': self.parameters.get('from_destination'),
                'gateway': self.parameters.get('from_gateway'),
                'metric': self.parameters.get('from_metric')
            }
            # if any from_* param is present in playbook, check for modify action
            if any(modify_params.values()):
                # destination and gateway combination is unique, and is considered like a id. so modify destination
                # or gateway is considered a rename action. metric is considered an attribute of the route so it is
                # considered as modify.
                if modify_params.get('metric') is not None:
                    modify = True
                    old_params = current
                else:
                    # get parameters that are eligible for modify
                    old_params = self.get_net_route(modify_params)
                    modify = self.na_helper.is_rename_action(
                        old_params, current)
                if modify is None:
                    self.module.fail_json(
                        msg="Error modifying: route %s does not exist" %
                        self.parameters['from_destination'])
            else:
                cd_action = self.na_helper.get_cd_action(
                    current, self.parameters)
        if cd_action == 'create':
            if not self.module.check_mode:
                self.create_net_route()
        elif cd_action == 'delete':
            if not self.module.check_mode:
                self.delete_net_route(current)
        elif modify:
            desired = {}
            for key, value in old_params.items():
                desired[key] = value
            for key, value in modify_params.items():
                if value is not None:
                    desired[key] = self.parameters.get(key)
            self.modify_net_route(old_params, desired)
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 3
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.º 4
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.º 5
0
class NetAppOntapSVM(object):
    def __init__(self):
        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(required=False,
                            type='str',
                            choices=['present', 'absent'],
                            default='present'),
                 name=dict(required=True, type='str'),
                 from_name=dict(required=False, type='str'),
                 root_volume=dict(type='str'),
                 root_volume_aggregate=dict(type='str'),
                 root_volume_security_style=dict(
                     type='str', choices=['unix', 'ntfs', 'mixed', 'unified']),
                 allowed_protocols=dict(type='list', elements='str'),
                 aggr_list=dict(type='list', elements='str'),
                 ipspace=dict(type='str', required=False),
                 snapshot_policy=dict(type='str', required=False),
                 language=dict(type='str', required=False),
                 subtype=dict(type='str',
                              choices=[
                                  'default', 'dp_destination', 'sync_source',
                                  'sync_destination'
                              ]),
                 comment=dict(type="str", required=False)))

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

        self.restApi = OntapRestAPI(self.module)
        # with REST, to force synchronous operations
        self.timeout = self.restApi.timeout
        # root volume not supported with rest api
        unsupported_rest_properties = [
            'root_volume', 'root_volume_aggregate',
            'root_volume_security_style'
        ]
        used_unsupported_rest_properties = [
            x for x in unsupported_rest_properties if x in self.parameters
        ]
        self.use_rest, error = self.restApi.is_rest(
            used_unsupported_rest_properties)
        if error is not None:
            self.module.fail_json(msg=error)
        if not self.use_rest:
            if HAS_NETAPP_LIB is False:
                self.module.fail_json(
                    msg="the python NetApp-Lib module is required")
            else:
                self.server = netapp_utils.setup_na_ontap_zapi(
                    module=self.module)

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

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

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

        if self.use_rest:
            api = 'svm/svms'
            params = {
                'fields':
                'subtype,aggregates,language,snapshot_policy,ipspace,comment,nfs,cifs,fcp,iscsi,nvme'
            }
            message, error = self.restApi.get(api, params)
            if error:
                self.module.fail_json(msg=error)
            if len(message.keys()) == 0:
                return None
            elif 'records' in message and len(message['records']) == 0:
                return None
            elif 'records' not in message:
                error = "Unexpected response in get_net_route from %s: %s" % (
                    api, repr(message))
                self.module.fail_json(msg=error)
            vserver_details = None
            for record in message['records']:
                if record['name'] == vserver_name:
                    vserver_details = copy.deepcopy(record)
                    break
            if vserver_details is None:
                return None
            return self.clean_up_output(vserver_details)

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

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

            result = self.server.invoke_successfully(vserver_info,
                                                     enable_tunneling=False)
            vserver_details = None
            if (result.get_child_by_name('num-records')
                    and int(result.get_child_content('num-records')) >= 1):
                attributes_list = result.get_child_by_name('attributes-list')
                vserver_info = attributes_list.get_child_by_name(
                    'vserver-info')
                aggr_list = list()
                ''' vserver aggr-list can be empty by default'''
                get_list = vserver_info.get_child_by_name('aggr-list')
                if get_list is not None:
                    aggregates = get_list.get_children()
                    for aggr in aggregates:
                        aggr_list.append(aggr.get_content())

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

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

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

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

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

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

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

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

    def apply(self):
        '''Call create/modify/delete operations.'''
        if not self.use_rest:
            self.asup_log_for_cserver("na_ontap_svm")
        current = self.get_vserver()
        cd_action, rename = None, None
        if self.parameters.get('from_name'):
            old_svm = self.get_vserver(self.parameters['from_name'])
            rename = self.na_helper.is_rename_action(old_svm, current)
            if rename is None:
                self.module.fail_json(
                    msg='Error renaming SVM %s: no SVM with from_name %s.' %
                    (self.parameters['name'], self.parameters['from_name']))
        else:
            cd_action = self.na_helper.get_cd_action(current, self.parameters)
        modify = self.na_helper.get_modified_attributes(
            current, self.parameters)
        for attribute in modify:
            if attribute in [
                    'root_volume', 'root_volume_aggregate',
                    'root_volume_security_style', 'subtype', 'ipspace'
            ]:
                self.module.fail_json(
                    msg='Error modifying SVM %s: can not modify %s.' %
                    (self.parameters['name'], attribute))
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if rename:
                    self.rename_vserver(old_svm)
                # If rename is True, cd_action is None, but modify could be true or false.
                if cd_action == 'create':
                    self.create_vserver()
                elif cd_action == 'delete':
                    self.delete_vserver(current)
                elif modify:
                    self.modify_vserver(modify, current)
        self.module.exit_json(changed=self.na_helper.changed)

    def asup_log_for_cserver(self, event_name):
        """
        Fetch admin vserver for the given cluster
        Create and Autosupport log event with the given module name
        :param event_name: Name of the event log
        :return: None
        """
        results = netapp_utils.get_cserver(self.server)
        cserver = netapp_utils.setup_na_ontap_zapi(module=self.module,
                                                   vserver=results)
        netapp_utils.ems_log_event(event_name, cserver)
Exemplo n.º 6
0
class NetAppOntapIgroup(object):
    """Create/Delete/Rename Igroups and Modify initiators list"""
    def __init__(self):

        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
            name=dict(required=True, type='str'),
            from_name=dict(required=False, type='str', default=None),
            os_type=dict(required=False, type='str', aliases=['ostype']),
            igroups=dict(required=False, type='list', elements='str'),
            initiator_group_type=dict(required=False, type='str',
                                      choices=['fcp', 'iscsi', 'mixed'],
                                      aliases=['protocol']),
            initiators=dict(required=False, type='list', elements='str', aliases=['initiator']),
            vserver=dict(required=True, type='str'),
            force_remove_initiator=dict(required=False, type='bool', default=False, aliases=['allow_delete_while_mapped']),
            bind_portset=dict(required=False, type='str')
        ))

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        if self.na_helper.changed and not self.module.check_mode:
            if rename:
                self.rename_igroup()
            elif cd_action == 'create':
                self.create_igroup()
            elif cd_action == 'delete':
                self.delete_igroup(uuid)
            if modify:
                for attr in ('igroups', 'initiators'):
                    if attr in current:
                        # we need to remove everything first
                        self.remove_initiators_or_igroups(uuid, attr, current[attr], current['name_to_uuid'][attr])
                for attr in ('igroups', 'initiators'):
                    if attr in current:
                        self.add_initiators_or_igroups(uuid, attr, current[attr])
                    modify.pop(attr, None)
                if modify:
                    self.modify_igroup_rest(uuid, modify)
        self.module.exit_json(changed=self.na_helper.changed, current=current, modify=modify)
Exemplo n.º 7
0
class NetAppOntapQuotaPolicy(object):
    """
        Create, rename or delete a quota policy
    """
    def __init__(self):
        """
            Initialize the ONTAP quota policy 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'),
                from_name=dict(required=False, type='str'),
            ))

        self.module = AnsibleModule(argument_spec=self.argument_spec,
                                    required_if=[('state', 'present',
                                                  ['name', 'vserver'])],
                                    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_quota_policy(self, policy_name=None):

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

        return_value = None
        quota_policy_get_iter = netapp_utils.zapi.NaElement(
            'quota-policy-get-iter')
        quota_policy_info = netapp_utils.zapi.NaElement('quota-policy-info')
        quota_policy_info.add_new_child('policy-name', policy_name)
        quota_policy_info.add_new_child('vserver', self.parameters['vserver'])
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(quota_policy_info)
        quota_policy_get_iter.add_child_elem(query)
        try:
            result = self.server.invoke_successfully(quota_policy_get_iter,
                                                     True)
            if result.get_child_by_name('attributes-list'):
                quota_policy_attributes = result['attributes-list'][
                    'quota-policy-info']
                return_value = {'name': quota_policy_attributes['policy-name']}
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error fetching quota policy %s: %s' %
                                  (policy_name, to_native(error)),
                                  exception=traceback.format_exc())
        return return_value

    def create_quota_policy(self):
        """
        Creates a new quota policy
        """
        quota_policy_obj = netapp_utils.zapi.NaElement("quota-policy-create")
        quota_policy_obj.add_new_child("policy-name", self.parameters['name'])
        quota_policy_obj.add_new_child("vserver", self.parameters['vserver'])
        try:
            self.server.invoke_successfully(quota_policy_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error creating quota policy %s: %s' %
                                  (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())

    def delete_quota_policy(self):
        """
        Deletes a quota policy
        """
        quota_policy_obj = netapp_utils.zapi.NaElement("quota-policy-delete")
        quota_policy_obj.add_new_child("policy-name", self.parameters['name'])
        try:
            self.server.invoke_successfully(quota_policy_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error deleting quota policy %s: %s' %
                                  (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())

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

    def apply(self):
        netapp_utils.ems_log_event("na_ontap_efficiency_policy", self.server)
        current = self.get_quota_policy()
        # rename and create are mutually exclusive
        rename, cd_action = None, None
        if self.parameters.get('from_name'):
            rename = self.na_helper.is_rename_action(
                self.get_quota_policy(self.parameters['from_name']), current)
        else:
            cd_action = self.na_helper.get_cd_action(current, self.parameters)

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_quota_policy()
                elif cd_action == 'delete':
                    self.delete_quota_policy()
                elif rename:
                    self.rename_quota_policy()
        self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapQosPolicyGroup(object):
    """
    Create, delete, modify and rename a policy group.
    """
    def __init__(self):
        """
        Initialize the Ontap qos policy group class.
        """
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(required=False,
                            type='str',
                            choices=['present', 'absent'],
                            default='present'),
                 name=dict(required=True, type='str'),
                 from_name=dict(required=False, type='str'),
                 vserver=dict(required=True, type='str'),
                 max_throughput=dict(required=False, type='str'),
                 min_throughput=dict(required=False, type='str'),
                 is_shared=dict(required=False, type='bool'),
                 force=dict(required=False, type='bool', default=False)))

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

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

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

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

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

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

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

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

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

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

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

    def asup_log_for_cserver(self, event_name):
        """
        Fetch admin vserver for the given cluster
        Create and Autosupport log event with the given module name
        :param event_name: Name of the event log
        :return: None
        """
        results = netapp_utils.get_cserver(self.server)
        cserver = netapp_utils.setup_na_ontap_zapi(module=self.module,
                                                   vserver=results)
        netapp_utils.ems_log_event(event_name, cserver)
Exemplo n.º 9
0
class NetAppOntapSnapshot(object):
    """
    Creates, modifies, and deletes a Snapshot
    """
    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'),
                from_name=dict(required=False, type='str'),
                snapshot=dict(required=True, type="str"),
                volume=dict(required=True, type="str"),
                async_bool=dict(required=False, type="bool", default=False),
                comment=dict(required=False, type="str"),
                snapmirror_label=dict(required=False, type="str"),
                ignore_owners=dict(required=False, type="bool", default=False),
                snapshot_instance_uuid=dict(required=False, type="str"),
                vserver=dict(required=True, type="str"),
            ))
        self.module = AnsibleModule(argument_spec=self.argument_spec,
                                    supports_check_mode=True)

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

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

    def get_snapshot(self, snapshot_name=None):
        """
        Checks to see if a snapshot exists or not
        :return: Return True if a snapshot exists, False if it doesn't
        """
        if snapshot_name is None:
            snapshot_name = self.parameters['snapshot']
        snapshot_obj = netapp_utils.zapi.NaElement("snapshot-get-iter")
        desired_attr = netapp_utils.zapi.NaElement("desired-attributes")
        snapshot_info = netapp_utils.zapi.NaElement('snapshot-info')
        comment = netapp_utils.zapi.NaElement('comment')
        snapmirror_label = netapp_utils.zapi.NaElement('snapmirror-label')
        # add more desired attributes that are allowed to be modified
        snapshot_info.add_child_elem(comment)
        snapshot_info.add_child_elem(snapmirror_label)
        desired_attr.add_child_elem(snapshot_info)
        snapshot_obj.add_child_elem(desired_attr)
        # compose query
        query = netapp_utils.zapi.NaElement("query")
        snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info")
        snapshot_info_obj.add_new_child("name", snapshot_name)
        snapshot_info_obj.add_new_child("volume", self.parameters['volume'])
        snapshot_info_obj.add_new_child("vserver", self.parameters['vserver'])
        query.add_child_elem(snapshot_info_obj)
        snapshot_obj.add_child_elem(query)
        result = self.server.invoke_successfully(snapshot_obj, True)
        return_value = 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')
            snap_info = attributes_list.get_child_by_name('snapshot-info')
            return_value = {'comment': snap_info.get_child_content('comment')}
            if snap_info.get_child_by_name('snapmirror-label'):
                return_value['snapmirror_label'] = snap_info.get_child_content(
                    'snapmirror-label')
            else:
                return_value['snapmirror_label'] = None
        return return_value

    def create_snapshot(self):
        """
        Creates a new snapshot
        """
        snapshot_obj = netapp_utils.zapi.NaElement("snapshot-create")

        # set up required variables to create a snapshot
        snapshot_obj.add_new_child("snapshot", self.parameters['snapshot'])
        snapshot_obj.add_new_child("volume", self.parameters['volume'])
        # Set up optional variables to create a snapshot
        if self.parameters.get('async_bool'):
            snapshot_obj.add_new_child("async",
                                       str(self.parameters['async_bool']))
        if self.parameters.get('comment'):
            snapshot_obj.add_new_child("comment", self.parameters['comment'])
        if self.parameters.get('snapmirror_label'):
            snapshot_obj.add_new_child("snapmirror-label",
                                       self.parameters['snapmirror_label'])
        try:
            self.server.invoke_successfully(snapshot_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error creating snapshot %s: %s' %
                (self.parameters['snapshot'], to_native(error)),
                exception=traceback.format_exc())

    def delete_snapshot(self):
        """
        Deletes an existing snapshot
        """
        snapshot_obj = netapp_utils.zapi.NaElement("snapshot-delete")

        # Set up required variables to delete a snapshot
        snapshot_obj.add_new_child("snapshot", self.parameters['snapshot'])
        snapshot_obj.add_new_child("volume", self.parameters['volume'])
        # set up optional variables to delete a snapshot
        if self.parameters.get('ignore_owners'):
            snapshot_obj.add_new_child("ignore-owners",
                                       str(self.parameters['ignore_owners']))
        if self.parameters.get('snapshot_instance_uuid'):
            snapshot_obj.add_new_child(
                "snapshot-instance-uuid",
                self.parameters['snapshot_instance_uuid'])
        try:
            self.server.invoke_successfully(snapshot_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error deleting snapshot %s: %s' %
                (self.parameters['snapshot'], to_native(error)),
                exception=traceback.format_exc())

    def modify_snapshot(self):
        """
        Modify an existing snapshot
        :return:
        """
        snapshot_obj = netapp_utils.zapi.NaElement("snapshot-modify-iter")
        # Create query object, this is the existing object
        query = netapp_utils.zapi.NaElement("query")
        snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info")
        snapshot_info_obj.add_new_child("name", self.parameters['snapshot'])
        snapshot_info_obj.add_new_child("vserver", self.parameters['vserver'])
        query.add_child_elem(snapshot_info_obj)
        snapshot_obj.add_child_elem(query)

        # this is what we want to modify in the snapshot object
        attributes = netapp_utils.zapi.NaElement("attributes")
        snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info")
        snapshot_info_obj.add_new_child("name", self.parameters['snapshot'])
        if self.parameters.get('comment'):
            snapshot_info_obj.add_new_child("comment",
                                            self.parameters['comment'])
        if self.parameters.get('snapmirror_label'):
            snapshot_info_obj.add_new_child(
                "snapmirror-label", self.parameters['snapmirror_label'])
        attributes.add_child_elem(snapshot_info_obj)
        snapshot_obj.add_child_elem(attributes)
        try:
            self.server.invoke_successfully(snapshot_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error modifying snapshot %s: %s' %
                (self.parameters['snapshot'], to_native(error)),
                exception=traceback.format_exc())

    def rename_snapshot(self):
        """
        Rename the sanpshot
        """
        snapshot_obj = netapp_utils.zapi.NaElement("snapshot-rename")

        # set up required variables to rename a snapshot
        snapshot_obj.add_new_child("current-name",
                                   self.parameters['from_name'])
        snapshot_obj.add_new_child("new-name", self.parameters['snapshot'])
        snapshot_obj.add_new_child("volume", self.parameters['volume'])
        try:
            self.server.invoke_successfully(snapshot_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error renaming snapshot %s to %s: %s' %
                (self.parameters['from_name'], self.parameters['snapshot'],
                 to_native(error)),
                exception=traceback.format_exc())

    def apply(self):
        """
        Check to see which play we should run
        """
        current = self.get_snapshot()
        netapp_utils.ems_log_event("na_ontap_snapshot", self.server)
        rename, cd_action = None, None
        modify = {}
        if self.parameters.get('from_name'):
            current_old_name = self.get_snapshot(self.parameters['from_name'])
            rename = self.na_helper.is_rename_action(current_old_name, current)
            modify = self.na_helper.get_modified_attributes(
                current_old_name, self.parameters)
        else:
            cd_action = self.na_helper.get_cd_action(current, self.parameters)
            if cd_action is None:
                modify = self.na_helper.get_modified_attributes(
                    current, self.parameters)
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if rename:
                    self.rename_snapshot()
                if cd_action == 'create':
                    self.create_snapshot()
                elif cd_action == 'delete':
                    self.delete_snapshot()
                elif modify:
                    self.modify_snapshot()
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 10
0
class NetAppOntapAggregate(object):
    ''' object initialize and class methods '''
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(name=dict(required=True, type='str'),
                 disks=dict(required=False, type='list'),
                 disk_count=dict(required=False, type='int', default=None),
                 disk_size=dict(required=False, type='int'),
                 disk_type=dict(required=False,
                                choices=[
                                    'ATA', 'BSAS', 'FCAL', 'FSAS', 'LUN',
                                    'MSATA', 'SAS', 'SSD', 'VMDISK'
                                ]),
                 from_name=dict(required=False, type='str'),
                 mirror_disks=dict(required=False, type='list'),
                 nodes=dict(required=False, type='list'),
                 is_mirrored=dict(required=False, type='bool'),
                 raid_size=dict(required=False, type='int'),
                 raid_type=dict(required=False,
                                choices=['raid4', 'raid_dp', 'raid_tec']),
                 service_state=dict(required=False,
                                    choices=['online', 'offline']),
                 spare_pool=dict(required=False, choices=['Pool0', 'Pool1']),
                 state=dict(required=False,
                            choices=['present', 'absent'],
                            default='present'),
                 unmount_volumes=dict(required=False, type='bool'),
                 wait_for_online=dict(required=False,
                                      type='bool',
                                      default=False),
                 time_out=dict(required=False, type='int', default=100),
                 object_store_name=dict(required=False, type='str'),
                 snaplock_type=dict(
                     required=False,
                     type='str',
                     choices=['compliance', 'enterprise', 'non_snaplock'])))

        self.module = AnsibleModule(argument_spec=self.argument_spec,
                                    required_if=[
                                        ('service_state', 'offline',
                                         ['unmount_volumes']),
                                    ],
                                    mutually_exclusive=[
                                        ('is_mirrored', 'disks'),
                                        ('is_mirrored', 'mirror_disks'),
                                        ('is_mirrored', 'spare_pool'),
                                        ('spare_pool', 'disks')
                                    ],
                                    supports_check_mode=True)

        self.na_helper = NetAppModule()
        self.using_vserver_msg = None  # This module should be run as cluster admin
        self.parameters = self.na_helper.set_parameters(self.module.params)
        if self.parameters.get(
                'mirror_disks'
        ) is not None and self.parameters.get('disks') is None:
            self.module.fail_json(
                mgs="mirror_disks require disks options to be set")
        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 aggr_get_iter(self, name):
        """
        Return aggr-get-iter query results
        :param name: Name of the aggregate
        :return: NaElement if aggregate found, None otherwise
        """

        aggr_get_iter = netapp_utils.zapi.NaElement('aggr-get-iter')
        query_details = netapp_utils.zapi.NaElement.create_node_with_children(
            'aggr-attributes', **{'aggregate-name': name})
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(query_details)
        aggr_get_iter.add_child_elem(query)
        result = None
        try:
            result = self.server.invoke_successfully(aggr_get_iter,
                                                     enable_tunneling=False)
        except netapp_utils.zapi.NaApiError as error:
            # Error 13040 denotes an aggregate not being found.
            if to_native(error.code) == "13040":
                pass
            else:
                msg = to_native(error)
                if self.using_vserver_msg is not None:
                    msg += '.  Added info: %s.' % self.using_vserver_msg
                self.module.fail_json(msg=msg,
                                      exception=traceback.format_exc())
        return result

    def get_aggr(self, name=None):
        """
        Fetch details if aggregate exists.
        :param name: Name of the aggregate to be fetched
        :return:
            Dictionary of current details if aggregate found
            None if aggregate is not found
        """
        if name is None:
            name = self.parameters['name']
        aggr_get = self.aggr_get_iter(name)
        if (aggr_get and aggr_get.get_child_by_name('num-records')
                and int(aggr_get.get_child_content('num-records')) >= 1):
            current_aggr = dict()
            attr = aggr_get.get_child_by_name(
                'attributes-list').get_child_by_name('aggr-attributes')
            current_aggr['service_state'] = attr.get_child_by_name(
                'aggr-raid-attributes').get_child_content('state')
            return current_aggr
        return None

    def object_store_get_iter(self):
        """
        Return aggr-object-store-get query results
        :return: NaElement if object-store for given aggregate found, None otherwise
        """

        object_store_get_iter = netapp_utils.zapi.NaElement(
            'aggr-object-store-get-iter')
        query_details = netapp_utils.zapi.NaElement.create_node_with_children(
            'object-store-information', **{
                'object-store-name': self.parameters.get('object_store_name'),
                'aggregate': self.parameters.get('name')
            })
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(query_details)
        object_store_get_iter.add_child_elem(query)
        result = None
        try:
            result = self.server.invoke_successfully(object_store_get_iter,
                                                     enable_tunneling=False)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg=to_native(error),
                                  exception=traceback.format_exc())
        return result

    def get_object_store(self):
        """
        Fetch details if object store attached to the given aggregate exists.
        :return:
            Dictionary of current details if object store attached to the given aggregate is found
            None if object store is not found
        """
        object_store_get = self.object_store_get_iter()
        if (object_store_get
                and object_store_get.get_child_by_name('num-records') and
                int(object_store_get.get_child_content('num-records')) >= 1):
            current_object_store = dict()
            attr = object_store_get.get_child_by_name('attributes-list').\
                get_child_by_name('object-store-information')
            current_object_store['object_store_name'] = attr.get_child_content(
                'object-store-name')
            return current_object_store
        return None

    def aggregate_online(self):
        """
        Set state of an offline aggregate to online
        :return: None
        """
        online_aggr = netapp_utils.zapi.NaElement.create_node_with_children(
            'aggr-online', **{
                'aggregate': self.parameters['name'],
                'force-online': 'true'
            })
        try:
            self.server.invoke_successfully(online_aggr, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error changing the state of aggregate %s to %s: %s' %
                (self.parameters['name'], self.parameters['service_state'],
                 to_native(error)),
                exception=traceback.format_exc())

    def aggregate_offline(self):
        """
        Set state of an online aggregate to offline
        :return: None
        """
        offline_aggr = netapp_utils.zapi.NaElement.create_node_with_children(
            'aggr-offline', **{
                'aggregate': self.parameters['name'],
                'force-offline': 'false',
                'unmount-volumes': str(self.parameters['unmount_volumes'])
            })
        try:
            self.server.invoke_successfully(offline_aggr,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error changing the state of aggregate %s to %s: %s' %
                (self.parameters['name'], self.parameters['service_state'],
                 to_native(error)),
                exception=traceback.format_exc())

    def create_aggr(self):
        """
        Create aggregate
        :return: None
        """
        if not self.parameters.get('disk_count'):
            self.module.fail_json(msg='Error provisioning aggregate %s: \
                                             disk_count is required' %
                                  self.parameters['name'])
        options = {
            'aggregate': self.parameters['name'],
            'disk-count': str(self.parameters['disk_count'])
        }
        if self.parameters.get('disk_type'):
            options['disk-type'] = self.parameters['disk_type']
        if self.parameters.get('raid_size'):
            options['raid-size'] = str(self.parameters['raid_size'])
        if self.parameters.get('raid_type'):
            options['raid-type'] = self.parameters['raid_type']
        if self.parameters.get('disk_size'):
            options['disk-size'] = str(self.parameters['disk_size'])
        if self.parameters.get('is_mirrored'):
            options['is-mirrored'] = str(self.parameters['is_mirrored'])
        if self.parameters.get('spare_pool'):
            options['spare-pool'] = self.parameters['spare_pool']
        if self.parameters.get('raid_type'):
            options['raid-type'] = self.parameters['raid_type']
        if self.parameters.get('snaplock_type'):
            options['snaplock-type'] = self.parameters['snaplock_type']
        aggr_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'aggr-create', **options)
        if self.parameters.get('nodes'):
            nodes_obj = netapp_utils.zapi.NaElement('nodes')
            aggr_create.add_child_elem(nodes_obj)
            for node in self.parameters['nodes']:
                nodes_obj.add_new_child('node-name', node)
        if self.parameters.get('disks'):
            disks_obj = netapp_utils.zapi.NaElement('disk-info')
            for disk in self.parameters.get('disks'):
                disks_obj.add_new_child('name', disk)
            aggr_create.add_child_elem(disks_obj)
        if self.parameters.get('mirror_disks'):
            mirror_disks_obj = netapp_utils.zapi.NaElement('disk-info')
            for disk in self.parameters.get('mirror_disks'):
                mirror_disks_obj.add_new_child('name', disk)
            aggr_create.add_child_elem(mirror_disks_obj)

        try:
            self.server.invoke_successfully(aggr_create,
                                            enable_tunneling=False)
            if self.parameters.get('wait_for_online'):
                # round off time_out
                retries = (self.parameters['time_out'] + 5) / 10
                current = self.get_aggr()
                status = None if current is None else current['service_state']
                while status != 'online' and retries > 0:
                    time.sleep(10)
                    retries = retries - 1
                    current = self.get_aggr()
                    status = None if current is None else current[
                        'service_state']
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg="Error provisioning aggregate %s: %s" %
                                  (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())

    def delete_aggr(self):
        """
        Delete aggregate.
        :return: None
        """
        aggr_destroy = netapp_utils.zapi.NaElement.create_node_with_children(
            'aggr-destroy', **{'aggregate': self.parameters['name']})

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

    def rename_aggregate(self):
        """
        Rename aggregate.
        """
        aggr_rename = netapp_utils.zapi.NaElement.create_node_with_children(
            'aggr-rename', **{
                'aggregate': self.parameters['from_name'],
                'new-aggregate-name': self.parameters['name']
            })

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

    def modify_aggr(self, modify):
        """
        Modify state of the aggregate
        :param modify: dictionary of parameters to be modified
        :return: None
        """
        if modify['service_state'] == 'offline':
            self.aggregate_offline()
        elif modify['service_state'] == 'online':
            self.aggregate_online()

    def attach_object_store_to_aggr(self):
        """
        Attach object store to aggregate.
        :return: None
        """
        attach_object_store = netapp_utils.zapi.NaElement.create_node_with_children(
            'aggr-object-store-attach', **{
                'aggregate': self.parameters['name'],
                'object-store-name': self.parameters['object_store_name']
            })

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

    def asup_log_for_cserver(self, event_name):
        """
        Fetch admin vserver for the given cluster
        Create and Autosupport log event with the given module name
        :param event_name: Name of the event log
        :return: None
        """
        cserver = netapp_utils.get_cserver(self.server)
        if cserver is None:
            server = self.server
            self.using_vserver_msg = netapp_utils.ERROR_MSG['no_cserver']
            event_name += ':error_no_cserver'
        else:
            server = netapp_utils.setup_na_ontap_zapi(module=self.module,
                                                      vserver=cserver)
        netapp_utils.ems_log_event(event_name, server)

    def apply(self):
        """
        Apply action to the aggregate
        :return: None
        """
        self.asup_log_for_cserver("na_ontap_aggregate")
        object_store_cd_action = None
        current = self.get_aggr()
        # rename and create are mutually exclusive
        rename, cd_action, object_store_current = None, None, None
        if self.parameters.get('from_name'):
            rename = self.na_helper.is_rename_action(
                self.get_aggr(self.parameters['from_name']), current)
            if rename is None:
                self.module.fail_json(
                    msg="Error renaming: aggregate %s does not exist" %
                    self.parameters['from_name'])
        else:
            cd_action = self.na_helper.get_cd_action(current, self.parameters)
        modify = self.na_helper.get_modified_attributes(
            current, self.parameters)
        if self.parameters.get(
                'object_store_name') and cd_action is None and rename is None:
            object_store_current = self.get_object_store()
            object_store_cd_action = self.na_helper.get_cd_action(
                object_store_current, self.parameters.get('object_store_name'))

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if rename:
                    self.rename_aggregate()
                elif cd_action == 'create':
                    self.create_aggr()
                elif cd_action == 'delete':
                    self.delete_aggr()
                else:
                    if modify:
                        self.modify_aggr(modify)
                    if object_store_cd_action is not None:
                        self.attach_object_store_to_aggr()
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 11
0
class NetAppOntapNetRoutes(object):
    """
    Create, Modifies and Destroys a Net Route
    """
    def __init__(self):
        """
        Initialize the Ontap Net Route 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'),
                destination=dict(required=True, type='str'),
                gateway=dict(required=True, type='str'),
                metric=dict(required=False, type='str'),
                from_destination=dict(required=False, type='str',
                                      default=None),
                from_gateway=dict(required=False, type='str', default=None),
                from_metric=dict(required=False, type='str', default=None),
            ))

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

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

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

    def create_net_route(self, current_metric=None):
        """
        Creates a new Route
        """
        route_obj = netapp_utils.zapi.NaElement('net-routes-create')
        route_obj.add_new_child("destination", self.parameters['destination'])
        route_obj.add_new_child("gateway", self.parameters['gateway'])
        if current_metric is None and self.parameters.get(
                'metric') is not None:
            metric = self.parameters['metric']
        else:
            metric = current_metric
        # Metric can be None, Can't set metric to none
        if metric is not None:
            route_obj.add_new_child("metric", metric)
        try:
            self.server.invoke_successfully(route_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error creating net route: %s' %
                                  (to_native(error)),
                                  exception=traceback.format_exc())

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

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

    def get_net_route(self, params=None):
        """
        Checks to see if a route exist or not
        :return: NaElement object if a route exists, None otherwise
        """
        if params is not None:
            # we need at least on of the new_destination or new_gateway to fetch desired route
            if params.get('destination') is None and params.get(
                    'gateway') is None:
                return None
        current = None
        route_obj = netapp_utils.zapi.NaElement('net-routes-get')
        for attr in ['destination', 'gateway']:
            if params and params.get(attr) is not None:
                value = params[attr]
            else:
                value = self.parameters[attr]
            route_obj.add_new_child(attr, value)
        try:
            result = self.server.invoke_successfully(route_obj, True)
            if result.get_child_by_name('attributes') is not None:
                route_info = result.get_child_by_name(
                    'attributes').get_child_by_name('net-vs-routes-info')
                current = {
                    'destination': route_info.get_child_content('destination'),
                    'gateway': route_info.get_child_content('gateway'),
                    'metric': route_info.get_child_content('metric')
                }

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

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

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

    def apply(self):
        """
        Run Module based on play book
        """
        netapp_utils.ems_log_event("na_ontap_net_routes", self.server)
        current = self.get_net_route()
        modify, cd_action = None, None
        modify_params = {
            'destination': self.parameters.get('from_destination'),
            'gateway': self.parameters.get('from_gateway'),
            'metric': self.parameters.get('from_metric')
        }
        # if any from_* param is present in playbook, check for modify action
        if any(modify_params.values()):
            # destination and gateway combination is unique, and is considered like a id. so modify destination
            # or gateway is considered a rename action. metric is considered an attribute of the route so it is
            # considered as modify.
            if modify_params.get('metric') is not None:
                modify = True
                old_params = current
            else:
                # get parameters that are eligible for modify
                old_params = self.get_net_route(modify_params)
                modify = self.na_helper.is_rename_action(old_params, current)
            if modify is None:
                self.module.fail_json(
                    msg="Error modifying: route %s does not exist" %
                    self.parameters['from_destination'])
        else:
            cd_action = self.na_helper.get_cd_action(current, self.parameters)

        if cd_action == 'create':
            self.create_net_route()
        elif cd_action == 'delete':
            self.delete_net_route()
        elif modify:
            desired = {}
            for key, value in old_params.items():
                desired[key] = value
            for key, value in modify_params.items():
                if value is not None:
                    desired[key] = self.parameters.get(key)
            self.modify_net_route(old_params, desired)
        self.module.exit_json(changed=self.na_helper.changed)
class NetAppOntapBroadcastDomain(object):
    """
        Create, Modifies and Destroys a Broadcast domain
    """
    def __init__(self):
        """
            Initialize the ONTAP Broadcast Domain 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',
                          aliases=["broadcast_domain"]),
                ipspace=dict(required=False, type='str'),
                mtu=dict(required=False, type='str'),
                ports=dict(required=False, type='list', elements='str'),
                from_name=dict(required=False, type='str'),
            ))

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

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

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

    def get_broadcast_domain(self, broadcast_domain=None):
        """
        Return details about the broadcast domain
        :param broadcast_domain: specific broadcast domain to get.
        :return: Details about the broadcast domain. None if not found.
        :rtype: dict
        """
        if broadcast_domain is None:
            broadcast_domain = self.parameters['name']
        domain_get_iter = netapp_utils.zapi.NaElement(
            'net-port-broadcast-domain-get-iter')
        broadcast_domain_info = netapp_utils.zapi.NaElement(
            'net-port-broadcast-domain-info')
        broadcast_domain_info.add_new_child('broadcast-domain',
                                            broadcast_domain)
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(broadcast_domain_info)
        domain_get_iter.add_child_elem(query)
        result = self.server.invoke_successfully(domain_get_iter, True)
        domain_exists = None
        # check if broadcast_domain exists
        if result.get_child_by_name('num-records') and \
                int(result.get_child_content('num-records')) == 1:
            domain_info = result.get_child_by_name('attributes-list').\
                get_child_by_name('net-port-broadcast-domain-info')
            domain_name = domain_info.get_child_content('broadcast-domain')
            domain_mtu = domain_info.get_child_content('mtu')
            domain_ipspace = domain_info.get_child_content('ipspace')
            domain_ports = domain_info.get_child_by_name('ports')
            if domain_ports is not None:
                ports = [
                    port.get_child_content('port')
                    for port in domain_ports.get_children()
                ]
            else:
                ports = []
            domain_exists = {
                'domain-name': domain_name,
                'mtu': domain_mtu,
                'ipspace': domain_ipspace,
                'ports': ports
            }
        return domain_exists

    def create_broadcast_domain(self):
        """
        Creates a new broadcast domain
        """
        domain_obj = netapp_utils.zapi.NaElement(
            'net-port-broadcast-domain-create')
        domain_obj.add_new_child("broadcast-domain", self.parameters['name'])
        if self.parameters.get('ipspace'):
            domain_obj.add_new_child("ipspace", self.parameters['ipspace'])
        if self.parameters.get('mtu'):
            domain_obj.add_new_child("mtu", self.parameters['mtu'])
        if self.parameters.get('ports'):
            ports_obj = netapp_utils.zapi.NaElement('ports')
            domain_obj.add_child_elem(ports_obj)
            for port in self.parameters['ports']:
                ports_obj.add_new_child('net-qualified-port-name', port)
        try:
            self.server.invoke_successfully(domain_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error creating broadcast domain %s: %s' %
                (self.parameters['name'], to_native(error)),
                exception=traceback.format_exc())

    def delete_broadcast_domain(self, broadcast_domain=None):
        """
        Deletes a broadcast domain
        """
        if broadcast_domain is None:
            broadcast_domain = self.parameters['name']
        domain_obj = netapp_utils.zapi.NaElement(
            'net-port-broadcast-domain-destroy')
        domain_obj.add_new_child("broadcast-domain", broadcast_domain)
        if self.parameters.get('ipspace'):
            domain_obj.add_new_child("ipspace", self.parameters['ipspace'])
        try:
            self.server.invoke_successfully(domain_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error deleting broadcast domain %s: %s' %
                (broadcast_domain, to_native(error)),
                exception=traceback.format_exc())

    def modify_broadcast_domain(self):
        """
        Modifies ipspace and mtu options of a broadcast domain
        """
        domain_obj = netapp_utils.zapi.NaElement(
            'net-port-broadcast-domain-modify')
        domain_obj.add_new_child("broadcast-domain", self.parameters['name'])
        if self.parameters.get('mtu'):
            domain_obj.add_new_child("mtu", self.parameters['mtu'])
        if self.parameters.get('ipspace'):
            domain_obj.add_new_child("ipspace", self.parameters['ipspace'])
        try:
            self.server.invoke_successfully(domain_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error modifying broadcast domain %s: %s' %
                (self.parameters['name'], to_native(error)),
                exception=traceback.format_exc())

    def split_broadcast_domain(self):
        """
        split broadcast domain
        """
        domain_obj = netapp_utils.zapi.NaElement(
            'net-port-broadcast-domain-split')
        domain_obj.add_new_child("broadcast-domain",
                                 self.parameters['from_name'])
        domain_obj.add_new_child("new-broadcast-domain",
                                 self.parameters['name'])
        if self.parameters.get('ports'):
            ports_obj = netapp_utils.zapi.NaElement('ports')
            domain_obj.add_child_elem(ports_obj)
            for port in self.parameters['ports']:
                ports_obj.add_new_child('net-qualified-port-name', port)
        if self.parameters.get('ipspace'):
            domain_obj.add_new_child("ipspace", self.parameters['ipspace'])
        try:
            self.server.invoke_successfully(domain_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error splitting broadcast domain %s: %s' %
                (self.parameters['name'], to_native(error)),
                exception=traceback.format_exc())
        if len(self.get_broadcast_domain_ports(
                self.parameters['from_name'])) == 0:
            self.delete_broadcast_domain(self.parameters['from_name'])

    def modify_redirect(self, modify):
        """
        :param modify: modify attributes.
        """
        for attribute in modify.keys():
            if attribute == 'mtu':
                self.modify_broadcast_domain()
            if attribute == 'ports':
                self.modify_broadcast_domain_ports()

    def get_modify_attributes(self, current, split):
        """
        :param current: current state.
        :param split: True or False of split action.
        :return: list of modified attributes.
        """
        modify = None
        if self.parameters['state'] == 'present':
            # split already handled ipspace and ports.
            if self.parameters.get('from_name'):
                current = self.get_broadcast_domain(
                    self.parameters['from_name'])
                if split:
                    modify = self.na_helper.get_modified_attributes(
                        current, self.parameters)
                    if modify.get('ipspace'):
                        del modify['ipspace']
                    if modify.get('ports'):
                        del modify['ports']
        # ipspace can not be modified.
            else:
                modify = self.na_helper.get_modified_attributes(
                    current, self.parameters)
                if modify.get('ipspace'):
                    self.module.fail_json(
                        msg=
                        'A domain ipspace can not be modified after the domain has been created.',
                        exception=traceback.format_exc())
        return modify

    def modify_broadcast_domain_ports(self):
        """
        compare current and desire ports. Call add or remove ports methods if needed.
        :return: None.
        """
        current_ports = self.get_broadcast_domain_ports()
        expect_ports = self.parameters['ports']
        # if want to remove all ports, simply delete the broadcast domain.
        if len(expect_ports) == 0:
            self.delete_broadcast_domain()
            return
        ports_to_remove = list(set(current_ports) - set(expect_ports))
        ports_to_add = list(set(expect_ports) - set(current_ports))

        if len(ports_to_add) > 0:
            self.add_broadcast_domain_ports(ports_to_add)

        if len(ports_to_remove) > 0:
            self.delete_broadcast_domain_ports(ports_to_remove)

    def add_broadcast_domain_ports(self, ports):
        """
        Creates new broadcast domain ports
        """
        domain_obj = netapp_utils.zapi.NaElement(
            'net-port-broadcast-domain-add-ports')
        domain_obj.add_new_child("broadcast-domain", self.parameters['name'])
        if self.parameters.get('ipspace'):
            domain_obj.add_new_child("ipspace", self.parameters['ipspace'])
        if ports:
            ports_obj = netapp_utils.zapi.NaElement('ports')
            domain_obj.add_child_elem(ports_obj)
            for port in ports:
                ports_obj.add_new_child('net-qualified-port-name', port)
        try:
            self.server.invoke_successfully(domain_obj, True)
            return True
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error creating port for broadcast domain %s: %s' %
                (self.parameters['name'], to_native(error)),
                exception=traceback.format_exc())

    def delete_broadcast_domain_ports(self, ports):
        """
        Deletes broadcast domain ports
        :param: ports to be deleted.
        """
        domain_obj = netapp_utils.zapi.NaElement(
            'net-port-broadcast-domain-remove-ports')
        domain_obj.add_new_child("broadcast-domain", self.parameters['name'])
        if self.parameters.get('ipspace'):
            domain_obj.add_new_child("ipspace", self.parameters['ipspace'])
        if ports:
            ports_obj = netapp_utils.zapi.NaElement('ports')
            domain_obj.add_child_elem(ports_obj)
            for port in ports:
                ports_obj.add_new_child('net-qualified-port-name', port)
        try:
            self.server.invoke_successfully(domain_obj, True)
            return True
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error deleting port for broadcast domain %s: %s' %
                (self.parameters['name'], to_native(error)),
                exception=traceback.format_exc())

    def get_broadcast_domain_ports(self, broadcast_domain=None):
        """
        Return details about the broadcast domain ports.
        :return: Details about the broadcast domain ports. None if not found.
        :rtype: list
        """
        if broadcast_domain is None:
            broadcast_domain = self.parameters['name']
        domain_get_iter = netapp_utils.zapi.NaElement(
            'net-port-broadcast-domain-get-iter')
        broadcast_domain_info = netapp_utils.zapi.NaElement(
            'net-port-broadcast-domain-info')
        broadcast_domain_info.add_new_child('broadcast-domain',
                                            broadcast_domain)
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(broadcast_domain_info)
        domain_get_iter.add_child_elem(query)
        result = self.server.invoke_successfully(domain_get_iter, True)
        ports = []
        if result.get_child_by_name('num-records') and \
                int(result.get_child_content('num-records')) == 1:
            domain_info = result.get_child_by_name(
                'attributes-list').get_child_by_name(
                    'net-port-broadcast-domain-info')
            domain_ports = domain_info.get_child_by_name('ports')
            if domain_ports is not None:
                ports = [
                    port.get_child_content('port')
                    for port in domain_ports.get_children()
                ]
        return ports

    def apply(self):
        """
        Run Module based on play book
        """
        self.asup_log_for_cserver("na_ontap_broadcast_domain")
        current = self.get_broadcast_domain()
        cd_action, split = None, None
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if cd_action == 'create':
            # either create new domain or split domain.
            if self.parameters.get('from_name'):
                split = self.na_helper.is_rename_action(
                    self.get_broadcast_domain(self.parameters['from_name']),
                    current)
                if split is None:
                    self.module.fail_json(
                        msg='A domain can not be split if it does not exist.',
                        exception=traceback.format_exc())
                if split:
                    cd_action = None
        modify = self.get_modify_attributes(current, split)
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if split:
                    self.split_broadcast_domain()
                if cd_action == 'create':
                    self.create_broadcast_domain()
                elif cd_action == 'delete':
                    self.delete_broadcast_domain()
                elif modify:
                    self.modify_redirect(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.º 13
0
class NetAppOntapSVM(object):
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(required=False,
                            choices=['present', 'absent'],
                            default='present'),
                 name=dict(required=True, type='str'),
                 from_name=dict(required=False, type='str'),
                 root_volume=dict(type='str'),
                 root_volume_aggregate=dict(type='str'),
                 root_volume_security_style=dict(
                     type='str', choices=['unix', 'ntfs', 'mixed', 'unified']),
                 allowed_protocols=dict(type='list'),
                 aggr_list=dict(type='list'),
                 ipspace=dict(type='str', required=False),
                 snapshot_policy=dict(type='str', required=False),
                 language=dict(type='str', required=False),
                 subtype=dict(choices=[
                     'default', 'dp_destination', 'sync_source',
                     'sync_destination'
                 ]),
                 comment=dict(type="str", required=False)))

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

        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_vserver(self, vserver_name=None):
        """
        Checks if vserver exists.

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

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

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

        result = self.server.invoke_successfully(vserver_info,
                                                 enable_tunneling=False)
        vserver_details = None
        if (result.get_child_by_name('num-records')
                and int(result.get_child_content('num-records')) >= 1):
            attributes_list = result.get_child_by_name('attributes-list')
            vserver_info = attributes_list.get_child_by_name('vserver-info')
            aggr_list = list()
            ''' vserver aggr-list can be empty by default'''
            get_list = vserver_info.get_child_by_name('aggr-list')
            if get_list is not None:
                aggregates = get_list.get_children()
                for aggr in aggregates:
                    aggr_list.append(aggr.get_content())

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

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

    def delete_vserver(self):
        vserver_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'vserver-destroy', **{'vserver-name': self.parameters['name']})

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

    def rename_vserver(self):
        vserver_rename = netapp_utils.zapi.NaElement.create_node_with_children(
            'vserver-rename', **{
                'vserver-name': self.parameters['from_name'],
                'new-name': self.parameters['name']
            })

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

    def modify_vserver(self, modify):
        '''
        Modify vserver.
        :param modify: list of modify attributes
        '''
        vserver_modify = netapp_utils.zapi.NaElement('vserver-modify')
        vserver_modify.add_new_child('vserver-name', self.parameters['name'])
        for attribute in modify:
            if attribute == 'language':
                vserver_modify.add_new_child('language',
                                             self.parameters['language'])
            if attribute == 'snapshot_policy':
                vserver_modify.add_new_child(
                    'snapshot_policy', self.parameters['snapshot_policy'])
            if attribute == 'comment':
                vserver_modify.add_new_child('comment',
                                             self.parameters['comment'])
            if attribute == 'allowed_protocols':
                allowed_protocols = netapp_utils.zapi.NaElement(
                    'allowed-protocols')
                for protocol in self.parameters['allowed_protocols']:
                    allowed_protocols.add_new_child('protocol', protocol)
                vserver_modify.add_child_elem(allowed_protocols)
            if attribute == 'aggr_list':
                aggregates = netapp_utils.zapi.NaElement('aggr-list')
                for aggr in self.parameters['aggr_list']:
                    aggregates.add_new_child('aggr-name', aggr)
                vserver_modify.add_child_elem(aggregates)
        try:
            self.server.invoke_successfully(vserver_modify,
                                            enable_tunneling=False)
        except netapp_utils.zapi.NaApiError as e:
            self.module.fail_json(msg='Error modifying SVM %s: %s' %
                                  (self.parameters['name'], to_native(e)),
                                  exception=traceback.format_exc())

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

    def apply(self):
        '''Call create/modify/delete operations.'''
        self.asup_log_for_cserver("na_ontap_svm")
        current = self.get_vserver()
        cd_action, rename = None, None
        if self.parameters.get('from_name'):
            rename = self.na_helper.is_rename_action(
                self.get_vserver(self.parameters['from_name']), current)
        else:
            cd_action = self.na_helper.get_cd_action(current, self.parameters)
        modify = self.na_helper.get_modified_attributes(
            current, self.parameters)
        for attribute in modify:
            if attribute in [
                    'root_volume', 'root_volume_aggregate',
                    'root_volume_security_style', 'subtype', 'ipspace'
            ]:
                self.module.fail_json(
                    msg='Error modifying SVM %s: can not modify %s.' %
                    (self.parameters['name'], attribute))
            if attribute == 'language':
                # Ontap documentation uses C.UTF-8, but actually stores as c.utf_8.
                if self.parameters['language'].lower() == 'c.utf-8':
                    self.parameters['language'] = 'c.utf_8'
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if rename:
                    self.rename_vserver()
                # If rename is True, cd_action is None, but modify could be true or false.
                if cd_action == 'create':
                    self.create_vserver()
                elif cd_action == 'delete':
                    self.delete_vserver()
                elif modify:
                    self.modify_vserver(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.º 14
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)
Exemplo n.º 15
0
class NetAppOntapAdaptiveQosPolicyGroup(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'),
            absolute_min_iops=dict(required=False, type='str'),
            expected_iops=dict(required=False, type='str'),
            peak_iops=dict(required=False, type='str'),
            peak_iops_allocation=dict(choices=['allocated_space', 'used_space'], default='used_space'),
            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-adaptive-policy-group-get-iter')
        policy_group_info = netapp_utils.zapi.NaElement('qos-adaptive-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-adaptive-policy-group-info')

            policy_group_detail = {
                'name': policy_info.get_child_content('policy-group'),
                'vserver': policy_info.get_child_content('vserver'),
                'absolute_min_iops': policy_info.get_child_content('absolute-min-iops'),
                'expected_iops': policy_info.get_child_content('expected-iops'),
                'peak_iops': policy_info.get_child_content('peak-iops'),
                'peak_iops_allocation': policy_info.get_child_content('peak-iops-allocation')
            }
        return policy_group_detail

    def create_policy_group(self):
        """
        create a policy group name.
        """
        policy_group = netapp_utils.zapi.NaElement('qos-adaptive-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('absolute_min_iops'):
            policy_group.add_new_child('absolute-min-iops', self.parameters['absolute_min_iops'])
        if self.parameters.get('expected_iops'):
            policy_group.add_new_child('expected-iops', self.parameters['expected_iops'])
        if self.parameters.get('peak_iops'):
            policy_group.add_new_child('peak-iops', self.parameters['peak_iops'])
        if self.parameters.get('peak_iops_allocation'):
            policy_group.add_new_child('peak-iops-allocation', self.parameters['peak_iops_allocation'])
        try:
            self.server.invoke_successfully(policy_group, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error creating adaptive 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-adaptive-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 adaptive 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-adaptive-policy-group-modify')
        policy_group_obj.add_new_child('policy-group', self.parameters['name'])
        if self.parameters.get('absolute_min_iops'):
            policy_group_obj.add_new_child('absolute-min-iops', self.parameters['absolute_min_iops'])
        if self.parameters.get('expected_iops'):
            policy_group_obj.add_new_child('expected-iops', self.parameters['expected_iops'])
        if self.parameters.get('peak_iops'):
            policy_group_obj.add_new_child('peak-iops', self.parameters['peak_iops'])
        if self.parameters.get('peak_iops_allocation'):
            policy_group_obj.add_new_child('peak-iops-allocation', self.parameters['peak_iops_allocation'])
        try:
            self.server.invoke_successfully(policy_group_obj, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error modifying adaptive 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-adaptive-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 adaptive 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.
        """
        for attribute in modify.keys():
            if attribute in ['absolute_min_iops', 'expected_iops', 'peak_iops', 'peak_iops_allocation']:
                self.modify_policy_group()

    def apply(self):
        """
        Run module based on playbook
        """
        self.autosupport_log("na_ontap_qos_policy_group")
        current = self.get_policy_group()
        rename, cd_action = None, None
        if self.parameters.get('from_name'):
            rename = self.na_helper.is_rename_action(self.get_policy_group(self.parameters['from_name']), current)
        else:
            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 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 autosupport_log(self, event_name):
        """
        Create a log event against the provided vserver
        """
        server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
        netapp_utils.ems_log_event(event_name, server)
class NetAppOntapLUN(object):
    ''' create, modify, delete LUN '''
    def __init__(self):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return True

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        if app_modify_warning:
            self.warnings.append(app_modify_warning)
        results['changed'] = self.na_helper.changed
        results['actions'] = actions
        if self.warnings:
            results['warnings'] = self.warnings
        results.update(self.debug)
        self.module.exit_json(**results)
class NetAppOntapAggregate(object):
    ''' object initialize and class methods '''

    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            name=dict(required=True, type='str'),
            disks=dict(required=False, type='list', elements='str'),
            disk_count=dict(required=False, type='int', default=None),
            disk_size=dict(required=False, type='int'),
            disk_size_with_unit=dict(required=False, type='str'),
            disk_type=dict(required=False, choices=['ATA', 'BSAS', 'FCAL', 'FSAS', 'LUN', 'MSATA', 'SAS', 'SSD', 'VMDISK']),
            from_name=dict(required=False, type='str'),
            mirror_disks=dict(required=False, type='list', elements='str'),
            nodes=dict(required=False, type='list', elements='str'),
            is_mirrored=dict(required=False, type='bool'),
            raid_size=dict(required=False, type='int'),
            raid_type=dict(required=False, choices=['raid4', 'raid_dp', 'raid_tec', 'raid_0']),
            service_state=dict(required=False, choices=['online', 'offline']),
            spare_pool=dict(required=False, choices=['Pool0', 'Pool1']),
            state=dict(required=False, choices=['present', 'absent'], default='present'),
            unmount_volumes=dict(required=False, type='bool'),
            wait_for_online=dict(required=False, type='bool', default=False),
            time_out=dict(required=False, type='int', default=100),
            object_store_name=dict(required=False, type='str'),
            snaplock_type=dict(required=False, type='str', choices=['compliance', 'enterprise', 'non_snaplock']),
            ignore_pool_checks=dict(required=False, type='bool')
        ))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            required_if=[
                ('service_state', 'offline', ['unmount_volumes']),
            ],
            mutually_exclusive=[
                ('is_mirrored', 'disks'),
                ('is_mirrored', 'mirror_disks'),
                ('is_mirrored', 'spare_pool'),
                ('spare_pool', 'disks'),
                ('disk_count', 'disks'),
                ('disk_size', 'disk_size_with_unit')
            ],
            supports_check_mode=True
        )

        self.na_helper = NetAppModule()
        self.using_vserver_msg = None   # This module should be run as cluster admin
        self.parameters = self.na_helper.set_parameters(self.module.params)
        if self.parameters.get('mirror_disks') is not None and self.parameters.get('disks') is None:
            self.module.fail_json(msg="mirror_disks require disks options to be set")
        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 aggr_get_iter(self, name):
        """
        Return aggr-get-iter query results
        :param name: Name of the aggregate
        :return: NaElement if aggregate found, None otherwise
        """

        aggr_get_iter = netapp_utils.zapi.NaElement('aggr-get-iter')
        query_details = netapp_utils.zapi.NaElement.create_node_with_children(
            'aggr-attributes', **{'aggregate-name': name})
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(query_details)
        aggr_get_iter.add_child_elem(query)
        result = None
        try:
            result = self.server.invoke_successfully(aggr_get_iter, enable_tunneling=False)
        except netapp_utils.zapi.NaApiError as error:
            # Error 13040 denotes an aggregate not being found.
            if to_native(error.code) == "13040":
                pass
            else:
                msg = to_native(error)
                if self.using_vserver_msg is not None:
                    msg += '.  Added info: %s.' % self.using_vserver_msg
                self.module.fail_json(msg=msg, exception=traceback.format_exc())
        return result

    def get_aggr(self, name=None):
        """
        Fetch details if aggregate exists.
        :param name: Name of the aggregate to be fetched
        :return:
            Dictionary of current details if aggregate found
            None if aggregate is not found
        """
        if name is None:
            name = self.parameters['name']
        aggr_get = self.aggr_get_iter(name)
        if (aggr_get and aggr_get.get_child_by_name('num-records') and
                int(aggr_get.get_child_content('num-records')) >= 1):
            current_aggr = dict()
            attr = aggr_get.get_child_by_name('attributes-list').get_child_by_name('aggr-attributes')
            current_aggr['service_state'] = attr.get_child_by_name('aggr-raid-attributes').get_child_content('state')
            if attr.get_child_by_name('aggr-raid-attributes').get_child_content('disk-count'):
                current_aggr['disk_count'] = int(attr.get_child_by_name('aggr-raid-attributes').get_child_content('disk-count'))
            return current_aggr
        return None

    def disk_get_iter(self, name):
        """
        Return storage-disk-get-iter query results
        Filter disk list by aggregate name, and only reports disk-name and plex-name
        :param name: Name of the aggregate
        :return: NaElement
        """

        disk_get_iter = netapp_utils.zapi.NaElement('storage-disk-get-iter')
        query_details = {
            'query': {
                'storage-disk-info': {
                    'disk-raid-info': {
                        'disk-aggregate-info': {
                            'aggregate-name': name
                        }
                    }
                }
            }
        }
        disk_get_iter.translate_struct(query_details)
        attributes = {
            'desired-attributes': {
                'storage-disk-info': {
                    'disk-name': None,
                    'disk-raid-info': {
                        'disk_aggregate_info': {
                            'plex-name': None
                        }
                    }
                }
            }
        }
        disk_get_iter.translate_struct(attributes)

        result = None
        try:
            result = self.server.invoke_successfully(disk_get_iter, enable_tunneling=False)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg=to_native(error), exception=traceback.format_exc())
        return result

    def get_aggr_disks(self, name):
        """
        Fetch disks that are used for this aggregate.
        :param name: Name of the aggregate to be fetched
        :return:
            list of tuples (disk-name, plex-name)
            empty list if aggregate is not found
        """
        disks = list()
        aggr_get = self.disk_get_iter(name)
        if (aggr_get and aggr_get.get_child_by_name('num-records') and
                int(aggr_get.get_child_content('num-records')) >= 1):
            attr = aggr_get.get_child_by_name('attributes-list')
            disks = [(disk_info.get_child_content('disk-name'),
                      disk_info.get_child_by_name('disk-raid-info').get_child_by_name('disk-aggregate-info').get_child_content('plex-name'))
                     for disk_info in attr.get_children()]
        return disks

    def object_store_get_iter(self, name):
        """
        Return aggr-object-store-get query results
        :return: NaElement if object-store for given aggregate found, None otherwise
        """

        object_store_get_iter = netapp_utils.zapi.NaElement('aggr-object-store-get-iter')
        query_details = netapp_utils.zapi.NaElement.create_node_with_children(
            'object-store-information', **{'object-store-name': self.parameters.get('object_store_name'),
                                           'aggregate': name})
        query = netapp_utils.zapi.NaElement('query')
        query.add_child_elem(query_details)
        object_store_get_iter.add_child_elem(query)
        result = None
        try:
            result = self.server.invoke_successfully(object_store_get_iter, enable_tunneling=False)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg=to_native(error), exception=traceback.format_exc())
        return result

    def get_object_store(self, name):
        """
        Fetch details if object store attached to the given aggregate exists.
        :return:
            Dictionary of current details if object store attached to the given aggregate is found
            None if object store is not found
        """
        object_store_get = self.object_store_get_iter(name)
        if (object_store_get and object_store_get.get_child_by_name('num-records') and
                int(object_store_get.get_child_content('num-records')) >= 1):
            current_object_store = dict()
            attr = object_store_get.get_child_by_name('attributes-list').\
                get_child_by_name('object-store-information')
            current_object_store['object_store_name'] = attr.get_child_content('object-store-name')
            return current_object_store
        return None

    def aggregate_online(self):
        """
        Set state of an offline aggregate to online
        :return: None
        """
        online_aggr = netapp_utils.zapi.NaElement.create_node_with_children(
            'aggr-online', **{'aggregate': self.parameters['name'],
                              'force-online': 'true'})
        try:
            self.server.invoke_successfully(online_aggr,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error changing the state of aggregate %s to %s: %s' %
                                  (self.parameters['name'], self.parameters['service_state'], to_native(error)),
                                  exception=traceback.format_exc())

    def aggregate_offline(self):
        """
        Set state of an online aggregate to offline
        :return: None
        """
        offline_aggr = netapp_utils.zapi.NaElement.create_node_with_children(
            'aggr-offline', **{'aggregate': self.parameters['name'],
                               'force-offline': 'false',
                               'unmount-volumes': str(self.parameters['unmount_volumes'])})
        try:
            self.server.invoke_successfully(offline_aggr, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error changing the state of aggregate %s to %s: %s' %
                                  (self.parameters['name'], self.parameters['service_state'], to_native(error)),
                                  exception=traceback.format_exc())

    @staticmethod
    def get_disks_or_mirror_disks_object(name, disks):
        '''
        create ZAPI object for disks or mirror_disks
        '''
        disks_obj = netapp_utils.zapi.NaElement(name)
        for disk in disks:
            disk_info_obj = netapp_utils.zapi.NaElement('disk-info')
            disk_info_obj.add_new_child('name', disk)
            disks_obj.add_child_elem(disk_info_obj)
        return disks_obj

    def create_aggr(self):
        """
        Create aggregate
        :return: None
        """
        options = {'aggregate': self.parameters['name']}
        if self.parameters.get('disk_count'):
            options['disk-count'] = str(self.parameters['disk_count'])
        if self.parameters.get('disk_type'):
            options['disk-type'] = self.parameters['disk_type']
        if self.parameters.get('raid_size'):
            options['raid-size'] = str(self.parameters['raid_size'])
        if self.parameters.get('raid_type'):
            options['raid-type'] = self.parameters['raid_type']
        if self.parameters.get('disk_size'):
            options['disk-size'] = str(self.parameters['disk_size'])
        if self.parameters.get('disk_size_with_unit'):
            options['disk-size-with-unit'] = str(self.parameters['disk_size_with_unit'])
        if self.parameters.get('is_mirrored'):
            options['is-mirrored'] = str(self.parameters['is_mirrored'])
        if self.parameters.get('spare_pool'):
            options['spare-pool'] = self.parameters['spare_pool']
        if self.parameters.get('raid_type'):
            options['raid-type'] = self.parameters['raid_type']
        if self.parameters.get('snaplock_type'):
            options['snaplock-type'] = self.parameters['snaplock_type']
        if self.parameters.get('ignore_pool_checks'):
            options['ignore-pool-checks'] = str(self.parameters['ignore_pool_checks'])
        aggr_create = netapp_utils.zapi.NaElement.create_node_with_children('aggr-create', **options)
        if self.parameters.get('nodes'):
            nodes_obj = netapp_utils.zapi.NaElement('nodes')
            aggr_create.add_child_elem(nodes_obj)
            for node in self.parameters['nodes']:
                nodes_obj.add_new_child('node-name', node)
        if self.parameters.get('disks'):
            aggr_create.add_child_elem(self.get_disks_or_mirror_disks_object('disks', self.parameters.get('disks')))
        if self.parameters.get('mirror_disks'):
            aggr_create.add_child_elem(self.get_disks_or_mirror_disks_object('mirror-disks', self.parameters.get('mirror_disks')))

        try:
            self.server.invoke_successfully(aggr_create, enable_tunneling=False)
            if self.parameters.get('wait_for_online'):
                # round off time_out
                retries = (self.parameters['time_out'] + 5) / 10
                current = self.get_aggr()
                status = None if current is None else current['service_state']
                while status != 'online' and retries > 0:
                    time.sleep(10)
                    retries = retries - 1
                    current = self.get_aggr()
                    status = None if current is None else current['service_state']
            else:
                current = self.get_aggr()
            if current is not None and current.get('disk_count') != self.parameters.get('disk_count'):
                self.module.exit_json(changed=self.na_helper.changed,
                                      warnings="Aggregate created with mismatched disk_count: created %s not %s"
                                      % (current.get('disk_count'), self.parameters.get('disk_count')))
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg="Error provisioning aggregate %s: %s"
                                  % (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())

    def delete_aggr(self):
        """
        Delete aggregate.
        :return: None
        """
        aggr_destroy = netapp_utils.zapi.NaElement.create_node_with_children(
            'aggr-destroy', **{'aggregate': self.parameters['name']})

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

    def rename_aggregate(self):
        """
        Rename aggregate.
        """
        aggr_rename = netapp_utils.zapi.NaElement.create_node_with_children(
            'aggr-rename', **{'aggregate': self.parameters['from_name'],
                              'new-aggregate-name': self.parameters['name']})

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

    def modify_aggr(self, modify):
        """
        Modify state of the aggregate
        :param modify: dictionary of parameters to be modified
        :return: None
        """
        if modify.get('service_state') == 'offline':
            self.aggregate_offline()
        else:
            disk_size = 0
            disk_size_with_unit = None
            if modify.get('service_state') == 'online':
                self.aggregate_online()
            if modify.get('disk_size'):
                disk_size = modify.get('disk_size')
            if modify.get('disk_size_with_unit'):
                disk_size_with_unit = modify.get('disk_size_with_unit')
            if modify.get('disk_count'):
                self.add_disks(modify['disk_count'], disk_size=disk_size, disk_size_with_unit=disk_size_with_unit)
            if modify.get('disks_to_add') or modify.get('mirror_disks_to_add'):
                self.add_disks(0, modify.get('disks_to_add'), modify.get('mirror_disks_to_add'))

    def attach_object_store_to_aggr(self):
        """
        Attach object store to aggregate.
        :return: None
        """
        attach_object_store = netapp_utils.zapi.NaElement.create_node_with_children(
            'aggr-object-store-attach', **{'aggregate': self.parameters['name'],
                                           'object-store-name': self.parameters['object_store_name']})

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

    def add_disks(self, count=0, disks=None, mirror_disks=None, disk_size=0, disk_size_with_unit=None):
        """
        Add additional disks to aggregate.
        :return: None
        """
        options = {'aggregate': self.parameters['name']}
        if count:
            options['disk-count'] = str(count)
        if disks and self.parameters.get('ignore_pool_checks'):
            options['ignore-pool-checks'] = str(self.parameters['ignore_pool_checks'])
        if disk_size:
            options['disk-size'] = str(disk_size)
        if disk_size_with_unit:
            options['disk-size-with-unit'] = disk_size_with_unit
        aggr_add = netapp_utils.zapi.NaElement.create_node_with_children(
            'aggr-add', **options)
        if disks:
            aggr_add.add_child_elem(self.get_disks_or_mirror_disks_object('disks', disks))
        if mirror_disks:
            aggr_add.add_child_elem(self.get_disks_or_mirror_disks_object('mirror-disks', mirror_disks))

        try:
            self.server.invoke_successfully(aggr_add,
                                            enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error adding additional disks to aggregate %s: %s' %
                                  (self.parameters['name'], to_native(error)),
                                  exception=traceback.format_exc())

    def asup_log_for_cserver(self, event_name):
        """
        Fetch admin vserver for the given cluster
        Create and Autosupport log event with the given module name
        :param event_name: Name of the event log
        :return: None
        """
        cserver = netapp_utils.get_cserver(self.server)
        if cserver is None:
            server = self.server
            self.using_vserver_msg = netapp_utils.ERROR_MSG['no_cserver']
            event_name += ':error_no_cserver'
        else:
            server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=cserver)
        netapp_utils.ems_log_event(event_name, server)

    def map_plex_to_primary_and_mirror(self, plex_disks, disks, mirror_disks):
        '''
        we have N plexes, and disks, and maybe mirror_disks
        we're trying to find which plex is used for disks, and which one, if applicable, for mirror_disks
        :return: a tuple with the names of the two plexes (disks_plex, mirror_disks_plex)
        the second one can be None
        '''
        disks_plex = None
        mirror_disks_plex = None
        error = None
        for plex in plex_disks:
            common = set(plex_disks[plex]).intersection(set(disks))
            if common:
                if disks_plex is None:
                    disks_plex = plex
                else:
                    error = 'found overlapping plexes: %s and %s' % (disks_plex, plex)
            if mirror_disks is not None:
                common = set(plex_disks[plex]).intersection(set(mirror_disks))
                if common:
                    if mirror_disks_plex is None:
                        mirror_disks_plex = plex
                    else:
                        error = 'found overlapping mirror plexes: %s and %s' % (mirror_disks_plex, plex)
        if error is None:
            # make sure we found a match
            if disks_plex is None:
                error = 'cannot not match disks with current aggregate disks'
            if mirror_disks is not None and mirror_disks_plex is None:
                if error is not None:
                    error += ', and '
                error = 'cannot not match mirror_disks with current aggregate disks'
        if error:
            self.module.fail_json(msg="Error mapping disks for aggregate %s: %s.  Found: %s" %
                                  (self.parameters['name'], error, str(plex_disks)))
        return disks_plex, mirror_disks_plex

    def get_disks_to_add(self, aggr_name, disks, mirror_disks):
        '''
        Get list of disks used by the aggregate, as primary and mirror.
        Report error if:
          the plexes in use cannot be matched with user inputs (we expect some overlap)
          the user request requires some disks to be removed (not supported)
        : return: a tuple of two lists of disks: disks_to_add, mirror_disks_to_add
        '''
        # let's see if we need to add disks
        disks_in_use = self.get_aggr_disks(aggr_name)
        # we expect a list of tuples (disk_name, plex_name), if there is a mirror, we should have 2 plexes
        # let's get a list of disks for each plex
        plex_disks = dict()
        for disk_name, plex_name in disks_in_use:
            plex_disks.setdefault(plex_name, []).append(disk_name)
        # find who is who
        disks_plex, mirror_disks_plex = self.map_plex_to_primary_and_mirror(plex_disks, disks, mirror_disks)
        # Now that we know what is which, find what needs to be removed (error), and what needs to be added
        disks_to_remove = [disk for disk in plex_disks[disks_plex] if disk not in disks]
        if mirror_disks_plex:
            disks_to_remove.extend([disk for disk in plex_disks[mirror_disks_plex] if disk not in mirror_disks])
        if disks_to_remove:
            error = 'these disks cannot be removed: %s' % str(disks_to_remove)
            self.module.fail_json(msg="Error removing disks is not supported.  Aggregate %s: %s.  In use: %s" %
                                  (aggr_name, error, str(plex_disks)))
        # finally, what's to be added
        disks_to_add = [disk for disk in disks if disk not in plex_disks[disks_plex]]
        mirror_disks_to_add = list()
        if mirror_disks_plex:
            mirror_disks_to_add = [disk for disk in mirror_disks if disk not in plex_disks[mirror_disks_plex]]
        if mirror_disks_to_add and not disks_to_add:
            self.module.fail_json(msg="Error cannot add mirror disks %s without adding disks for aggregate %s.  In use: %s" %
                                  (str(mirror_disks_to_add), aggr_name, str(plex_disks)))
        if disks_to_add or mirror_disks_to_add:
            self.na_helper.changed = True

        return disks_to_add, mirror_disks_to_add

    def apply(self):
        """
        Apply action to the aggregate
        :return: None
        """
        self.asup_log_for_cserver("na_ontap_aggregate")
        object_store_cd_action = None
        aggr_name = self.parameters['name']
        current = self.get_aggr()
        # rename and create are mutually exclusive
        rename, cd_action, object_store_current = None, None, None
        if self.parameters.get('from_name'):
            old_aggr = self.get_aggr(self.parameters['from_name'])
            rename = self.na_helper.is_rename_action(old_aggr, current)
            if rename is None:
                self.module.fail_json(msg="Error renaming: aggregate %s does not exist" % self.parameters['from_name'])
            if rename:
                current = old_aggr
                aggr_name = self.parameters['from_name']
        else:
            cd_action = self.na_helper.get_cd_action(current, self.parameters)
        modify = self.na_helper.get_modified_attributes(current, self.parameters)

        if cd_action is None and self.parameters.get('disks') and current is not None:
            modify['disks_to_add'], modify['mirror_disks_to_add'] = \
                self.get_disks_to_add(aggr_name, self.parameters['disks'], self.parameters.get('mirror_disks'))

        if modify.get('disk_count'):
            if int(modify['disk_count']) < int(current['disk_count']):
                self.module.fail_json(msg="specified disk_count is less than current disk_count. Only adding_disk is allowed.")
            else:
                modify['disk_count'] = modify['disk_count'] - current['disk_count']

        if self.parameters.get('object_store_name'):
            object_store_current = None
            if current:
                object_store_current = self.get_object_store(aggr_name)
            object_store_cd_action = self.na_helper.get_cd_action(object_store_current, self.parameters.get('object_store_name'))
            if object_store_cd_action is None and object_store_current is not None\
                    and object_store_current['object_store_name'] != self.parameters.get('object_store_name'):
                self.module.fail_json(msg='Error: object store %s is already associated with aggregate %s.' %
                                      (object_store_current['object_store_name'], aggr_name))

        if self.na_helper.changed and not self.module.check_mode:
            if cd_action == 'create':
                self.create_aggr()
            elif cd_action == 'delete':
                self.delete_aggr()
            else:
                if rename:
                    self.rename_aggregate()
                if modify:
                    self.modify_aggr(modify)
            if object_store_cd_action == 'create':
                self.attach_object_store_to_aggr()
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 18
0
class NetAppONTAPExportPolicy(object):
    """
    Class with export policy methods
    """

    def __init__(self):
        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
            name=dict(required=True, type='str'),
            from_name=dict(required=False, type='str', default=None),
            vserver=dict(required=True, type='str')
        ))
        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True
        )

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

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

    def get_export_policy(self, name=None, uuid=None):
        """
        Return details about the export-policy
        :param:
            name : Name of the export-policy
        :return: Details about the export-policy. None if not found.
        :rtype: dict
        """
        if name is None:
            name = self.parameters['name']
        if self.use_rest:
            params = {'fields': 'name',
                      'name': name,
                      'svm.uuid': uuid}
            api = 'protocols/nfs/export-policies/'
            message, error = self.restApi.get(api, params)
            if error is not None:
                self.module.fail_json(msg="Error on fetching export policy: %s" % error)
            if message['num_records'] > 0:
                return {'policy-name': message['records'][0]['name']}
            else:
                return None

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

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

    def create_export_policy(self, uuid=None):
        """
        Creates an export policy
        """
        if self.use_rest:
            params = {'name': self.parameters['name'],
                      'svm.uuid': uuid}
            api = 'protocols/nfs/export-policies'
            message, error = self.restApi.post(api, params)
            if error is not None:
                self.module.fail_json(msg="Error on creating export policy: %s" % error)
        else:
            export_policy_create = netapp_utils.zapi.NaElement.create_node_with_children(
                'export-policy-create', **{'policy-name': self.parameters['name']})
            try:
                self.server.invoke_successfully(export_policy_create,
                                                enable_tunneling=True)
            except netapp_utils.zapi.NaApiError as error:
                self.module.fail_json(msg='Error on creating export-policy %s: %s'
                                      % (self.parameters['name'], to_native(error)),
                                      exception=traceback.format_exc())

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

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

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

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

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

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

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

        current = self.get_export_policy(uuid=uuid)

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

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if rename:
                    self.rename_export_policy(policy_id=policy_id)
                elif cd_action == 'create':
                    self.create_export_policy(uuid=uuid)
                elif cd_action == 'delete':
                    self.delete_export_policy(policy_id=policy_id)
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 19
0
class NetAppOntapQTree(object):
    '''Class with qtree operations'''

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

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

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

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

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

            return return_q

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

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

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

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

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

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

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

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

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

        if modify:
            self.na_helper.changed = True
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_qtree()
                elif cd_action == 'delete':
                    self.delete_qtree(current)
                else:
                    if rename:
                        self.rename_qtree(current)
                    if modify:
                        self.modify_qtree(current)
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 20
0
class NetAppOntapIpspace(object):
    '''Class with ipspace operations'''
    def __init__(self):
        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(
                state=dict(required=False,
                           type='str',
                           choices=['present', 'absent'],
                           default='present'),
                name=dict(required=True, type='str'),
                from_name=dict(required=False, type='str'),
            ))
        self.module = AnsibleModule(argument_spec=self.argument_spec,
                                    supports_check_mode=True)

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

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

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

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

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

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

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

    def apply(self):
        """
        Apply action to the ipspace
        :return: Nothing
        """
        current = self.get_ipspace()
        # rename and create are mutually exclusive
        rename, cd_action = None, None
        if self.parameters.get('from_name'):
            rename = self.na_helper.is_rename_action(
                self.get_ipspace(self.parameters['from_name']), current)
            if rename is None:
                self.module.fail_json(
                    msg="Error renaming: ipspace %s does not exist" %
                    self.parameters['from_name'])
        else:
            cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if rename:
                    self.rename_ipspace()
                elif cd_action == 'create':
                    self.create_ipspace()
                elif cd_action == 'delete':
                    self.delete_ipspace()
        self.module.exit_json(changed=self.na_helper.changed)
Exemplo n.º 21
0
class NetAppOntapSubnet(object):
    """
    Create, Modifies and Destroys a subnet
    """
    def __init__(self):
        """
        Initialize the ONTAP Subnet 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'),
                 name=dict(required=True, type='str'),
                 from_name=dict(required=False, type='str'),
                 broadcast_domain=dict(required=False, type='str'),
                 gateway=dict(required=False, type='str'),
                 ip_ranges=dict(required=False, type='list'),
                 ipspace=dict(required=False, type='str'),
                 subnet=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)
        return

    def get_subnet(self, name=None):
        """
        Return details about the subnet
        :param:
            name : Name of the subnet
        :return: Details about the subnet. None if not found.
        :rtype: dict
        """
        if name is None:
            name = self.parameters.get('name')

        subnet_iter = netapp_utils.zapi.NaElement('net-subnet-get-iter')
        subnet_info = netapp_utils.zapi.NaElement('net-subnet-info')
        subnet_info.add_new_child('subnet-name', name)

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

        subnet_iter.add_child_elem(query)

        result = self.server.invoke_successfully(subnet_iter, True)
        return_value = None
        # check if query returns the expected subnet
        if result.get_child_by_name('num-records') and \
                int(result.get_child_content('num-records')) == 1:

            subnet_attributes = result.get_child_by_name(
                'attributes-list').get_child_by_name('net-subnet-info')
            broadcast_domain = subnet_attributes.get_child_content(
                'broadcast-domain')
            gateway = subnet_attributes.get_child_content('gateway')
            ipspace = subnet_attributes.get_child_content('ipspace')
            subnet = subnet_attributes.get_child_content('subnet')
            name = subnet_attributes.get_child_content('subnet-name')

            ip_ranges = []
            if subnet_attributes.get_child_by_name('ip-ranges'):
                range_obj = subnet_attributes.get_child_by_name(
                    'ip-ranges').get_children()
                for elem in range_obj:
                    ip_ranges.append(elem.get_content())

            return_value = {
                'name': name,
                'broadcast_domain': broadcast_domain,
                'gateway': gateway,
                'ip_ranges': ip_ranges,
                'ipspace': ipspace,
                'subnet': subnet
            }

        return return_value

    def create_subnet(self):
        """
        Creates a new subnet
        """
        options = {
            'subnet-name': self.parameters.get('name'),
            'broadcast-domain': self.parameters.get('broadcast_domain'),
            'subnet': self.parameters.get('subnet')
        }
        subnet_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'net-subnet-create', **options)

        if self.parameters.get('gateway'):
            subnet_create.add_new_child('gateway',
                                        self.parameters.get('gateway'))
        if self.parameters.get('ip_ranges'):
            subnet_ips = netapp_utils.zapi.NaElement('ip-ranges')
            subnet_create.add_child_elem(subnet_ips)
            for ip_range in self.parameters.get('ip_ranges'):
                subnet_ips.add_new_child('ip-range', ip_range)
        if self.parameters.get('ipspace'):
            subnet_create.add_new_child('ipspace',
                                        self.parameters.get('ipspace'))

        try:
            self.server.invoke_successfully(subnet_create, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error creating subnet %s: %s' %
                (self.parameters.get('name'), to_native(error)),
                exception=traceback.format_exc())

    def delete_subnet(self):
        """
        Deletes a subnet
        """
        subnet_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'net-subnet-destroy',
            **{'subnet-name': self.parameters.get('name')})

        try:
            self.server.invoke_successfully(subnet_delete, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error deleting subnet %s: %s' %
                (self.parameters.get('name'), to_native(error)),
                exception=traceback.format_exc())

    def modify_subnet(self):
        """
        Modifies a subnet
        """
        options = {'subnet-name': self.parameters.get('name')}

        subnet_modify = netapp_utils.zapi.NaElement.create_node_with_children(
            'net-subnet-modify', **options)

        if self.parameters.get('gateway'):
            subnet_modify.add_new_child('gateway',
                                        self.parameters.get('gateway'))
        if self.parameters.get('ip_ranges'):
            subnet_ips = netapp_utils.zapi.NaElement('ip-ranges')
            subnet_modify.add_child_elem(subnet_ips)
            for ip_range in self.parameters.get('ip_ranges'):
                subnet_ips.add_new_child('ip-range', ip_range)
        if self.parameters.get('ipspace'):
            subnet_modify.add_new_child('ipspace',
                                        self.parameters.get('ipspace'))
        if self.parameters.get('subnet'):
            subnet_modify.add_new_child('subnet',
                                        self.parameters.get('subnet'))

        try:
            self.server.invoke_successfully(subnet_modify, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error modifying subnet %s: %s' %
                (self.parameters.get('name'), to_native(error)),
                exception=traceback.format_exc())

    def rename_subnet(self):
        """
        TODO
        """
        options = {
            'subnet-name': self.parameters.get('from_name'),
            'new-name': self.parameters.get('name')
        }

        subnet_rename = netapp_utils.zapi.NaElement.create_node_with_children(
            'net-subnet-rename', **options)

        if self.parameters.get('ipspace'):
            subnet_rename.add_new_child('ipspace',
                                        self.parameters.get('ipspace'))

        try:
            self.server.invoke_successfully(subnet_rename, True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(
                msg='Error renaming subnet %s: %s' %
                (self.parameters.get('name'), to_native(error)),
                exception=traceback.format_exc())

    def apply(self):
        '''Apply action to subnet'''
        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_net_subnet", cserver)
        current = self.get_subnet()
        cd_action, rename = None, None

        if self.parameters.get('from_name'):
            rename = self.na_helper.is_rename_action(
                self.get_subnet(self.parameters.get('from_name')), current)
            if rename is None:
                self.module.fail_json(
                    msg="Error renaming: subnet %s does not exist" %
                    self.parameters.get('from_name'))
        else:
            cd_action = self.na_helper.get_cd_action(current, self.parameters)
        modify = self.na_helper.get_modified_attributes(
            current, self.parameters)
        for attribute in modify:
            if attribute in ['broadcast_domain']:
                self.module.fail_json(
                    msg=
                    'Error modifying subnet %s: cannot modify broadcast_domain parameter.'
                    % self.parameters.get('name'))

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if rename:
                    self.rename_subnet()
                # If rename is True, cd_action is NOne but modify could be true
                if cd_action == 'create':
                    for attribute in ['subnet', 'broadcast_domain']:
                        if not self.parameters.get(attribute):
                            self.module.fail_json(
                                msg='Error - missing required arguments: %s.' %
                                attribute)
                    self.create_subnet()
                elif cd_action == 'delete':
                    self.delete_subnet()
                elif modify:
                    self.modify_subnet()
        self.module.exit_json(changed=self.na_helper.changed)