コード例 #1
0
ファイル: vlan.py プロジェクト: th3architect/maas
    def _set_up_rack_fields(self):
        qs = RackController.objects.filter_by_vids([self.instance.vid])
        self.fields["primary_rack"] = NodeChoiceField(
            required=False,
            initial=None,
            empty_label="No rack controller",
            queryset=qs,
        )
        self.fields["secondary_rack"] = NodeChoiceField(
            required=False,
            initial=None,
            empty_label="No rack controller",
            queryset=qs,
        )

        # Convert the initial values pulled from the database from id to
        # system_id so form validation doesn't complain
        primary_rack_id = self.initial.get("primary_rack")
        if primary_rack_id is not None:
            primary_rack = RackController.objects.get(id=primary_rack_id)
            self.initial["primary_rack"] = primary_rack.system_id
        secondary_rack_id = self.initial.get("secondary_rack")
        if secondary_rack_id is not None:
            secondary_rack = RackController.objects.get(id=secondary_rack_id)
            self.initial["secondary_rack"] = secondary_rack.system_id
コード例 #2
0
 def test_works_with_multiple_entries_in_queryset(self):
     # Regression test for lp:1551399
     vlan = factory.make_VLAN()
     node = factory.make_Node_with_Interface_on_Subnet(vlan=vlan)
     factory.make_Interface(node=node, vlan=vlan)
     qs = Node.objects.filter_by_vids([vlan.vid])
     node_field = NodeChoiceField(qs)
     # Double check that we have duplicated entires
     self.assertEqual(2, len(qs.filter(system_id=node.system_id)))
     self.assertEqual(node, node_field.clean(node.system_id))
