Merge lp:~jameinel/maas/1.2-kernel-option-tags into lp:maas/1.2

Proposed by John A Meinel
Status: Merged
Approved by: John A Meinel
Approved revision: no longer in the source branch.
Merged at revision: 1289
Proposed branch: lp:~jameinel/maas/1.2-kernel-option-tags
Merge into: lp:maas/1.2
Diff against target: 109 lines (+80/-1)
2 files modified
src/maasserver/models/node.py (+24/-1)
src/maasserver/tests/test_node.py (+56/-0)
To merge this branch: bzr merge lp:~jameinel/maas/1.2-kernel-option-tags
Reviewer Review Type Date Requested Status
Martin Packman (community) Approve
Review via email: mp+133228@code.launchpad.net

Commit message

Teach Node.get_effective_kernel_options how to handle tags.

This adds the lookup into the tags table for tags applied to this node, which have kernel options set, and in the right sort order.

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

There *should* be a prereq on the other 1.2 branch I wrote, but the actual patch is still small enough it may not be worth doing.

Revision history for this message
Martin Packman (gz) wrote :

Looks good.

+ should be used when booting this node. May be None if no tags match
+ and no global setting has been configured.

I think we should probably ensure the second part of the tuple here is never None. If the global config is not set, we still want some kernel params. Maybe they should live in the maasserver code, or just be the empty string?

+ tags = tags.order_by('name')[:1]
+ tag = get_one(tags)
+ if tag is not None:

I trust this is sensible ORM magic.

review: Approve
Revision history for this message
John A Meinel (jameinel) wrote :

I'm not sure what you're thinking on the 'second part'. We can make it the empty string if we would rather not have it be None.

As for the "we still want some kernel params", a lot of the commandline we pass to the booting node is generated from state held in the region controller. So we will always be passing something.

If there are a few particular static parameters that we are passing, we could probably move those into a default global setting (especially if we want to be able to clearly override them). If we do that, though, we'll want some sort of 'reset to defaults' setting, in case you royally hose your system.

Revision history for this message
John A Meinel (jameinel) wrote :

> + tags = tags.order_by('name')[:1]
> + tag = get_one(tags)
> + if tag is not None:
>
> I trust this is sensible ORM magic.

