Merge lp:~ltrager/maas/lp1566419 into lp:~maas-committers/maas/trunk

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: no longer in the source branch.
Merged at revision: 4897
Proposed branch: lp:~ltrager/maas/lp1566419
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 195 lines (+110/-3)
5 files modified
src/maasserver/api/rackcontrollers.py (+14/-0)
src/maasserver/api/tests/test_rackcontroller.py (+10/-0)
src/maasserver/models/node.py (+32/-1)
src/maasserver/models/tests/test_node.py (+52/-0)
src/provisioningserver/rpc/cluster.py (+2/-2)
To merge this branch: bzr merge lp:~ltrager/maas/lp1566419
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+291066@code.launchpad.net

Commit message

Output boot_resources_in_sync on the rack controller API

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

This seems like an expensive operation to be doing every time we
serialise a rack model object. What is the use case? Perhaps this would
be better modelled as an operation instead of a field?

Then, it's doing non-database IO (RPC calls) within a transaction, which
is bad because it keeps the transaction open for an indefinite time.
Plus, if the transaction must be retried, the non-database IO must be
done again.

These latter points are really hard to fix from within a Piston/Django
handler. MAAS is littered with examples of them, and to fix them we need
a degree of change that's way way out of scope right now. So, you get a
pass on that.

However, the first point, and the points I make in the diff, do need
fixing.

review: Needs Fixing
Revision history for this message
Lee Trager (ltrager) wrote :

The purpose of this is to help users automate MAAS deployments. If you create a script which installs MAAS, sets up user config, downloads images, and then enlists and commissions them you end up with the machines you tried to commission stuck in the BIOS with a 'No such file or directory' message. This is because you can use the API to check if the boot images have been downloaded to the region, but there is no way to see if they've been downloaded to the rack.

I've taken your suggestion and created a separate operation, list-boot-images on the rack-controller API endpoint. This lists all the boot images available currently on the rack controller. The ListBootImagesV2 RPC call returns a list of boot images with one entry per osystem/release/architecture/subarchitecture. I compressed this list down to osystem/release/architecture with the sub-architectures being an array. This is similar to how maas <profile> boot-resources read presents its data.

When the rack controller is disconnected list-boot-images returns an empty list. I added a new element returned on a read operation, is_connected. This returns True/False depending on if the rack controller is currently connected.

Revision history for this message
Gavin Panella (allenap) wrote :

This is a good improvement. I have more questions though!

review: Needs Information
Revision history for this message
Blake Rouse (blake-rouse) :
Revision history for this message
Gavin Panella (allenap) :
Revision history for this message
Blake Rouse (blake-rouse) :
Revision history for this message
Lee Trager (ltrager) wrote :

I've removed is_connected and added a connected field to list_boot_images.

Gavin, to answer you question about why I combine the osystem and release fields into one name fields its to try and output similarly to how the boot-resources API outputs. I'm on the fence whether I combine the fields or output them individually so let me know what you think and I'll do that.

Revision history for this message
Gavin Panella (allenap) wrote :

Cool, thank you for making all those changes. Looks good!

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

Attempt to merge into lp:maas failed due to conflicts:

text conflict in src/maasserver/models/tests/test_node.py

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/rackcontrollers.py'
2--- src/maasserver/api/rackcontrollers.py 2016-03-28 13:54:47 +0000
3+++ src/maasserver/api/rackcontrollers.py 2016-04-11 07:25:45 +0000
4@@ -6,6 +6,7 @@
5 'RackControllersHandler',
6 ]
7
8+
9 from django.conf import settings
10 from django.http import HttpResponse
11 from maasserver.api.nodes import (
12@@ -104,6 +105,19 @@
13 "Import of boot images started on %s" % rack.hostname,
14 content_type=("text/plain; charset=%s" % settings.DEFAULT_CHARSET))
15
16+ @operation(idempotent=True)
17+ def list_boot_images(self, request, system_id):
18+ """List all available boot images.
19+
20+ Shows all available boot images and lists whether they are in sync with
21+ the region.
22+
23+ Returns 404 if the rack controller is not found.
24+ """
25+ rack = self.model.objects.get_node_or_404(
26+ system_id=system_id, user=request.user, perm=NODE_PERMISSION.VIEW)
27+ return rack.list_boot_images()
28+
29 @classmethod
30 def resource_uri(cls, rackcontroller=None):
31 rackcontroller_id = "system_id"
32
33=== modified file 'src/maasserver/api/tests/test_rackcontroller.py'
34--- src/maasserver/api/tests/test_rackcontroller.py 2016-03-28 13:54:47 +0000
35+++ src/maasserver/api/tests/test_rackcontroller.py 2016-04-11 07:25:45 +0000
36@@ -70,6 +70,16 @@
37 http.client.FORBIDDEN, response.status_code,
38 explain_unexpected_response(http.client.FORBIDDEN, response))
39
40+ def test_GET_list_boot_images(self):
41+ rack = factory.make_RackController(owner=factory.make_User())
42+ response = self.client.get(
43+ self.get_rack_uri(rack), {'op': 'list_boot_images'})
44+ self.assertEqual(
45+ http.client.OK, response.status_code,
46+ explain_unexpected_response(http.client.OK, response))
47+ self.assertItemsEqual(
48+ ['connected', 'images'], json_load_bytes(response.content))
49+
50
51 class TestRackControllersAPI(APITestCase):
52 """Tests for /api/2.0/rackcontrollers/."""
53
54=== modified file 'src/maasserver/models/node.py'
55--- src/maasserver/models/node.py 2016-04-10 16:17:48 +0000
56+++ src/maasserver/models/node.py 2016-04-11 07:25:45 +0000
57@@ -12,7 +12,10 @@
58 ]
59
60
61-from collections import namedtuple
62+from collections import (
63+ defaultdict,
64+ namedtuple,
65+)
66 from datetime import timedelta
67 from functools import partial
68 from operator import attrgetter
69@@ -3604,6 +3607,34 @@
70 "Missing connections to %d region controller(s)." % (
71 dead_regions + len(missing_regions)))
72
73+ def list_boot_images(self):
74+ """Return a list of boot images available on the rack controller."""
75+ # Avoid circular imports
76+ from maasserver.clusterrpc.boot_images import get_boot_images
77+ try:
78+ # Combine all boot images one per name and arch
79+ downloaded_boot_images = defaultdict(set)
80+ for image in get_boot_images(self):
81+ if image['osystem'] == 'custom':
82+ name = image['release']
83+ else:
84+ name = "%s/%s" % (image['osystem'], image['release'])
85+ arch = image['architecture']
86+ subarch = image['subarchitecture']
87+ downloaded_boot_images[name, arch].add(subarch)
88+ # Prevent lint error 'list comprehension redefines'
89+ del name, arch
90+ # Return a list of dictionaries each containing one entry per
91+ # name, architecture like boot-resources does
92+ images = [{
93+ 'name': name,
94+ 'architecture': arch,
95+ 'subarches': sorted(subarches),
96+ } for (name, arch), subarches in downloaded_boot_images.items()]
97+ return {'images': images, 'connected': True}
98+ except NoConnectionsAvailable:
99+ return {'images': [], 'connected': False}
100+
101
102 class RegionController(Node):
103 """A node which is running multiple regiond's."""
104
105=== modified file 'src/maasserver/models/tests/test_node.py'
106--- src/maasserver/models/tests/test_node.py 2016-04-08 20:50:21 +0000
107+++ src/maasserver/models/tests/test_node.py 2016-04-11 07:25:45 +0000
108@@ -16,6 +16,7 @@
109 from django.db import transaction
110 from fixtures import LoggerFixture
111 from maasserver import preseed as preseed_module
112+from maasserver.clusterrpc import boot_images
113 from maasserver.clusterrpc.power import (
114 power_off_node,
115 power_query,
116@@ -6847,6 +6848,57 @@
117 len(regions_with_processes) +
118 len(regions_without_processes)))))
119
120+ def test_list_boot_images(self):
121+ rack_controller = factory.make_RackController()
122+ self.patch(boot_images, 'get_boot_images').return_value = [
123+ {
124+ 'release': 'custom_os',
125+ 'osystem': 'custom',
126+ 'architecture': 'amd64',
127+ 'subarchitecture': 'generic',
128+ },
129+ {
130+ 'release': 'trusty',
131+ 'osystem': 'ubuntu',
132+ 'architecture': 'amd64',
133+ 'subarchitecture': 'generic',
134+ },
135+ {
136+ 'release': 'trusty',
137+ 'osystem': 'ubuntu',
138+ 'architecture': 'amd64',
139+ 'subarchitecture': 'hwe-t',
140+ },
141+ {
142+ 'release': 'trusty',
143+ 'osystem': 'ubuntu',
144+ 'architecture': 'amd64',
145+ 'subarchitecture': 'hwe-x',
146+ },
147+ ]
148+ self.assertItemsEqual(
149+ {
150+ 'connected': True,
151+ 'images': [
152+ {
153+ 'name': 'ubuntu/trusty',
154+ 'architecture': 'amd64',
155+ 'subarches': ['generic', 'hwe-t', 'hwe-x'],
156+ },
157+ {
158+ 'name': 'custom_os',
159+ 'architecture': 'amd64',
160+ 'subarches': ['generic'],
161+ }
162+ ]
163+ }, rack_controller.list_boot_images())
164+
165+ def test_list_boot_images_when_disconnected(self):
166+ rack_controller = factory.make_RackController()
167+ self.assertItemsEqual(
168+ {'connected': False, 'images': []},
169+ rack_controller.list_boot_images())
170+
171
172 class TestRegionController(MAASServerTestCase):
173
174
175=== modified file 'src/provisioningserver/rpc/cluster.py'
176--- src/provisioningserver/rpc/cluster.py 2016-04-01 21:14:24 +0000
177+++ src/provisioningserver/rpc/cluster.py 2016-04-11 07:25:45 +0000
178@@ -39,7 +39,7 @@
179
180
181 class ListBootImages(amp.Command):
182- """List the boot images available on this cluster controller.
183+ """List the boot images available on this rack controller.
184
185 :since: 1.5
186 """
187@@ -60,7 +60,7 @@
188
189
190 class ListBootImagesV2(amp.Command):
191- """List the boot images available on this cluster controller.
192+ """List the boot images available on this rack controller.
193
194 This command compresses the images list to allow more images in the
195 response and to remove the amp.TooLong error.