Merge lp:~tycho-s/maas/fix-1181334 into lp:~maas-committers/maas/trunk

Proposed by Tycho Andersen
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 1740
Proposed branch: lp:~tycho-s/maas/fix-1181334
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 168 lines (+72/-14)
4 files modified
src/maasserver/api.py (+21/-14)
src/maasserver/models/bootimage.py (+10/-0)
src/maasserver/models/tests/test_bootimage.py (+30/-0)
src/maasserver/tests/test_api_pxeconfig.py (+11/-0)
To merge this branch: bzr merge lp:~tycho-s/maas/fix-1181334
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Julian Edwards (community) Approve
Review via email: mp+195132@code.launchpad.net

Commit message

Use existing image instead of defaulting to i386

Fixes bug #1181334

Description of the change

Use existing image instead of defaulting to i386

Fixes bug #1181334

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :

Thanks for the fix Tycho. It's got a bug though!

26 + images = BootImage.objects.filter(release=series)

This will not restrict images by nodegroup. Remember that each nodegroup (cluster) has its own set of images.

It would be better if you added a new, tested, BootImagesManager method so it's widely available. Something like:

    get_default_arch_image_in_nodegroup(self, nodegroup, series, purpose):
        """Return the first image available for any architecture in the nodegroup/series supplied."""
        images = BootImage.objects.filter(release=series, nodegroup=nodegroup, purpose=purpose)
        return get_one(images)

You need to test that:
 * it returns None if no images are available
 * it returns the only image if only one image is available
 * it returns the first image if multiple images are available

As an aside, pxeconfig is far too big already and needs better unit tests, but we'll leave that for another day...

Cheers.

review: Needs Fixing
Revision history for this message
Tycho Andersen (tycho-s) wrote :

On Thu, Nov 14, 2013 at 12:30:20AM -0000, Julian Edwards wrote:
> Review: Needs Fixing
>
> Thanks for the fix Tycho. It's got a bug though!
>
> 26 + images = BootImage.objects.filter(release=series)
>
> This will not restrict images by nodegroup. Remember that each nodegroup (cluster) has its own set of images.
>
> It would be better if you added a new, tested, BootImagesManager method so it's widely available. Something like:
>
> get_default_arch_image_in_nodegroup(self, nodegroup, series, purpose):
> """Return the first image available for any architecture in the nodegroup/series supplied."""
> images = BootImage.objects.filter(release=series, nodegroup=nodegroup, purpose=purpose)
> return get_one(images)
>
> You need to test that:
> * it returns None if no images are available
> * it returns the only image if only one image is available
> * it returns the first image if multiple images are available
>
> As an aside, pxeconfig is far too big already and needs better unit tests, but we'll leave that for another day...

Tried to make those changes, except now I can't get the test framework
to behave. As near as I can tell, I'm doing the same thing as I was
before. Thoughts?

\t

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

I've tried getting the tests passing, and came up with: http://paste.ubuntu.com/6418301/

There needs to be some ordering in get_default_arch_image_in_nodegroup() to give a stable result.

I had to specify purpose. You were passing None. If that was meant to mean "no preference" then you'll need some more logic in get_default_arch_image_in_nodegroup() too.

Revision history for this message
Tycho Andersen (tycho-s) wrote :

On Thu, Nov 14, 2013 at 10:36:30PM -0000, Gavin Panella wrote:
> I've tried getting the tests passing, and came up with: http://paste.ubuntu.com/6418301/
>
> There needs to be some ordering in get_default_arch_image_in_nodegroup() to give a stable result.
>
> I had to specify purpose. You were passing None. If that was meant to mean "no preference" then you'll need some more logic in get_default_arch_image_in_nodegroup() too.

Ah ha, I felt like ignoring purpose was probably a bad idea. Thanks!

\t

Revision history for this message
Julian Edwards (julian-edwards) wrote :

Looks ok to me now, unless Gavin has any more to add?

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

