class ReadNodesForm(FilterNodeForm):

    id = UnconstrainedMultipleChoiceField(label="System IDs to filter on",
                                          required=False)

    hostname = UnconstrainedMultipleChoiceField(label="Hostnames to filter on",
                                                required=False)

    mac_address = ValidatorMultipleChoiceField(
        validator=mac_validator,
        label="MAC addresses to filter on",
        required=False,
        error_messages={
            "invalid_list": "Invalid parameter: invalid MAC address format"
        },
    )

    domain = UnconstrainedMultipleChoiceField(
        label="Domain names to filter on", required=False)

    agent_name = forms.CharField(
        label="Only include nodes with events matching the agent name",
        required=False,
    )

    status = forms.ChoiceField(
        label="Only includes nodes with the specified status",
        choices=NODE_STATUS_SHORT_LABEL_CHOICES,
        required=False,
    )

    def _apply_filters(self, nodes):
        nodes = super()._apply_filters(nodes)
        nodes = self.filter_by_ids(nodes)
        nodes = self.filter_by_hostnames(nodes)
        nodes = self.filter_by_mac_addresses(nodes)
        nodes = self.filter_by_domain(nodes)
        nodes = self.filter_by_agent_name(nodes)
        nodes = self.filter_by_status(nodes)
        return nodes

    def filter_by_ids(self, filtered_nodes):
        ids = self.cleaned_data.get(self.get_field_name("id"))
        if ids:
            filtered_nodes = filtered_nodes.filter(system_id__in=ids)
        return filtered_nodes

    def filter_by_hostnames(self, filtered_nodes):
        hostnames = self.cleaned_data.get(self.get_field_name("hostname"))
        if hostnames:
            filtered_nodes = filtered_nodes.filter(hostname__in=hostnames)
        return filtered_nodes

    def filter_by_mac_addresses(self, filtered_nodes):
        mac_addresses = self.cleaned_data.get(
            self.get_field_name("mac_address"))
        if mac_addresses:
            filtered_nodes = filtered_nodes.filter(
                interface__mac_address__in=mac_addresses)
        return filtered_nodes

    def filter_by_domain(self, filtered_nodes):
        domains = self.cleaned_data.get(self.get_field_name("domain"))
        if domains:
            filtered_nodes = filtered_nodes.filter(domain__name__in=domains)
        return filtered_nodes

    def filter_by_agent_name(self, filtered_nodes):
        field_name = self.get_field_name("agent_name")
        if field_name in self.data:
            agent_name = self.cleaned_data.get(field_name)
            filtered_nodes = filtered_nodes.filter(agent_name=agent_name)
        return filtered_nodes

    def filter_by_status(self, filtered_nodes):
        status = self.cleaned_data.get(self.get_field_name("status"))
        if status:
            status_id = getattr(NODE_STATUS, status.upper())
            filtered_nodes = filtered_nodes.filter(status=status_id)
        return filtered_nodes
