Merge ~mfo/maas:2.9 into maas: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)
Reviewer Review Type Date Requested Status
MAAS Lander Needs Fixing
Mauricio Faria de Oliveira Pending
Review via email: mp+444306@code.launchpad.net

Description of the change

Please ignore; this is only for testing.

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

UNIT TESTS
-b 2.9 lp:~mfo/maas/+git/maas into -b 2.9 lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas-tester/2692/console
COMMIT: 9feb47e7506138d130d04f6da345b70beedac0a5

review: Needs Fixing
Revision history for this message
Mauricio Faria de Oliveira (mfo) wrote :

UNIT TESTS: pass

http://maas-ci.internal:8080/job/maas-tester/2700/

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
1diff --git a/debian/changelog b/debian/changelog
2index 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
22diff --git a/setup.py b/setup.py
23index 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",
35diff --git a/snap/local/tree/bin/maas-deb-migrate b/snap/local/tree/bin/maas-deb-migrate
36index 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
68diff --git a/src/maasserver/models/interface.py b/src/maasserver/models/interface.py
69index 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
122diff --git a/src/maasserver/models/node.py b/src/maasserver/models/node.py
123index 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
179diff --git a/src/maasserver/models/tests/test_interface.py b/src/maasserver/models/tests/test_interface.py
180index 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):
286diff --git a/src/maasserver/models/tests/test_node.py b/src/maasserver/models/tests/test_node.py
287index 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):
503diff --git a/src/maasserver/websockets/handlers/pod.py b/src/maasserver/websockets/handlers/pod.py
504index 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
579diff --git a/src/maasserver/websockets/handlers/tests/test_pod.py b/src/maasserver/websockets/handlers/tests/test_pod.py
580index 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)
613diff --git a/src/provisioningserver/__main__.py b/src/provisioningserver/__main__.py
614index 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,
640diff --git a/src/provisioningserver/boot/__init__.py b/src/provisioningserver/boot/__init__.py
641index 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
660diff --git a/src/provisioningserver/boot/install_grub.py b/src/provisioningserver/boot/install_grub.py
661deleted file mode 100644
662index 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)
702diff --git a/src/provisioningserver/boot/tests/test_boot.py b/src/provisioningserver/boot/tests/test_boot.py
703index 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
739diff --git a/src/provisioningserver/boot/tests/test_install_grub.py b/src/provisioningserver/boot/tests/test_install_grub.py
740deleted file mode 100644
741index 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())
774diff --git a/src/provisioningserver/boot/tests/test_uefi_amd64.py b/src/provisioningserver/boot/tests/test_uefi_amd64.py
775index 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:
961diff --git a/src/provisioningserver/boot/uefi_amd64.py b/src/provisioningserver/boot/uefi_amd64.py
962index 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):
1035diff --git a/src/provisioningserver/config.py b/src/provisioningserver/config.py
1036index 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
1064diff --git a/src/provisioningserver/templates/uefi/config.commissioning.amd64.template b/src/provisioningserver/templates/uefi/config.commissioning.amd64.template
1065deleted file mode 100644
1066index 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-}
1078diff --git a/src/provisioningserver/templates/uefi/config.commissioning.template b/src/provisioningserver/templates/uefi/config.commissioning.template
1079index 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 }}
1092diff --git a/src/provisioningserver/templates/uefi/config.enlist.amd64.template b/src/provisioningserver/templates/uefi/config.enlist.amd64.template
1093deleted file mode 100644
1094index 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-}
1106diff --git a/src/provisioningserver/templates/uefi/config.enlist.template b/src/provisioningserver/templates/uefi/config.enlist.template
1107deleted file mode 100644
1108index 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-}
1120diff --git a/src/provisioningserver/templates/uefi/config.enlist.template b/src/provisioningserver/templates/uefi/config.enlist.template
1121new file mode 120000
1122index 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
1128diff --git a/src/provisioningserver/templates/uefi/config.install.amd64.template b/src/provisioningserver/templates/uefi/config.install.amd64.template
1129deleted file mode 100644
1130index 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-}
1142diff --git a/src/provisioningserver/templates/uefi/config.install.template b/src/provisioningserver/templates/uefi/config.install.template
1143deleted file mode 100644
1144index 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-}
1156diff --git a/src/provisioningserver/templates/uefi/config.local.amd64.template b/src/provisioningserver/templates/uefi/config.local.amd64.template
1157index 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
1165diff --git a/src/provisioningserver/templates/uefi/config.local.arm64.template b/src/provisioningserver/templates/uefi/config.local.arm64.template
1166index 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
1174diff --git a/src/provisioningserver/templates/uefi/config.local.ppc64el.template b/src/provisioningserver/templates/uefi/config.local.ppc64el.template
1175index 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
1183diff --git a/src/provisioningserver/templates/uefi/config.poweroff.template b/src/provisioningserver/templates/uefi/config.poweroff.template
1184index 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
1192diff --git a/src/provisioningserver/templates/uefi/config.xinstall.amd64.template b/src/provisioningserver/templates/uefi/config.xinstall.amd64.template
1193deleted file mode 120000
1194index 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
1200diff --git a/src/provisioningserver/templates/uefi/config.xinstall.template b/src/provisioningserver/templates/uefi/config.xinstall.template
1201index 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
1209diff --git a/src/provisioningserver/tests/test_config.py b/src/provisioningserver/tests/test_config.py
1210index 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()

Subscribers

People subscribed via source and target branches