Nope, looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2013-11-15 13:24:55 +0000
+++ src/maasserver/api.py 2013-11-15 15:29:36 +0000
@@ -2229,6 +2229,13 @@
2229 """2229 """
2230 node = get_node_from_mac_string(request.GET.get('mac', None))2230 node = get_node_from_mac_string(request.GET.get('mac', None))
22312231
2232 if node is None or node.status == NODE_STATUS.COMMISSIONING:
2233 series = Config.objects.get_config('commissioning_distro_series')
2234 else:
2235 series = node.get_distro_series()
2236
2237 purpose = get_boot_purpose(node)
2238
2232 if node:2239 if node:
2233 arch, subarch = node.architecture.split('/')2240 arch, subarch = node.architecture.split('/')
2234 preseed_url = compose_preseed_url(node)2241 preseed_url = compose_preseed_url(node)
@@ -2238,6 +2245,11 @@
2238 nodegroup = node.nodegroup2245 nodegroup = node.nodegroup
2239 domain = nodegroup.name2246 domain = nodegroup.name
2240 else:2247 else:
2248 nodegroup = find_nodegroup_for_pxeconfig_request(request)
2249 preseed_url = compose_enlistment_preseed_url(nodegroup=nodegroup)
2250 hostname = 'maas-enlist'
2251 domain = Config.objects.get_config('enlistment_domain')
2252
2241 try:2253 try:
2242 pxelinux_arch = request.GET['arch']2254 pxelinux_arch = request.GET['arch']
2243 except KeyError:2255 except KeyError:
@@ -2246,9 +2258,15 @@
2246 # to pxelinux.cfg/default-<arch>-<subarch> for arch detection.2258 # to pxelinux.cfg/default-<arch>-<subarch> for arch detection.
2247 return HttpResponse(status=httplib.NO_CONTENT)2259 return HttpResponse(status=httplib.NO_CONTENT)
2248 else:2260 else:
2249 # Request has already fallen back, so if arch is still not2261 # Look in BootImage for an image that actually exists for the
2250 # provided then use i386.2262 # current series. If nothing is found, fall back to i386 like
2251 arch = ARCHITECTURE.i386.split('/')[0]2263 # we used to. LP #1181334
2264 image = BootImage.objects.get_default_arch_image_in_nodegroup(
2265 nodegroup, series, purpose=purpose)
2266 if image is None:
2267 arch = ARCHITECTURE.i386.split('/')[0]
2268 else:
2269 arch = image.architecture
2252 else:2270 else:
2253 # Map from pxelinux namespace architecture names to MAAS namespace2271 # Map from pxelinux namespace architecture names to MAAS namespace
2254 # architecture names. If this gets bigger, an external lookup table2272 # architecture names. If this gets bigger, an external lookup table
@@ -2270,16 +2288,6 @@
2270 # 1-1 mapping.2288 # 1-1 mapping.
2271 subarch = pxelinux_subarch2289 subarch = pxelinux_subarch
22722290
2273 nodegroup = find_nodegroup_for_pxeconfig_request(request)
2274 preseed_url = compose_enlistment_preseed_url(nodegroup=nodegroup)
2275 hostname = 'maas-enlist'
2276 domain = Config.objects.get_config('enlistment_domain')
2277
2278 if node is None or node.status == NODE_STATUS.COMMISSIONING:
2279 series = Config.objects.get_config('commissioning_distro_series')
2280 else:
2281 series = node.get_distro_series()
2282
2283 if node is not None:2291 if node is not None:
2284 # We don't care if the kernel opts is from the global setting or a tag,2292 # We don't care if the kernel opts is from the global setting or a tag,
2285 # just get the options2293 # just get the options
@@ -2289,7 +2297,6 @@
2289 # we still need to return the global kernel options.2297 # we still need to return the global kernel options.
2290 extra_kernel_opts = Config.objects.get_config("kernel_opts")2298 extra_kernel_opts = Config.objects.get_config("kernel_opts")
22912299
2292 purpose = get_boot_purpose(node)
2293 server_address = get_maas_facing_server_address(nodegroup=nodegroup)2300 server_address = get_maas_facing_server_address(nodegroup=nodegroup)
2294 cluster_address = get_mandatory_param(request.GET, "local")2301 cluster_address = get_mandatory_param(request.GET, "local")
22952302
22962303
=== modified file 'src/maasserver/models/bootimage.py'
--- src/maasserver/models/bootimage.py 2013-10-07 09:12:40 +0000
+++ src/maasserver/models/bootimage.py 2013-11-15 15:29:36 +0000
@@ -25,6 +25,7 @@
25 )25 )
26from maasserver import DefaultMeta26from maasserver import DefaultMeta
27from maasserver.models.nodegroup import NodeGroup27from maasserver.models.nodegroup import NodeGroup
28from maasserver.utils.orm import get_first
2829
2930
30class BootImageManager(Manager):31class BootImageManager(Manager):
@@ -61,6 +62,15 @@
61 except BootImage.DoesNotExist:62 except BootImage.DoesNotExist:
62 return False63 return False
6364
65 def get_default_arch_image_in_nodegroup(self, nodegroup, series, purpose):
66 """Return the first image available for any architecture in the
67 nodegroup/series supplied.
68 """
69 images = BootImage.objects.filter(
70 release=series, nodegroup=nodegroup, purpose=purpose)
71 images = images.order_by('architecture')
72 return get_first(images)
73
6474
65class BootImage(Model):75class BootImage(Model):
66 """Available boot image (i.e. kernel and initrd).76 """Available boot image (i.e. kernel and initrd).
6777
=== modified file 'src/maasserver/models/tests/test_bootimage.py'
--- src/maasserver/models/tests/test_bootimage.py 2013-10-07 09:12:40 +0000
+++ src/maasserver/models/tests/test_bootimage.py 2013-11-15 15:29:36 +0000
@@ -17,6 +17,7 @@
17from maasserver.models import (17from maasserver.models import (
18 BootImage,18 BootImage,
19 NodeGroup,19 NodeGroup,
20 Config,
20 )21 )
21from maasserver.testing.factory import factory22from maasserver.testing.factory import factory
22from maasserver.testing.testcase import MAASServerTestCase23from maasserver.testing.testcase import MAASServerTestCase
@@ -52,3 +53,32 @@
52 BootImage.objects.register_image(self.nodegroup, **params)53 BootImage.objects.register_image(self.nodegroup, **params)
53 self.assertTrue(54 self.assertTrue(
54 BootImage.objects.have_image(self.nodegroup, **params))55 BootImage.objects.have_image(self.nodegroup, **params))
56
57 def test_default_arch_image_none(self):
58 series = Config.objects.get_config('commissioning_distro_series')
59 result = BootImage.objects.get_default_arch_image_in_nodegroup(
60 self.nodegroup, series, None)
61 self.assertIsNone(result)
62
63 def test_default_arch_image_one(self):
64 series = Config.objects.get_config('commissioning_distro_series')
65 purpose = factory.make_name("purpose")
66 factory.make_boot_image(
67 architecture="amd64", release=series, nodegroup=self.nodegroup,
68 purpose=purpose)
69 result = BootImage.objects.get_default_arch_image_in_nodegroup(
70 self.nodegroup, series, purpose=purpose)
71 self.assertEqual(result.architecture, "amd64")
72
73 def test_default_arch_image_many(self):
74 series = Config.objects.get_config('commissioning_distro_series')
75 purpose = factory.make_name("purpose")
76 factory.make_boot_image(
77 architecture="amd64", release=series, nodegroup=self.nodegroup,
78 purpose=purpose)
79 factory.make_boot_image(
80 architecture="other", release=series, nodegroup=self.nodegroup,
81 purpose=purpose)
82 result = BootImage.objects.get_default_arch_image_in_nodegroup(
83 self.nodegroup, series, purpose=purpose)
84 self.assertEqual(result.architecture, "amd64")
5585
=== modified file 'src/maasserver/tests/test_api_pxeconfig.py'
--- src/maasserver/tests/test_api_pxeconfig.py 2013-10-07 09:12:40 +0000
+++ src/maasserver/tests/test_api_pxeconfig.py 2013-11-15 15:29:36 +0000
@@ -134,6 +134,17 @@
134 response_dict = json.loads(response.content)134 response_dict = json.loads(response.content)
135 self.assertEqual(value, response_dict['extra_opts'])135 self.assertEqual(value, response_dict['extra_opts'])
136136
137 def test_pxeconfig_uses_present_boot_image(self):
138 release = Config.objects.get_config('commissioning_distro_series')
139 nodegroup = factory.make_node_group()
140 factory.make_boot_image(
141 architecture="amd64", release=release, nodegroup=nodegroup,
142 purpose="commissioning")
143 params = self.get_default_params()
144 params['cluster_uuid'] = nodegroup.uuid
145 params_out = self.get_pxeconfig(params)
146 self.assertEqual("amd64", params_out["arch"])
147
137 def test_pxeconfig_defaults_to_i386_for_default(self):148 def test_pxeconfig_defaults_to_i386_for_default(self):
138 # As a lowest-common-denominator, i386 is chosen when the node is not149 # As a lowest-common-denominator, i386 is chosen when the node is not
139 # yet known to MAAS.150 # yet known to MAAS.