class FilterNodeForm(RenamableFieldsForm):
    """A form for filtering nodes."""

    # This becomes a multiple-choice field during cleaning, to accommodate
    # architecture wildcards.
    arch = forms.CharField(label="Architecture", required=False)

    tags = UnconstrainedMultipleChoiceField(label="Tags", required=False)

    not_tags = UnconstrainedMultipleChoiceField(label="Not having tags",
                                                required=False)

    # XXX mpontillo 2015-10-30 need validators for fabric constraints
    fabrics = ValidatorMultipleChoiceField(
        validator=lambda x: True,
        label="Attached to fabrics",
        required=False,
        error_messages={
            "invalid_list": "Invalid parameter: list of fabrics required."
        },
    )

    not_fabrics = ValidatorMultipleChoiceField(
        validator=lambda x: True,
        label="Not attached to fabrics",
        required=False,
        error_messages={
            "invalid_list": "Invalid parameter: list of fabrics required."
        },
    )

    fabric_classes = ValidatorMultipleChoiceField(
        validator=lambda x: True,
        label="Attached to fabric with specified classes",
        required=False,
        error_messages={
            "invalid_list":
            "Invalid parameter: list of fabric classes required."
        },
    )

    not_fabric_classes = ValidatorMultipleChoiceField(
        validator=lambda x: True,
        label="Not attached to fabric with specified classes",
        required=False,
        error_messages={
            "invalid_list":
            "Invalid parameter: list of fabric classes required."
        },
    )

    subnets = ValidatorMultipleChoiceField(
        validator=Subnet.objects.validate_filter_specifiers,
        label="Attached to subnets",
        required=False,
        error_messages={
            "invalid_list":
            "Invalid parameter: list of subnet specifiers required."
        },
    )

    not_subnets = ValidatorMultipleChoiceField(
        validator=Subnet.objects.validate_filter_specifiers,
        label="Not attached to subnets",
        required=False,
        error_messages={
            "invalid_list":
            "Invalid parameter: list of subnet specifiers required."
        },
    )

    link_speed = forms.FloatField(
        label="Link speed",
        required=False,
        error_messages={"invalid": "Invalid link speed: number required."},
    )

    vlans = ValidatorMultipleChoiceField(
        validator=VLAN.objects.validate_filter_specifiers,
        label="Attached to VLANs",
        required=False,
        error_messages={
            "invalid_list":
            "Invalid parameter: list of VLAN specifiers required."
        },
    )

    not_vlans = ValidatorMultipleChoiceField(
        validator=VLAN.objects.validate_filter_specifiers,
        label="Not attached to VLANs",
        required=False,
        error_messages={
            "invalid_list":
            "Invalid parameter: list of VLAN specifiers required."
        },
    )

    connected_to = ValidatorMultipleChoiceField(
        validator=mac_validator,
        label="Connected to",
        required=False,
        error_messages={
            "invalid_list":
            "Invalid parameter: list of MAC addresses required."
        },
    )

    not_connected_to = ValidatorMultipleChoiceField(
        validator=mac_validator,
        label="Not connected to",
        required=False,
        error_messages={
            "invalid_list":
            "Invalid parameter: list of MAC addresses required."
        },
    )

    zone = forms.CharField(label="Physical zone", required=False)

    not_in_zone = ValidatorMultipleChoiceField(
        validator=MODEL_NAME_VALIDATOR,
        label="Not in zone",
        required=False,
        error_messages={
            "invalid_list": "Invalid parameter: must list physical zones."
        },
    )

    pool = forms.CharField(label="Resource pool", required=False)

    not_in_pool = ValidatorMultipleChoiceField(
        validator=MODEL_NAME_VALIDATOR,
        label="Not in resource pool",
        required=False,
        error_messages={
            "invalid_list": "Invalid parameter: must list resource pools."
        },
    )

    storage = forms.CharField(validators=[storage_validator],
                              label="Storage",
                              required=False)

    interfaces = LabeledConstraintMapField(validators=[interfaces_validator],
                                           label="Interfaces",
                                           required=False)

    cpu_count = forms.FloatField(
        label="CPU count",
        required=False,
        error_messages={"invalid": "Invalid CPU count: number required."},
    )

    mem = forms.FloatField(
        label="Memory",
        required=False,
        error_messages={"invalid": "Invalid memory: number of MiB required."},
    )

    pod = forms.CharField(label="The name of the desired pod", required=False)

    not_pod = forms.CharField(label="The name of the undesired pod",
                              required=False)

    pod_type = forms.CharField(label="The power_type of the desired pod",
                               required=False)

    not_pod_type = forms.CharField(label="The power_type of the undesired pod",
                                   required=False)

    ignore_unknown_constraints = False

    @classmethod
    def Strict(cls, *args, **kwargs):
        """A stricter version of the form which rejects unknown parameters."""
        form = cls(*args, **kwargs)
        form.ignore_unknown_constraints = False
        return form

    def clean_arch(self):
        """Turn `arch` parameter into a list of architecture names.

        Even though `arch` is a single-value field, it turns into a list
        during cleaning.  The architecture parameter may be a wildcard.
        """
        # Import list_all_usable_architectures as part of its module, not
        # directly, so that patch_usable_architectures can easily patch it
        # for testing purposes.
        usable_architectures = maasserver_forms.list_all_usable_architectures()
        architecture_wildcards = get_architecture_wildcards(
            usable_architectures)
        value = self.cleaned_data[self.get_field_name("arch")]
        if value:
            if value in usable_architectures:
                # Full 'arch/subarch' specified directly.
                return [value]
            elif value in architecture_wildcards:
                # Try to expand 'arch' to all available 'arch/subarch'
                # matches.
                return architecture_wildcards[value]
            set_form_error(
                self,
                self.get_field_name("arch"),
                "Architecture not recognised.",
            )
        return None

    def clean_tags(self):
        value = self.cleaned_data[self.get_field_name("tags")]
        if value:
            tag_names = parse_legacy_tags(value)
            # Validate tags.
            tag_names = set(tag_names)
            db_tag_names = set(
                Tag.objects.filter(name__in=tag_names).values_list("name",
                                                                   flat=True))
            if len(tag_names) != len(db_tag_names):
                unknown_tags = tag_names.difference(db_tag_names)
                error_msg = "No such tag(s): %s." % ", ".join(
                    "'%s'" % tag for tag in unknown_tags)
                set_form_error(self, self.get_field_name("tags"), error_msg)
                return None
            return tag_names
        return None

    def clean_zone(self):
        value = self.cleaned_data[self.get_field_name("zone")]
        if value:
            nonexistent_names = detect_nonexistent_names(Zone, [value])
            if nonexistent_names:
                error_msg = "No such zone: '%s'." % value
                set_form_error(self, self.get_field_name("zone"), error_msg)
                return None
            return value
        return None

    def clean_not_in_zone(self):
        value = self.cleaned_data[self.get_field_name("not_in_zone")]
        if not value:
            return None
        nonexistent_names = detect_nonexistent_names(Zone, value)
        if nonexistent_names:
            error_msg = "No such zone(s): %s." % ", ".join(nonexistent_names)
            set_form_error(self, self.get_field_name("not_in_zone"), error_msg)
            return None
        return value

    def clean_pool(self):
        value = self.cleaned_data[self.get_field_name("pool")]
        if value:
            nonexistent_names = detect_nonexistent_names(ResourcePool, [value])
            if nonexistent_names:
                error_msg = "No such pool: '%s'." % value
                set_form_error(self, self.get_field_name("pool"), error_msg)
                return None
            return value
        return None

    def clean_not_in_pool(self):
        value = self.cleaned_data[self.get_field_name("not_in_pool")]
        if not value:
            return None
        nonexistent_names = detect_nonexistent_names(ResourcePool, value)
        if nonexistent_names:
            error_msg = "No such pool(s): %s." % ", ".join(nonexistent_names)
            set_form_error(self, self.get_field_name("not_in_pool"), error_msg)
            return None
        return value

    def _clean_specifiers(self, model, specifiers):
        if not specifiers:
            return None
        objects = set(model.objects.filter_by_specifiers(specifiers))
        if len(objects) == 0:
            raise ValidationError("No matching %s found." %
                                  model._meta.verbose_name_plural)
        return objects

    def clean_subnets(self):
        value = self.cleaned_data[self.get_field_name("subnets")]
        return self._clean_specifiers(Subnet, value)

    def clean_not_subnets(self):
        value = self.cleaned_data[self.get_field_name("not_subnets")]
        return self._clean_specifiers(Subnet, value)

    def clean_vlans(self):
        value = self.cleaned_data[self.get_field_name("vlans")]
        return self._clean_specifiers(VLAN, value)

    def clean_not_vlans(self):
        value = self.cleaned_data[self.get_field_name("not_vlans")]
        return self._clean_specifiers(VLAN, value)

    def clean(self):
        if not self.ignore_unknown_constraints:
            unknown_constraints = set(self.data).difference(
                set(self.field_mapping.values()))
            for constraint in unknown_constraints:
                if constraint not in IGNORED_FIELDS:
                    msg = "No such constraint."
                    self._errors[constraint] = self.error_class([msg])
        return super().clean()

    def describe_constraint(self, field_name):
        """Return a human-readable representation of a constraint.

        Turns a constraint value as passed to the form into a Juju-like
        representation for display: `name=foo`.  Multi-valued constraints are
        shown as comma-separated values, e.g. `tags=do,re,mi`.

        :param field_name: Name of the constraint on this form, e.g. `zone`.
        :return: A constraint string, or `None` if the constraint is not set.
        """
        value = self.cleaned_data.get(field_name, None)
        if value is None:
            return None
        if isinstance(self.fields[field_name], MultipleChoiceField):
            output = describe_multi_constraint_value(value)
        elif field_name == "arch" and not isinstance(value, str):
            # The arch field is a special case.  It's defined as a string
            # field, but may become a list/tuple/... of strings in cleaning.
            output = describe_multi_constraint_value(value)
        else:
            output = describe_single_constraint_value(value)
        if output is None:
            return None
        else:
            return "%s=%s" % (field_name, output)

    def describe_constraints(self):
        """Return a human-readable representation of the given constraints.

        The description is Juju-like, e.g. `arch=amd64 cpu=16 zone=rack3`.
        Constraints are listed in alphabetical order.
        """
        constraints = (self.describe_constraint(name)
                       for name in sorted(self.fields.keys()))
        return " ".join(constraint for constraint in constraints
                        if constraint is not None)

    def filter_nodes(self, nodes):
        """Return the subset of nodes that match the form's constraints.

        :param nodes:  The set of nodes on which the form should apply
            constraints.
        :type nodes: `django.db.models.query.QuerySet`
        :return: A QuerySet of the nodes that match the form's constraints.
        :rtype: `django.db.models.query.QuerySet`
        """
        filtered_nodes = self._apply_filters(nodes)
        compatible_nodes, filtered_nodes = self.filter_by_storage(
            filtered_nodes)
        compatible_interfaces, filtered_nodes = self.filter_by_interfaces(
            filtered_nodes)
        return filtered_nodes, compatible_nodes, compatible_interfaces

    def _apply_filters(self, nodes):
        nodes = self.filter_by_arch(nodes)
        nodes = self.filter_by_tags(nodes)
        nodes = self.filter_by_zone(nodes)
        nodes = self.filter_by_pool(nodes)
        nodes = self.filter_by_subnets(nodes)
        nodes = self.filter_by_link_speed(nodes)
        nodes = self.filter_by_vlans(nodes)
        nodes = self.filter_by_fabrics(nodes)
        nodes = self.filter_by_fabric_classes(nodes)
        nodes = self.filter_by_cpu_count(nodes)
        nodes = self.filter_by_mem(nodes)
        nodes = self.filter_by_pod_or_pod_type(nodes)
        return nodes.distinct()

    def filter_by_interfaces(self, filtered_nodes):
        compatible_interfaces = {}
        interfaces_label_map = self.cleaned_data.get(
            self.get_field_name("interfaces"))
        if interfaces_label_map is not None:
            result = nodes_by_interface(interfaces_label_map)
            if result.node_ids is not None:
                filtered_nodes = filtered_nodes.filter(id__in=result.node_ids)
                compatible_interfaces = result.label_map
        return compatible_interfaces, filtered_nodes

    def filter_by_storage(self, filtered_nodes):
        compatible_nodes = {}  # Maps node/storage to named storage constraints
        storage = self.cleaned_data.get(self.get_field_name("storage"))
        if storage:
            compatible_nodes = nodes_by_storage(storage)
            node_ids = list(compatible_nodes)
            if node_ids is not None:
                filtered_nodes = filtered_nodes.filter(id__in=node_ids)
        return compatible_nodes, filtered_nodes

    def filter_by_fabric_classes(self, filtered_nodes):
        fabric_classes = self.cleaned_data.get(
            self.get_field_name("fabric_classes"))
        if fabric_classes is not None and len(fabric_classes) > 0:
            filtered_nodes = filtered_nodes.filter(
                interface__vlan__fabric__class_type__in=fabric_classes)
        not_fabric_classes = self.cleaned_data.get(
            self.get_field_name("not_fabric_classes"))
        if not_fabric_classes is not None and len(not_fabric_classes) > 0:
            filtered_nodes = filtered_nodes.exclude(
                interface__vlan__fabric__class_type__in=not_fabric_classes)
        return filtered_nodes

    def filter_by_fabrics(self, filtered_nodes):
        fabrics = self.cleaned_data.get(self.get_field_name("fabrics"))
        if fabrics is not None and len(fabrics) > 0:
            # XXX mpontillo 2015-10-30 need to also handle fabrics whose name
            # is null (fabric-<id>).
            filtered_nodes = filtered_nodes.filter(
                interface__vlan__fabric__name__in=fabrics)
        not_fabrics = self.cleaned_data.get(self.get_field_name("not_fabrics"))
        if not_fabrics is not None and len(not_fabrics) > 0:
            # XXX mpontillo 2015-10-30 need to also handle fabrics whose name
            # is null (fabric-<id>).
            filtered_nodes = filtered_nodes.exclude(
                interface__vlan__fabric__name__in=not_fabrics)
        return filtered_nodes

    def filter_by_vlans(self, filtered_nodes):
        vlans = self.cleaned_data.get(self.get_field_name("vlans"))
        if vlans is not None and len(vlans) > 0:
            for vlan in set(vlans):
                filtered_nodes = filtered_nodes.filter(interface__vlan=vlan)
        not_vlans = self.cleaned_data.get(self.get_field_name("not_vlans"))
        if not_vlans is not None and len(not_vlans) > 0:
            for not_vlan in set(not_vlans):
                filtered_nodes = filtered_nodes.exclude(
                    interface__vlan=not_vlan)
        return filtered_nodes

    def filter_by_subnets(self, filtered_nodes):
        subnets = self.cleaned_data.get(self.get_field_name("subnets"))
        if subnets is not None and len(subnets) > 0:
            for subnet in set(subnets):
                filtered_nodes = filtered_nodes.filter(
                    interface__ip_addresses__subnet=subnet)
        not_subnets = self.cleaned_data.get(self.get_field_name("not_subnets"))
        if not_subnets is not None and len(not_subnets) > 0:
            for not_subnet in set(not_subnets):
                filtered_nodes = filtered_nodes.exclude(
                    interface__ip_addresses__subnet=not_subnet)
        return filtered_nodes

    def filter_by_link_speed(self, filtered_nodes):
        link_speed = self.cleaned_data.get(self.get_field_name("link_speed"))
        if link_speed:
            filtered_nodes = filtered_nodes.filter(
                interface__link_speed__gte=link_speed)
        return filtered_nodes

    def filter_by_zone(self, filtered_nodes):
        zone = self.cleaned_data.get(self.get_field_name("zone"))
        if zone:
            zone_obj = Zone.objects.get(name=zone)
            filtered_nodes = filtered_nodes.filter(zone=zone_obj)
        not_in_zone = self.cleaned_data.get(self.get_field_name("not_in_zone"))
        if not_in_zone:
            not_in_zones = Zone.objects.filter(name__in=not_in_zone)
            filtered_nodes = filtered_nodes.exclude(zone__in=not_in_zones)
        return filtered_nodes

    def filter_by_pool(self, filtered_nodes):
        pool_name = self.cleaned_data.get(self.get_field_name("pool"))
        if pool_name:
            pool = ResourcePool.objects.get(name=pool_name)
            filtered_nodes = filtered_nodes.filter(pool=pool)
        not_in_pool = self.cleaned_data.get(self.get_field_name("not_in_pool"))
        if not_in_pool:
            pools_to_exclude = ResourcePool.objects.filter(
                name__in=not_in_pool)
            filtered_nodes = filtered_nodes.exclude(pool__in=pools_to_exclude)
        return filtered_nodes

    def filter_by_tags(self, filtered_nodes):
        tags = self.cleaned_data.get(self.get_field_name("tags"))
        if tags:
            for tag in tags:
                filtered_nodes = filtered_nodes.filter(tags__name=tag)
        not_tags = self.cleaned_data.get(self.get_field_name("not_tags"))
        if len(not_tags) > 0:
            for not_tag in not_tags:
                filtered_nodes = filtered_nodes.exclude(tags__name=not_tag)
        return filtered_nodes

    def filter_by_mem(self, filtered_nodes):
        mem = self.cleaned_data.get(self.get_field_name("mem"))
        if mem:
            filtered_nodes = filtered_nodes.filter(memory__gte=mem)
        return filtered_nodes

    def filter_by_cpu_count(self, filtered_nodes):
        cpu_count = self.cleaned_data.get(self.get_field_name("cpu_count"))
        if cpu_count:
            filtered_nodes = filtered_nodes.filter(cpu_count__gte=cpu_count)
        return filtered_nodes

    def filter_by_arch(self, filtered_nodes):
        arch = self.cleaned_data.get(self.get_field_name("arch"))
        if arch:
            filtered_nodes = filtered_nodes.filter(architecture__in=arch)
        return filtered_nodes

    def filter_by_system_id(self, filtered_nodes):
        # Filter by system_id.
        system_id = self.cleaned_data.get(self.get_field_name("system_id"))
        if system_id:
            filtered_nodes = filtered_nodes.filter(system_id=system_id)
        return filtered_nodes

    def filter_by_pod_or_pod_type(self, filtered_nodes):
        # Filter by pod, pod type, not_pod or not_pod_type.
        # We are filtering for all of these to keep the query count down.
        pod = self.cleaned_data.get(self.get_field_name("pod"))
        not_pod = self.cleaned_data.get(self.get_field_name("not_pod"))
        pod_type = self.cleaned_data.get(self.get_field_name("pod_type"))
        not_pod_type = self.cleaned_data.get(
            self.get_field_name("not_pod_type"))
        if pod or pod_type or not_pod or not_pod_type:
            pods = Pod.objects.all()
            if pod:
                pods = pods.filter(name=pod)
            if not pod:
                pods = pods.exclude(name=not_pod)
            if pod_type:
                pods = pods.filter(power_type=pod_type)
            if not_pod_type:
                pods = pods.exclude(power_type=not_pod_type)
            filtered_nodes = filtered_nodes.filter(
                bmc_id__in=pods.values_list("id", flat=True))
        return filtered_nodes.distinct()