コード例 #3
0
ファイル: pods.py プロジェクト: zeronewb/maas
class PodForm(MAASModelForm):
    class Meta:
        model = Pod
        fields = [
            'name',
            'tags',
            'zone',
            'pool',
            'cpu_over_commit_ratio',
            'memory_over_commit_ratio',
            'default_storage_pool',
            'default_macvlan_mode',
            'host',
        ]

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

    zone = forms.ModelChoiceField(label="Physical zone",
                                  required=False,
                                  initial=Zone.objects.get_default_zone,
                                  queryset=Zone.objects.all(),
                                  to_field_name='name')

    pool = forms.ModelChoiceField(
        label="Default pool of created machines",
        required=False,
        initial=lambda: ResourcePool.objects.get_default_resource_pool().name,
        queryset=ResourcePool.objects.all(),
        to_field_name='name')

    cpu_over_commit_ratio = forms.FloatField(label="CPU over commit ratio",
                                             initial=1,
                                             required=False,
                                             min_value=0,
                                             max_value=10)

    memory_over_commit_ratio = forms.FloatField(
        label="Memory over commit ratio",
        initial=1,
        required=False,
        min_value=0,
        max_value=10)

    default_storage_pool = forms.ModelChoiceField(
        label="Default storage pool",
        required=False,
        queryset=PodStoragePool.objects.none(),
        to_field_name='pool_id')

    default_macvlan_mode = forms.ChoiceField(label="Default MACVLAN mode",
                                             required=False,
                                             choices=MACVLAN_MODE_CHOICES,
                                             initial=MACVLAN_MODE_CHOICES[0])

    host = NodeChoiceField(label="Pod host",
                           queryset=Node.objects.all(),
                           required=False,
                           initial=None,
                           help_text=("The node that hosts this Pod."))

    def __init__(self, data=None, instance=None, request=None, **kwargs):
        self.is_new = instance is None
        self.request = request
        super(PodForm, self).__init__(data=data, instance=instance, **kwargs)
        if data is None:
            data = {}
        type_value = data.get('type', self.initial.get('type'))
        self.drivers_orig = driver_parameters.get_all_power_types()
        self.drivers = {
            driver['name']: driver
            for driver in self.drivers_orig if driver['driver_type'] == 'pod'
        }
        if len(self.drivers) == 0:
            type_value = ''
        elif type_value not in self.drivers:
            type_value = ('' if self.instance is None else
                          self.instance.power_type)
        choices = [(name, driver['description'])
                   for name, driver in self.drivers.items()]
        self.fields['type'] = forms.ChoiceField(required=True,
                                                choices=choices,
                                                initial=type_value)
        if not self.is_new:
            if self.instance.power_type != '':
                self.initial['type'] = self.instance.power_type
        if instance is not None:
            self.initial['zone'] = instance.zone.name
            self.initial['pool'] = instance.pool.name
            self.fields['default_storage_pool'].queryset = (
                instance.storage_pools.all())
            if instance.default_storage_pool:
                self.initial['default_storage_pool'] = (
                    instance.default_storage_pool.pool_id)
            if instance.host is not None:
                self.initial['host'] = self.instance.host.system_id

    def _clean_fields(self):
        """Override to dynamically add fields based on the value of `type`
        field."""
        # Process the built-in fields first.
        super(PodForm, self)._clean_fields()
        # If no errors then we re-process with the fields required by the
        # selected type for the pod.
        if len(self.errors) == 0:
            driver_fields = get_driver_parameters_from_json(
                self.drivers_orig, scope=SETTING_SCOPE.BMC)
            self.param_fields = (
                driver_fields[self.cleaned_data['type']].field_dict)
            self.fields.update(self.param_fields)
            if not self.is_new:
                for key, value in self.instance.power_parameters.items():
                    if key not in self.data:
                        self.data[key] = value
            super(PodForm, self)._clean_fields()

    def clean(self):
        cleaned_data = super(PodForm, self).clean()
        if len(self.drivers) == 0:
            set_form_error(
                self, 'type',
                "No rack controllers are connected, unable to validate.")
        elif (not self.is_new
              and self.instance.power_type != self.cleaned_data.get('type')):
            set_form_error(
                self, 'type',
                "Cannot change the type of a pod. Delete and re-create the "
                "pod with a different type.")
        return cleaned_data

    def save(self, *args, **kwargs):
        """Persist the pod into the database."""
        def check_for_duplicate(power_type, power_parameters):
            # When the Pod is new try to get a BMC of the same type and
            # parameters to convert the BMC to a new Pod. When the Pod is not
            # new the form will use the already existing pod instance to update
            # those fields. If updating the fields causes a duplicate BMC then
            # a validation erorr will be raised from the model level.
            if self.is_new:
                bmc = BMC.objects.filter(
                    power_type=power_type,
                    power_parameters=power_parameters).first()
                if bmc is not None:
                    if bmc.bmc_type == BMC_TYPE.BMC:
                        # Convert the BMC to a Pod and set as the instance for
                        # the PodForm.
                        bmc.bmc_type = BMC_TYPE.POD
                        bmc.pool = (
                            ResourcePool.objects.get_default_resource_pool())
                        return bmc.as_pod()
                    else:
                        # Pod already exists with the same power_type and
                        # parameters.
                        raise ValidationError("Pod %s with type and "
                                              "parameters already exist." %
                                              bmc.name)

        def update_obj(existing_obj):
            if existing_obj is not None:
                self.instance = existing_obj
            self.instance = super(PodForm, self).save(commit=False)
            self.instance.power_type = power_type
            self.instance.power_parameters = power_parameters
            if (self.data.get('host') is ""
                    and self.instance.host is not None):
                # 'host' is being cleared.
                self.instance.host = None
            return self.instance

        power_type = self.cleaned_data['type']
        # Set power_parameters to the generated param_fields.
        power_parameters = {
            param_name: self.cleaned_data[param_name]
            for param_name in self.param_fields.keys()
            if param_name in self.cleaned_data
        }

        if isInIOThread():
            # Running in twisted reactor, do the work inside the reactor.
            d = deferToDatabase(transactional(check_for_duplicate), power_type,
                                power_parameters)
            d.addCallback(update_obj)
            d.addCallback(lambda _: self.discover_and_sync_pod())
            return d
        else:
            # Perform the actions inside the executing thread.
            existing_obj = check_for_duplicate(power_type, power_parameters)
            if existing_obj is not None:
                self.instance = existing_obj
            self.instance = update_obj(self.instance)
            return self.discover_and_sync_pod()

    def discover_and_sync_pod(self):
        """Discover and sync the pod information."""
        def update_db(result):
            discovered_pod, discovered = result

            # When called with an instance that has no name, be sure to set
            # it before going any further. If this is a new instance this will
            # also create it in the database.
            if not self.instance.name:
                self.instance.set_random_name()
            self.instance.sync(discovered_pod, self.request.user)

            # Save which rack controllers can route and which cannot.
            discovered_rack_ids = [
                rack_id for rack_id, _ in discovered[0].items()
            ]
            for rack_controller in RackController.objects.all():
                routable = rack_controller.system_id in discovered_rack_ids
                bmc_route_model = BMCRoutableRackControllerRelationship
                relation, created = (bmc_route_model.objects.get_or_create(
                    bmc=self.instance.as_bmc(),
                    rack_controller=rack_controller,
                    defaults={'routable': routable}))
                if not created and relation.routable != routable:
                    relation.routable = routable
                    relation.save()
            return self.instance

        if isInIOThread():
            # Running in twisted reactor, do the work inside the reactor.
            d = discover_pod(self.instance.power_type,
                             self.instance.power_parameters,
                             pod_id=self.instance.id,
                             name=self.instance.name)
            d.addCallback(lambda discovered:
                          (get_best_discovered_result(discovered), discovered))

            def catch_no_racks(result):
                discovered_pod, discovered = result
                if discovered_pod is None:
                    raise PodProblem(
                        "Unable to start the pod discovery process. "
                        "No rack controllers connected.")
                return discovered_pod, discovered

            def wrap_errors(failure):
                if failure.check(PodProblem):
                    return failure
                else:
                    raise PodProblem(str(failure.value))

            d.addCallback(catch_no_racks)
            d.addCallback(partial(deferToDatabase, transactional(update_db)))
            d.addErrback(wrap_errors)
            return d
        else:
            # Perform the actions inside the executing thread.
            try:
                discovered = discover_pod(self.instance.power_type,
                                          self.instance.power_parameters,
                                          pod_id=self.instance.id,
                                          name=self.instance.name)
            except Exception as exc:
                raise PodProblem(str(exc)) from exc

            # Use the first discovered pod object. All other objects are
            # ignored. The other rack controllers that also provided a result
            # can route to the pod.
            try:
                discovered_pod = get_best_discovered_result(discovered)
            except Exception as error:
                raise PodProblem(str(error))
            if discovered_pod is None:
                raise PodProblem("Unable to start the pod discovery process. "
                                 "No rack controllers connected.")
            return update_db((discovered_pod, discovered))
