diff --git a/networks/forms.py b/networks/forms.py index 691dd8a1..002f2f1f 100644 --- a/networks/forms.py +++ b/networks/forms.py @@ -24,64 +24,80 @@ def checked_subnet(subnet, version): class AddNetPool(forms.Form): + """A new network of the host. networks: the host's networks + (get_networks_info), whose names it may not take nor overlap their + subnets. NAT and routed networks need an IPv4 subnet; an isolated one + may have none; a bridge or macvtap network takes its addresses from the + outside network, and needs the host's bridge or interface.""" + + MODES = ("nat", "route", "none", "bridge", "macvtap") + name = forms.CharField( error_messages={"required": _("No pool name has been entered")}, max_length=20 ) - subnet = forms.CharField( - error_messages={"required": _("No IPv4 subnet has been entered")}, - max_length=20, - required=False, - ) - subnet6 = forms.CharField( - error_messages={"required": _("No IPv6 subnet has been entered")}, - max_length=42, - required=False, - ) - forward = forms.CharField(max_length=100) + subnet = forms.CharField(label=_("IPv4 subnet"), max_length=20, required=False) + subnet6 = forms.CharField(label=_("IPv6 subnet"), max_length=42, required=False) + forward = forms.ChoiceField(choices=[(mode, mode) for mode in MODES]) dhcp4 = forms.BooleanField(required=False) + enable_ipv6 = forms.BooleanField(required=False) dhcp6 = forms.BooleanField(required=False) - fixed = forms.BooleanField(required=False) - bridge_name = forms.CharField(max_length=20, required=False) + bridge_name = forms.CharField(label=_("Bridge or interface"), max_length=20, required=False) openvswitch = forms.BooleanField(required=False) + def __init__(self, *args, networks=(), **kwargs): + super().__init__(*args, **kwargs) + self.networks = networks + def clean_name(self): name = self.cleaned_data["name"] - have_symbol = re.match(r"^[a-zA-Z0-9\.\_\-]+$", name) - if not have_symbol: + if not re.fullmatch(r"[a-zA-Z0-9._-]+", name): raise forms.ValidationError( _("The pool name must not contain any special characters") ) - elif len(name) > 20: - raise forms.ValidationError( - _("The pool name must not exceed 20 characters") - ) + if any(net["name"] == name for net in self.networks): + raise forms.ValidationError(_("This host has a network of that name"), code="taken") return name def clean_subnet(self): return checked_subnet(self.cleaned_data["subnet"], 4) - def clean(self): - cleaned = super().clean() - # one fixed address per address of the DHCP range - subnet = cleaned.get("subnet") - if cleaned.get("fixed") and subnet and ipaddress.ip_network(subnet, strict=False).prefixlen < 24: - self.add_error("fixed", _("Fixed addresses need an IPv4 subnet of /24 or smaller")) - return cleaned - def clean_subnet6(self): return checked_subnet(self.cleaned_data["subnet6"], 6) def clean_bridge_name(self): bridge_name = self.cleaned_data["bridge_name"] - if self.cleaned_data["forward"] in ["bridge", "macvtap"]: - have_symbol = re.match(r"^[a-zA-Z0-9\.\_\:\-]+$", bridge_name) - if not have_symbol: - raise forms.ValidationError( - _("The pool bridge name must not contain any special characters") - ) - elif len(bridge_name) > 20: - raise forms.ValidationError( - _("The pool bridge name must not exceed 20 characters") - ) - return bridge_name + if bridge_name and not re.fullmatch(r"[a-zA-Z0-9._:-]+", bridge_name): + raise forms.ValidationError( + _("The pool bridge name must not contain any special characters") + ) + return bridge_name + + def clean(self): + data = super().clean() + mode = data.get("forward") + if mode in ("bridge", "macvtap"): + # the outside network gives the addresses: none of libvirt's + data.update(subnet="", subnet6="", dhcp4=False, dhcp6=False) + if not data.get("bridge_name") and "bridge_name" not in self.errors: + self.add_error("bridge_name", _("This field is required.")) + else: + data["bridge_name"] = "" + if mode in ("nat", "route") and not data.get("subnet") and "subnet" not in self.errors: + self.add_error("subnet", _("This field is required.")) + if not data.get("enable_ipv6"): + data["subnet6"] = "" + elif not data.get("subnet6") and "subnet6" not in self.errors: + self.add_error("subnet6", _("This field is required.")) + if mode != "bridge": + data["openvswitch"] = False + for field in ("subnet", "subnet6"): + if data.get(field) and field not in self.errors: + subnet = ipaddress.ip_network(data[field], strict=False) + for net in self.networks: + taken = next((ip["subnet"] for ip in net["ips"] + if ip["subnet"] and ip["subnet"].version == subnet.version and ip["subnet"].overlaps(subnet)), None) + if taken: + self.add_error(field, _("Network %(net)s has %(subnet)s") % {"net": net["name"], "subnet": taken}) + break + return data diff --git a/networks/templates/create_net_block.html b/networks/templates/create_net_block.html index 4caf6db6..9a180b77 100644 --- a/networks/templates/create_net_block.html +++ b/networks/templates/create_net_block.html @@ -45,16 +45,10 @@ -
- -
- -
-
- +
diff --git a/networks/templates/modify_ipv4_fixed_address.html b/networks/templates/modify_ipv4_fixed_address.html index 8a93108a..7f5ada1b 100644 --- a/networks/templates/modify_ipv4_fixed_address.html +++ b/networks/templates/modify_ipv4_fixed_address.html @@ -18,7 +18,7 @@
- +
@@ -41,7 +41,7 @@
diff --git a/networks/templates/modify_ipv6_fixed_address.html b/networks/templates/modify_ipv6_fixed_address.html index d09e3f4d..bf782bda 100644 --- a/networks/templates/modify_ipv6_fixed_address.html +++ b/networks/templates/modify_ipv6_fixed_address.html @@ -18,7 +18,7 @@
- +
@@ -41,7 +41,7 @@
diff --git a/networks/templates/network.html b/networks/templates/network.html index 64e1b95f..4920d182 100644 --- a/networks/templates/network.html +++ b/networks/templates/network.html @@ -86,27 +86,27 @@ {% endif %}
{% trans "Network" %}
-
{{ ipv4_network|default:"-" }}
+
{{ ipv4.subnet|default:"-" }}
{% trans "DHCP" %}:
- {% if ipv4_dhcp_range_start and ipv4_dhcp_range_end %} + {% if ipv4.dhcp %} {% trans "ON" %} {% else %} {% trans "OFF" %} {% endif %}
- {% if ipv4_dhcp_range_start and ipv4_dhcp_range_end %} + {% if ipv4.range %} {% if state %}
{% trans "Start" %}
-
{{ ipv4_dhcp_range_start }}
+
{{ ipv4.range.0 }}
{% trans "End" %}
-
{{ ipv4_dhcp_range_end }}
+
{{ ipv4.range.1 }}
{% else %}
{% csrf_token %}
{% trans "Start" %}
-
+
{% trans "End" %}
-
+
- +
{% endif %} {% endif %} - {% if ipv4_dhcp_range_start and ipv4_dhcp_range_end %} - {% if state %} - {% include 'modify_ipv4_fixed_address.html' %} - {% endif %} + {% if ipv4.dhcp %} + {% include 'modify_ipv4_fixed_address.html' %}
{% trans "IPv4 Fixed Addresses" %}
{% endif %} - {% if ipv4_fixed_address %} + {% if ipv4.hosts %}