class StaticRouteForm(MAASModelForm): """Static route creation/edition form.""" source = SpecifierOrModelChoiceField( label="Source", queryset=Subnet.objects.all(), required=True, help_text="The source subnet for the route.", ) destination = SpecifierOrModelChoiceField( label="Destination", queryset=Subnet.objects.all(), required=True, help_text="The destination subnet for the route.", ) class Meta: model = StaticRoute fields = ("source", "destination", "gateway_ip", "metric") def __init__(self, *args, **kwargs): super(StaticRouteForm, self).__init__(*args, **kwargs) # Metric field is not a required field, but is required in the model. self.fields["metric"].required = False def clean(self): gateway_ip = self.cleaned_data.get("gateway_ip") source = self.cleaned_data.get("source") if gateway_ip: # This will not raise an AddrFormatErorr because it is validated at # the field first and if that fails the gateway_ip will be blank. if IPAddress(gateway_ip) not in source.get_ipnetwork(): set_form_error( self, "gateway_ip", "Enter an IP address in %s." % source.cidr, ) def save(self): static_route = super().save(commit=False) if static_route.metric is None: # Set the initial value for the model. static_route.metric = 0 static_route.save() return static_route
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
class InterfaceLinkForm(forms.Form): """Interface link form.""" subnet = SpecifierOrModelChoiceField(queryset=None, required=False) ip_address = forms.GenericIPAddressField(required=False) default_gateway = forms.BooleanField(initial=False, required=False) def __init__(self, *args, **kwargs): # Get list of allowed modes for this interface. allowed_modes = kwargs.pop("allowed_modes", [ INTERFACE_LINK_TYPE.AUTO, INTERFACE_LINK_TYPE.DHCP, INTERFACE_LINK_TYPE.STATIC, INTERFACE_LINK_TYPE.LINK_UP, ]) self.force = kwargs.pop("force", False) mode_choices = [ (key, value) for key, value in INTERFACE_LINK_TYPE_CHOICES if key in allowed_modes ] self.instance = kwargs.pop("instance") super(InterfaceLinkForm, self).__init__(*args, **kwargs) # Create the mode field and setup the queryset on the subnet. self.fields['mode'] = CaseInsensitiveChoiceField( choices=mode_choices, required=True, error_messages={ 'invalid_choice': compose_invalid_choice_text( 'mode', mode_choices), }) if self.instance.vlan is None or self.force is True: self.fields['subnet'].queryset = Subnet.objects.all() else: self.fields['subnet'].queryset = ( self.instance.vlan.subnet_set.all()) def clean(self): for interface_set in self.instance.interface_set.all(): if isinstance(interface_set, BondInterface): set_form_error( self, "bond", ("Cannot link interface(%s) when interface is in a " "bond(%s)." % (self.instance.name, interface_set.name))) cleaned_data = super(InterfaceLinkForm, self).clean() mode = cleaned_data.get("mode", None) if mode is None: return cleaned_data elif mode == INTERFACE_LINK_TYPE.AUTO: self._clean_mode_auto(cleaned_data) elif mode == INTERFACE_LINK_TYPE.DHCP: self._clean_mode_dhcp() elif mode == INTERFACE_LINK_TYPE.STATIC: self._clean_mode_static(cleaned_data) elif mode == INTERFACE_LINK_TYPE.LINK_UP: self._clean_mode_link_up() self._clean_default_gateway(cleaned_data) return cleaned_data def _clean_mode_auto(self, cleaned_data): subnet = cleaned_data.get("subnet", None) if subnet is None: set_form_error(self, "subnet", "This field is required.") def _clean_mode_dhcp(self): # Can only have one DHCP link on an interface. dhcp_address = get_one( self.instance.ip_addresses.filter( alloc_type=IPADDRESS_TYPE.DHCP)) if dhcp_address is not None: if dhcp_address.subnet is not None: set_form_error( self, "mode", "Interface is already set to DHCP from '%s'." % ( dhcp_address.subnet)) else: set_form_error( self, "mode", "Interface is already set to DHCP.") def _clean_mode_static(self, cleaned_data): subnet = cleaned_data.get("subnet", None) ip_address = cleaned_data.get("ip_address", None) if subnet is None: set_form_error(self, "subnet", "This field is required.") elif ip_address: ip_address = IPAddress(ip_address) if ip_address not in subnet.get_ipnetwork(): set_form_error( self, "ip_address", "IP address is not in the given subnet '%s'." % subnet) ip_range = subnet.get_dynamic_range_for_ip(ip_address) if ip_range is not None: set_form_error( self, "ip_address", "IP address is inside a dynamic range " "%s to %s." % (ip_range.start_ip, ip_range.end_ip)) def _clean_mode_link_up(self): # Cannot set LINK_UP unless no other IP address are attached to # this interface. But exclude STICKY addresses where the IP address is # null, because the user could be trying to change the subnet for a # LINK_UP address. And exclude DISCOVERED because what MAAS discovered # doesn't matter with regard to the user's intention. exclude_types = ( IPADDRESS_TYPE.STICKY, IPADDRESS_TYPE.DISCOVERED ) has_active_links = self.instance.ip_addresses.exclude( alloc_type__in=exclude_types, ip__isnull=True).count() > 0 if has_active_links and self.force is not True: set_form_error( self, "mode", "Cannot configure interface to link up (with no IP address) " "while other links are already configured. Specify force=True " "to override this behavior and delete all links.") def _clean_default_gateway(self, cleaned_data): mode = cleaned_data.get("mode", None) subnet = cleaned_data.get("subnet", None) default_gateway = cleaned_data.get("default_gateway", False) if not default_gateway: return if mode not in GATEWAY_OPTION_MODES: set_form_error( self, "default_gateway", "Cannot use in mode '%s'." % mode) else: if subnet is None: set_form_error( self, "default_gateway", "Subnet is required when default_gateway is True.") elif not subnet.gateway_ip: set_form_error( self, "default_gateway", "Cannot set as default gateway because subnet " "%s doesn't provide a gateway IP address." % subnet) def save(self): mode = self.cleaned_data.get("mode", None) subnet = self.cleaned_data.get("subnet", None) ip_address = self.cleaned_data.get("ip_address", None) default_gateway = self.cleaned_data.get("default_gateway", False) # If force=True, allow the user to select any subnet (this will # implicitly change the VLAN). if mode == INTERFACE_LINK_TYPE.LINK_UP or self.force is True: # We're either setting the LINK_UP to a new subnet, or we're # forcing the issue. self.instance.clear_all_links(clearing_config=True) # If the user wants to force a particular subnet to be linked, clear # out the VLAN so that link_subnet() will reset the interface's subnet # to be correct. should_clear_vlan = ( self.force is True and subnet is not None and self.instance.vlan is not None ) if should_clear_vlan: self.instance.vlan = None if not ip_address: ip_address = None link_ip = self.instance.link_subnet( mode, subnet, ip_address=ip_address) if default_gateway: node = self.instance.get_node() network = subnet.get_ipnetwork() if network.version == IPADDRESS_FAMILY.IPv4: node.gateway_link_ipv4 = link_ip elif network.version == IPADDRESS_FAMILY.IPv6: node.gateway_link_ipv6 = link_ip else: raise ValueError( "Unknown subnet IP version: %s" % network.version) node.save() return Interface.objects.get(id=self.instance.id)
class VLANForm(MAASModelForm): """VLAN creation/edition form.""" # Linux doesn't allow lower than 552 for the MTU. mtu = forms.IntegerField(min_value=552, required=False) space = SpecifierOrModelChoiceField(queryset=Space.objects.all(), required=False, empty_label="") fabric = SpecifierOrModelChoiceField(queryset=Fabric.objects.all(), required=False, empty_label="") class Meta: model = VLAN fields = ( "name", "description", "vid", "mtu", "dhcp_on", "primary_rack", "secondary_rack", "relay_vlan", "space", "fabric", ) def __init__(self, *args, **kwargs): self.fabric = kwargs.pop("fabric", None) super().__init__(*args, **kwargs) instance = kwargs.get("instance") if instance is None and self.fabric is None: raise ValueError("Form requires either a instance or a fabric.") self._set_up_rack_fields() self._set_up_relay_vlan() 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 def _set_up_relay_vlan(self): # Configure the relay_vlan fields to include only VLAN's that are # not already on a relay_vlan. If this is an update then it cannot # be itself or never set when dhcp_on is True. possible_relay_vlans = VLAN.objects.filter(relay_vlan__isnull=True) if self.instance is not None: possible_relay_vlans = possible_relay_vlans.exclude( id=self.instance.id) if self.instance.dhcp_on: possible_relay_vlans = VLAN.objects.none() if self.instance.relay_vlan is not None: possible_relay_vlans = VLAN.objects.filter( id=self.instance.relay_vlan.id) self.fields["relay_vlan"] = forms.ModelChoiceField( queryset=possible_relay_vlans, required=False) def clean(self): cleaned_data = super().clean() # Automatically promote the secondary rack controller to the primary # if the primary is removed. if (not cleaned_data.get("primary_rack") and self.instance.secondary_rack is not None): cleaned_data["primary_rack"] = self.instance.secondary_rack cleaned_data["secondary_rack"] = None # If the primary is set to the secondary remove the secondary if (cleaned_data.get("primary_rack") and cleaned_data["primary_rack"] == self.instance.secondary_rack): cleaned_data["secondary_rack"] = None # Disallow setting the secondary to the existing primary if (cleaned_data.get("secondary_rack") and cleaned_data["secondary_rack"] == self.instance.primary_rack): raise ValidationError( "%s is already set as the primary rack controller" % cleaned_data["secondary_rack"].system_id) if (cleaned_data.get("primary_rack") and cleaned_data.get("secondary_rack") and cleaned_data.get("primary_rack") == cleaned_data.get("secondary_rack")): raise ValidationError( "The primary and secondary rack must be different") # Fix LP: #1798476 - When setting the secondary rack and the primary # rack was originally set (and not being changed), require the primary # rack to be up and running. primary_rack = cleaned_data.get("primary_rack") if (primary_rack and primary_rack == self.instance.primary_rack and cleaned_data.get("secondary_rack") and cleaned_data["secondary_rack"] != (self.instance.secondary_rack)): # Uses the `rackd` service not `dhcpd` or `dhcpd6` because if # the rackd is on it will ensure those services make it to a good # state. rackd_service = Service.objects.filter(node=primary_rack, name="rackd").first() if rackd_service and rackd_service.status == SERVICE_STATUS.DEAD: raise ValidationError( "The primary rack controller must be up and running to " "set a secondary rack controller. Without the primary " "the secondary DHCP service will not be able to " "synchronize, preventing it from responding to DHCP " "requests.") # Only allow dhcp_on when the primary_rack is set if (cleaned_data.get("dhcp_on") and not self.cleaned_data.get("primary_rack") and not self.instance.primary_rack): raise ValidationError( "dhcp can only be turned on when a primary rack controller " "is set.") # XXX ltrager 2016-02-09 - Hack to get around # https://code.djangoproject.com/ticket/25349 # https://github.com/django/django/pull/5658 if (cleaned_data.get("primary_rack") is None and self.instance.primary_rack is not None): self.instance.primary_rack = None if (cleaned_data.get("secondary_rack") is None and self.instance.secondary_rack is not None): self.instance.secondary_rack = None if cleaned_data.get("space") == "" and self.instance.space is not None: self.instance.space = None return cleaned_data def clean_dhcp_on(self): dhcp_on = self.cleaned_data.get("dhcp_on") if not dhcp_on: return dhcp_on for subnet in self.instance.subnet_set.all(): if subnet.get_dynamic_ranges(): return dhcp_on raise ValidationError( "dhcp can only be turned on when a dynamic IP range is defined.") def save(self): """Persist the VLAN into the database.""" vlan = super().save(commit=False) if self.fabric is not None: vlan.fabric = self.fabric if "space" in self.data and not self.cleaned_data.get("space"): # 'space' is being cleared. vlan.space = None if "relay_vlan" in self.data and not self.cleaned_data.get( "relay_vlan"): # 'relay_vlan' is being cleared. vlan.relay_vlan = None if vlan.dhcp_on: # 'relay_vlan' cannot be set when dhcp is on. vlan.relay_vlan = None vlan.save() return vlan
class VLANForm(MAASModelForm): """VLAN creation/edition form.""" # Linux doesn't allow lower than 552 for the MTU. mtu = forms.IntegerField(min_value=552, required=False) space = SpecifierOrModelChoiceField( queryset=Space.objects.all(), required=False, empty_label="") class Meta: model = VLAN fields = ( 'name', 'description', 'vid', 'mtu', 'dhcp_on', 'primary_rack', 'secondary_rack', 'relay_vlan', 'space', ) def __init__(self, *args, **kwargs): self.fabric = kwargs.pop('fabric', None) super(VLANForm, self).__init__(*args, **kwargs) instance = kwargs.get('instance') if instance is None and self.fabric is None: raise ValueError("Form requires either a instance or a fabric.") self._set_up_rack_fields() self._set_up_relay_vlan() 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 def _set_up_relay_vlan(self): # Configure the relay_vlan fields to include only VLAN's that are # not already on a relay_vlan. If this is an update then it cannot # be itself or never set when dhcp_on is True. possible_relay_vlans = VLAN.objects.filter(relay_vlan__isnull=True) if self.instance is not None: possible_relay_vlans = possible_relay_vlans.exclude( id=self.instance.id) if self.instance.dhcp_on: possible_relay_vlans = VLAN.objects.none() if self.instance.relay_vlan is not None: possible_relay_vlans = VLAN.objects.filter( id=self.instance.relay_vlan.id) self.fields['relay_vlan'] = forms.ModelChoiceField( queryset=possible_relay_vlans, required=False) def clean(self): cleaned_data = super(VLANForm, self).clean() # Automatically promote the secondary rack controller to the primary # if the primary is removed. if (not cleaned_data.get('primary_rack') and self.instance.secondary_rack is not None): cleaned_data['primary_rack'] = self.instance.secondary_rack cleaned_data['secondary_rack'] = None # If the primary is set to the secondary remove the secondary if (cleaned_data.get('primary_rack') and cleaned_data['primary_rack'] == self.instance.secondary_rack): cleaned_data['secondary_rack'] = None # Disallow setting the secondary to the existing primary if (cleaned_data.get('secondary_rack') and cleaned_data['secondary_rack'] == self.instance.primary_rack): raise ValidationError( "%s is already set as the primary rack controller" % cleaned_data['secondary_rack'].system_id ) if (cleaned_data.get('primary_rack') and cleaned_data.get('secondary_rack') and cleaned_data.get('primary_rack') == cleaned_data.get('secondary_rack')): raise ValidationError( "The primary and secondary rack must be different" ) # Only allow dhcp_on when the primary_rack is set if (cleaned_data.get('dhcp_on') and not self.cleaned_data.get('primary_rack') and not self.instance.primary_rack): raise ValidationError( "dhcp can only be turned on when a primary rack controller" "is set.") # XXX ltrager 2016-02-09 - Hack to get around # https://code.djangoproject.com/ticket/25349 # https://github.com/django/django/pull/5658 if (cleaned_data.get('primary_rack') is None and self.instance.primary_rack is not None): self.instance.primary_rack = None if (cleaned_data.get('secondary_rack') is None and self.instance.secondary_rack is not None): self.instance.secondary_rack = None if (cleaned_data.get('space') is "" and self.instance.space is not None): self.instance.space = None return cleaned_data def clean_dhcp_on(self): dhcp_on = self.cleaned_data.get('dhcp_on') if not dhcp_on: return dhcp_on for subnet in self.instance.subnet_set.all(): if subnet.get_dynamic_ranges(): return dhcp_on raise ValidationError( "dhcp can only be turned on when a dynamic IP range is defined.") def save(self): """Persist the VLAN into the database.""" vlan = super(VLANForm, self).save(commit=False) if self.fabric is not None: vlan.fabric = self.fabric if ('space' in self.data and not self.cleaned_data.get('space')): # 'space' is being cleared. vlan.space = None if ('relay_vlan' in self.data and not self.cleaned_data.get('relay_vlan')): # 'relay_vlan' is being cleared. vlan.relay_vlan = None if vlan.dhcp_on: # 'relay_vlan' cannot be set when dhcp is on. vlan.relay_vlan = None vlan.save() return vlan
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