コード例 #4
0
class DHCPSnippetForm(MAASModelForm):
    """DHCP snippet creation/edition form."""

    name = forms.CharField(label="Name",
                           required=False,
                           help_text="The name of the DHCP snippet.")

    value = VersionedTextFileField(label="DHCP Snippet",
                                   required=False,
                                   help_text="The DHCP Snippet")

    description = forms.CharField(
        label="Description",
        required=False,
        help_text="The description of what the DHCP snippet does.",
    )

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

    node = NodeChoiceField(
        label="Node",
        queryset=Node.objects.all(),
        required=False,
        initial=None,
        help_text="The node which the DHCP snippet is for.",
    )

    subnet = SpecifierOrModelChoiceField(
        label="Subnet",
        queryset=Subnet.objects.all(),
        required=False,
        help_text="The subnet which the DHCP snippet is for.",
    )

    global_snippet = forms.BooleanField(
        label="Global DHCP Snippet",
        required=False,
        help_text=(
            "Set the DHCP snippet to be global, removes links to nodes or "
            "subnets"),
    )

    class Meta:
        model = DHCPSnippet
        fields = (
            "name",
            "value",
            "description",
            "enabled",
            "node",
            "subnet",
            "global_snippet",
        )

    def __init__(self, instance=None, request=None, **kwargs):
        super().__init__(instance=instance, **kwargs)
        if instance is None:
            for field in ["name", "value"]:
                self.fields[field].required = True
            self.initial["enabled"] = True
        else:
            self.fields["value"].initial = self.instance.value
        if instance is not None and instance.node is not None:
            self.initial["node"] = self.instance.node.system_id

    def clean(self):
        cleaned_data = super().clean()
        if cleaned_data.get("global_snippet", False):
            cleaned_data["node"] = None
            self.instance.node = None
            cleaned_data["subnet"] = None
            self.instance.subnet = None
        elif (self.instance.subnet == cleaned_data.get("subnet")
              and cleaned_data.get("node") is not None):
            cleaned_data["subnet"] = None
            self.instance.subnet = None
        elif (self.instance.node == cleaned_data.get("node")
              and cleaned_data.get("subnet") is not None):
            cleaned_data["node"] = None
            self.instance.node = None
        return cleaned_data

    def is_valid(self):
        valid = super().is_valid()
        if valid:
            # Often the first error can cause cascading errors. Showing all of
            # these errors can be confusing so only show the first if there is
            # one.
            first_error = None
            for error in validate_dhcp_config(self.instance):
                valid = False
                if first_error is None:
                    first_error = error
                else:
                    if error["line_num"] < first_error["line_num"]:
                        first_error = error
            if first_error is not None:
                set_form_error(self, "value", first_error["error"])

        # If the DHCPSnippet isn't valid cleanup the value
        if not valid and self.initial.get("value") != self.instance.value_id:
            self.instance.value.delete()
        return valid

    def save(self, endpoint, request):
        dhcp_snippet = super(DHCPSnippetForm, self).save()
        create_audit_event(
            EVENT_TYPES.SETTINGS,
            endpoint,
            request,
            None,
            description=("%s DHCP snippet '%s'." % (
                "Updated" if self.is_update else "Created",
                dhcp_snippet.name,
            )),
        )
        return dhcp_snippet
