Merge ~mfo/maas:2.9 into maas:2.9
- Git
- lp:~mfo/maas
- 2.9
- Merge into 2.9
Proposed by
Mauricio Faria de Oliveira
Status: | Merged |
---|---|
Merged at revision: | 9feb47e7506138d130d04f6da345b70beedac0a5 |
Proposed branch: | ~mfo/maas:2.9 |
Merge into: | maas:2.9 |
Diff against target: |
1265 lines (+406/-123) 24 files modified
debian/changelog (+10/-0) dev/null (+0/-1) setup.py (+1/-1) snap/local/tree/bin/maas-deb-migrate (+11/-7) src/maasserver/models/interface.py (+7/-20) src/maasserver/models/node.py (+15/-8) src/maasserver/models/tests/test_interface.py (+12/-22) src/maasserver/models/tests/test_node.py (+125/-14) src/maasserver/websockets/handlers/pod.py (+29/-27) src/maasserver/websockets/handlers/tests/test_pod.py (+16/-0) src/provisioningserver/__main__.py (+1/-3) src/provisioningserver/boot/__init__.py (+2/-0) src/provisioningserver/boot/tests/test_boot.py (+14/-1) src/provisioningserver/boot/tests/test_uefi_amd64.py (+97/-6) src/provisioningserver/boot/uefi_amd64.py (+19/-9) src/provisioningserver/config.py (+9/-1) src/provisioningserver/templates/uefi/config.commissioning.template (+2/-1) src/provisioningserver/templates/uefi/config.enlist.template (+1/-0) src/provisioningserver/templates/uefi/config.local.amd64.template (+1/-0) src/provisioningserver/templates/uefi/config.local.arm64.template (+1/-0) src/provisioningserver/templates/uefi/config.local.ppc64el.template (+1/-0) src/provisioningserver/templates/uefi/config.poweroff.template (+1/-0) src/provisioningserver/templates/uefi/config.xinstall.template (+1/-1) src/provisioningserver/tests/test_config.py (+30/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
MAAS Lander | Needs Fixing | ||
Mauricio Faria de Oliveira | Pending | ||
Review via email: mp+444306@code.launchpad.net |
Commit message
Description of the change
Please ignore; this is only for testing.
To post a comment you must log in.
Revision history for this message
Mauricio Faria de Oliveira (mfo) wrote : | # |
UNIT TESTS: pass
http://
Test Result (no failures)
0 failures,
22 skipped
17,551 tests
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/debian/changelog b/debian/changelog |
2 | index 64f9a3c..f3f1f89 100644 |
3 | --- a/debian/changelog |
4 | +++ b/debian/changelog |
5 | @@ -1,6 +1,16 @@ |
6 | +maas (1:2.9.3~rc2-0ubuntu1) lunar; urgency=medium |
7 | + |
8 | + * New upstream release, MAAS 2.9.3 RC2. |
9 | + * Reapply 2.8.x fixes since MAAS 2.9.2. |
10 | + |
11 | + -- Mauricio Faria de Oliveira <mfo@canonical.com> Wed, 07 Jun 2023 17:12:21 -0300 |
12 | + |
13 | maas (1:2.9.3~rc1-0ubuntu1) focal; urgency=medium |
14 | |
15 | * New upstream release, MAAS 2.9.3 RC1. |
16 | + * Reapply SE fixes since MAAS 2.9.2. |
17 | + * Reapply ui fixes since MAAS 2.9.2 and more |
18 | + with maas-ui update to top of 2.9 branch. |
19 | |
20 | -- Mauricio Faria de Oliveira <mfo@canonical.com> Tue, 06 Jun 2023 21:13:12 -0300 |
21 | |
22 | diff --git a/setup.py b/setup.py |
23 | index 6489e5e..24751ce 100644 |
24 | --- a/setup.py |
25 | +++ b/setup.py |
26 | @@ -17,7 +17,7 @@ def read(filename): |
27 | |
28 | setup( |
29 | name="maas", |
30 | - version="2.9.3rc1", |
31 | + version="2.9.3rc2", |
32 | url="https://maas.io/", |
33 | license="AGPLv3", |
34 | description="Metal As A Service", |
35 | diff --git a/snap/local/tree/bin/maas-deb-migrate b/snap/local/tree/bin/maas-deb-migrate |
36 | index febca85..f646f0f 100755 |
37 | --- a/snap/local/tree/bin/maas-deb-migrate |
38 | +++ b/snap/local/tree/bin/maas-deb-migrate |
39 | @@ -65,17 +65,21 @@ cleanup_data() { |
40 | } |
41 | |
42 | apply_db_patches() { |
43 | - if maas_snap_mode | grep -q "region"; then |
44 | - snap_run "maas-region migrate" |
45 | + if ! maas_snap_mode | grep -q "region"; then |
46 | + return |
47 | fi |
48 | |
49 | + snap_run "maas-region migrate" |
50 | # patch the value of the default cloud images keyring to point to the one |
51 | # in the snap |
52 | - local keyring="/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg" |
53 | - cat <<EOF | pg_do psql maasdb |
54 | -UPDATE maasserver_bootsource |
55 | -SET keyring_filename = '${MAAS_SNAP}${keyring}' |
56 | -WHERE keyring_filename = '${keyring}' |
57 | + cat <<EOF | snap_run "maas-region shell" |
58 | +from maasserver.models import BootSource |
59 | +keyring = "/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg" |
60 | +( |
61 | + BootSource.objects |
62 | + .filter(keyring_filename=keyring) |
63 | + .update(keyring_filename=f"/snap/maas/current{keyring}") |
64 | +) |
65 | EOF |
66 | } |
67 | |
68 | diff --git a/src/maasserver/models/interface.py b/src/maasserver/models/interface.py |
69 | index b7738dc..b5d9625 100644 |
70 | --- a/src/maasserver/models/interface.py |
71 | +++ b/src/maasserver/models/interface.py |
72 | @@ -111,7 +111,7 @@ class InterfaceQueriesMixin(MAASQueriesMixin): |
73 | specifiers, |
74 | specifier_types=specifier_types, |
75 | separator=separator, |
76 | - **kwargs |
77 | + **kwargs, |
78 | ) |
79 | |
80 | def _add_interface_id_query(self, current_q, op, item): |
81 | @@ -1522,20 +1522,10 @@ class Interface(CleanSave, TimestampedModel): |
82 | % (self.get_log_string(), vid, vlan.fabric.get_name()) |
83 | ) |
84 | |
85 | - def update_neighbour(self, neighbour_json: dict): |
86 | - """Updates the neighbour table for this interface. |
87 | - |
88 | - Input is expected to be the neighbour JSON from the controller. |
89 | - """ |
90 | - # Circular imports |
91 | + def update_neighbour(self, ip, mac, time, vid=None): |
92 | + """Updates the neighbour table for this interface.""" |
93 | from maasserver.models.neighbour import Neighbour |
94 | |
95 | - if self.neighbour_discovery_state is False: |
96 | - return None |
97 | - ip = neighbour_json["ip"] |
98 | - mac = neighbour_json["mac"] |
99 | - time = neighbour_json["time"] |
100 | - vid = neighbour_json.get("vid", None) |
101 | deleted = Neighbour.objects.delete_and_log_obsolete_neighbours( |
102 | ip, mac, interface=self, vid=vid |
103 | ) |
104 | @@ -1552,13 +1542,10 @@ class Interface(CleanSave, TimestampedModel): |
105 | # generated a log statement about this neighbour. |
106 | if not deleted: |
107 | maaslog.info( |
108 | - "%s: New MAC, IP binding observed%s: %s, %s" |
109 | - % ( |
110 | - self.get_log_string(), |
111 | - Neighbour.objects.get_vid_log_snippet(vid), |
112 | - mac, |
113 | - ip, |
114 | - ) |
115 | + f"{self.get_log_string()}: " |
116 | + "New MAC, IP binding " |
117 | + f"observed{Neighbour.objects.get_vid_log_snippet(vid)}: " |
118 | + f"{mac}, {ip}" |
119 | ) |
120 | else: |
121 | neighbour.time = time |
122 | diff --git a/src/maasserver/models/node.py b/src/maasserver/models/node.py |
123 | index 5d506b4..48852de 100644 |
124 | --- a/src/maasserver/models/node.py |
125 | +++ b/src/maasserver/models/node.py |
126 | @@ -4337,9 +4337,11 @@ class Node(CleanSave, TimestampedModel): |
127 | bridge_fd=bridge_fd, |
128 | ) |
129 | |
130 | - def claim_auto_ips(self, temp_expires_after=None): |
131 | + def claim_auto_ips(self, exclude_addresses=None, temp_expires_after=None): |
132 | """Assign IP addresses to all interface links set to AUTO.""" |
133 | - exclude_addresses = set() |
134 | + exclude_addresses = ( |
135 | + exclude_addresses.copy() if exclude_addresses else set() |
136 | + ) |
137 | allocated_ips = set() |
138 | # Query for the interfaces again here; if we use the cached |
139 | # interface_set, we could skip a newly-created bridge if it was created |
140 | @@ -4480,11 +4482,9 @@ class Node(CleanSave, TimestampedModel): |
141 | rack_interface = rack_interface.order_by("id") |
142 | rack_interface = rack_interface.first() |
143 | rack_interface.update_neighbour( |
144 | - { |
145 | - "ip": ip_obj.ip, |
146 | - "mac": ip_result.get("mac_address"), |
147 | - "time": time.time(), |
148 | - } |
149 | + ip_obj.ip, |
150 | + ip_result.get("mac_address"), |
151 | + time.time(), |
152 | ) |
153 | ip_obj.ip = None |
154 | ip_obj.temp_expires_on = None |
155 | @@ -4502,6 +4502,7 @@ class Node(CleanSave, TimestampedModel): |
156 | yield deferToDatabase(clean_expired) |
157 | allocated_ips = yield deferToDatabase( |
158 | transactional(self.claim_auto_ips), |
159 | + exclude_addresses=attempted_ips, |
160 | temp_expires_after=timedelta(minutes=5), |
161 | ) |
162 | if not allocated_ips: |
163 | @@ -6723,8 +6724,14 @@ class Controller(Node): |
164 | for neighbour in neighbours: |
165 | interface = interfaces.get(neighbour["interface"], None) |
166 | if interface is not None: |
167 | - interface.update_neighbour(neighbour) |
168 | vid = neighbour.get("vid", None) |
169 | + if interface.neighbour_discovery_state: |
170 | + interface.update_neighbour( |
171 | + neighbour["ip"], |
172 | + neighbour["mac"], |
173 | + neighbour["time"], |
174 | + vid=vid, |
175 | + ) |
176 | if vid is not None: |
177 | interface.report_vid(vid) |
178 | |
179 | diff --git a/src/maasserver/models/tests/test_interface.py b/src/maasserver/models/tests/test_interface.py |
180 | index b86dbfb..ee16f56 100644 |
181 | --- a/src/maasserver/models/tests/test_interface.py |
182 | +++ b/src/maasserver/models/tests/test_interface.py |
183 | @@ -1363,7 +1363,7 @@ class InterfaceTest(MAASServerTestCase): |
184 | self.assertEquals(0, interface.link_speed) |
185 | |
186 | |
187 | -class InterfaceUpdateNeighbourTest(MAASServerTestCase): |
188 | +class TestInterfaceUpdateNeighbour(MAASServerTestCase): |
189 | """Tests for `Interface.update_neighbour`.""" |
190 | |
191 | def make_neighbour_json(self, ip=None, mac=None, time=None, **kwargs): |
192 | @@ -1384,22 +1384,15 @@ class InterfaceUpdateNeighbourTest(MAASServerTestCase): |
193 | vid = None |
194 | return {"ip": ip, "mac": mac, "time": time, "vid": vid} |
195 | |
196 | - def test_ignores_updates_if_neighbour_discovery_state_is_false(self): |
197 | + def test_adds_new_neighbour(self): |
198 | iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
199 | - iface.update_neighbour(self.make_neighbour_json()) |
200 | - self.assertThat(Neighbour.objects.count(), Equals(0)) |
201 | - |
202 | - def test_adds_new_neighbour_if_neighbour_discovery_state_is_true(self): |
203 | - iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
204 | - iface.neighbour_discovery_state = True |
205 | - iface.update_neighbour(self.make_neighbour_json()) |
206 | + iface.update_neighbour(**self.make_neighbour_json()) |
207 | self.assertThat(Neighbour.objects.count(), Equals(1)) |
208 | |
209 | def test_updates_existing_neighbour(self): |
210 | iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
211 | - iface.neighbour_discovery_state = True |
212 | json = self.make_neighbour_json() |
213 | - iface.update_neighbour(json) |
214 | + iface.update_neighbour(**json) |
215 | neighbour = get_one(Neighbour.objects.all()) |
216 | # Pretend this was updated one day ago. |
217 | yesterday = datetime.datetime.now() - datetime.timedelta(days=1) |
218 | @@ -1410,7 +1403,7 @@ class InterfaceUpdateNeighbourTest(MAASServerTestCase): |
219 | Equals(int(yesterday.timestamp())), |
220 | ) |
221 | json["time"] += 1 |
222 | - iface.update_neighbour(json) |
223 | + iface.update_neighbour(**json) |
224 | neighbour = reload_object(neighbour) |
225 | self.assertThat(Neighbour.objects.count(), Equals(1)) |
226 | self.assertThat(neighbour.time, Equals(json["time"])) |
227 | @@ -1422,13 +1415,12 @@ class InterfaceUpdateNeighbourTest(MAASServerTestCase): |
228 | |
229 | def test_replaces_obsolete_neighbour(self): |
230 | iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
231 | - iface.neighbour_discovery_state = True |
232 | json = self.make_neighbour_json() |
233 | - iface.update_neighbour(json) |
234 | + iface.update_neighbour(**json) |
235 | # Have a different MAC address claim ownership of the IP. |
236 | json["time"] += 1 |
237 | json["mac"] = factory.make_mac_address() |
238 | - iface.update_neighbour(json) |
239 | + iface.update_neighbour(**json) |
240 | self.assertThat(Neighbour.objects.count(), Equals(1)) |
241 | self.assertThat( |
242 | list(Neighbour.objects.all())[0].mac_address, Equals(json["mac"]) |
243 | @@ -1439,10 +1431,8 @@ class InterfaceUpdateNeighbourTest(MAASServerTestCase): |
244 | |
245 | def test_logs_new_binding(self): |
246 | iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
247 | - iface.neighbour_discovery_state = True |
248 | - json = self.make_neighbour_json() |
249 | with FakeLogger("maas.interface") as maaslog: |
250 | - iface.update_neighbour(json) |
251 | + iface.update_neighbour(**self.make_neighbour_json()) |
252 | self.assertDocTestMatches( |
253 | "...: New MAC, IP binding observed...", maaslog.output |
254 | ) |
255 | @@ -1451,18 +1441,18 @@ class InterfaceUpdateNeighbourTest(MAASServerTestCase): |
256 | iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
257 | iface.neighbour_discovery_state = True |
258 | json = self.make_neighbour_json() |
259 | - iface.update_neighbour(json) |
260 | + iface.update_neighbour(**json) |
261 | # Have a different MAC address claim ownership of the IP. |
262 | json["time"] += 1 |
263 | json["mac"] = factory.make_mac_address() |
264 | with FakeLogger("maas.neighbour") as maaslog: |
265 | - iface.update_neighbour(json) |
266 | + iface.update_neighbour(**json) |
267 | self.assertDocTestMatches( |
268 | "...: IP address...moved from...to...", maaslog.output |
269 | ) |
270 | |
271 | |
272 | -class InterfaceUpdateMDNSEntryTest(MAASServerTestCase): |
273 | +class TestInterfaceUpdateMDNSEntry(MAASServerTestCase): |
274 | """Tests for `Interface.update_mdns_entry`.""" |
275 | |
276 | def make_mdns_entry_json(self, ip=None, hostname=None): |
277 | @@ -1477,7 +1467,7 @@ class InterfaceUpdateMDNSEntryTest(MAASServerTestCase): |
278 | |
279 | def test_ignores_updates_if_mdns_discovery_state_is_false(self): |
280 | iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
281 | - iface.update_neighbour(self.make_mdns_entry_json()) |
282 | + iface.update_mdns_entry(self.make_mdns_entry_json()) |
283 | self.assertThat(MDNS.objects.count(), Equals(0)) |
284 | |
285 | def test_adds_new_entry_if_mdns_discovery_state_is_true(self): |
286 | diff --git a/src/maasserver/models/tests/test_node.py b/src/maasserver/models/tests/test_node.py |
287 | index a5a5aa7..61f1fe6 100644 |
288 | --- a/src/maasserver/models/tests/test_node.py |
289 | +++ b/src/maasserver/models/tests/test_node.py |
290 | @@ -79,6 +79,7 @@ from maasserver.exceptions import ( |
291 | StaticIPAddressExhaustion, |
292 | ) |
293 | from maasserver.models import ( |
294 | + BMCRoutableRackControllerRelationship, |
295 | BondInterface, |
296 | BootResource, |
297 | BridgeInterface, |
298 | @@ -86,11 +87,13 @@ from maasserver.models import ( |
299 | Controller, |
300 | Device, |
301 | Domain, |
302 | + Event, |
303 | EventType, |
304 | Fabric, |
305 | Interface, |
306 | LicenseKey, |
307 | Machine, |
308 | + Neighbour, |
309 | Node, |
310 | ) |
311 | from maasserver.models import ( |
312 | @@ -100,6 +103,7 @@ from maasserver.models import ( |
313 | RAID, |
314 | RegionController, |
315 | RegionRackRPCConnection, |
316 | + ResourcePool, |
317 | Service, |
318 | StaticIPAddress, |
319 | Subnet, |
320 | @@ -108,12 +112,10 @@ from maasserver.models import ( |
321 | VLANInterface, |
322 | VolumeGroup, |
323 | ) |
324 | -from maasserver.models import Bcache |
325 | +from maasserver.models import Bcache, BMC |
326 | from maasserver.models import bmc as bmc_module |
327 | from maasserver.models import node as node_module |
328 | -from maasserver.models.bmc import BMC, BMCRoutableRackControllerRelationship |
329 | from maasserver.models.config import NetworkDiscoveryConfig |
330 | -from maasserver.models.event import Event |
331 | import maasserver.models.interface as interface_module |
332 | from maasserver.models.node import ( |
333 | DEFAULT_BIOS_BOOT_METHOD, |
334 | @@ -123,7 +125,6 @@ from maasserver.models.node import ( |
335 | PowerInfo, |
336 | ) |
337 | from maasserver.models.partitiontable import PARTITION_TABLE_EXTRA_SPACE |
338 | -from maasserver.models.resourcepool import ResourcePool |
339 | from maasserver.models.signals import power as node_query |
340 | from maasserver.models.timestampedmodel import now |
341 | from maasserver.node_status import ( |
342 | @@ -8694,6 +8695,74 @@ class TestNode_Start(MAASTransactionServerTestCase): |
343 | self.assertThat(auto_ip.ip, Equals(third_ip.ip)) |
344 | self.assertThat(auto_ip.temp_expires_on, Is(None)) |
345 | |
346 | + def test_claims_auto_ip_addresses_skips_used_ip_discovery_disabled(self): |
347 | + user = factory.make_User() |
348 | + node = self.make_acquired_node_with_interface( |
349 | + user, power_type="manual" |
350 | + ) |
351 | + node_interface = node.get_boot_interface() |
352 | + [auto_ip] = node_interface.ip_addresses.filter( |
353 | + alloc_type=IPADDRESS_TYPE.AUTO |
354 | + ) |
355 | + |
356 | + # Create a rack controller that has an interface on the same subnet |
357 | + # as the node. Don't enable neighbour discovery |
358 | + rack = factory.make_RackController() |
359 | + rack.interface_set.all().delete() |
360 | + rackif = factory.make_Interface(vlan=node_interface.vlan, node=rack) |
361 | + rackif_ip = factory.pick_ip_in_Subnet(auto_ip.subnet) |
362 | + rackif.link_subnet( |
363 | + INTERFACE_LINK_TYPE.STATIC, auto_ip.subnet, rackif_ip |
364 | + ) |
365 | + |
366 | + # Mock the rack controller connected to the region controller. |
367 | + client = Mock() |
368 | + client.ident = rack.system_id |
369 | + self.patch(node_module, "getAllClients").return_value = [client] |
370 | + |
371 | + # Must be executed in a transaction as `allocate_new` uses savepoints. |
372 | + with transaction.atomic(): |
373 | + # Get two IPs and remove them so they're unknown |
374 | + ip = StaticIPAddress.objects.allocate_new( |
375 | + subnet=auto_ip.subnet, alloc_type=IPADDRESS_TYPE.AUTO |
376 | + ) |
377 | + ip1 = ip.ip |
378 | + ip.delete() |
379 | + ip = StaticIPAddress.objects.allocate_new( |
380 | + subnet=auto_ip.subnet, |
381 | + alloc_type=IPADDRESS_TYPE.AUTO, |
382 | + exclude_addresses=[ip1], |
383 | + ) |
384 | + ip2 = ip.ip |
385 | + ip.delete() |
386 | + |
387 | + client.side_effect = [ |
388 | + defer.succeed( |
389 | + { |
390 | + "ip_addresses": [ |
391 | + { |
392 | + "ip_address": ip1, |
393 | + "used": True, |
394 | + "mac_address": factory.make_mac_address(), |
395 | + } |
396 | + ] |
397 | + } |
398 | + ), |
399 | + defer.succeed( |
400 | + {"ip_addresses": [{"ip_address": ip2, "used": False}]} |
401 | + ), |
402 | + ] |
403 | + |
404 | + with post_commit_hooks: |
405 | + node.start(user) |
406 | + |
407 | + auto_ip = reload_object(auto_ip) |
408 | + self.assertThat(auto_ip.ip, Equals(ip2)) |
409 | + self.assertThat(auto_ip.temp_expires_on, Is(None)) |
410 | + self.assertCountEqual( |
411 | + [ip1], Neighbour.objects.values_list("ip", flat=True) |
412 | + ) |
413 | + |
414 | def test_claims_auto_ip_addresses_retries_on_failure_from_rack(self): |
415 | user = factory.make_User() |
416 | node = self.make_acquired_node_with_interface( |
417 | @@ -9914,21 +9983,51 @@ class TestControllerUpdateDiscoveryState(MAASServerTestCase): |
418 | class TestReportNeighbours(MAASServerTestCase): |
419 | """Tests for `Controller.report_neighbours().""" |
420 | |
421 | - def test_calls_update_neighbour_for_each_neighbour(self): |
422 | + def test_no_update_neighbours_calls_if_discovery_disabled(self): |
423 | rack = factory.make_RackController() |
424 | factory.make_Interface(name="eth0", node=rack) |
425 | - factory.make_Interface(name="eth1", node=rack) |
426 | update_neighbour = self.patch( |
427 | interface_module.Interface, "update_neighbour" |
428 | ) |
429 | neighbours = [ |
430 | - {"interface": "eth0", "mac": factory.make_mac_address()}, |
431 | - {"interface": "eth1", "mac": factory.make_mac_address()}, |
432 | + { |
433 | + "interface": "eth0", |
434 | + "mac": factory.make_mac_address(), |
435 | + "ip": factory.make_ipv4_address(), |
436 | + "time": datetime.now(), |
437 | + }, |
438 | + ] |
439 | + rack.report_neighbours(neighbours) |
440 | + update_neighbour.assert_not_called() |
441 | + |
442 | + def test_calls_update_neighbour_for_each_neighbour(self): |
443 | + rack = factory.make_RackController() |
444 | + if1 = factory.make_Interface(name="eth0", node=rack) |
445 | + if1.neighbour_discovery_state = True |
446 | + if1.save() |
447 | + if2 = factory.make_Interface(name="eth1", node=rack) |
448 | + if2.neighbour_discovery_state = True |
449 | + if2.save() |
450 | + update_neighbour = self.patch( |
451 | + interface_module.Interface, "update_neighbour" |
452 | + ) |
453 | + neighbours = [ |
454 | + { |
455 | + "interface": "eth0", |
456 | + "mac": factory.make_mac_address(), |
457 | + "ip": factory.make_ipv4_address(), |
458 | + "time": datetime.now(), |
459 | + }, |
460 | + { |
461 | + "interface": "eth1", |
462 | + "mac": factory.make_mac_address(), |
463 | + "ip": factory.make_ipv4_address(), |
464 | + "time": datetime.now(), |
465 | + }, |
466 | ] |
467 | rack.report_neighbours(neighbours) |
468 | - self.assertThat( |
469 | - update_neighbour, |
470 | - MockCallsMatch(*[call(neighbour) for neighbour in neighbours]), |
471 | + update_neighbour.assert_has_calls( |
472 | + [call(n["ip"], n["mac"], n["time"], vid=None) for n in neighbours] |
473 | ) |
474 | |
475 | def test_calls_report_vid_for_each_vid(self): |
476 | @@ -9939,11 +10038,23 @@ class TestReportNeighbours(MAASServerTestCase): |
477 | self.patch(interface_module.Interface, "update_neighbour") |
478 | report_vid = self.patch(interface_module.Interface, "report_vid") |
479 | neighbours = [ |
480 | - {"interface": "eth0", "mac": factory.make_mac_address(), "vid": 3}, |
481 | - {"interface": "eth1", "mac": factory.make_mac_address(), "vid": 7}, |
482 | + { |
483 | + "interface": "eth0", |
484 | + "ip": factory.make_ipv4_address(), |
485 | + "time": datetime.now(), |
486 | + "mac": factory.make_mac_address(), |
487 | + "vid": 3, |
488 | + }, |
489 | + { |
490 | + "interface": "eth1", |
491 | + "ip": factory.make_ipv4_address(), |
492 | + "time": datetime.now(), |
493 | + "mac": factory.make_mac_address(), |
494 | + "vid": 7, |
495 | + }, |
496 | ] |
497 | rack.report_neighbours(neighbours) |
498 | - self.assertThat(report_vid, MockCallsMatch(call(3), call(7))) |
499 | + report_vid.assert_has_calls([call(3), call(7)]) |
500 | |
501 | |
502 | class TestReportMDNSEntries(MAASServerTestCase): |
503 | diff --git a/src/maasserver/websockets/handlers/pod.py b/src/maasserver/websockets/handlers/pod.py |
504 | index bfe3676..686f9a9 100644 |
505 | --- a/src/maasserver/websockets/handlers/pod.py |
506 | +++ b/src/maasserver/websockets/handlers/pod.py |
507 | @@ -97,33 +97,37 @@ class PodHandler(TimestampedModelHandler): |
508 | """Add extra fields to `data`.""" |
509 | if self.user.is_superuser: |
510 | data.update(obj.power_parameters) |
511 | - data["type"] = obj.power_type |
512 | - data["total"] = self.dehydrate_total(obj) |
513 | - data["used"] = self.dehydrate_used(obj) |
514 | - data["available"] = self.dehydrate_available(obj) |
515 | - data["composed_machines_count"] = obj.node_set.filter( |
516 | - node_type=NODE_TYPE.MACHINE |
517 | - ).count() |
518 | - data["owners_count"] = ( |
519 | - obj.node_set.exclude(owner=None) |
520 | - .values_list("owner") |
521 | - .distinct() |
522 | - .count() |
523 | + data.update( |
524 | + { |
525 | + "type": obj.power_type, |
526 | + "total": self.dehydrate_total(obj), |
527 | + "used": self.dehydrate_used(obj), |
528 | + "available": self.dehydrate_available(obj), |
529 | + "composed_machines_count": obj.node_set.filter( |
530 | + node_type=NODE_TYPE.MACHINE |
531 | + ).count(), |
532 | + "owners_count": ( |
533 | + obj.node_set.exclude(owner=None) |
534 | + .values_list("owner") |
535 | + .distinct() |
536 | + .count() |
537 | + ), |
538 | + "hints": self.dehydrate_hints(obj.hints), |
539 | + "storage_pools": [ |
540 | + self.dehydrate_storage_pool(pool) |
541 | + for pool in obj.storage_pools.all() |
542 | + ], |
543 | + "default_storage_pool": ( |
544 | + obj.default_storage_pool.pool_id |
545 | + if obj.default_storage_pool |
546 | + else None |
547 | + ), |
548 | + "host": obj.host.system_id if obj.host else None, |
549 | + "numa_pinning": self.dehydrate_numa_pinning(obj), |
550 | + } |
551 | ) |
552 | - data["hints"] = self.dehydrate_hints(obj.hints) |
553 | - storage_pools = obj.storage_pools.all() |
554 | - if len(storage_pools) > 0: |
555 | - pools_data = [] |
556 | - for pool in storage_pools: |
557 | - pools_data.append(self.dehydrate_storage_pool(pool)) |
558 | - data["storage_pools"] = pools_data |
559 | - data["default_storage_pool"] = obj.default_storage_pool.pool_id |
560 | - if obj.host is not None: |
561 | - data["host"] = obj.host.system_id |
562 | - else: |
563 | - data["host"] = None |
564 | if not for_list: |
565 | - if obj.host is not None: |
566 | + if obj.host: |
567 | data["attached_vlans"] = list( |
568 | obj.host.interface_set.all().values_list( |
569 | "vlan_id", flat=True |
570 | @@ -141,8 +145,6 @@ class PodHandler(TimestampedModelHandler): |
571 | data["attached_vlans"] = [] |
572 | data["boot_vlans"] = [] |
573 | |
574 | - data["numa_pinning"] = self.dehydrate_numa_pinning(obj) |
575 | - |
576 | if self.user.has_perm(PodPermission.compose, obj): |
577 | data["permissions"].append("compose") |
578 | |
579 | diff --git a/src/maasserver/websockets/handlers/tests/test_pod.py b/src/maasserver/websockets/handlers/tests/test_pod.py |
580 | index 6137c38..f4e857f 100644 |
581 | --- a/src/maasserver/websockets/handlers/tests/test_pod.py |
582 | +++ b/src/maasserver/websockets/handlers/tests/test_pod.py |
583 | @@ -14,6 +14,7 @@ from twisted.internet.defer import inlineCallbacks, succeed |
584 | from maasserver.enum import INTERFACE_TYPE |
585 | from maasserver.forms import pods |
586 | from maasserver.forms.pods import PodForm |
587 | +from maasserver.models import PodStoragePool |
588 | from maasserver.models.virtualmachine import MB, VirtualMachineInterface |
589 | from maasserver.testing.factory import factory |
590 | from maasserver.testing.testcase import MAASTransactionServerTestCase |
591 | @@ -226,6 +227,21 @@ class TestPodHandler(MAASTransactionServerTestCase): |
592 | ], |
593 | ) |
594 | |
595 | + def test_get_with_pod_host_no_storage_pools(self): |
596 | + admin = factory.make_admin() |
597 | + handler = PodHandler(admin, {}, None) |
598 | + node = factory.make_Node() |
599 | + pod = self.make_pod_with_hints( |
600 | + pod_type="lxd", |
601 | + host=node, |
602 | + ) |
603 | + pod.default_storage_pool = None |
604 | + pod.save() |
605 | + PodStoragePool.objects.all().delete() |
606 | + result = handler.get({"id": pod.id}) |
607 | + self.assertIsNone(result["default_storage_pool"]) |
608 | + self.assertEqual(result["storage_pools"], []) |
609 | + |
610 | def test_get_host_interfaces_no_sriov(self): |
611 | admin = factory.make_admin() |
612 | handler = PodHandler(admin, {}, None) |
613 | diff --git a/src/provisioningserver/__main__.py b/src/provisioningserver/__main__.py |
614 | index 6cf81d9..5f22393 100644 |
615 | --- a/src/provisioningserver/__main__.py |
616 | +++ b/src/provisioningserver/__main__.py |
617 | @@ -1,5 +1,5 @@ |
618 | #!/usr/bin/env python3 |
619 | -# Copyright 2012-2017 Canonical Ltd. This software is licensed under the |
620 | +# Copyright 2012-2021 Canonical Ltd. This software is licensed under the |
621 | # GNU Affero General Public License version 3 (see the file LICENSE). |
622 | |
623 | """Command-line interface for the MAAS provisioning component.""" |
624 | @@ -7,7 +7,6 @@ |
625 | import sys |
626 | |
627 | from provisioningserver import security |
628 | -import provisioningserver.boot.install_grub |
629 | import provisioningserver.cluster_config_command |
630 | import provisioningserver.dns.commands.edit_named_options |
631 | import provisioningserver.dns.commands.get_named_conf |
632 | @@ -39,7 +38,6 @@ RACK_ONLY_COMMANDS = { |
633 | "check-for-shared-secret": security.CheckForSharedSecretScript, |
634 | "config": provisioningserver.cluster_config_command, |
635 | "install-shared-secret": security.InstallSharedSecretScript, |
636 | - "install-uefi-config": provisioningserver.boot.install_grub, |
637 | "register": provisioningserver.register_command, |
638 | "support-dump": provisioningserver.support_dump, |
639 | "upgrade-cluster": provisioningserver.upgrade_cluster, |
640 | diff --git a/src/provisioningserver/boot/__init__.py b/src/provisioningserver/boot/__init__.py |
641 | index d4b13e2..5c4f4b6 100644 |
642 | --- a/src/provisioningserver/boot/__init__.py |
643 | +++ b/src/provisioningserver/boot/__init__.py |
644 | @@ -17,6 +17,7 @@ from twisted.internet.defer import inlineCallbacks, returnValue |
645 | from zope.interface import implementer |
646 | |
647 | from provisioningserver.boot.tftppath import compose_image_path |
648 | +from provisioningserver.config import debug_enabled |
649 | from provisioningserver.events import EVENT_TYPES, try_send_rack_event |
650 | from provisioningserver.kernel_opts import compose_kernel_command_line |
651 | from provisioningserver.logger import get_maas_logger |
652 | @@ -418,6 +419,7 @@ class BootMethod(metaclass=ABCMeta): |
653 | "kernel_path": kernel_path, |
654 | "kernel_name": kernel_name, |
655 | "dtb_path": dtb_path, |
656 | + "debug": debug_enabled(), |
657 | } |
658 | |
659 | return namespace |
660 | diff --git a/src/provisioningserver/boot/install_grub.py b/src/provisioningserver/boot/install_grub.py |
661 | deleted file mode 100644 |
662 | index 27ebd36..0000000 |
663 | --- a/src/provisioningserver/boot/install_grub.py |
664 | +++ /dev/null |
665 | @@ -1,36 +0,0 @@ |
666 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
667 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
668 | - |
669 | -"""Install a GRUB2 pre-boot loader config for TFTP download.""" |
670 | - |
671 | - |
672 | -import os |
673 | - |
674 | -from provisioningserver.config import ClusterConfiguration |
675 | -from provisioningserver.utils.fs import write_text_file |
676 | - |
677 | -CONFIG_FILE = """ |
678 | -# MAAS GRUB2 pre-loader configuration file |
679 | - |
680 | -# Load based on MAC address first. |
681 | -configfile /grub/grub.cfg-${net_default_mac} |
682 | - |
683 | -# Failed to load based on MAC address. |
684 | -# Load amd64 by default, UEFI only supported by 64-bit |
685 | -configfile /grub/grub.cfg-default-amd64 |
686 | -""" |
687 | - |
688 | - |
689 | -def add_arguments(parser): |
690 | - pass |
691 | - |
692 | - |
693 | -def run(args): |
694 | - """Install a GRUB2 pre-boot loader config into the TFTP |
695 | - directory structure. |
696 | - """ |
697 | - with ClusterConfiguration.open() as config: |
698 | - if not os.path.exists(config.grub_root): |
699 | - os.makedirs(config.grub_root) |
700 | - destination_file = os.path.join(config.grub_root, "grub.cfg") |
701 | - write_text_file(destination_file, CONFIG_FILE) |
702 | diff --git a/src/provisioningserver/boot/tests/test_boot.py b/src/provisioningserver/boot/tests/test_boot.py |
703 | index 7e591ce..f5711ec 100644 |
704 | --- a/src/provisioningserver/boot/tests/test_boot.py |
705 | +++ b/src/provisioningserver/boot/tests/test_boot.py |
706 | @@ -1,4 +1,4 @@ |
707 | -# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
708 | +# Copyright 2014-2021 Canonical Ltd. This software is licensed under the |
709 | # GNU Affero General Public License version 3 (see the file LICENSE). |
710 | |
711 | """Tests for `provisioningserver.boot`.""" |
712 | @@ -30,6 +30,7 @@ from provisioningserver.boot.tftppath import compose_image_path |
713 | from provisioningserver.kernel_opts import compose_kernel_command_line |
714 | from provisioningserver.rpc import region |
715 | from provisioningserver.rpc.testing import MockLiveClusterToRegionRPCFixture |
716 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
717 | from provisioningserver.tests.test_kernel_opts import make_kernel_parameters |
718 | from provisioningserver.utils.fs import atomic_symlink, tempdir |
719 | |
720 | @@ -331,6 +332,18 @@ class TestBootMethod(MAASTestCase): |
721 | template_namespace["dtb_path"](kernel_params), |
722 | ) |
723 | |
724 | + def test_compose_template_namespace_include_debug(self): |
725 | + debug = factory.pick_bool() |
726 | + boot.debug_enabled.cache_clear() |
727 | + self.addClassCleanup(boot.debug_enabled.cache_clear) |
728 | + self.useFixture(ClusterConfigurationFixture(debug=debug)) |
729 | + kernel_params = make_kernel_parameters() |
730 | + method = FakeBootMethod() |
731 | + |
732 | + template_namespace = method.compose_template_namespace(kernel_params) |
733 | + |
734 | + self.assertEqual(debug, template_namespace["debug"]) |
735 | + |
736 | |
737 | class TestGetArchiveUrl(MAASTestCase): |
738 | |
739 | diff --git a/src/provisioningserver/boot/tests/test_install_grub.py b/src/provisioningserver/boot/tests/test_install_grub.py |
740 | deleted file mode 100644 |
741 | index 6cb876f..0000000 |
742 | --- a/src/provisioningserver/boot/tests/test_install_grub.py |
743 | +++ /dev/null |
744 | @@ -1,29 +0,0 @@ |
745 | -# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
746 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
747 | - |
748 | -"""Tests for the install_grub command.""" |
749 | - |
750 | - |
751 | -import os.path |
752 | - |
753 | -from testtools.matchers import FileExists |
754 | - |
755 | -from maastesting.factory import factory |
756 | -from maastesting.testcase import MAASTestCase |
757 | -import provisioningserver.boot.install_grub |
758 | -from provisioningserver.testing.config import ClusterConfigurationFixture |
759 | -from provisioningserver.utils.script import MainScript |
760 | - |
761 | - |
762 | -class TestInstallGrub(MAASTestCase): |
763 | - def test_integration(self): |
764 | - tftproot = self.make_dir() |
765 | - self.useFixture(ClusterConfigurationFixture(tftp_root=tftproot)) |
766 | - |
767 | - action = factory.make_name("action") |
768 | - script = MainScript(action) |
769 | - script.register(action, provisioningserver.boot.install_grub) |
770 | - script.execute((action,)) |
771 | - |
772 | - config_filename = os.path.join("grub", "grub.cfg") |
773 | - self.assertThat(os.path.join(tftproot, config_filename), FileExists()) |
774 | diff --git a/src/provisioningserver/boot/tests/test_uefi_amd64.py b/src/provisioningserver/boot/tests/test_uefi_amd64.py |
775 | index 328e7dd..886dca5 100644 |
776 | --- a/src/provisioningserver/boot/tests/test_uefi_amd64.py |
777 | +++ b/src/provisioningserver/boot/tests/test_uefi_amd64.py |
778 | @@ -1,10 +1,11 @@ |
779 | -# Copyright 2014-2018 Canonical Ltd. This software is licensed under the |
780 | +# Copyright 2014-2021 Canonical Ltd. This software is licensed under the |
781 | # GNU Affero General Public License version 3 (see the file LICENSE). |
782 | |
783 | """Tests for `provisioningserver.boot.uefi_amd64`.""" |
784 | |
785 | |
786 | import os |
787 | +import random |
788 | import re |
789 | from unittest.mock import sentinel |
790 | |
791 | @@ -19,6 +20,7 @@ from testtools.matchers import ( |
792 | from maastesting.factory import factory |
793 | from maastesting.matchers import FileContains, MockAnyCall, MockCalledOnce |
794 | from maastesting.testcase import MAASTestCase |
795 | +from provisioningserver import boot |
796 | from provisioningserver.boot import BytesReader |
797 | from provisioningserver.boot import uefi_amd64 as uefi_amd64_module |
798 | from provisioningserver.boot.testing import TFTPPath, TFTPPathAndComponents |
799 | @@ -29,6 +31,7 @@ from provisioningserver.boot.uefi_amd64 import ( |
800 | UEFIAMD64BootMethod, |
801 | UEFIAMD64HTTPBootMethod, |
802 | ) |
803 | +from provisioningserver.testing.config import ClusterConfigurationFixture |
804 | from provisioningserver.tests.test_kernel_opts import make_kernel_parameters |
805 | from provisioningserver.utils import typed |
806 | from provisioningserver.utils.fs import tempdir |
807 | @@ -68,6 +71,12 @@ class TestUEFIAMD64BootMethodRender(MAASTestCase): |
808 | """Tests for |
809 | `provisioningserver.boot_amd64.uefi.UEFIAMD64BootMethod.render`.""" |
810 | |
811 | + def setUp(self): |
812 | + super().setUp() |
813 | + boot.debug_enabled.cache_clear() |
814 | + self.addClassCleanup(boot.debug_enabled.cache_clear) |
815 | + self.useFixture(ClusterConfigurationFixture(debug=False)) |
816 | + |
817 | def test_get_reader(self): |
818 | # Given the right configuration options, the UEFI configuration is |
819 | # correctly rendered. |
820 | @@ -101,7 +110,7 @@ class TestUEFIAMD64BootMethodRender(MAASTestCase): |
821 | re.MULTILINE | re.DOTALL, |
822 | ), |
823 | MatchesRegex( |
824 | - r".*^\s+linuxefi %s/%s/%s .+?$" |
825 | + r".*^\s+linux %s/%s/%s .+?$" |
826 | % ( |
827 | re.escape(fs_host), |
828 | re.escape(image_dir), |
829 | @@ -110,7 +119,7 @@ class TestUEFIAMD64BootMethodRender(MAASTestCase): |
830 | re.MULTILINE | re.DOTALL, |
831 | ), |
832 | MatchesRegex( |
833 | - r".*^\s+initrdefi %s/%s/%s$" |
834 | + r".*^\s+initrd %s/%s/%s$" |
835 | % ( |
836 | re.escape(fs_host), |
837 | re.escape(image_dir), |
838 | @@ -126,7 +135,7 @@ class TestUEFIAMD64BootMethodRender(MAASTestCase): |
839 | method = UEFIAMD64BootMethod() |
840 | options = { |
841 | "backend": None, |
842 | - "kernel_params": make_kernel_parameters(purpose="install"), |
843 | + "kernel_params": make_kernel_parameters(purpose="xinstall"), |
844 | } |
845 | # Capture the output before sprinking in some random options. |
846 | output_before = method.get_reader(**options).read(10000) |
847 | @@ -167,7 +176,7 @@ class TestUEFIAMD64BootMethodRender(MAASTestCase): |
848 | output, |
849 | ContainsAll( |
850 | [ |
851 | - "menuentry 'Enlist'", |
852 | + "menuentry 'Ephemeral'", |
853 | "%s/%s/%s" % (params.osystem, params.arch, params.subarch), |
854 | params.kernel, |
855 | ] |
856 | @@ -185,7 +194,7 @@ class TestUEFIAMD64BootMethodRender(MAASTestCase): |
857 | output, |
858 | ContainsAll( |
859 | [ |
860 | - "menuentry 'Commission'", |
861 | + "menuentry 'Ephemeral'", |
862 | "%s/%s/%s" % (params.osystem, params.arch, params.subarch), |
863 | params.kernel, |
864 | ] |
865 | @@ -261,6 +270,8 @@ class TestUEFIAMD64BootMethodRegex(MAASTestCase): |
866 | ) |
867 | |
868 | def test_re_config_file_does_not_match_default_grub_config_file(self): |
869 | + # The default grub.cfg is on the filesystem let the normal handler |
870 | + # grab it. |
871 | self.assertIsNone(re_config_file.match(b"grub/grub.cfg")) |
872 | |
873 | def test_re_config_file_with_default(self): |
874 | @@ -293,6 +304,86 @@ class TestUEFIAMD64BootMethodRegex(MAASTestCase): |
875 | class TestUEFIAMD64BootMethod(MAASTestCase): |
876 | """Tests `provisioningserver.boot.uefi_amd64.UEFIAMD64BootMethod`.""" |
877 | |
878 | + def test_match_path_none(self): |
879 | + method = UEFIAMD64BootMethod() |
880 | + backend = random.choice(["http", "tftp"]) |
881 | + self.assertIsNone( |
882 | + method.match_path(backend, factory.make_string().encode()) |
883 | + ) |
884 | + |
885 | + def test_match_path_mac_colon(self): |
886 | + method = UEFIAMD64BootMethod() |
887 | + backend = random.choice(["http", "tftp"]) |
888 | + mac = factory.make_mac_address() |
889 | + self.assertEqual( |
890 | + {"mac": mac.replace(":", "-")}, |
891 | + method.match_path(backend, f"/grub/grub.cfg-{mac}".encode()), |
892 | + ) |
893 | + |
894 | + def test_match_path_mac_dash(self): |
895 | + method = UEFIAMD64BootMethod() |
896 | + backend = random.choice(["http", "tftp"]) |
897 | + mac = factory.make_mac_address().replace(":", "-") |
898 | + self.assertEqual( |
899 | + {"mac": mac}, |
900 | + method.match_path(backend, f"/grub/grub.cfg-{mac}".encode()), |
901 | + ) |
902 | + |
903 | + def test_match_path_arch(self): |
904 | + method = UEFIAMD64BootMethod() |
905 | + backend = random.choice(["http", "tftp"]) |
906 | + arch = factory.make_string() |
907 | + self.assertEqual( |
908 | + {"arch": arch}, |
909 | + method.match_path( |
910 | + backend, f"/grub/grub.cfg-default-{arch}".encode() |
911 | + ), |
912 | + ) |
913 | + |
914 | + def test_match_path_arch_x86_64(self): |
915 | + method = UEFIAMD64BootMethod() |
916 | + backend = random.choice(["http", "tftp"]) |
917 | + self.assertEqual( |
918 | + {"arch": "amd64"}, |
919 | + method.match_path(backend, b"/grub/grub.cfg-default-x86_64"), |
920 | + ) |
921 | + |
922 | + def test_match_path_arch_powerpc(self): |
923 | + method = UEFIAMD64BootMethod() |
924 | + backend = random.choice(["http", "tftp"]) |
925 | + self.assertEqual( |
926 | + {"arch": "ppc64el"}, |
927 | + method.match_path(backend, b"/grub/grub.cfg-default-powerpc"), |
928 | + ) |
929 | + |
930 | + def test_match_path_arch_ppc64(self): |
931 | + method = UEFIAMD64BootMethod() |
932 | + backend = random.choice(["http", "tftp"]) |
933 | + self.assertEqual( |
934 | + {"arch": "ppc64el"}, |
935 | + method.match_path(backend, b"/grub/grub.cfg-default-ppc64"), |
936 | + ) |
937 | + |
938 | + def test_match_path_arch_ppc64le(self): |
939 | + method = UEFIAMD64BootMethod() |
940 | + backend = random.choice(["http", "tftp"]) |
941 | + self.assertEqual( |
942 | + {"arch": "ppc64el"}, |
943 | + method.match_path(backend, b"/grub/grub.cfg-default-ppc64le"), |
944 | + ) |
945 | + |
946 | + def test_match_path_arch_subarch(self): |
947 | + method = UEFIAMD64BootMethod() |
948 | + backend = random.choice(["http", "tftp"]) |
949 | + arch = factory.make_string() |
950 | + subarch = factory.make_string() |
951 | + self.assertEqual( |
952 | + {"arch": arch, "subarch": subarch}, |
953 | + method.match_path( |
954 | + backend, f"/grub/grub.cfg-default-{arch}-{subarch}".encode() |
955 | + ), |
956 | + ) |
957 | + |
958 | def test_link_bootloader_creates_grub_cfg(self): |
959 | method = UEFIAMD64BootMethod() |
960 | with tempdir() as tmp: |
961 | diff --git a/src/provisioningserver/boot/uefi_amd64.py b/src/provisioningserver/boot/uefi_amd64.py |
962 | index 4e42c78..1399fa0 100644 |
963 | --- a/src/provisioningserver/boot/uefi_amd64.py |
964 | +++ b/src/provisioningserver/boot/uefi_amd64.py |
965 | @@ -1,4 +1,4 @@ |
966 | -# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
967 | +# Copyright 2014-2021 Canonical Ltd. This software is licensed under the |
968 | # GNU Affero General Public License version 3 (see the file LICENSE). |
969 | |
970 | """UEFI AMD64 Boot Method""" |
971 | @@ -24,11 +24,11 @@ CONFIG_FILE = dedent( |
972 | # MAAS GRUB2 pre-loader configuration file |
973 | |
974 | # Load based on MAC address first. |
975 | - configfile (pxe)/grub/grub.cfg-${net_default_mac} |
976 | + configfile /grub/grub.cfg-${net_default_mac} |
977 | |
978 | - # Failed to load based on MAC address. |
979 | - # Load amd64 by default, UEFI only supported by 64-bit |
980 | - configfile (pxe)/grub/grub.cfg-default-amd64 |
981 | + # Failed to load based on MAC address. Load based on the CPU |
982 | + # architecture. |
983 | + configfile /grub/grub.cfg-default-${grub_cpu} |
984 | """ |
985 | ) |
986 | |
987 | @@ -36,7 +36,7 @@ CONFIG_FILE = dedent( |
988 | # format. Required for UEFI as GRUB2 only presents the MAC address |
989 | # in colon-seperated format. |
990 | re_mac_address_octet = r"[0-9a-f]{2}" |
991 | -re_mac_address = re.compile(":".join(repeat(re_mac_address_octet, 6))) |
992 | +re_mac_address = re.compile("[:-]".join(repeat(re_mac_address_octet, 6))) |
993 | |
994 | # Match the grub/grub.cfg-* request for UEFI (aka. GRUB2) |
995 | re_config_file = r""" |
996 | @@ -48,10 +48,10 @@ re_config_file = r""" |
997 | (?P<mac>{re_mac_address.pattern}) # Capture UEFI MAC. |
998 | | # or "default" |
999 | default |
1000 | - (?: # perhaps with specified arch, with a separator of '-' |
1001 | + (?: # perhaps with specified arch, with a separator of '-' |
1002 | [-](?P<arch>\w+) # arch |
1003 | (?:-(?P<subarch>\w+))? # optional subarch |
1004 | - )? |
1005 | + )? |
1006 | ) |
1007 | $ |
1008 | """ |
1009 | @@ -90,6 +90,14 @@ class UEFIAMD64BootMethod(BootMethod): |
1010 | if mac is not None: |
1011 | params["mac"] = mac.replace(":", "-") |
1012 | |
1013 | + # MAAS uses Debian architectures while GRUB uses standard Linux |
1014 | + # architectures. |
1015 | + arch = params.get("arch") |
1016 | + if arch == "x86_64": |
1017 | + params["arch"] = "amd64" |
1018 | + elif arch in {"powerpc", "ppc64", "ppc64le"}: |
1019 | + params["arch"] = "ppc64el" |
1020 | + |
1021 | return params |
1022 | |
1023 | def get_reader(self, backend, kernel_params, **extra): |
1024 | @@ -124,7 +132,9 @@ class UEFIAMD64BootMethod(BootMethod): |
1025 | # UEFI. And so we fix it here, instead of in the common code. See |
1026 | # also src/provisioningserver/kernel_opts.py. |
1027 | namespace["kernel_command"] = kernel_command |
1028 | - return BytesReader(template.substitute(namespace).encode("utf-8")) |
1029 | + return BytesReader( |
1030 | + template.substitute(namespace).strip().encode("utf-8") |
1031 | + ) |
1032 | |
1033 | def _find_and_copy_bootloaders(self, destination, log_missing=True): |
1034 | if not super()._find_and_copy_bootloaders(destination, False): |
1035 | diff --git a/src/provisioningserver/config.py b/src/provisioningserver/config.py |
1036 | index 5177450..49f4da2 100644 |
1037 | --- a/src/provisioningserver/config.py |
1038 | +++ b/src/provisioningserver/config.py |
1039 | @@ -1,4 +1,4 @@ |
1040 | -# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
1041 | +# Copyright 2012-2021 Canonical Ltd. This software is licensed under the |
1042 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1043 | |
1044 | """Configuration for the MAAS cluster. |
1045 | @@ -104,6 +104,7 @@ It can be used like so:: |
1046 | |
1047 | from contextlib import closing, contextmanager |
1048 | from copy import deepcopy |
1049 | +from functools import lru_cache |
1050 | from itertools import islice |
1051 | import json |
1052 | import logging |
1053 | @@ -812,3 +813,10 @@ def is_dev_environment(): |
1054 | return False |
1055 | else: |
1056 | return True |
1057 | + |
1058 | + |
1059 | +@lru_cache(1) |
1060 | +def debug_enabled(): |
1061 | + """Return and cache whether debug has been enabled.""" |
1062 | + with ClusterConfiguration.open() as config: |
1063 | + return config.debug |
1064 | diff --git a/src/provisioningserver/templates/uefi/config.commissioning.amd64.template b/src/provisioningserver/templates/uefi/config.commissioning.amd64.template |
1065 | deleted file mode 100644 |
1066 | index 47dbec9..0000000 |
1067 | --- a/src/provisioningserver/templates/uefi/config.commissioning.amd64.template |
1068 | +++ /dev/null |
1069 | @@ -1,8 +0,0 @@ |
1070 | -set default="0" |
1071 | -set timeout=0 |
1072 | - |
1073 | -menuentry 'Commission' { |
1074 | - echo 'Booting under MAAS direction...' |
1075 | - linuxefi {{kernel_params | fs_efihost}}{{kernel_params | kernel_path }} {{kernel_params | kernel_command}} BOOTIF=01-${net_default_mac} |
1076 | - initrdefi {{kernel_params | fs_efihost}}{{kernel_params | initrd_path }} |
1077 | -} |
1078 | diff --git a/src/provisioningserver/templates/uefi/config.commissioning.template b/src/provisioningserver/templates/uefi/config.commissioning.template |
1079 | index c860a79..cc3f2fc 100644 |
1080 | --- a/src/provisioningserver/templates/uefi/config.commissioning.template |
1081 | +++ b/src/provisioningserver/templates/uefi/config.commissioning.template |
1082 | @@ -1,7 +1,8 @@ |
1083 | +{{if debug}}set debug="all"{{endif}} |
1084 | set default="0" |
1085 | set timeout=0 |
1086 | |
1087 | -menuentry 'Commission' { |
1088 | +menuentry 'Ephemeral' { |
1089 | echo 'Booting under MAAS direction...' |
1090 | linux {{kernel_params | fs_efihost}}{{kernel_params | kernel_path }} {{kernel_params | kernel_command}} BOOTIF=01-${net_default_mac} |
1091 | initrd {{kernel_params | fs_efihost}}{{kernel_params | initrd_path }} |
1092 | diff --git a/src/provisioningserver/templates/uefi/config.enlist.amd64.template b/src/provisioningserver/templates/uefi/config.enlist.amd64.template |
1093 | deleted file mode 100644 |
1094 | index edf800e..0000000 |
1095 | --- a/src/provisioningserver/templates/uefi/config.enlist.amd64.template |
1096 | +++ /dev/null |
1097 | @@ -1,8 +0,0 @@ |
1098 | -set default="0" |
1099 | -set timeout=0 |
1100 | - |
1101 | -menuentry 'Enlist' { |
1102 | - echo 'Booting under MAAS direction...' |
1103 | - linuxefi {{kernel_params | fs_efihost}}{{kernel_params | kernel_path }} {{kernel_params | kernel_command}} BOOTIF=01-${net_default_mac} |
1104 | - initrdefi {{kernel_params | fs_efihost}}{{kernel_params | initrd_path }} |
1105 | -} |
1106 | diff --git a/src/provisioningserver/templates/uefi/config.enlist.template b/src/provisioningserver/templates/uefi/config.enlist.template |
1107 | deleted file mode 100644 |
1108 | index 3546960..0000000 |
1109 | --- a/src/provisioningserver/templates/uefi/config.enlist.template |
1110 | +++ /dev/null |
1111 | @@ -1,8 +0,0 @@ |
1112 | -set default="0" |
1113 | -set timeout=0 |
1114 | - |
1115 | -menuentry 'Enlist' { |
1116 | - echo 'Booting under MAAS direction...' |
1117 | - linux {{kernel_params | fs_efihost}}{{kernel_params | kernel_path }} {{kernel_params | kernel_command}} BOOTIF=01-${net_default_mac} |
1118 | - initrd {{kernel_params | fs_efihost}}{{kernel_params | initrd_path }} |
1119 | -} |
1120 | diff --git a/src/provisioningserver/templates/uefi/config.enlist.template b/src/provisioningserver/templates/uefi/config.enlist.template |
1121 | new file mode 120000 |
1122 | index 0000000..f0f1b87 |
1123 | --- /dev/null |
1124 | +++ b/src/provisioningserver/templates/uefi/config.enlist.template |
1125 | @@ -0,0 +1 @@ |
1126 | +config.commissioning.template |
1127 | \ No newline at end of file |
1128 | diff --git a/src/provisioningserver/templates/uefi/config.install.amd64.template b/src/provisioningserver/templates/uefi/config.install.amd64.template |
1129 | deleted file mode 100644 |
1130 | index eea84e0..0000000 |
1131 | --- a/src/provisioningserver/templates/uefi/config.install.amd64.template |
1132 | +++ /dev/null |
1133 | @@ -1,8 +0,0 @@ |
1134 | -set default="0" |
1135 | -set timeout=0 |
1136 | - |
1137 | -menuentry 'Install' { |
1138 | - echo 'Booting under MAAS direction...' |
1139 | - linuxefi {{kernel_params | fs_efihost}}{{kernel_params | kernel_path }} {{kernel_params | kernel_command}} BOOTIF=01-${net_default_mac} |
1140 | - initrdefi {{kernel_params | fs_efihost}}{{kernel_params | initrd_path }} |
1141 | -} |
1142 | diff --git a/src/provisioningserver/templates/uefi/config.install.template b/src/provisioningserver/templates/uefi/config.install.template |
1143 | deleted file mode 100644 |
1144 | index 7bd9538..0000000 |
1145 | --- a/src/provisioningserver/templates/uefi/config.install.template |
1146 | +++ /dev/null |
1147 | @@ -1,8 +0,0 @@ |
1148 | -set default="0" |
1149 | -set timeout=0 |
1150 | - |
1151 | -menuentry 'Install' { |
1152 | - echo 'Booting under MAAS direction...' |
1153 | - linux {{kernel_params | fs_efihost}}{{kernel_params | kernel_path }} {{kernel_params | kernel_command}} BOOTIF=01-${net_default_mac} |
1154 | - initrd {{kernel_params | fs_efihost}}{{kernel_params | initrd_path }} |
1155 | -} |
1156 | diff --git a/src/provisioningserver/templates/uefi/config.local.amd64.template b/src/provisioningserver/templates/uefi/config.local.amd64.template |
1157 | index c7d3d4c..7cfc382 100644 |
1158 | --- a/src/provisioningserver/templates/uefi/config.local.amd64.template |
1159 | +++ b/src/provisioningserver/templates/uefi/config.local.amd64.template |
1160 | @@ -1,3 +1,4 @@ |
1161 | +{{if debug}}set debug="all"{{endif}} |
1162 | set default="0" |
1163 | set timeout=0 |
1164 | |
1165 | diff --git a/src/provisioningserver/templates/uefi/config.local.arm64.template b/src/provisioningserver/templates/uefi/config.local.arm64.template |
1166 | index cb34b1b..ab10c41 100644 |
1167 | --- a/src/provisioningserver/templates/uefi/config.local.arm64.template |
1168 | +++ b/src/provisioningserver/templates/uefi/config.local.arm64.template |
1169 | @@ -1,3 +1,4 @@ |
1170 | +{{if debug}}set debug="all"{{endif}} |
1171 | set default="0" |
1172 | set timeout=0 |
1173 | |
1174 | diff --git a/src/provisioningserver/templates/uefi/config.local.ppc64el.template b/src/provisioningserver/templates/uefi/config.local.ppc64el.template |
1175 | index b592f26..406451b 100644 |
1176 | --- a/src/provisioningserver/templates/uefi/config.local.ppc64el.template |
1177 | +++ b/src/provisioningserver/templates/uefi/config.local.ppc64el.template |
1178 | @@ -1,3 +1,4 @@ |
1179 | +{{if debug}}set debug="all"{{endif}} |
1180 | set default="0" |
1181 | set timeout=0 |
1182 | |
1183 | diff --git a/src/provisioningserver/templates/uefi/config.poweroff.template b/src/provisioningserver/templates/uefi/config.poweroff.template |
1184 | index 451be03..95016c7 100644 |
1185 | --- a/src/provisioningserver/templates/uefi/config.poweroff.template |
1186 | +++ b/src/provisioningserver/templates/uefi/config.poweroff.template |
1187 | @@ -1,3 +1,4 @@ |
1188 | +{{if debug}}set debug="all"{{endif}} |
1189 | set default="0" |
1190 | set timeout=0 |
1191 | |
1192 | diff --git a/src/provisioningserver/templates/uefi/config.xinstall.amd64.template b/src/provisioningserver/templates/uefi/config.xinstall.amd64.template |
1193 | deleted file mode 120000 |
1194 | index d321baa..0000000 |
1195 | --- a/src/provisioningserver/templates/uefi/config.xinstall.amd64.template |
1196 | +++ /dev/null |
1197 | @@ -1 +0,0 @@ |
1198 | -config.install.amd64.template |
1199 | \ No newline at end of file |
1200 | diff --git a/src/provisioningserver/templates/uefi/config.xinstall.template b/src/provisioningserver/templates/uefi/config.xinstall.template |
1201 | index 44389e4..f0f1b87 120000 |
1202 | --- a/src/provisioningserver/templates/uefi/config.xinstall.template |
1203 | +++ b/src/provisioningserver/templates/uefi/config.xinstall.template |
1204 | @@ -1 +1 @@ |
1205 | -config.install.template |
1206 | \ No newline at end of file |
1207 | +config.commissioning.template |
1208 | \ No newline at end of file |
1209 | diff --git a/src/provisioningserver/tests/test_config.py b/src/provisioningserver/tests/test_config.py |
1210 | index 1488283..90c4841 100644 |
1211 | --- a/src/provisioningserver/tests/test_config.py |
1212 | +++ b/src/provisioningserver/tests/test_config.py |
1213 | @@ -1,4 +1,4 @@ |
1214 | -# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
1215 | +# Copyright 2012-2021 Canonical Ltd. This software is licensed under the |
1216 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1217 | |
1218 | """Tests for provisioning configuration.""" |
1219 | @@ -29,6 +29,7 @@ from maastesting.factory import factory |
1220 | from maastesting.fixtures import ImportErrorFixture |
1221 | from maastesting.matchers import MockCalledOnceWith, MockNotCalled |
1222 | from maastesting.testcase import MAASTestCase |
1223 | +from provisioningserver import config as config_module |
1224 | from provisioningserver.config import ( |
1225 | ClusterConfiguration, |
1226 | Configuration, |
1227 | @@ -37,6 +38,7 @@ from provisioningserver.config import ( |
1228 | ConfigurationImmutable, |
1229 | ConfigurationMeta, |
1230 | ConfigurationOption, |
1231 | + debug_enabled, |
1232 | is_dev_environment, |
1233 | ) |
1234 | from provisioningserver.path import get_data_path |
1235 | @@ -724,3 +726,30 @@ class TestConfig(MAASTestCase): |
1236 | |
1237 | def test_is_dev_environment_returns_true(self): |
1238 | self.assertTrue(is_dev_environment()) |
1239 | + |
1240 | + |
1241 | +class TestDebugEnabled(MAASTestCase): |
1242 | + """Tests for `debug_enabled`.""" |
1243 | + |
1244 | + def setUp(self): |
1245 | + super().setUp() |
1246 | + # Make sure things aren't pulled from cache |
1247 | + debug_enabled.cache_clear() |
1248 | + |
1249 | + def test_debug_enabled_false(self): |
1250 | + # Verifies that the default state of debug is false. |
1251 | + self.assertFalse(debug_enabled()) |
1252 | + |
1253 | + def test_debug_enabled(self): |
1254 | + debug = factory.pick_bool() |
1255 | + self.useFixture(ClusterConfigurationFixture(debug=debug)) |
1256 | + self.assertEqual(debug, debug_enabled()) |
1257 | + |
1258 | + def test_debug_enabled_cached(self): |
1259 | + debug = factory.pick_bool() |
1260 | + self.useFixture(ClusterConfigurationFixture(debug=debug)) |
1261 | + # Prime cache |
1262 | + debug_enabled() |
1263 | + mock_open = self.patch(config_module.ClusterConfiguration, "open") |
1264 | + self.assertEqual(debug, debug_enabled()) |
1265 | + mock_open.assert_not_called() |
UNIT TESTS
-b 2.9 lp:~mfo/maas/+git/maas into -b 2.9 lp:~maas-committers/maas
STATUS: FAILED maas-ci. internal: 8080/job/ maas-tester/ 2692/console 130d04f6da345b7 0beedac0a5
LOG: http://
COMMIT: 9feb47e7506138d