예제 #3
0
class PackageRepositoryForm(MAASModelForm):
    """Package Repository creation/edition form."""
    class Meta:
        model = PackageRepository
        fields = (
            'name',
            'url',
            'distributions',
            'disabled_pockets',
            'disabled_components',
            'components',
            'arches',
            'key',
            'enabled',
        )

    name = forms.CharField(label="Name",
                           required=True,
                           help_text=("The name of the Package Repository."))

    url = URLOrPPAFormField(label="Package Repository URL",
                            required=True,
                            help_text="The Package Repository URL")

    # Use UnconstrainedMultipleChoiceField fields for multiple-choices
    # fields instead of the default (djorm-ext-pgarray's ArrayFormField):
    # ArrayFormField deals with comma-separated lists and here we want to
    # handle multiple-values submissions.
    distributions = UnconstrainedMultipleChoiceField(label="Distribution list")

    disabled_pockets = UnconstrainedMultipleChoiceField(
        label="Disabled Pocket list")

    disabled_components = UnconstrainedMultipleChoiceField(
        label="Disabled Component list")

    components = UnconstrainedMultipleChoiceField(label="Component list")

    arches = UnconstrainedMultipleChoiceField(label="Architecture list")

    key = forms.CharField(
        label="Key",
        required=False,
        help_text=("The key used to authenticate the Package Repository."))

    enabled = forms.BooleanField(
        label="Enabled",
        required=False,
        help_text=("Whether or not the Package Repository is enabled."))

    def __init__(self, data=None, instance=None, request=None, **kwargs):
        super().__init__(data=data, instance=instance, **kwargs)
        if self.instance.id is None:
            self.initial['enabled'] = True
        else:
            self.fields['url'].initial = self.instance.url

    def clean(self):
        cleaned_data = super().clean()
        if self.instance.default and not cleaned_data.get('enabled', False):
            raise ValidationError("Default repositories may not be disabled.")

    def clean_arches(self):
        arches = []
        for value in self.cleaned_data.get('arches', []):
            arches.extend([s.strip() for s in value.split(',')])
        known_arches = set(PackageRepository.objects.get_known_architectures())
        for value in arches:
            if value not in known_arches:
                raise ValidationError(
                    "'%s' is not a valid architecture. Known architectures: "
                    "%s" % (value, ", ".join(sorted(known_arches))))
        # If no arches provided, use MAIN_ARCHES.
        if len(arches) == 0:
            arches = PackageRepository.MAIN_ARCHES
        return arches

    def clean_distributions(self):
        values = []
        for value in self.cleaned_data.get('distributions', []):
            values.extend([s.strip() for s in value.split(',')])
        return values

    def clean_disabled_pockets(self):
        values = []
        for value in self.cleaned_data.get('disabled_pockets', []):
            values.extend([s.strip() for s in value.split(',')])
        # This allows to reset the values of disabled_pockets if one of the
        # following is passed over the API:
        #   disabled_pockets=
        #   disabled_pockets=''
        #   disabled_pockets=None
        #   disabled_pockets=[]
        if values == [''] or values == ['None'] or values == ['[]']:
            return []
        # Check that a valid pocket is being disable.
        for pocket in values:
            if pocket not in PackageRepository.POCKETS_TO_DISABLE:
                raise ValidationError(
                    "'%s' is not a valid Ubuntu archive pocket. You "
                    "can only disable %s." %
                    (pocket, PackageRepository.POCKETS_TO_DISABLE))
        return values

    def clean_disabled_components(self):
        values = []
        for value in self.cleaned_data.get('disabled_components', []):
            values.extend([s.strip() for s in value.split(',')])
        # This allows to reset the values of disabled_components if one of the
        # following is passed over the API:
        #   disabled_components=
        #   disabled_components=''
        #   disabled_components=None
        #   disabled_components=[]
        if values == [''] or values == ['None'] or values == ['[]']:
            return []
        if self.instance is not None and not self.instance.default and values:
            raise ValidationError(
                "This is a custom repository. Please update 'components' "
                "instead.")
        # Check that a valid component is being passed.
        for component in values:
            if component not in PackageRepository.COMPONENTS_TO_DISABLE:
                raise ValidationError(
                    "'%s' is not a valid Ubuntu archive component. You "
                    "can only disable %s." %
                    (component, PackageRepository.COMPONENTS_TO_DISABLE))
        return values

    def clean_components(self):
        values = []
        for value in self.cleaned_data.get('components', []):
            values.extend([s.strip() for s in value.split(',')])
        if self.instance is not None and self.instance.default and values:
            raise ValidationError(
                "This is a default Ubuntu repository. Please update "
                "'disabled_components' instead.")
        return values