コード例 #5
0
 def test_raises_exception_when_not_found(self):
     for _ in range(3):
         factory.make_Node()
     node_field = NodeChoiceField(Node.objects.filter())
     self.assertRaises(ValidationError, node_field.clean,
                       factory.make_name("query"))
コード例 #6
0
 def test_allows_selecting_by_hostname(self):
     node = factory.make_Node()
     for _ in range(3):
         factory.make_Node()
     node_field = NodeChoiceField(Node.objects.filter())
     self.assertEqual(node, node_field.clean(node.hostname))
コード例 #7
0
class CloneForm(forms.Form):
    """Clone storage/interface form."""

    source = NodeChoiceField(
        label="Source",
        queryset=Machine.objects.all(),
        required=True,
        initial=None,
        help_text="The source machine to clone from.",
    )

    destinations = SimpleArrayField(
        NodeChoiceField(queryset=Machine.objects.all()),
        label="Destinations",
        min_length=1,
        error_messages={
            "item_invalid": "Machine %(nth)s in the array did not validate:"
        },
        help_text="The destinations to clone to.",
    )

    storage = forms.BooleanField(
        label="Storage",
        required=False,
        help_text="Clone the storage configuration.",
    )

    interfaces = forms.BooleanField(
        label="Interfaces",
        required=False,
        help_text="Clone the interfaces configuration.",
    )

    def __init__(self, user, **kwargs):
        self.user = user
        super().__init__(**kwargs)
        self.fields["source"].queryset = Machine.objects.get_nodes(
            self.user, NodePermission.view)
        self.fields[
            "destinations"].base_field.queryset = Machine.objects.get_nodes(
                self.user,
                NodePermission.admin,
                from_nodes=Machine.objects.filter(
                    status__in={NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING
                                }),
            )

    def clean(self):
        """Validate that the form is valid and that the destinations can accept
        the storage and/or interfaces configuration from the source."""
        cleaned_data = super().clean()
        source = self.cleaned_data.get("source")
        if not source:
            # Django should be placing this automatically, but it does not
            # occur. So we force the setting of this error here.
            set_form_error(self, "source", "This field is required.")
        destinations = self.cleaned_data.get("destinations")
        destination_field = self.fields["destinations"]
        item_invalid = destination_field.error_messages["item_invalid"]
        storage = self.cleaned_data.get("storage", False)
        interfaces = self.cleaned_data.get("interfaces", False)
        if source and destinations:
            for index, dest in enumerate(destinations, 1):
                if source == dest:
                    error = prefix_validation_error(
                        ValidationError(
                            "Source machine cannot be a destination machine."),
                        prefix=item_invalid,
                        code="item_invalid",
                        params={"nth": index},
                    )
                    set_form_error(self, "destinations", error)
                else:
                    if storage:
                        try:
                            dest._get_storage_mapping_between_nodes(source)
                        except ValidationError as exc:
                            error = prefix_validation_error(
                                exc,
                                prefix=item_invalid,
                                code="item_invalid",
                                params={"nth": index},
                            )
                            set_form_error(self, "destinations", error)
                    if interfaces:
                        try:
                            dest._get_interface_mapping_between_nodes(source)
                        except ValidationError as exc:
                            error = prefix_validation_error(
                                exc,
                                prefix=item_invalid,
                                code="item_invalid",
                                params={"nth": index},
                            )
                            set_form_error(self, "destinations", error)
        if not storage and not interfaces:
            set_form_error(self, "__all__",
                           "Either storage or interfaces must be true.")
        return cleaned_data

    def save(self):
        """Clone the storage and/or interfaces configuration to the
        destinations."""
        source = self.cleaned_data.get("source")
        destinations = self.cleaned_data.get("destinations")
        storage = self.cleaned_data.get("storage", False)
        interfaces = self.cleaned_data.get("interfaces", False)
        for dest in destinations:
            if storage:
                dest.set_storage_configuration_from_node(source)
            if interfaces:
                dest.set_networking_configuration_from_node(source)
