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

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

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.5 lp:~r00ta/maas/+git/maas into -b 3.5 lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 0902d2a4870be15662f2cfe841f0f960ceac3e8b

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 f04da81..148f3d7 100644
3--- a/src/maasserver/rpc/boot.py
4+++ b/src/maasserver/rpc/boot.py
5@@ -417,6 +417,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@@ -500,50 +512,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 bcc2b83..cf6da27 100644
115--- a/src/maasserver/rpc/tests/test_boot.py
116+++ b/src/maasserver/rpc/tests/test_boot.py
117@@ -23,6 +23,7 @@ from maasserver.models import (
118 BootResourceSet,
119 Config,
120 Event,
121+ Machine,
122 )
123 from maasserver.models.timestampedmodel import now
124 from maasserver.node_status import get_node_timeout, MONITORED_STATUSES
125@@ -1295,6 +1296,100 @@ class TestGetConfig(MAASServerTestCase):
126 )
127 self.assertEqual("hwe-22.04", observed_config["subarch"])
128
129+ def test_machine_boots_from_another_vlan_and_gets_updated_when_mac_is_unknown(
130+ self,
131+ ):
132+ make_usable_architecture(self)
133+
134+ vlan_10 = factory.make_VLAN(vid=10)
135+ subnet_10 = factory.make_Subnet(cidr="10.0.0.0/24", vlan=vlan_10)
136+
137+ vlan_20 = factory.make_VLAN(vid=20)
138+ subnet_20 = factory.make_Subnet(cidr="20.0.0.0/24", vlan=vlan_20)
139+
140+ rack = factory.make_rack_with_interfaces(
141+ eth0=[subnet_10.cidr],
142+ eth1=[subnet_20.cidr],
143+ )
144+
145+ # The machine is configured to use the subnet_10
146+ machine = factory.make_Machine_with_Interface_on_Subnet(
147+ status=NODE_STATUS.DEPLOYING, subnet=subnet_10
148+ )
149+ factory.make_Interface(
150+ INTERFACE_TYPE.PHYSICAL, node=machine, subnet=subnet_20
151+ )
152+
153+ # But actually the machine boots from the other subnet
154+ local_ip = (
155+ rack.current_config.interface_set.filter(vlan=vlan_20)
156+ .first()
157+ .ip_addresses.first()
158+ .ip
159+ )
160+ remote_ip = factory.pick_ip_in_Subnet(subnet_20)
161+
162+ get_config(
163+ rack.system_id,
164+ local_ip,
165+ remote_ip,
166+ hardware_uuid=machine.hardware_uuid,
167+ mac=None,
168+ )
169+
170+ reloaded_machine = Machine.objects.filter(
171+ system_id=machine.system_id
172+ ).first()
173+
174+ # The second NIC is the new boot interface
175+ self.assertEqual(reloaded_machine.boot_interface.vlan.id, vlan_20.id)
176+
177+ def test_machine_boots_from_another_vlan_and_gets_updated(self):
178+ vlan_10 = factory.make_VLAN(vid=10)
179+ subnet_10 = factory.make_Subnet(cidr="10.0.0.0/24", vlan=vlan_10)
180+
181+ vlan_20 = factory.make_VLAN(vid=20)
182+ subnet_20 = factory.make_Subnet(cidr="20.0.0.0/24", vlan=vlan_20)
183+
184+ rack = factory.make_rack_with_interfaces(
185+ eth0=[subnet_10.cidr],
186+ eth1=[subnet_20.cidr],
187+ )
188+
189+ # The machine is configured to use the subnet_10
190+ machine = factory.make_Machine_with_Interface_on_Subnet(
191+ status=NODE_STATUS.DEPLOYING, subnet=subnet_10
192+ )
193+
194+ # But actually the machine boots from the other subnet
195+ local_ip = (
196+ rack.current_config.interface_set.filter(vlan=vlan_20)
197+ .first()
198+ .ip_addresses.first()
199+ .ip
200+ )
201+ remote_ip = factory.pick_ip_in_Subnet(subnet_20)
202+
203+ # The MAC address coming from the rack is always in the format XX-XX-XX-XX-XX-XX
204+ unformatted_mac_address = machine.boot_interface.mac_address.replace(
205+ ":", "-"
206+ )
207+
208+ get_config(
209+ rack.system_id,
210+ local_ip,
211+ remote_ip,
212+ mac=unformatted_mac_address,
213+ query_count=50,
214+ )
215+
216+ reloaded_machine = Machine.objects.filter(
217+ system_id=machine.system_id
218+ ).first()
219+
220+ # Expect that the boot interface vlan is moved to the same rack controller's vlan
221+ self.assertEqual(reloaded_machine.boot_interface.vlan.id, vlan_20.id)
222+
223 def test_commissioning_node_uses_min_hwe_kernel_reports_missing(self):
224 factory.make_BootSourceCache(
225 release="22.10",

Subscribers

People subscribed via source and target branches