예제 #4
0
class AcquireNodeForm(RenamableFieldsForm):
    """A form handling the constraints used to acquire a node."""

    name = forms.CharField(label="Host name", required=False)

    arch = forms.CharField(label="Architecture", required=False)

    cpu_count = forms.FloatField(
        label="CPU count",
        required=False,
        error_messages={'invalid': "Invalid CPU count: number required."})

    mem = forms.FloatField(
        label="Memory",
        required=False,
        error_messages={'invalid': "Invalid memory: number of MB required."})

    tags = UnconstrainedMultipleChoiceField(label="Tags", required=False)

    connected_to = ValidatorMultipleChoiceField(
        validator=mac_validator,
        label="Connected to",
        required=False,
        error_messages={
            'invalid_list':
            "Invalid parameter: list of MAC addresses required."
        })

    not_connected_to = ValidatorMultipleChoiceField(
        validator=mac_validator,
        label="Not connected to",
        required=False,
        error_messages={
            'invalid_list':
            "Invalid parameter: list of MAC addresses required."
        })

    zone = forms.CharField(label="Availability zone", required=False)

    ignore_unknown_constraints = True

    @classmethod
    def Strict(cls, *args, **kwargs):
        """A stricter version of the form which rejects unknown parameters."""
        form = cls(*args, **kwargs)
        form.ignore_unknown_constraints = False
        return form

    def clean_arch(self):
        value = self.cleaned_data[self.get_field_name('arch')]
        if value:
            if value in ARCHITECTURE_CHOICES_DICT:
                # Full 'arch/subarch' specified directly.
                return [value]
            elif value in architecture_wildcards:
                # Try to expand 'arch' to all available 'arch/subarch'
                # matches.
                return architecture_wildcards[value]
            raise ValidationError({
                self.get_field_name('arch'): ['Architecture not recognised.']
            })
        return None

    def clean_tags(self):
        value = self.cleaned_data[self.get_field_name('tags')]
        if value:
            tag_names = parse_legacy_tags(value)
            # Validate tags.
            tag_names = set(tag_names)
            db_tag_names = set(
                Tag.objects.filter(name__in=tag_names).values_list('name',
                                                                   flat=True))
            if len(tag_names) != len(db_tag_names):
                unknown_tags = tag_names.difference(db_tag_names)
                error_msg = 'No such tag(s): %s.' % ', '.join(
                    "'%s'" % tag for tag in unknown_tags)
                raise ValidationError(
                    {self.get_field_name('arch'): [error_msg]})
            return tag_names
        return None

    def clean_zone(self):
        value = self.cleaned_data[self.get_field_name('zone')]
        if value:
            zone_names = Zone.objects.all().values_list('name', flat=True)
            if value not in zone_names:
                error_msg = "No such zone: '%s'." % value
                raise ValidationError(
                    {self.get_field_name('zone'): [error_msg]})
            return value
        return None

    def clean(self):
        if not self.ignore_unknown_constraints:
            unknown_constraints = set(self.data).difference(
                set(self.field_mapping.values()))
            for constraint in unknown_constraints:
                msg = "No such constraint."
                self._errors[constraint] = self.error_class([msg])
        return super(AcquireNodeForm, self).clean()

    def filter_nodes(self, nodes):
        filtered_nodes = nodes

        # Filter by hostname.
        hostname = self.cleaned_data.get(self.get_field_name('name'))
        if hostname:
            clause = Q(hostname=hostname)
            # If the given hostname has a domain part, try matching
            # against the nodes' FQDNs as well (the FQDN is built using
            # the nodegroup's name as the domain name).
            if "." in hostname:
                host, domain = hostname.split('.', 1)
                hostname_clause = (Q(hostname__startswith="%s." % host)
                                   | Q(hostname=host))
                domain_clause = Q(nodegroup__name=domain)
                clause = clause | (hostname_clause & domain_clause)
            filtered_nodes = filtered_nodes.filter(clause)

        # Filter by architecture.
        arch = self.cleaned_data.get(self.get_field_name('arch'))
        if arch:
            filtered_nodes = filtered_nodes.filter(architecture__in=arch)

        # Filter by cpu_count.
        cpu_count = self.cleaned_data.get(self.get_field_name('cpu_count'))
        if cpu_count:
            filtered_nodes = filtered_nodes.filter(cpu_count__gte=cpu_count)

        # Filter by memory.
        mem = self.cleaned_data.get(self.get_field_name('mem'))
        if mem:
            filtered_nodes = filtered_nodes.filter(memory__gte=mem)

        # Filter by tags.
        tags = self.cleaned_data.get(self.get_field_name('tags'))
        if tags:
            for tag in tags:
                filtered_nodes = filtered_nodes.filter(tags__name=tag)

        # Filter by zone.
        zone = self.cleaned_data.get(self.get_field_name('zone'))
        if zone:
            filtered_nodes = filtered_nodes.filter(zone=zone)

        # Filter by connected_to.
        connected_to = self.cleaned_data.get(
            self.get_field_name('connected_to'))
        if connected_to:
            where, params = macs_contain("routers", connected_to)
            filtered_nodes = filtered_nodes.extra(where=[where], params=params)

        # Filter by not_connected_to.
        not_connected_to = self.cleaned_data.get(
            self.get_field_name('not_connected_to'))
        if not_connected_to:
            where, params = macs_do_not_contain("routers", not_connected_to)
            filtered_nodes = filtered_nodes.extra(where=[where], params=params)

        return filtered_nodes