コード例 #8
0
ファイル: clone.py プロジェクト: casual-lemon/maas
class CloneForm(forms.Form):
    """Clone storage/interface form."""

    source = NodeChoiceField(
        label="Source",
        queryset=Machine.objects.all(),
        required=True,
        initial=None,
        help_text="The source machine to clone from.",
    )

    destinations = SimpleArrayField(
        NodeChoiceField(queryset=Machine.objects.all()),
        label="Destinations",
        min_length=1,
        error_messages={
            "item_invalid": "Machine %(nth)s is invalid:",
            "is-source":
            "Source machine %(machine)s cannot be a destination machine.",
            "storage": "%(machine)s is invalid:",
            "networking": "%(machine)s is invalid:",
        },
        help_text="The destinations to clone to.",
    )

    storage = forms.BooleanField(
        label="Storage",
        required=False,
        help_text="Clone the storage configuration.",
    )

    interfaces = forms.BooleanField(
        label="Interfaces",
        required=False,
        help_text="Clone the interfaces configuration.",
    )

    def __init__(self, user, **kwargs):
        self.user = user
        super().__init__(**kwargs)
        self.fields["source"].queryset = Machine.objects.get_nodes(
            self.user, NodePermission.view)
        self.fields[
            "destinations"].base_field.queryset = Machine.objects.get_nodes(
                self.user,
                NodePermission.admin,
                from_nodes=Machine.objects.filter(
                    status__in={NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING
                                }),
            )

    def clean(self):
        """Validate that the form is valid and that the destinations can accept
        the storage and/or interfaces configuration from the source."""
        cleaned_data = super().clean()
        source = self.cleaned_data.get("source")
        if not source:
            # Django should be placing this automatically, but it does not
            # occur. So we force the setting of this error here.
            self.add_error("source", "This field is required.")
        destinations = self.cleaned_data.get("destinations")
        destination_field = self.fields["destinations"]
        storage = self.cleaned_data.get("storage", False)
        interfaces = self.cleaned_data.get("interfaces", False)
        if source and destinations:
            for dest in destinations:
                if source == dest:
                    error = ValidationError(
                        destination_field.error_messages["is-source"],
                        code="is-source",
                        params={
                            "machine": str(dest),
                            "system_id": dest.system_id,
                        },
                    )
                    self.add_error("destinations", error)
                else:
                    if storage:
                        try:
                            dest._get_storage_mapping_between_nodes(source)
                        except ValidationError as exc:
                            error = prefix_validation_error(
                                exc,
                                prefix=destination_field.
                                error_messages["storage"],
                                code="storage",
                                params={
                                    "machine": str(dest),
                                    "system_id": dest.system_id,
                                },
                            )
                            self.add_error("destinations", error)
                    if interfaces:
                        try:
                            dest._get_interface_mapping_between_nodes(source)
                        except ValidationError as exc:
                            error = prefix_validation_error(
                                exc,
                                prefix=destination_field.
                                error_messages["networking"],
                                code="networking",
                                params={
                                    "machine": str(dest),
                                    "system_id": dest.system_id,
                                },
                            )
                            self.add_error("destinations", error)
        if not storage and not interfaces:
            self.add_error(
                "__all__",
                ValidationError(
                    "Either storage or interfaces must be true.",
                    code="required",
                ),
            )
        return cleaned_data

    def strip_failed_destinations(self):
        """Remove destinations that have errors."""
        if "destinations" not in self.cleaned_data:
            submitted_destinations = self.data.get("destinations")
            # Don't try and manipulate empty submission
            if not submitted_destinations:
                return
            errors = self.errors.as_data()
            bogus_system_ids = {
                error.params["system_id"]
                for error in errors["destinations"]
            }
            return [
                dest for dest in submitted_destinations
                if dest not in bogus_system_ids
            ]

    def save(self):
        """Clone the storage and/or interfaces configuration to the
        destinations."""
        source = self.cleaned_data.get("source")
        destinations = self.cleaned_data.get("destinations", [])
        storage = self.cleaned_data.get("storage", False)
        interfaces = self.cleaned_data.get("interfaces", False)
        for dest in destinations:
            if storage:
                dest.set_storage_configuration_from_node(source)
            if interfaces:
                dest.set_networking_configuration_from_node(source)
