Merge ~ack/maas:1930227-full-netmask-fix into maas:master

Proposed by Alberto Donato
Status: Merged
Approved by: Alberto Donato
Approved revision: 2b137cd95c26c6001f4a23d447c334aa374292e5
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ack/maas:1930227-full-netmask-fix
Merge into: maas:master
Diff against target: 221 lines (+105/-31)
4 files modified
src/metadataserver/builtin_scripts/network.py (+11/-6)
src/metadataserver/builtin_scripts/tests/test_network.py (+26/-0)
src/provisioningserver/utils/network.py (+28/-21)
src/provisioningserver/utils/tests/test_network.py (+40/-4)
Reviewer Review Type Date Requested Status
Björn Tillenius Approve
MAAS Lander Approve
Review via email: mp+403519@code.launchpad.net

Commit message

LP:1930227 - fix netmask for /32 and /128 to match actual subnet

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b 1930227-full-netmask-fix lp:~ack/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 2b137cd95c26c6001f4a23d447c334aa374292e5

review: Approve
Revision history for this message
Björn Tillenius (bjornt) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/metadataserver/builtin_scripts/network.py b/src/metadataserver/builtin_scripts/network.py
2index 44d8691..ed1e0f1 100644
3--- a/src/metadataserver/builtin_scripts/network.py
4+++ b/src/metadataserver/builtin_scripts/network.py
5@@ -16,6 +16,7 @@ from maasserver.models.vlan import VLAN
6 from maasserver.utils.orm import transactional
7 from provisioningserver.logger import get_maas_logger
8 from provisioningserver.utils import flatten, sorttop
9+from provisioningserver.utils.network import fix_link_addresses
10 from provisioningserver.utils.twisted import synchronous
11
12 maaslog = get_maas_logger("metadataserver.network")
13@@ -160,13 +161,17 @@ def update_interface(node, name, data, address_extra, hints=None):
14 # same, hard-coded OpenBMC MAC address.
15 return None
16
17- links = [
18- address.copy()
19- for address in network["addresses"]
20- if address["scope"] == "global"
21- ]
22- for link in links:
23+ links = []
24+ for address in network["addresses"]:
25+ if address["scope"] != "global":
26+ continue
27+ link = address.copy()
28 link.update(address_extra.get(link["address"], {}))
29+ links.append(link)
30+ # fix networks that have a /32 or /128 netmask to the closest wider known
31+ # subnet
32+ fix_link_addresses(links)
33+
34 if network["vlan"]:
35 return update_vlan_interface(node, name, network, links)
36 elif network["bond"] or network["bridge"]:
37diff --git a/src/metadataserver/builtin_scripts/tests/test_network.py b/src/metadataserver/builtin_scripts/tests/test_network.py
38index c8667d5..1162ba4 100644
39--- a/src/metadataserver/builtin_scripts/tests/test_network.py
40+++ b/src/metadataserver/builtin_scripts/tests/test_network.py
41@@ -2419,6 +2419,32 @@ class TestUpdateInterfaces(MAASServerTestCase, UpdateInterfacesMixin):
42 self.assertTrue(eth0.enabled)
43 self.assertEqual(data.networks["eth0"].hwaddr, eth0.mac_address)
44
45+ def test_fixes_link_netmasks(self):
46+ controller = self.create_empty_controller(with_empty_script_sets=True)
47+ data = FakeCommissioningData()
48+ card = data.create_network_card()
49+ card.vendor = factory.make_name("vendor")
50+ card.product = factory.make_name("product")
51+ card.firmware_version = factory.make_name("firmware_version")
52+ network = data.create_physical_network("eth0", card=card)
53+ network.addresses = [
54+ LXDAddress("192.168.0.1", 24),
55+ LXDAddress("192.168.0.2", 32),
56+ LXDAddress("2001::aaaa:1", 112),
57+ LXDAddress("2001::aaaa:2", 128),
58+ ]
59+ self.update_interfaces(controller, data)
60+ eth0 = Interface.objects.get(name="eth0", node=controller)
61+ self.assertItemsEqual(
62+ eth0.ip_addresses.values_list("ip", "subnet__cidr"),
63+ [
64+ ("192.168.0.1", "192.168.0.0/24"),
65+ ("192.168.0.2", "192.168.0.0/24"),
66+ ("2001::aaaa:1", "2001::aaaa:0/112"),
67+ ("2001::aaaa:2", "2001::aaaa:0/112"),
68+ ],
69+ )
70+
71
72 class TestUpdateInterfacesWithHints(
73 MAASTransactionServerTestCase, UpdateInterfacesMixin
74diff --git a/src/provisioningserver/utils/network.py b/src/provisioningserver/utils/network.py
75index 794521b..d8c781e 100644
76--- a/src/provisioningserver/utils/network.py
77+++ b/src/provisioningserver/utils/network.py
78@@ -939,6 +939,11 @@ def get_mac_organization(mac):
79 def fix_link_addresses(links):
80 """Fix the addresses defined in `links`.
81
82+ Each link entry can contain addresses in the form:
83+ {"address": "1.2.3.4/24", ...}
84+ or
85+ {"address": "1.2.3.4", "netmask": 24, ...}
86+
87 Some address will have a prefixlen of 32 or 128 depending if IPv4 or IPv6.
88 Fix those address to fall within a subnet that is already defined in
89 another link. The addresses that get fixed will be placed into the smallest
90@@ -952,7 +957,10 @@ def fix_link_addresses(links):
91 # Loop through and build a list of subnets where the prefixlen is not
92 # 32 or 128 for IPv4 and IPv6 respectively.
93 for link in links:
94- ip_addr = IPNetwork(link["address"])
95+ if "netmask" in link:
96+ ip_addr = IPNetwork(f"{link['address']}/{link['netmask']}")
97+ else:
98+ ip_addr = IPNetwork(link["address"])
99 if ip_addr.version == 4:
100 if ip_addr.prefixlen == 32:
101 links_v4.append(link)
102@@ -964,26 +972,25 @@ def fix_link_addresses(links):
103 else:
104 subnets_v6.append(ip_addr.cidr)
105
106- # Sort the subnets so the smallest prefixlen is first.
107- subnets_v4 = sorted(subnets_v4, key=attrgetter("prefixlen"), reverse=True)
108- subnets_v6 = sorted(subnets_v6, key=attrgetter("prefixlen"), reverse=True)
109-
110- # Fix all addresses that have prefixlen of 32 or 128 that fit in inside
111- # one of the already defined subnets.
112- for link in links_v4:
113- ip_addr = IPNetwork(link["address"])
114- for subnet in subnets_v4:
115- if ip_addr.ip in subnet:
116- ip_addr.prefixlen = subnet.prefixlen
117- link["address"] = str(ip_addr)
118- break
119- for link in links_v6:
120- ip_addr = IPNetwork(link["address"])
121- for subnet in subnets_v6:
122- if ip_addr.ip in subnet:
123- ip_addr.prefixlen = subnet.prefixlen
124- link["address"] = str(ip_addr)
125- break
126+ for links, subnets in ((links_v4, subnets_v4), (links_v6, subnets_v6)):
127+ subnets = sorted(subnets, key=attrgetter("prefixlen"), reverse=True)
128+ # Fix all addresses that have prefixlen of 32 or 128 that fit in inside
129+ # one of the already defined subnets.
130+ for link in links:
131+ has_separate_netmask = "netmask" in link
132+ if has_separate_netmask:
133+ ip_addr = IPNetwork(f"{link['address']}/{link['netmask']}")
134+ else:
135+ ip_addr = IPNetwork(link["address"])
136+
137+ for subnet in subnets:
138+ if ip_addr.ip in subnet:
139+ if has_separate_netmask:
140+ link["netmask"] = subnet.prefixlen
141+ else:
142+ ip_addr.prefixlen = subnet.prefixlen
143+ link["address"] = str(ip_addr)
144+ break
145
146
147 def fix_link_gateways(links, iproute_info):
148diff --git a/src/provisioningserver/utils/tests/test_network.py b/src/provisioningserver/utils/tests/test_network.py
149index 6a0d438..3b96869 100644
150--- a/src/provisioningserver/utils/tests/test_network.py
151+++ b/src/provisioningserver/utils/tests/test_network.py
152@@ -1,15 +1,12 @@
153 # Copyright 2014-2016 Canonical Ltd. This software is licensed under the
154 # GNU Affero General Public License version 3 (see the file LICENSE).
155
156-"""Tests for network helpers."""
157-
158-
159 import itertools
160 import json
161 import socket
162 from socket import EAI_BADFLAGS, EAI_NODATA, EAI_NONAME, gaierror, IPPROTO_TCP
163 from typing import List
164-from unittest import mock
165+from unittest import mock, TestCase
166 from unittest.mock import Mock
167
168 from netaddr import EUI, IPAddress, IPNetwork, IPRange
169@@ -57,6 +54,7 @@ from provisioningserver.utils.network import (
170 enumerate_ipv4_addresses,
171 find_ip_via_arp,
172 find_mac_via_arp,
173+ fix_link_addresses,
174 format_eui,
175 generate_mac_address,
176 get_all_addresses_for_interface,
177@@ -94,6 +92,44 @@ from provisioningserver.utils.network import (
178 from provisioningserver.utils.shell import get_env_with_locale
179
180
181+class TestFixLinkAddresses(TestCase):
182+ def test_fixes_with_cidr(self):
183+ links = [
184+ {"address": "1.2.3.1/24", "mode": "static"},
185+ {"address": "1.2.3.4/32", "mode": "static"},
186+ {"address": "2001::1/96", "mode": "static"},
187+ {"address": "2001::123/128", "mode": "static"},
188+ ]
189+ fix_link_addresses(links)
190+ self.assertEqual(
191+ links,
192+ [
193+ {"address": "1.2.3.1/24", "mode": "static"},
194+ {"address": "1.2.3.4/24", "mode": "static"},
195+ {"address": "2001::1/96", "mode": "static"},
196+ {"address": "2001::123/96", "mode": "static"},
197+ ],
198+ )
199+
200+ def test_fixes_with_netmask(self):
201+ links = [
202+ {"address": "1.2.3.1", "netmask": 24, "mode": "static"},
203+ {"address": "1.2.3.4", "netmask": 32, "mode": "static"},
204+ {"address": "2001::1", "netmask": 96, "mode": "static"},
205+ {"address": "2001::123", "netmask": 128, "mode": "static"},
206+ ]
207+ fix_link_addresses(links)
208+ self.assertEqual(
209+ links,
210+ [
211+ {"address": "1.2.3.1", "netmask": 24, "mode": "static"},
212+ {"address": "1.2.3.4", "netmask": 24, "mode": "static"},
213+ {"address": "2001::1", "netmask": 96, "mode": "static"},
214+ {"address": "2001::123", "netmask": 96, "mode": "static"},
215+ ],
216+ )
217+
218+
219 class TestMakeNetwork(MAASTestCase):
220 def test_constructs_IPNetwork(self):
221 network = make_network("10.22.82.0", 24)

Subscribers

People subscribed via source and target branches