예제 #5
0
 def test_accepts_list(self):
     value = ['a', 'b']
     instance = UnconstrainedMultipleChoiceField()
     self.assertEqual(value, instance.clean(value))
예제 #6
0
class PackageRepositoryForm(MAASModelForm):
    """Package Repository creation/edition form."""
    class Meta:
        model = PackageRepository
        fields = (
            "name",
            "url",
            "distributions",
            "disabled_pockets",
            "disabled_components",
            "disable_sources",
            "components",
            "arches",
            "key",
            "enabled",
        )

    name = forms.CharField(
        label="Name",
        required=True,
        help_text="The name of the Package Repository.",
    )

    url = URLOrPPAFormField(
        label="Package Repository URL",
        required=True,
        help_text="The Package Repository URL",
    )

    # Use UnconstrainedMultipleChoiceField fields for multiple-choices
    # fields instead of the default as we want to handle
    # multiple-values submissions.
    distributions = UnconstrainedMultipleChoiceField(label="Distribution list")

    disabled_pockets = UnconstrainedMultipleChoiceField(
        label="Disabled Pocket list")

    disabled_components = UnconstrainedMultipleChoiceField(
        label="Disabled Component list")

    disable_sources = forms.BooleanField(
        label="Disable Sources",
        required=False,
        help_text="Whether or not deb-src lines are disabled.",
    )

    components = UnconstrainedMultipleChoiceField(label="Component list")

    arches = UnconstrainedMultipleChoiceField(label="Architecture list")

    key = forms.CharField(
        label="Key",
        required=False,
        help_text="The key used to authenticate the Package Repository.",
    )

    enabled = forms.BooleanField(
        label="Enabled",
        required=False,
        help_text="Whether or not the Package Repository is enabled.",
    )

    def __init__(self, data=None, instance=None, request=None, **kwargs):
        super().__init__(data=data, instance=instance, **kwargs)
        if self.instance.id is None:
            self.initial["enabled"] = True
        else:
            self.fields["url"].initial = self.instance.url

    def clean(self):
        cleaned_data = super().clean()
        if self.instance.default and not cleaned_data.get("enabled", False):
            raise ValidationError("Default repositories may not be disabled.")

    def clean_arches(self):
        arches = []
        for value in self.cleaned_data.get("arches", []):
            arches.extend([s.strip() for s in value.split(",")])
        known_arches = set(PackageRepository.objects.get_known_architectures())
        for value in arches:
            if value not in known_arches:
                raise ValidationError(
                    "'%s' is not a valid architecture. Known architectures: "
                    "%s" % (value, ", ".join(sorted(known_arches))))
        # If no arches provided, use MAIN_ARCHES.
        if len(arches) == 0:
            arches = PackageRepository.MAIN_ARCHES
        return arches

    def clean_distributions(self):
        values = []
        for value in self.cleaned_data.get("distributions", []):
            values.extend([s.strip() for s in value.split(",")])
        return values

    def clean_disabled_pockets(self):
        values = []
        for value in self.cleaned_data.get("disabled_pockets", []):
            values.extend([s.strip() for s in value.split(",")])
        # This allows to reset the values of disabled_pockets if one of the
        # following is passed over the API:
        #   disabled_pockets=
        #   disabled_pockets=''
        #   disabled_pockets=None
        #   disabled_pockets=[]
        if values == [""] or values == ["None"] or values == ["[]"]:
            return []
        # Check that a valid pocket is being disable.
        for pocket in values:
            if pocket not in PackageRepository.POCKETS_TO_DISABLE:
                raise ValidationError(
                    "'%s' is not a valid Ubuntu archive pocket. You "
                    "can only disable %s." %
                    (pocket, PackageRepository.POCKETS_TO_DISABLE))
        return values

    def clean_disabled_components(self):
        values = []
        for value in self.cleaned_data.get("disabled_components", []):
            values.extend([s.strip() for s in value.split(",")])
        # This allows to reset the values of disabled_components if one of the
        # following is passed over the API:
        #   disabled_components=
        #   disabled_components=''
        #   disabled_components=None
        #   disabled_components=[]
        if values == [""] or values == ["None"] or values == ["[]"]:
            return []
        if self.instance is not None and not self.instance.default and values:
            raise ValidationError(
                "This is a custom repository. Please update 'components' "
                "instead.")
        # Check that a valid component is being passed.
        for component in values:
            if component not in PackageRepository.COMPONENTS_TO_DISABLE:
                raise ValidationError(
                    "'%s' is not a valid Ubuntu archive component. You "
                    "can only disable %s." %
                    (component, PackageRepository.COMPONENTS_TO_DISABLE))
        return values

    def clean_components(self):
        values = []
        for value in self.cleaned_data.get("components", []):
            values.extend([s.strip() for s in value.split(",")])
        if self.instance is not None and self.instance.default and values:
            raise ValidationError(
                "This is a default Ubuntu repository. Please update "
                "'disabled_components' instead.")
        return values

    def save(self, endpoint, request):
        package_repository = super().save()
        create_audit_event(
            EVENT_TYPES.SETTINGS,
            endpoint,
            request,
            None,
            description=("%s package repository '%s'." % (
                "Updated" if self.is_update else "Created",
                package_repository.name,
            )),
        )
        return package_repository