コード例 #9
0
class DHCPSnippetForm(MAASModelForm):
    """DHCP snippet creation/edition form."""

    name = forms.CharField(label="Name",
                           required=False,
                           help_text=("The name of the DHCP snippet."))

    value = VersionedTextFileField(label="DHCP Snippet",
                                   required=False,
                                   help_text="The DHCP Snippet")

    description = forms.CharField(
        label="Description",
        required=False,
        help_text=("The description of what the DHCP snippet does."))

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

    node = NodeChoiceField(
        label="Node",
        queryset=Node.objects.all(),
        required=False,
        initial=None,
        help_text=("The node which the DHCP snippet is for."))

    subnet = SpecifierOrModelChoiceField(
        label="Subnet",
        queryset=Subnet.objects.all(),
        required=False,
        help_text="The subnet which the DHCP snippet is for.")

    global_snippet = forms.BooleanField(
        label="Global DHCP Snippet",
        required=False,
        help_text=(
            "Set the DHCP snippet to be global, removes links to nodes or "
            "subnets"))

    class Meta:
        model = DHCPSnippet
        fields = (
            'name',
            'value',
            'description',
            'enabled',
            'node',
            'subnet',
            'global_snippet',
        )

    def __init__(self, instance=None, request=None, **kwargs):
        super().__init__(instance=instance, **kwargs)
        if instance is None:
            for field in ['name', 'value']:
                self.fields[field].required = True
            self.initial['enabled'] = True
        else:
            self.fields['value'].initial = self.instance.value
        if instance is not None and instance.node is not None:
            self.initial['node'] = self.instance.node.system_id

    def clean(self):
        cleaned_data = super().clean()
        if cleaned_data.get('global_snippet', False):
            cleaned_data['node'] = None
            self.instance.node = None
            cleaned_data['subnet'] = None
            self.instance.subnet = None
        elif (self.instance.subnet == cleaned_data.get('subnet')
              and cleaned_data.get('node') is not None):
            cleaned_data['subnet'] = None
            self.instance.subnet = None
        elif (self.instance.node == cleaned_data.get('node')
              and cleaned_data.get('subnet') is not None):
            cleaned_data['node'] = None
            self.instance.node = None
        return cleaned_data

    def is_valid(self):
        valid = super().is_valid()
        if valid:
            # Often the first error can cause cascading errors. Showing all of
            # these errors can be confusing so only show the first if there is
            # one.
            first_error = None
            for error in validate_dhcp_config(self.instance):
                valid = False
                if first_error is None:
                    first_error = error
                else:
                    if error['line_num'] < first_error['line_num']:
                        first_error = error
            if first_error is not None:
                set_form_error(self, 'value', first_error['error'])

        # If the DHCPSnippet isn't valid cleanup the value
        if not valid and self.initial.get('value') != self.instance.value_id:
            self.instance.value.delete()
        return valid