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
=== modified file 'src/maasserver/api/rackcontrollers.py'
--- src/maasserver/api/rackcontrollers.py 2016-03-28 13:54:47 +0000
+++ src/maasserver/api/rackcontrollers.py 2016-04-11 07:25:45 +0000
@@ -6,6 +6,7 @@
6 'RackControllersHandler',6 'RackControllersHandler',
7 ]7 ]
88
9
9from django.conf import settings10from django.conf import settings
10from django.http import HttpResponse11from django.http import HttpResponse
11from maasserver.api.nodes import (12from maasserver.api.nodes import (
@@ -104,6 +105,19 @@
104 "Import of boot images started on %s" % rack.hostname,105 "Import of boot images started on %s" % rack.hostname,
105 content_type=("text/plain; charset=%s" % settings.DEFAULT_CHARSET))106 content_type=("text/plain; charset=%s" % settings.DEFAULT_CHARSET))
106107
108 @operation(idempotent=True)
109 def list_boot_images(self, request, system_id):
110 """List all available boot images.
111
112 Shows all available boot images and lists whether they are in sync with
113 the region.
114
115 Returns 404 if the rack controller is not found.
116 """
117 rack = self.model.objects.get_node_or_404(
118 system_id=system_id, user=request.user, perm=NODE_PERMISSION.VIEW)
119 return rack.list_boot_images()
120
107 @classmethod121 @classmethod
108 def resource_uri(cls, rackcontroller=None):122 def resource_uri(cls, rackcontroller=None):
109 rackcontroller_id = "system_id"123 rackcontroller_id = "system_id"
110124
=== modified file 'src/maasserver/api/tests/test_rackcontroller.py'
--- src/maasserver/api/tests/test_rackcontroller.py 2016-03-28 13:54:47 +0000
+++ src/maasserver/api/tests/test_rackcontroller.py 2016-04-11 07:25:45 +0000
@@ -70,6 +70,16 @@
70 http.client.FORBIDDEN, response.status_code,70 http.client.FORBIDDEN, response.status_code,
71 explain_unexpected_response(http.client.FORBIDDEN, response))71 explain_unexpected_response(http.client.FORBIDDEN, response))
7272
73 def test_GET_list_boot_images(self):
74 rack = factory.make_RackController(owner=factory.make_User())
75 response = self.client.get(
76 self.get_rack_uri(rack), {'op': 'list_boot_images'})
77 self.assertEqual(
78 http.client.OK, response.status_code,
79 explain_unexpected_response(http.client.OK, response))
80 self.assertItemsEqual(
81 ['connected', 'images'], json_load_bytes(response.content))
82
7383
74class TestRackControllersAPI(APITestCase):84class TestRackControllersAPI(APITestCase):
75 """Tests for /api/2.0/rackcontrollers/."""85 """Tests for /api/2.0/rackcontrollers/."""
7686
=== modified file 'src/maasserver/models/node.py'
--- src/maasserver/models/node.py 2016-04-10 16:17:48 +0000
+++ src/maasserver/models/node.py 2016-04-11 07:25:45 +0000
@@ -12,7 +12,10 @@
12 ]12 ]
1313
1414
15from collections import namedtuple15from collections import (
16 defaultdict,
17 namedtuple,
18)
16from datetime import timedelta19from datetime import timedelta
17from functools import partial20from functools import partial
18from operator import attrgetter21from operator import attrgetter
@@ -3604,6 +3607,34 @@
3604 "Missing connections to %d region controller(s)." % (3607 "Missing connections to %d region controller(s)." % (
3605 dead_regions + len(missing_regions)))3608 dead_regions + len(missing_regions)))
36063609
3610 def list_boot_images(self):
3611 """Return a list of boot images available on the rack controller."""
3612 # Avoid circular imports
3613 from maasserver.clusterrpc.boot_images import get_boot_images
3614 try:
3615 # Combine all boot images one per name and arch
3616 downloaded_boot_images = defaultdict(set)
3617 for image in get_boot_images(self):
3618 if image['osystem'] == 'custom':
3619 name = image['release']
3620 else:
3621 name = "%s/%s" % (image['osystem'], image['release'])
3622 arch = image['architecture']
3623 subarch = image['subarchitecture']
3624 downloaded_boot_images[name, arch].add(subarch)
3625 # Prevent lint error 'list comprehension redefines'
3626 del name, arch
3627 # Return a list of dictionaries each containing one entry per
3628 # name, architecture like boot-resources does
3629 images = [{
3630 'name': name,
3631 'architecture': arch,
3632 'subarches': sorted(subarches),
3633 } for (name, arch), subarches in downloaded_boot_images.items()]
3634 return {'images': images, 'connected': True}
3635 except NoConnectionsAvailable:
3636 return {'images': [], 'connected': False}
3637
36073638
3608class RegionController(Node):3639class RegionController(Node):
3609 """A node which is running multiple regiond's."""3640 """A node which is running multiple regiond's."""
36103641
=== modified file 'src/maasserver/models/tests/test_node.py'
--- src/maasserver/models/tests/test_node.py 2016-04-08 20:50:21 +0000
+++ src/maasserver/models/tests/test_node.py 2016-04-11 07:25:45 +0000
@@ -16,6 +16,7 @@
16from django.db import transaction16from django.db import transaction
17from fixtures import LoggerFixture17from fixtures import LoggerFixture
18from maasserver import preseed as preseed_module18from maasserver import preseed as preseed_module
19from maasserver.clusterrpc import boot_images
19from maasserver.clusterrpc.power import (20from maasserver.clusterrpc.power import (
20 power_off_node,21 power_off_node,
21 power_query,22 power_query,
@@ -6847,6 +6848,57 @@
6847 len(regions_with_processes) +6848 len(regions_with_processes) +
6848 len(regions_without_processes)))))6849 len(regions_without_processes)))))
68496850
6851 def test_list_boot_images(self):
6852 rack_controller = factory.make_RackController()
6853 self.patch(boot_images, 'get_boot_images').return_value = [
6854 {
6855 'release': 'custom_os',
6856 'osystem': 'custom',
6857 'architecture': 'amd64',
6858 'subarchitecture': 'generic',
6859 },
6860 {
6861 'release': 'trusty',
6862 'osystem': 'ubuntu',
6863 'architecture': 'amd64',
6864 'subarchitecture': 'generic',
6865 },
6866 {
6867 'release': 'trusty',
6868 'osystem': 'ubuntu',
6869 'architecture': 'amd64',
6870 'subarchitecture': 'hwe-t',
6871 },
6872 {
6873 'release': 'trusty',
6874 'osystem': 'ubuntu',
6875 'architecture': 'amd64',
6876 'subarchitecture': 'hwe-x',
6877 },
6878 ]
6879 self.assertItemsEqual(
6880 {
6881 'connected': True,
6882 'images': [
6883 {
6884 'name': 'ubuntu/trusty',
6885 'architecture': 'amd64',
6886 'subarches': ['generic', 'hwe-t', 'hwe-x'],
6887 },
6888 {
6889 'name': 'custom_os',
6890 'architecture': 'amd64',
6891 'subarches': ['generic'],
6892 }
6893 ]
6894 }, rack_controller.list_boot_images())
6895
6896 def test_list_boot_images_when_disconnected(self):
6897 rack_controller = factory.make_RackController()
6898 self.assertItemsEqual(
6899 {'connected': False, 'images': []},
6900 rack_controller.list_boot_images())
6901
68506902
6851class TestRegionController(MAASServerTestCase):6903class TestRegionController(MAASServerTestCase):
68526904
68536905
=== modified file 'src/provisioningserver/rpc/cluster.py'
--- src/provisioningserver/rpc/cluster.py 2016-04-01 21:14:24 +0000
+++ src/provisioningserver/rpc/cluster.py 2016-04-11 07:25:45 +0000
@@ -39,7 +39,7 @@
3939
4040
41class ListBootImages(amp.Command):41class ListBootImages(amp.Command):
42 """List the boot images available on this cluster controller.42 """List the boot images available on this rack controller.
4343
44 :since: 1.544 :since: 1.5
45 """45 """
@@ -60,7 +60,7 @@
6060
6161
62class ListBootImagesV2(amp.Command):62class ListBootImagesV2(amp.Command):
63 """List the boot images available on this cluster controller.63 """List the boot images available on this rack controller.
6464
65 This command compresses the images list to allow more images in the65 This command compresses the images list to allow more images in the
66 response and to remove the amp.TooLong error.66 response and to remove the amp.TooLong error.