예제 #7
0
class AcquireNodeForm(RenamableFieldsForm):
    """A form handling the constraints used to acquire a node."""

    name = forms.CharField(label="The hostname of the desired node",
                           required=False)

    system_id = forms.CharField(label="The system_id of the desired node",
                                required=False)

    # This becomes a multiple-choice field during cleaning, to accommodate
    # architecture wildcards.
    arch = forms.CharField(label="Architecture", required=False)

    cpu_count = forms.FloatField(
        label="CPU count",
        required=False,
        error_messages={'invalid': "Invalid CPU count: number required."})

    mem = forms.FloatField(
        label="Memory",
        required=False,
        error_messages={'invalid': "Invalid memory: number of MiB required."})

    tags = UnconstrainedMultipleChoiceField(label="Tags", required=False)

    not_tags = UnconstrainedMultipleChoiceField(label="Not having tags",
                                                required=False)

    # XXX mpontillo 2015-10-30 need validators for fabric constraints
    fabrics = ValidatorMultipleChoiceField(
        validator=lambda x: True,
        label="Attached to fabrics",
        required=False,
        error_messages={
            'invalid_list': "Invalid parameter: list of fabrics required.",
        })

    not_fabrics = ValidatorMultipleChoiceField(
        validator=lambda x: True,
        label="Not attached to fabrics",
        required=False,
        error_messages={
            'invalid_list': "Invalid parameter: list of fabrics required.",
        })

    fabric_classes = ValidatorMultipleChoiceField(
        validator=lambda x: True,
        label="Attached to fabric with specified classes",
        required=False,
        error_messages={
            'invalid_list':
            "Invalid parameter: list of fabric classes required.",
        })

    not_fabric_classes = ValidatorMultipleChoiceField(
        validator=lambda x: True,
        label="Not attached to fabric with specified classes",
        required=False,
        error_messages={
            'invalid_list':
            "Invalid parameter: list of fabric classes required."
        })

    subnets = ValidatorMultipleChoiceField(
        validator=Subnet.objects.validate_filter_specifiers,
        label="Attached to subnets",
        required=False,
        error_messages={
            'invalid_list':
            "Invalid parameter: list of subnet specifiers required.",
        })

    not_subnets = ValidatorMultipleChoiceField(
        validator=Subnet.objects.validate_filter_specifiers,
        label="Not attached to subnets",
        required=False,
        error_messages={
            'invalid_list':
            "Invalid parameter: list of subnet specifiers required.",
        })

    vlans = ValidatorMultipleChoiceField(
        validator=VLAN.objects.validate_filter_specifiers,
        label="Attached to VLANs",
        required=False,
        error_messages={
            'invalid_list':
            "Invalid parameter: list of VLAN specifiers required.",
        })

    not_vlans = ValidatorMultipleChoiceField(
        validator=VLAN.objects.validate_filter_specifiers,
        label="Not attached to VLANs",
        required=False,
        error_messages={
            'invalid_list':
            "Invalid parameter: list of VLAN specifiers required.",
        })

    connected_to = ValidatorMultipleChoiceField(
        validator=mac_validator,
        label="Connected to",
        required=False,
        error_messages={
            'invalid_list':
            "Invalid parameter: list of MAC addresses required."
        })

    not_connected_to = ValidatorMultipleChoiceField(
        validator=mac_validator,
        label="Not connected to",
        required=False,
        error_messages={
            'invalid_list':
            "Invalid parameter: list of MAC addresses required."
        })

    zone = forms.CharField(label="Physical zone", required=False)

    not_in_zone = ValidatorMultipleChoiceField(
        validator=MODEL_NAME_VALIDATOR,
        label="Not in zone",
        required=False,
        error_messages={
            'invalid_list': "Invalid parameter: must list physical zones.",
        })

    storage = forms.CharField(validators=[storage_validator],
                              label="Storage",
                              required=False)

    interfaces = LabeledConstraintMapField(validators=[interfaces_validator],
                                           label="Interfaces",
                                           required=False)

    ignore_unknown_constraints = False

    pod = forms.CharField(label="The name of the desired pod", required=False)

    not_pod = forms.CharField(label="The name of the undesired pod",
                              required=False)

    pod_type = forms.CharField(label="The power_type of the desired pod",
                               required=False)

    not_pod_type = forms.CharField(label="The power_type of the undesired pod",
                                   required=False)

    @classmethod
    def Strict(cls, *args, **kwargs):
        """A stricter version of the form which rejects unknown parameters."""
        form = cls(*args, **kwargs)
        form.ignore_unknown_constraints = False
        return form

    def clean_arch(self):
        """Turn `arch` parameter into a list of architecture names.

        Even though `arch` is a single-value field, it turns into a list
        during cleaning.  The architecture parameter may be a wildcard.
        """
        # Import list_all_usable_architectures as part of its module, not
        # directly, so that patch_usable_architectures can easily patch it
        # for testing purposes.
        usable_architectures = maasserver_forms.list_all_usable_architectures()
        architecture_wildcards = get_architecture_wildcards(
            usable_architectures)
        value = self.cleaned_data[self.get_field_name('arch')]
        if value:
            if value in usable_architectures:
                # Full 'arch/subarch' specified directly.
                return [value]
            elif value in architecture_wildcards:
                # Try to expand 'arch' to all available 'arch/subarch'
                # matches.
                return architecture_wildcards[value]
            set_form_error(self, self.get_field_name('arch'),
                           'Architecture not recognised.')
        return None

    def clean_tags(self):
        value = self.cleaned_data[self.get_field_name('tags')]
        if value:
            tag_names = parse_legacy_tags(value)
            # Validate tags.
            tag_names = set(tag_names)
            db_tag_names = set(
                Tag.objects.filter(name__in=tag_names).values_list('name',
                                                                   flat=True))
            if len(tag_names) != len(db_tag_names):
                unknown_tags = tag_names.difference(db_tag_names)
                error_msg = 'No such tag(s): %s.' % ', '.join(
                    "'%s'" % tag for tag in unknown_tags)
                set_form_error(self, self.get_field_name('tags'), error_msg)
                return None
            return tag_names
        return None

    def clean_zone(self):
        value = self.cleaned_data[self.get_field_name('zone')]
        if value:
            nonexistent_names = detect_nonexistent_names(Zone, [value])
            if nonexistent_names:
                error_msg = "No such zone: '%s'." % value
                set_form_error(self, self.get_field_name('zone'), error_msg)
                return None
            return value
        return None

    def clean_not_in_zone(self):
        value = self.cleaned_data[self.get_field_name('not_in_zone')]
        if not value:
            return None
        nonexistent_names = detect_nonexistent_names(Zone, value)
        if nonexistent_names:
            error_msg = "No such zone(s): %s." % ', '.join(nonexistent_names)
            set_form_error(self, self.get_field_name('not_in_zone'), error_msg)
            return None
        return value

    def _clean_specifiers(self, model, specifiers):
        if not specifiers:
            return None
        objects = set(model.objects.filter_by_specifiers(specifiers))
        if len(objects) == 0:
            raise ValidationError("No matching %s found." %
                                  model._meta.verbose_name_plural)
        return objects

    def clean_subnets(self):
        value = self.cleaned_data[self.get_field_name('subnets')]
        return self._clean_specifiers(Subnet, value)

    def clean_not_subnets(self):
        value = self.cleaned_data[self.get_field_name('not_subnets')]
        return self._clean_specifiers(Subnet, value)

    def clean_vlans(self):
        value = self.cleaned_data[self.get_field_name('vlans')]
        return self._clean_specifiers(VLAN, value)

    def clean_not_vlans(self):
        value = self.cleaned_data[self.get_field_name('not_vlans')]
        return self._clean_specifiers(VLAN, value)

    def __init__(self, *args, **kwargs):
        super(AcquireNodeForm, self).__init__(*args, **kwargs)

    def clean(self):
        if not self.ignore_unknown_constraints:
            unknown_constraints = set(self.data).difference(
                set(self.field_mapping.values()))
            for constraint in unknown_constraints:
                if constraint not in IGNORED_FIELDS:
                    msg = "Unable to allocate a machine. No such constraint."
                    self._errors[constraint] = self.error_class([msg])
        return super(AcquireNodeForm, self).clean()

    def describe_constraint(self, field_name):
        """Return a human-readable representation of a constraint.

        Turns a constraint value as passed to the form into a Juju-like
        representation for display: `name=foo`.  Multi-valued constraints are
        shown as comma-separated values, e.g. `tags=do,re,mi`.

        :param field_name: Name of the constraint on this form, e.g. `zone`.
        :return: A constraint string, or `None` if the constraint is not set.
        """
        value = self.cleaned_data.get(field_name, None)
        if value is None:
            return None
        if isinstance(self.fields[field_name], MultipleChoiceField):
            output = describe_multi_constraint_value(value)
        elif field_name == 'arch' and not isinstance(value, str):
            # The arch field is a special case.  It's defined as a string
            # field, but may become a list/tuple/... of strings in cleaning.
            output = describe_multi_constraint_value(value)
        else:
            output = describe_single_constraint_value(value)
        if output is None:
            return None
        else:
            return '%s=%s' % (field_name, output)

    def describe_constraints(self):
        """Return a human-readable representation of the given constraints.

        The description is Juju-like, e.g. `arch=amd64 cpu=16 zone=rack3`.
        Constraints are listed in alphabetical order.
        """
        constraints = (self.describe_constraint(name)
                       for name in sorted(self.fields.keys()))
        return ' '.join(constraint for constraint in constraints
                        if constraint is not None)

    def filter_nodes(self, nodes):
        """Return the subset of nodes that match the form's constraints.

        :param nodes:  The set of nodes on which the form should apply
            constraints.
        :type nodes: `django.db.models.query.QuerySet`
        :return: A QuerySet of the nodes that match the form's constraints.
        :rtype: `django.db.models.query.QuerySet`
        """
        filtered_nodes = nodes
        filtered_nodes = self.filter_by_pod_or_pod_type(filtered_nodes)
        filtered_nodes = self.filter_by_hostname(filtered_nodes)
        filtered_nodes = self.filter_by_system_id(filtered_nodes)
        filtered_nodes = self.filter_by_arch(filtered_nodes)
        filtered_nodes = self.filter_by_cpu_count(filtered_nodes)
        filtered_nodes = self.filter_by_mem(filtered_nodes)
        filtered_nodes = self.filter_by_tags(filtered_nodes)
        filtered_nodes = self.filter_by_zone(filtered_nodes)
        filtered_nodes = self.filter_by_subnets(filtered_nodes)
        filtered_nodes = self.filter_by_vlans(filtered_nodes)
        filtered_nodes = self.filter_by_fabrics(filtered_nodes)
        filtered_nodes = self.filter_by_fabric_classes(filtered_nodes)
        compatible_nodes, filtered_nodes = self.filter_by_storage(
            filtered_nodes)
        compatible_interfaces, filtered_nodes = self.filter_by_interfaces(
            filtered_nodes)
        filtered_nodes = self.reorder_nodes_by_cost(filtered_nodes)
        return filtered_nodes, compatible_nodes, compatible_interfaces

    def reorder_nodes_by_cost(self, filtered_nodes):
        # This uses a very simple procedure to compute a machine's
        # cost. This procedure is loosely based on how ec2 computes
        # the costs of machines. This is here to give a hint to let
        # the call to acquire() decide which machine to return based
        # on the machine's cost when multiple machines match the
        # constraints.
        filtered_nodes = filtered_nodes.distinct().extra(
            select={'cost': "cpu_count + memory / 1024."})
        return filtered_nodes.order_by("cost")

    def filter_by_interfaces(self, filtered_nodes):
        compatible_interfaces = {}
        interfaces_label_map = self.cleaned_data.get(
            self.get_field_name('interfaces'))
        if interfaces_label_map is not None:
            node_ids, compatible_interfaces = nodes_by_interface(
                interfaces_label_map)
            if node_ids is not None:
                filtered_nodes = filtered_nodes.filter(id__in=node_ids)

        return compatible_interfaces, filtered_nodes

    def filter_by_storage(self, filtered_nodes):
        compatible_nodes = {}  # Maps node/storage to named storage constraints
        storage = self.cleaned_data.get(self.get_field_name('storage'))
        if storage:
            compatible_nodes = nodes_by_storage(storage)
            node_ids = list(compatible_nodes)
            if node_ids is not None:
                filtered_nodes = filtered_nodes.filter(id__in=node_ids)
        return compatible_nodes, filtered_nodes

    def filter_by_fabric_classes(self, filtered_nodes):
        fabric_classes = self.cleaned_data.get(
            self.get_field_name('fabric_classes'))
        if fabric_classes is not None and len(fabric_classes) > 0:
            filtered_nodes = filtered_nodes.filter(
                interface__vlan__fabric__class_type__in=fabric_classes)
        not_fabric_classes = self.cleaned_data.get(
            self.get_field_name('not_fabric_classes'))
        if not_fabric_classes is not None and len(not_fabric_classes) > 0:
            filtered_nodes = filtered_nodes.exclude(
                interface__vlan__fabric__class_type__in=not_fabric_classes)
        return filtered_nodes

    def filter_by_fabrics(self, filtered_nodes):
        fabrics = self.cleaned_data.get(self.get_field_name('fabrics'))
        if fabrics is not None and len(fabrics) > 0:
            # XXX mpontillo 2015-10-30 need to also handle fabrics whose name
            # is null (fabric-<id>).
            filtered_nodes = filtered_nodes.filter(
                interface__vlan__fabric__name__in=fabrics)
        not_fabrics = self.cleaned_data.get(self.get_field_name('not_fabrics'))
        if not_fabrics is not None and len(not_fabrics) > 0:
            # XXX mpontillo 2015-10-30 need to also handle fabrics whose name
            # is null (fabric-<id>).
            filtered_nodes = filtered_nodes.exclude(
                interface__vlan__fabric__name__in=not_fabrics)
        return filtered_nodes

    def filter_by_vlans(self, filtered_nodes):
        vlans = self.cleaned_data.get(self.get_field_name('vlans'))
        if vlans is not None and len(vlans) > 0:
            for vlan in set(vlans):
                filtered_nodes = filtered_nodes.filter(interface__vlan=vlan)
        not_vlans = self.cleaned_data.get(self.get_field_name('not_vlans'))
        if not_vlans is not None and len(not_vlans) > 0:
            for not_vlan in set(not_vlans):
                filtered_nodes = filtered_nodes.exclude(
                    interface__vlan=not_vlan)
        return filtered_nodes

    def filter_by_subnets(self, filtered_nodes):
        subnets = self.cleaned_data.get(self.get_field_name('subnets'))
        if subnets is not None and len(subnets) > 0:
            for subnet in set(subnets):
                filtered_nodes = filtered_nodes.filter(
                    interface__ip_addresses__subnet=subnet)
        not_subnets = self.cleaned_data.get(self.get_field_name('not_subnets'))
        if not_subnets is not None and len(not_subnets) > 0:
            for not_subnet in set(not_subnets):
                filtered_nodes = filtered_nodes.exclude(
                    interface__ip_addresses__subnet=not_subnet)
        return filtered_nodes

    def filter_by_zone(self, filtered_nodes):
        zone = self.cleaned_data.get(self.get_field_name('zone'))
        if zone:
            zone_obj = Zone.objects.get(name=zone)
            filtered_nodes = filtered_nodes.filter(zone=zone_obj)
        not_in_zone = self.cleaned_data.get(self.get_field_name('not_in_zone'))
        if not_in_zone:
            not_in_zones = Zone.objects.filter(name__in=not_in_zone)
            filtered_nodes = filtered_nodes.exclude(zone__in=not_in_zones)
        return filtered_nodes

    def filter_by_tags(self, filtered_nodes):
        tags = self.cleaned_data.get(self.get_field_name('tags'))
        if tags:
            for tag in tags:
                filtered_nodes = filtered_nodes.filter(tags__name=tag)
        not_tags = self.cleaned_data.get(self.get_field_name('not_tags'))
        if len(not_tags) > 0:
            for not_tag in not_tags:
                filtered_nodes = filtered_nodes.exclude(tags__name=not_tag)
        return filtered_nodes

    def filter_by_mem(self, filtered_nodes):
        mem = self.cleaned_data.get(self.get_field_name('mem'))
        if mem:
            filtered_nodes = filtered_nodes.filter(memory__gte=mem)
        return filtered_nodes

    def filter_by_cpu_count(self, filtered_nodes):
        cpu_count = self.cleaned_data.get(self.get_field_name('cpu_count'))
        if cpu_count:
            filtered_nodes = filtered_nodes.filter(cpu_count__gte=cpu_count)
        return filtered_nodes

    def filter_by_arch(self, filtered_nodes):
        arch = self.cleaned_data.get(self.get_field_name('arch'))
        if arch:
            filtered_nodes = filtered_nodes.filter(architecture__in=arch)
        return filtered_nodes

    def filter_by_system_id(self, filtered_nodes):
        # Filter by system_id.
        system_id = self.cleaned_data.get(self.get_field_name('system_id'))
        if system_id:
            filtered_nodes = filtered_nodes.filter(system_id=system_id)
        return filtered_nodes

    def filter_by_hostname(self, filtered_nodes):
        # Filter by hostname.
        hostname = self.cleaned_data.get(self.get_field_name('name'))
        if hostname:
            # If the given hostname has a domain part, try matching
            # against the nodes' FQDN.
            if "." in hostname:
                host, domain = hostname.split('.', 1)
                hostname_clause = Q(hostname=host)
                domain_clause = Q(domain__name=domain)
                clause = (hostname_clause & domain_clause)
            else:
                clause = Q(hostname=hostname)
            filtered_nodes = filtered_nodes.filter(clause)
        return filtered_nodes

    def filter_by_pod_or_pod_type(self, filtered_nodes):
        # Filter by pod, pod type, not_pod or not_pod_type.
        # We are filtering for all of these to keep the query count down.
        pod = self.cleaned_data.get(self.get_field_name('pod'))
        not_pod = self.cleaned_data.get(self.get_field_name('not_pod'))
        pod_type = self.cleaned_data.get(self.get_field_name('pod_type'))
        not_pod_type = self.cleaned_data.get(
            self.get_field_name('not_pod_type'))
        if pod or pod_type or not_pod or not_pod_type:
            pods = Pod.objects.all()
            if pod:
                pods = pods.filter(name=pod)
            if not pod:
                pods = pods.exclude(name=not_pod)
            if pod_type:
                pods = pods.filter(power_type=pod_type)
            if not_pod_type:
                pods = pods.exclude(power_type=not_pod_type)
            filtered_nodes = filtered_nodes.filter(
                bmc_id__in=pods.values_list('id', flat=True))
        return filtered_nodes