Merge ~ack/maas:1902425-again-2.8 into maas:2.8

Proposed by Alberto Donato
Status: Merged
Approved by: Alberto Donato
Approved revision: 54a6395ec631e1b8612b27a986bc7f07436ea9b6
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ack/maas:1902425-again-2.8
Merge into: maas:2.8
Diff against target: 413 lines (+154/-62)
4 files modified
src/maasserver/models/interface.py (+7/-20)
src/maasserver/models/node.py (+10/-6)
src/maasserver/models/tests/test_interface.py (+12/-22)
src/maasserver/models/tests/test_node.py (+125/-14)
Reviewer Review Type Date Requested Status
MAAS Lander Approve
MAAS Maintainers Pending
Review via email: mp+400090@code.launchpad.net

Commit message

LP: #1902425 - always create neighbour entries for IPs in use.

Rework Interface.update_neighbour move check for neighbour discovery to call sites.

This also changes IP claim checks to always create a neighbour entry when the IP is found to be in use (even if the rack interface doesn't have neighbour discovery enabled).

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

UNIT TESTS
-b 1902425-again-2.8 lp:~ack/maas/+git/maas into -b 2.8 lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/9590/console
COMMIT: be9256f379f35aa6ccc1512f0033ca2dce511d2c

review: Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b 1902425-again-2.8 lp:~ack/maas/+git/maas into -b 2.8 lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 54a6395ec631e1b8612b27a986bc7f07436ea9b6

review: Approve

There was an error fetching revisions from git servers. Please try again in a few minutes. If the problem persists, contact Launchpad support.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/models/interface.py b/src/maasserver/models/interface.py
2index 6415344..de96c89 100644
3--- a/src/maasserver/models/interface.py
4+++ b/src/maasserver/models/interface.py
5@@ -111,7 +111,7 @@ class InterfaceQueriesMixin(MAASQueriesMixin):
6 specifiers,
7 specifier_types=specifier_types,
8 separator=separator,
9- **kwargs
10+ **kwargs,
11 )
12
13 def _add_interface_id_query(self, current_q, op, item):
14@@ -1522,20 +1522,10 @@ class Interface(CleanSave, TimestampedModel):
15 % (self.get_log_string(), vid, vlan.fabric.get_name())
16 )
17
18- def update_neighbour(self, neighbour_json: dict):
19- """Updates the neighbour table for this interface.
20-
21- Input is expected to be the neighbour JSON from the controller.
22- """
23- # Circular imports
24+ def update_neighbour(self, ip, mac, time, vid=None):
25+ """Updates the neighbour table for this interface."""
26 from maasserver.models.neighbour import Neighbour
27
28- if self.neighbour_discovery_state is False:
29- return None
30- ip = neighbour_json["ip"]
31- mac = neighbour_json["mac"]
32- time = neighbour_json["time"]
33- vid = neighbour_json.get("vid", None)
34 deleted = Neighbour.objects.delete_and_log_obsolete_neighbours(
35 ip, mac, interface=self, vid=vid
36 )
37@@ -1552,13 +1542,10 @@ class Interface(CleanSave, TimestampedModel):
38 # generated a log statement about this neighbour.
39 if not deleted:
40 maaslog.info(
41- "%s: New MAC, IP binding observed%s: %s, %s"
42- % (
43- self.get_log_string(),
44- Neighbour.objects.get_vid_log_snippet(vid),
45- mac,
46- ip,
47- )
48+ f"{self.get_log_string()}: "
49+ "New MAC, IP binding "
50+ f"observed{Neighbour.objects.get_vid_log_snippet(vid)}: "
51+ f"{mac}, {ip}"
52 )
53 else:
54 neighbour.time = time
55diff --git a/src/maasserver/models/node.py b/src/maasserver/models/node.py
56index 3c521be..c8df076 100644
57--- a/src/maasserver/models/node.py
58+++ b/src/maasserver/models/node.py
59@@ -4413,11 +4413,9 @@ class Node(CleanSave, TimestampedModel):
60 rack_interface = rack_interface.order_by("id")
61 rack_interface = rack_interface.first()
62 rack_interface.update_neighbour(
63- {
64- "ip": ip_obj.ip,
65- "mac": ip_result.get("mac_address"),
66- "time": time.time(),
67- }
68+ ip_obj.ip,
69+ ip_result.get("mac_address"),
70+ time.time(),
71 )
72 ip_obj.ip = None
73 ip_obj.temp_expires_on = None
74@@ -6728,8 +6726,14 @@ class Controller(Node):
75 for neighbour in neighbours:
76 interface = interfaces.get(neighbour["interface"], None)
77 if interface is not None:
78- interface.update_neighbour(neighbour)
79 vid = neighbour.get("vid", None)
80+ if interface.neighbour_discovery_state:
81+ interface.update_neighbour(
82+ neighbour["ip"],
83+ neighbour["mac"],
84+ neighbour["time"],
85+ vid=vid,
86+ )
87 if vid is not None:
88 interface.report_vid(vid)
89
90diff --git a/src/maasserver/models/tests/test_interface.py b/src/maasserver/models/tests/test_interface.py
91index 7c9ee39..71a5553 100644
92--- a/src/maasserver/models/tests/test_interface.py
93+++ b/src/maasserver/models/tests/test_interface.py
94@@ -1364,7 +1364,7 @@ class InterfaceTest(MAASServerTestCase):
95 self.assertEquals(0, interface.link_speed)
96
97
98-class InterfaceUpdateNeighbourTest(MAASServerTestCase):
99+class TestInterfaceUpdateNeighbour(MAASServerTestCase):
100 """Tests for `Interface.update_neighbour`."""
101
102 def make_neighbour_json(self, ip=None, mac=None, time=None, **kwargs):
103@@ -1385,22 +1385,15 @@ class InterfaceUpdateNeighbourTest(MAASServerTestCase):
104 vid = None
105 return {"ip": ip, "mac": mac, "time": time, "vid": vid}
106
107- def test_ignores_updates_if_neighbour_discovery_state_is_false(self):
108+ def test_adds_new_neighbour(self):
109 iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
110- iface.update_neighbour(self.make_neighbour_json())
111- self.assertThat(Neighbour.objects.count(), Equals(0))
112-
113- def test_adds_new_neighbour_if_neighbour_discovery_state_is_true(self):
114- iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
115- iface.neighbour_discovery_state = True
116- iface.update_neighbour(self.make_neighbour_json())
117+ iface.update_neighbour(**self.make_neighbour_json())
118 self.assertThat(Neighbour.objects.count(), Equals(1))
119
120 def test_updates_existing_neighbour(self):
121 iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
122- iface.neighbour_discovery_state = True
123 json = self.make_neighbour_json()
124- iface.update_neighbour(json)
125+ iface.update_neighbour(**json)
126 neighbour = get_one(Neighbour.objects.all())
127 # Pretend this was updated one day ago.
128 yesterday = datetime.datetime.now() - datetime.timedelta(days=1)
129@@ -1411,7 +1404,7 @@ class InterfaceUpdateNeighbourTest(MAASServerTestCase):
130 Equals(int(yesterday.timestamp())),
131 )
132 json["time"] += 1
133- iface.update_neighbour(json)
134+ iface.update_neighbour(**json)
135 neighbour = reload_object(neighbour)
136 self.assertThat(Neighbour.objects.count(), Equals(1))
137 self.assertThat(neighbour.time, Equals(json["time"]))
138@@ -1423,13 +1416,12 @@ class InterfaceUpdateNeighbourTest(MAASServerTestCase):
139
140 def test_replaces_obsolete_neighbour(self):
141 iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
142- iface.neighbour_discovery_state = True
143 json = self.make_neighbour_json()
144- iface.update_neighbour(json)
145+ iface.update_neighbour(**json)
146 # Have a different MAC address claim ownership of the IP.
147 json["time"] += 1
148 json["mac"] = factory.make_mac_address()
149- iface.update_neighbour(json)
150+ iface.update_neighbour(**json)
151 self.assertThat(Neighbour.objects.count(), Equals(1))
152 self.assertThat(
153 list(Neighbour.objects.all())[0].mac_address, Equals(json["mac"])
154@@ -1440,10 +1432,8 @@ class InterfaceUpdateNeighbourTest(MAASServerTestCase):
155
156 def test_logs_new_binding(self):
157 iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
158- iface.neighbour_discovery_state = True
159- json = self.make_neighbour_json()
160 with FakeLogger("maas.interface") as maaslog:
161- iface.update_neighbour(json)
162+ iface.update_neighbour(**self.make_neighbour_json())
163 self.assertDocTestMatches(
164 "...: New MAC, IP binding observed...", maaslog.output
165 )
166@@ -1452,18 +1442,18 @@ class InterfaceUpdateNeighbourTest(MAASServerTestCase):
167 iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
168 iface.neighbour_discovery_state = True
169 json = self.make_neighbour_json()
170- iface.update_neighbour(json)
171+ iface.update_neighbour(**json)
172 # Have a different MAC address claim ownership of the IP.
173 json["time"] += 1
174 json["mac"] = factory.make_mac_address()
175 with FakeLogger("maas.neighbour") as maaslog:
176- iface.update_neighbour(json)
177+ iface.update_neighbour(**json)
178 self.assertDocTestMatches(
179 "...: IP address...moved from...to...", maaslog.output
180 )
181
182
183-class InterfaceUpdateMDNSEntryTest(MAASServerTestCase):
184+class TestInterfaceUpdateMDNSEntry(MAASServerTestCase):
185 """Tests for `Interface.update_mdns_entry`."""
186
187 def make_mdns_entry_json(self, ip=None, hostname=None):
188@@ -1478,7 +1468,7 @@ class InterfaceUpdateMDNSEntryTest(MAASServerTestCase):
189
190 def test_ignores_updates_if_mdns_discovery_state_is_false(self):
191 iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
192- iface.update_neighbour(self.make_mdns_entry_json())
193+ iface.update_mdns_entry(self.make_mdns_entry_json())
194 self.assertThat(MDNS.objects.count(), Equals(0))
195
196 def test_adds_new_entry_if_mdns_discovery_state_is_true(self):
197diff --git a/src/maasserver/models/tests/test_node.py b/src/maasserver/models/tests/test_node.py
198index b9ca303..583478c 100644
199--- a/src/maasserver/models/tests/test_node.py
200+++ b/src/maasserver/models/tests/test_node.py
201@@ -80,6 +80,7 @@ from maasserver.exceptions import (
202 StaticIPAddressExhaustion,
203 )
204 from maasserver.models import (
205+ BMCRoutableRackControllerRelationship,
206 BondInterface,
207 BootResource,
208 BridgeInterface,
209@@ -87,11 +88,13 @@ from maasserver.models import (
210 Controller,
211 Device,
212 Domain,
213+ Event,
214 EventType,
215 Fabric,
216 Interface,
217 LicenseKey,
218 Machine,
219+ Neighbour,
220 Node,
221 )
222 from maasserver.models import (
223@@ -101,6 +104,7 @@ from maasserver.models import (
224 RAID,
225 RegionController,
226 RegionRackRPCConnection,
227+ ResourcePool,
228 Service,
229 StaticIPAddress,
230 Subnet,
231@@ -109,12 +113,10 @@ from maasserver.models import (
232 VLANInterface,
233 VolumeGroup,
234 )
235-from maasserver.models import Bcache
236+from maasserver.models import Bcache, BMC
237 from maasserver.models import bmc as bmc_module
238 from maasserver.models import node as node_module
239-from maasserver.models.bmc import BMC, BMCRoutableRackControllerRelationship
240 from maasserver.models.config import NetworkDiscoveryConfig
241-from maasserver.models.event import Event
242 import maasserver.models.interface as interface_module
243 from maasserver.models.node import (
244 DefaultGateways,
245@@ -123,7 +125,6 @@ from maasserver.models.node import (
246 PowerInfo,
247 )
248 from maasserver.models.partitiontable import PARTITION_TABLE_EXTRA_SPACE
249-from maasserver.models.resourcepool import ResourcePool
250 from maasserver.models.signals import power as node_query
251 from maasserver.models.timestampedmodel import now
252 from maasserver.node_status import (
253@@ -8593,6 +8594,74 @@ class TestNode_Start(MAASTransactionServerTestCase):
254 self.assertThat(auto_ip.ip, Equals(third_ip.ip))
255 self.assertThat(auto_ip.temp_expires_on, Is(None))
256
257+ def test_claims_auto_ip_addresses_skips_used_ip_discovery_disabled(self):
258+ user = factory.make_User()
259+ node = self.make_acquired_node_with_interface(
260+ user, power_type="manual"
261+ )
262+ node_interface = node.get_boot_interface()
263+ [auto_ip] = node_interface.ip_addresses.filter(
264+ alloc_type=IPADDRESS_TYPE.AUTO
265+ )
266+
267+ # Create a rack controller that has an interface on the same subnet
268+ # as the node. Don't enable neighbour discovery
269+ rack = factory.make_RackController()
270+ rack.interface_set.all().delete()
271+ rackif = factory.make_Interface(vlan=node_interface.vlan, node=rack)
272+ rackif_ip = factory.pick_ip_in_Subnet(auto_ip.subnet)
273+ rackif.link_subnet(
274+ INTERFACE_LINK_TYPE.STATIC, auto_ip.subnet, rackif_ip
275+ )
276+
277+ # Mock the rack controller connected to the region controller.
278+ client = Mock()
279+ client.ident = rack.system_id
280+ self.patch(node_module, "getAllClients").return_value = [client]
281+
282+ # Must be executed in a transaction as `allocate_new` uses savepoints.
283+ with transaction.atomic():
284+ # Get two IPs and remove them so they're unknown
285+ ip = StaticIPAddress.objects.allocate_new(
286+ subnet=auto_ip.subnet, alloc_type=IPADDRESS_TYPE.AUTO
287+ )
288+ ip1 = ip.ip
289+ ip.delete()
290+ ip = StaticIPAddress.objects.allocate_new(
291+ subnet=auto_ip.subnet,
292+ alloc_type=IPADDRESS_TYPE.AUTO,
293+ exclude_addresses=[ip1],
294+ )
295+ ip2 = ip.ip
296+ ip.delete()
297+
298+ client.side_effect = [
299+ defer.succeed(
300+ {
301+ "ip_addresses": [
302+ {
303+ "ip_address": ip1,
304+ "used": True,
305+ "mac_address": factory.make_mac_address(),
306+ }
307+ ]
308+ }
309+ ),
310+ defer.succeed(
311+ {"ip_addresses": [{"ip_address": ip2, "used": False}]}
312+ ),
313+ ]
314+
315+ with post_commit_hooks:
316+ node.start(user)
317+
318+ auto_ip = reload_object(auto_ip)
319+ self.assertThat(auto_ip.ip, Equals(ip2))
320+ self.assertThat(auto_ip.temp_expires_on, Is(None))
321+ self.assertCountEqual(
322+ [ip1], Neighbour.objects.values_list("ip", flat=True)
323+ )
324+
325 def test_claims_auto_ip_addresses_retries_on_failure_from_rack(self):
326 user = factory.make_User()
327 node = self.make_acquired_node_with_interface(
328@@ -9790,21 +9859,51 @@ class TestControllerUpdateDiscoveryState(MAASServerTestCase):
329 class TestReportNeighbours(MAASServerTestCase):
330 """Tests for `Controller.report_neighbours()."""
331
332- def test_calls_update_neighbour_for_each_neighbour(self):
333+ def test_no_update_neighbours_calls_if_discovery_disabled(self):
334 rack = factory.make_RackController()
335 factory.make_Interface(name="eth0", node=rack)
336- factory.make_Interface(name="eth1", node=rack)
337 update_neighbour = self.patch(
338 interface_module.Interface, "update_neighbour"
339 )
340 neighbours = [
341- {"interface": "eth0", "mac": factory.make_mac_address()},
342- {"interface": "eth1", "mac": factory.make_mac_address()},
343+ {
344+ "interface": "eth0",
345+ "mac": factory.make_mac_address(),
346+ "ip": factory.make_ipv4_address(),
347+ "time": datetime.now(),
348+ }
349+ ]
350+ rack.report_neighbours(neighbours)
351+ update_neighbour.assert_not_called()
352+
353+ def test_calls_update_neighbour_for_each_neighbour(self):
354+ rack = factory.make_RackController()
355+ if1 = factory.make_Interface(name="eth0", node=rack)
356+ if1.neighbour_discovery_state = True
357+ if1.save()
358+ if2 = factory.make_Interface(name="eth1", node=rack)
359+ if2.neighbour_discovery_state = True
360+ if2.save()
361+ update_neighbour = self.patch(
362+ interface_module.Interface, "update_neighbour"
363+ )
364+ neighbours = [
365+ {
366+ "interface": "eth0",
367+ "mac": factory.make_mac_address(),
368+ "ip": factory.make_ipv4_address(),
369+ "time": datetime.now(),
370+ },
371+ {
372+ "interface": "eth1",
373+ "mac": factory.make_mac_address(),
374+ "ip": factory.make_ipv4_address(),
375+ "time": datetime.now(),
376+ },
377 ]
378 rack.report_neighbours(neighbours)
379- self.assertThat(
380- update_neighbour,
381- MockCallsMatch(*[call(neighbour) for neighbour in neighbours]),
382+ update_neighbour.assert_has_calls(
383+ [call(n["ip"], n["mac"], n["time"], vid=None) for n in neighbours]
384 )
385
386 def test_calls_report_vid_for_each_vid(self):
387@@ -9815,11 +9914,23 @@ class TestReportNeighbours(MAASServerTestCase):
388 self.patch(interface_module.Interface, "update_neighbour")
389 report_vid = self.patch(interface_module.Interface, "report_vid")
390 neighbours = [
391- {"interface": "eth0", "mac": factory.make_mac_address(), "vid": 3},
392- {"interface": "eth1", "mac": factory.make_mac_address(), "vid": 7},
393+ {
394+ "interface": "eth0",
395+ "ip": factory.make_ipv4_address(),
396+ "time": datetime.now(),
397+ "mac": factory.make_mac_address(),
398+ "vid": 3,
399+ },
400+ {
401+ "interface": "eth1",
402+ "ip": factory.make_ipv4_address(),
403+ "time": datetime.now(),
404+ "mac": factory.make_mac_address(),
405+ "vid": 7,
406+ },
407 ]
408 rack.report_neighbours(neighbours)
409- self.assertThat(report_vid, MockCallsMatch(call(3), call(7)))
410+ report_vid.assert_has_calls([call(3), call(7)])
411
412
413 class TestReportMDNSEntries(MAASServerTestCase):

Subscribers

People subscribed via source and target branches