Merge ~r00ta/maas:lp-2064281-3.4 into maas:3.4

Proposed by Jacopo Rota
Status: Merged
Approved by: Jacopo Rota
Approved revision: 433acdc23b481edfa969a442e46cf66e41b3cb98
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~r00ta/maas:lp-2064281-3.4
Merge into: maas:3.4
Diff against target: 226 lines (+145/-42)
2 files modified
src/maasserver/rpc/boot.py (+50/-41)
src/maasserver/rpc/tests/test_boot.py (+95/-1)
Reviewer Review Type Date Requested Status
MAAS Lander Approve
Jacopo Rota Approve
Review via email: mp+465476@code.launchpad.net

This proposal supersedes a proposal from 2024-05-03.

Commit message

fix: lp-2064281. The mac address that the rack controller communicates to the region when the machine's configuration is requested must be normalised

(cherry-picked from 5c528c657e916ec6dce9e231b54a26205a243885)

To post a comment you must log in.
Revision history for this message
Jacopo Rota (r00ta) wrote :

self-approving backport

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

UNIT TESTS
-b lp-2064281-3.4 lp:~r00ta/maas/+git/maas into -b 3.4 lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 433acdc23b481edfa969a442e46cf66e41b3cb98

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/rpc/boot.py b/src/maasserver/rpc/boot.py
2index 931a7e0..940b453 100644
3--- a/src/maasserver/rpc/boot.py
4+++ b/src/maasserver/rpc/boot.py
5@@ -363,6 +363,18 @@ def get_quirks_kernel_opts(
6 return None
7
8
9+def update_boot_interface_vlan(machine: Node, local_ip: str):
10+ subnet = Subnet.objects.get_best_subnet_for_ip(local_ip)
11+ boot_vlan = getattr(machine.boot_interface, "vlan", None)
12+ if subnet and subnet.vlan != boot_vlan:
13+ # This might choose the wrong interface, but we don't
14+ # have enough information to decide which interface is
15+ # the boot one.
16+ machine.boot_interface = machine.current_config.interface_set.filter(
17+ vlan=subnet.vlan
18+ ).first()
19+
20+
21 @synchronous
22 @transactional
23 def get_config(
24@@ -446,50 +458,47 @@ def get_config(
25 if machine.bios_boot_method != bios_boot_method:
26 machine.bios_boot_method = bios_boot_method
27
28- try:
29- machine.boot_interface = machine.current_config.interface_set.get(
30- type=INTERFACE_TYPE.PHYSICAL, mac_address=mac
31- )
32- except ObjectDoesNotExist:
33- # MAC is unknown or wasn't sent. Determine the boot_interface using
34- # the boot_cluster_ip.
35- subnet = Subnet.objects.get_best_subnet_for_ip(local_ip)
36- boot_vlan = getattr(machine.boot_interface, "vlan", None)
37- if subnet and subnet.vlan != boot_vlan:
38- # This might choose the wrong interface, but we don't
39- # have enough information to decide which interface is
40- # the boot one.
41+ if not mac:
42+ # MAC was not sent. Determine the boot_interface using the boot_cluster_ip.
43+ update_boot_interface_vlan(machine, local_ip)
44+ else:
45+ try:
46 machine.boot_interface = (
47- machine.current_config.interface_set.filter(
48- vlan=subnet.vlan
49- ).first()
50+ machine.current_config.interface_set.get(
51+ type=INTERFACE_TYPE.PHYSICAL,
52+ mac_address=normalise_macaddress(mac),
53+ )
54 )
55- else:
56- # Update the VLAN of the boot interface to be the same VLAN for the
57- # interface on the rack controller that the machine communicated
58- # with, unless the VLAN is being relayed.
59- rack_interface = (
60- rack_controller.current_config.interface_set.filter(
61- ip_addresses__ip=local_ip
62+ except ObjectDoesNotExist:
63+ # MAC is unknown. Determine the boot_interface using the boot_cluster_ip.
64+ update_boot_interface_vlan(machine, local_ip)
65+ else:
66+ # Update the VLAN of the boot interface to be the same VLAN for the
67+ # interface on the rack controller that the machine communicated
68+ # with, unless the VLAN is being relayed.
69+ rack_interface = (
70+ rack_controller.current_config.interface_set.filter(
71+ ip_addresses__ip=local_ip
72+ )
73+ .select_related("vlan")
74+ .first()
75 )
76- .select_related("vlan")
77- .first()
78- )
79- if (
80- rack_interface is not None
81- and machine.boot_interface.vlan_id != rack_interface.vlan_id
82- ):
83- # Rack controller and machine is not on the same VLAN, with
84- # DHCP relay this is possible. Lets ensure that the VLAN on the
85- # interface is setup to relay through the identified VLAN.
86- if not VLAN.objects.filter(
87- id=machine.boot_interface.vlan_id,
88- relay_vlan=rack_interface.vlan_id,
89- ).exists():
90- # DHCP relay is not being performed for that VLAN. Set the
91- # VLAN to the VLAN of the rack controller.
92- machine.boot_interface.vlan = rack_interface.vlan
93- machine.boot_interface.save()
94+ if (
95+ rack_interface is not None
96+ and machine.boot_interface.vlan_id
97+ != rack_interface.vlan_id
98+ ):
99+ # Rack controller and machine is not on the same VLAN, with
100+ # DHCP relay this is possible. Lets ensure that the VLAN on the
101+ # interface is setup to relay through the identified VLAN.
102+ if not VLAN.objects.filter(
103+ id=machine.boot_interface.vlan_id,
104+ relay_vlan=rack_interface.vlan_id,
105+ ).exists():
106+ # DHCP relay is not being performed for that VLAN. Set the
107+ # VLAN to the VLAN of the rack controller.
108+ machine.boot_interface.vlan = rack_interface.vlan
109+ machine.boot_interface.save()
110
111 # Reset the machine's status_expires whenever the boot_config is called
112 # on a known machine. This allows a machine to take up to the maximum
113diff --git a/src/maasserver/rpc/tests/test_boot.py b/src/maasserver/rpc/tests/test_boot.py
114index 6928a67..10f8c32 100644
115--- a/src/maasserver/rpc/tests/test_boot.py
116+++ b/src/maasserver/rpc/tests/test_boot.py
117@@ -18,7 +18,7 @@ from maasserver.enum import (
118 NODE_STATUS,
119 NODE_TYPE,
120 )
121-from maasserver.models import Config, Event
122+from maasserver.models import Config, Event, Machine
123 from maasserver.models.timestampedmodel import now
124 from maasserver.node_status import get_node_timeout, MONITORED_STATUSES
125 from maasserver.preseed import compose_enlistment_preseed_url
126@@ -1193,6 +1193,100 @@ class TestGetConfig(MAASServerTestCase):
127 )
128 self.assertEqual("hwe-20.04", observed_config["subarch"])
129
130+ def test_machine_boots_from_another_vlan_and_gets_updated_when_mac_is_unknown(
131+ self,
132+ ):
133+ make_usable_architecture(self)
134+
135+ vlan_10 = factory.make_VLAN(vid=10)
136+ subnet_10 = factory.make_Subnet(cidr="10.0.0.0/24", vlan=vlan_10)
137+
138+ vlan_20 = factory.make_VLAN(vid=20)
139+ subnet_20 = factory.make_Subnet(cidr="20.0.0.0/24", vlan=vlan_20)
140+
141+ rack = factory.make_rack_with_interfaces(
142+ eth0=[subnet_10.cidr],
143+ eth1=[subnet_20.cidr],
144+ )
145+
146+ # The machine is configured to use the subnet_10
147+ machine = factory.make_Machine_with_Interface_on_Subnet(
148+ status=NODE_STATUS.DEPLOYING, subnet=subnet_10
149+ )
150+ factory.make_Interface(
151+ INTERFACE_TYPE.PHYSICAL, node=machine, subnet=subnet_20
152+ )
153+
154+ # But actually the machine boots from the other subnet
155+ local_ip = (
156+ rack.current_config.interface_set.filter(vlan=vlan_20)
157+ .first()
158+ .ip_addresses.first()
159+ .ip
160+ )
161+ remote_ip = factory.pick_ip_in_Subnet(subnet_20)
162+
163+ get_config(
164+ rack.system_id,
165+ local_ip,
166+ remote_ip,
167+ hardware_uuid=machine.hardware_uuid,
168+ mac=None,
169+ )
170+
171+ reloaded_machine = Machine.objects.filter(
172+ system_id=machine.system_id
173+ ).first()
174+
175+ # The second NIC is the new boot interface
176+ self.assertEqual(reloaded_machine.boot_interface.vlan.id, vlan_20.id)
177+
178+ def test_machine_boots_from_another_vlan_and_gets_updated(self):
179+ vlan_10 = factory.make_VLAN(vid=10)
180+ subnet_10 = factory.make_Subnet(cidr="10.0.0.0/24", vlan=vlan_10)
181+
182+ vlan_20 = factory.make_VLAN(vid=20)
183+ subnet_20 = factory.make_Subnet(cidr="20.0.0.0/24", vlan=vlan_20)
184+
185+ rack = factory.make_rack_with_interfaces(
186+ eth0=[subnet_10.cidr],
187+ eth1=[subnet_20.cidr],
188+ )
189+
190+ # The machine is configured to use the subnet_10
191+ machine = factory.make_Machine_with_Interface_on_Subnet(
192+ status=NODE_STATUS.DEPLOYING, subnet=subnet_10
193+ )
194+
195+ # But actually the machine boots from the other subnet
196+ local_ip = (
197+ rack.current_config.interface_set.filter(vlan=vlan_20)
198+ .first()
199+ .ip_addresses.first()
200+ .ip
201+ )
202+ remote_ip = factory.pick_ip_in_Subnet(subnet_20)
203+
204+ # The MAC address coming from the rack is always in the format XX-XX-XX-XX-XX-XX
205+ unformatted_mac_address = machine.boot_interface.mac_address.replace(
206+ ":", "-"
207+ )
208+
209+ get_config(
210+ rack.system_id,
211+ local_ip,
212+ remote_ip,
213+ mac=unformatted_mac_address,
214+ query_count=50,
215+ )
216+
217+ reloaded_machine = Machine.objects.filter(
218+ system_id=machine.system_id
219+ ).first()
220+
221+ # Expect that the boot interface vlan is moved to the same rack controller's vlan
222+ self.assertEqual(reloaded_machine.boot_interface.vlan.id, vlan_20.id)
223+
224 def test_commissioning_node_uses_min_hwe_kernel_reports_missing(self):
225 factory.make_BootSourceCache(
226 release="20.10",

Subscribers

People subscribed via source and target branches