The order_by is obvious, the [:1] is how you do a LIMIT 1 request, get_one is a helper that checks that we get either None or exactly 1 object (there is also a get_first helper, though I'm not sure if that gets the LIMIT passed down into the DB. It uses islice instead of [:1], which makes me think it is iterating the cursor/queryset. Which means the DB might have to do all the work just to return 1 item.

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

On Thursday 08 November 2012 05:05:24 you wrote:
> > + tags = tags.order_by('name')[:1]
> > + tag = get_one(tags)
> > + if tag is not None:
> >
> > I trust this is sensible ORM magic.
>
> The order_by is obvious, the [:1] is how you do a LIMIT 1 request, get_one
> is a helper that checks that we get either None or exactly 1 object (there
> is also a get_first helper, though I'm not sure if that gets the LIMIT
> passed down into the DB. It uses islice instead of [:1], which makes me
> think it is iterating the cursor/queryset. Which means the DB might have to
> do all the work just to return 1 item.

You might want to fix get_first() if you think it's not DTRT, and then use it
(so we're consistent across the code base).

Cheers.

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 11/8/2012 9:28 AM, Julian Edwards wrote:
> On Thursday 08 November 2012 05:05:24 you wrote:
>>> + tags = tags.order_by('name')[:1] + tag = get_one(tags) + if
>>> tag is not None:
>>>
>>> I trust this is sensible ORM magic.
>>
>> The order_by is obvious, the [:1] is how you do a LIMIT 1
>> request, get_one is a helper that checks that we get either None
>> or exactly 1 object (there is also a get_first helper, though I'm
>> not sure if that gets the LIMIT passed down into the DB. It uses
>> islice instead of [:1], which makes me think it is iterating the
>> cursor/queryset. Which means the DB might have to do all the work
>> just to return 1 item.
>
> You might want to fix get_first() if you think it's not DTRT, and
> then use it (so we're consistent across the code base).
>
> Cheers.
>

So I think get_first is intending to support more than one use case.
Namely it is trying to allow you to pass any iterable, not just
something that can be passed a slice argument.

Specifically, the test suite explicitly passes in generators, etc.

So while I'm pretty sure it isn't doing a LIMIT in SQL, the
infrastructure as defined is intentionally not doing so.

Though I will say the only place that uses it in 'maasserver' that
isn't a test would be happy to LIMIT.

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.12 (Cygwin)
Comment: Using GnuPG with Mozilla - http://www.enigmail.net/

iEYEARECAAYFAlCbR1UACgkQJdeBCYSNAANVlACgge0uQRbpx6czMEqmxbRuya0F
gC4AoL6UcJLRD4UVEGyDreCEJ3E1ojax
=i4Ov
-----END PGP SIGNATURE-----

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

On Thursday 08 November 2012 05:49:18 you wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> On 11/8/2012 9:28 AM, Julian Edwards wrote:
> > On Thursday 08 November 2012 05:05:24 you wrote:
> >>> + tags = tags.order_by('name')[:1] + tag = get_one(tags) + if
> >>> tag is not None:
> >>>
> >>> I trust this is sensible ORM magic.
> >>
> >> The order_by is obvious, the [:1] is how you do a LIMIT 1
> >> request, get_one is a helper that checks that we get either None
> >> or exactly 1 object (there is also a get_first helper, though I'm
> >> not sure if that gets the LIMIT passed down into the DB. It uses
> >> islice instead of [:1], which makes me think it is iterating the
> >> cursor/queryset. Which means the DB might have to do all the work
> >> just to return 1 item.
> >
> > You might want to fix get_first() if you think it's not DTRT, and
> > then use it (so we're consistent across the code base).
> >
> > Cheers.
>
> So I think get_first is intending to support more than one use case.
> Namely it is trying to allow you to pass any iterable, not just
> something that can be passed a slice argument.
>
> Specifically, the test suite explicitly passes in generators, etc.
>
> So while I'm pretty sure it isn't doing a LIMIT in SQL, the
> infrastructure as defined is intentionally not doing so.
>
> Though I will say the only place that uses it in 'maasserver' that
> isn't a test would be happy to LIMIT.
>
> John
> =:->

I think we need to work out if it is causing a LIMIT when it generates the
SQL. If not, I think we should make it do what your code does and fix the
tests.

I'd add a comment about get_one near your code so it can be grepped and fixed
later if needs be, don't worry about it in this branch.

Cheers.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/models/node.py'
--- src/maasserver/models/node.py 2012-10-30 15:11:10 +0000
+++ src/maasserver/models/node.py 2012-11-07 12:51:22 +0000
@@ -67,7 +67,10 @@
67 get_db_state,67 get_db_state,
68 strip_domain,68 strip_domain,
69 )69 )
70from maasserver.utils.orm import get_first70from maasserver.utils.orm import (
71 get_first,
72 get_one,
73 )
71from piston.models import Token74from piston.models import Token
72from provisioningserver.enum import (75from provisioningserver.enum import (
73 POWER_TYPE,76 POWER_TYPE,
@@ -694,6 +697,26 @@
694 else:697 else:
695 return None698 return None
696699
700 def get_effective_kernel_options(self):
701 """Determine any special kernel parameters for this node.
702
703 :return: (tag, kernel_options)
704 tag is a Tag object or None. If None, the kernel_options came from
705 the global setting.
706 kernel_options, a string indicating extra kernel_options that
707 should be used when booting this node. May be None if no tags match
708 and no global setting has been configured.
709 """
710 # First, see if there are any tags associated with this node that has a
711 # custom kernel parameter
712 tags = self.tags.filter(kernel_opts__isnull=False)
713 tags = tags.order_by('name')[:1]
714 tag = get_one(tags)
715 if tag is not None:
716 return tag, tag.kernel_opts
717 global_value = Config.objects.get_config('kernel_opts')
718 return None, global_value
719
697 @property720 @property
698 def work_queue(self):721 def work_queue(self):
699 """The name of the queue for tasks specific to this node."""722 """The name of the queue for tasks specific to this node."""
700723
=== modified file 'src/maasserver/tests/test_node.py'
--- src/maasserver/tests/test_node.py 2012-10-30 15:11:10 +0000
+++ src/maasserver/tests/test_node.py 2012-11-07 12:51:22 +0000
@@ -303,6 +303,62 @@
303 successful_types = [node_power_types[node] for node in started_nodes]303 successful_types = [node_power_types[node] for node in started_nodes]
304 self.assertItemsEqual(configless_power_types, successful_types)304 self.assertItemsEqual(configless_power_types, successful_types)
305305
306 def test_get_effective_kernel_options_with_nothing_set(self):
307 node = factory.make_node()
308 self.assertEqual((None, None), node.get_effective_kernel_options())
309
310 def test_get_effective_kernel_options_sees_global_config(self):
311 node = factory.make_node()
312 kernel_opts = factory.getRandomString()
313 Config.objects.set_config('kernel_opts', kernel_opts)
314 self.assertEqual(
315 (None, kernel_opts), node.get_effective_kernel_options())
316
317 def test_get_effective_kernel_options_not_confused_by_empty_tag(self):
318 node = factory.make_node()
319 tag = factory.make_tag()
320 node.tags.add(tag)
321 kernel_opts = factory.getRandomString()
322 Config.objects.set_config('kernel_opts', kernel_opts)
323 self.assertEqual(
324 (None, kernel_opts), node.get_effective_kernel_options())
325
326 def test_get_effective_kernel_options_ignores_unassociated_tag_value(self):
327 node = factory.make_node()
328 tag = factory.make_tag(kernel_opts=factory.getRandomString())
329 self.assertEqual((None, None), node.get_effective_kernel_options())
330
331 def test_get_effective_kernel_options_uses_tag_value(self):
332 node = factory.make_node()
333 tag = factory.make_tag(kernel_opts=factory.getRandomString())
334 node.tags.add(tag)
335 self.assertEqual(
336 (tag, tag.kernel_opts), node.get_effective_kernel_options())
337
338 def test_get_effective_kernel_options_tag_overrides_global(self):
339 node = factory.make_node()
340 global_opts = factory.getRandomString()
341 Config.objects.set_config('kernel_opts', global_opts)
342 tag = factory.make_tag(kernel_opts=factory.getRandomString())
343 node.tags.add(tag)
344 self.assertEqual(
345 (tag, tag.kernel_opts), node.get_effective_kernel_options())
346
347 def test_get_effective_kernel_options_uses_first_real_tag_value(self):
348 node = factory.make_node()
349 # Intentionally create them in reverse order, so the default 'db' order
350 # doesn't work, and we have asserted that we sort them.
351 tag3 = factory.make_tag(factory.make_name('tag-03-'),
352 kernel_opts=factory.getRandomString())
353 tag2 = factory.make_tag(factory.make_name('tag-02-'),
354 kernel_opts=factory.getRandomString())
355 tag1 = factory.make_tag(factory.make_name('tag-01-'), kernel_opts=None)
356 self.assertTrue(tag1.name < tag2.name)
357 self.assertTrue(tag2.name < tag3.name)
358 node.tags.add(tag1, tag2, tag3)
359 self.assertEqual(
360 (tag2, tag2.kernel_opts), node.get_effective_kernel_options())
361
306 def test_acquire(self):362 def test_acquire(self):
307 node = factory.make_node(status=NODE_STATUS.READY)363 node = factory.make_node(status=NODE_STATUS.READY)
308 user = factory.make_user()364 user = factory.make_user()

Subscribers

People subscribed via source and target branches

to status/vote changes: