diff --git a/networks/tests/test_dhcp_range.py b/networks/tests/test_dhcp_range.py new file mode 100644 index 00000000..42e07690 --- /dev/null +++ b/networks/tests/test_dhcp_range.py @@ -0,0 +1,64 @@ +"""A network's DHCP range is changed while the network is stopped, and the +page says so only when it was: a running network, a network without a +range or an unknown address family is refused with the reason. Runs on a +private libvirt test driver: "test:///default" is shared by the whole +process.""" + +import tempfile +from unittest.mock import patch + +import libvirt +from computes.models import Compute +from django.contrib.auth import get_user_model +from django.contrib.messages import get_messages +from django.test import TestCase +from django.urls import reverse +from lxml import etree + +NODE = """ + lab + + + plain +""" + + +class DhcpRangeTests(TestCase): + def setUp(self): + node = tempfile.NamedTemporaryFile("w", suffix=".xml") + self.addCleanup(node.close) + node.write(NODE) + node.flush() + self.conn = libvirt.open(f"test://{node.name}") + self.addCleanup(self.conn.close) + patcher = patch("vrtManager.connection.connection_manager.get_connection", return_value=self.conn) + patcher.start() + self.addCleanup(patcher.stop) + self.compute = Compute.objects.create(name="dhcp", hostname="localhost", login="", password="", type=4) + self.client.force_login(get_user_model().objects.create_superuser("dhcp", "dhcp@example.com", "pw")) + + def change(self, network, family="ipv4"): + response = self.client.post(reverse("network", args=[self.compute.id, network]), { + "modify_dhcp_range": "", "range_start": "192.0.2.30", "range_end": "192.0.2.40", "family": family}) + return [str(m) for m in get_messages(response.wsgi_request)] + + def dhcp_range(self, network): + xml = etree.fromstring(self.conn.networkLookupByName(network).XMLDesc(0)) + return xml.xpath("ip/dhcp/range/@start|ip/dhcp/range/@end") + + def test_a_stopped_network_gets_the_new_range(self): + self.conn.networkLookupByName("lab").destroy() + self.assertEqual(self.change("lab"), ["IPV4 DHCP Range is Changed."]) + self.assertEqual(self.dhcp_range("lab"), ["192.0.2.30", "192.0.2.40"]) + + def test_a_running_network_is_refused(self): + messages = self.change("lab") + self.assertEqual(messages, ["Stop network lab to change its DHCP range"]) + self.assertEqual(self.dhcp_range("lab"), ["192.0.2.10", "192.0.2.20"]) + + def test_a_network_without_a_range_or_an_unknown_family_is_refused(self): + self.conn.networkLookupByName("lab").destroy() + self.conn.networkLookupByName("plain").destroy() + self.assertEqual(self.change("plain"), ["Network plain has no ipv4 DHCP range"]) + self.assertEqual(self.change("lab", family="ipx"), ["Network lab has no ipx DHCP range"]) + self.assertEqual(self.dhcp_range("lab"), ["192.0.2.10", "192.0.2.20"]) diff --git a/vrtManager/network.py b/vrtManager/network.py index 646333e7..0044b7c1 100644 --- a/vrtManager/network.py +++ b/vrtManager/network.py @@ -267,15 +267,17 @@ class wvmNetwork(wvmConnect): return result def modify_dhcp_range(self, range_start, range_end, family="ipv4"): - if not self.is_active(): - tree = etree.fromstring(self._XMLDesc(0)) - if family == "ipv4": - dhcp_range = tree.xpath("./ip[not(@family='ipv6')]/dhcp/range") - if family == "ipv6": - dhcp_range = tree.xpath("./ip[@family='ipv6']/dhcp/range") - dhcp_range[0].set("start", range_start) - dhcp_range[0].set("end", range_end) - self.wvm.networkDefineXML(etree.tostring(tree).decode()) + """Refused while the network runs: the running network would keep the old range.""" + if self.is_active(): + raise util.OperationError(f"Stop network {self.net.name()} to change its DHCP range") + tree = etree.fromstring(self._XMLDesc(0)) + ip = {"ipv4": "ip[not(@family='ipv6')]", "ipv6": "ip[@family='ipv6']"}.get(family) + dhcp_range = tree.xpath(f"{ip}/dhcp/range") if ip else [] + if not dhcp_range: + raise util.OperationError(f"Network {self.net.name()} has no {family} DHCP range") + dhcp_range[0].set("start", range_start) + dhcp_range[0].set("end", range_end) + self.wvm.networkDefineXML(etree.tostring(tree).decode()) def delete_fixed_address(self, ip, family="ipv4"): tree = etree.fromstring(self._XMLDesc(0))