Merge lp:~jameinel/maas/cpu_mem_tag_constraints into lp:maas/trunk
| Status: | Merged |
|---|---|
| Merged at revision: | 1102 |
| Proposed branch: | lp:~jameinel/maas/cpu_mem_tag_constraints |
| Merge into: | lp:maas/trunk |
| Diff against target: |
492 lines (+330/-38) 8 files modified
src/maasserver/api.py (+17/-4) src/maasserver/exceptions.py (+14/-0) src/maasserver/models/node.py (+4/-15) src/maasserver/models/node_constraint_filter.py (+77/-0) src/maasserver/tests/test_api.py (+80/-0) src/maasserver/tests/test_exceptions.py (+19/-0) src/maasserver/tests/test_node.py (+17/-19) src/maasserver/tests/test_node_constraint_filter.py (+102/-0) |
| To merge this branch: | bzr merge lp:~jameinel/maas/cpu_mem_tag_constraints |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Jeroen T. Vermeulen (community) | 2012-09-28 | Approve on 2012-09-28 | |
|
Review via email:
|
|||
Commit Message
Add CPU count, Memory and Tags as exposed logic for get_available_
Description of the Change
This is my version of gz`s branch: https:/
Things were getting big enough that I evolved a helper class, which I think cleans things up and makes it easier to add more constraints in the future.
It also makes it more obvious to directly test that class.
And I changed the tag lookup logic, so it now gives InvalidConstraint rather than 404.
I *think* this helper class will even let us do the same constraints for a search interface, though we'll probably want to name it differently.
| Jeroen T. Vermeulen (jtv) wrote : | # |
| Jeroen T. Vermeulen (jtv) wrote : | # |
OH THANK YOU VERY MUCH QUANTAL for submitting my notes before I was done entering them.
| Jeroen T. Vermeulen (jtv) wrote : | # |
I see that the API acceptance tests cover a lot of the same behaviours as the unit tests (and then there is some redundancy between unit tests as well). My guess is that the acceptance tests were written first, then the lower-level helpers were created to satisfy the tests, and those helpers were then unit-tested. I do that a lot as well, but I find it's usually best to strip down the acceptance tests to exercise integration paths only.
| John A Meinel (jameinel) wrote : | # |
A lot of the 'test_api' level tests are there to test the actual HTTP response codes wrt different errors. (So have 1 success level and 1 failure level test for each parameter based on different error codes.)
So I think the test coverage is reasonable as it stands, but we did switch things to using a module.
| Jeroen T. Vermeulen (jtv) wrote : | # |
Thanks for making the changes. I know I'm difficult.
| MAAS Lander (maas-lander) wrote : | # |
The Jenkins job https:/
Not merging it.
| Martin Packman (gz) wrote : | # |
Fixed the test failure back in the branch under my namespace and landed it.
| Jeroen T. Vermeulen (jtv) wrote : | # |
Please, *always* run "make lint" and the full test suite before putting a
| Jeroen T. Vermeulen (jtv) wrote : | # |
branch up for review!
(Thank you Quantal for moving my mouse pointer to the Save Comment button and then clicking it while I was typing)
| John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
On 9/29/2012 8:07 AM, Jeroen T. Vermeulen wrote:
> branch up for review!
>
> (Thank you Quantal for moving my mouse pointer to the Save Comment
> button and then clicking it while I was typing)
>
Unfortunately, it looks like upgrading to Quantal broke my 'make lint':
ImportError: ..python2.
_PyNode_Sizeof.
That seems to be from 'compiler' which means my py2.7 install is now
broken?
I have tried to make sure to run it. However, if we are going to be
pedantic about it, shouldn't we just have tarmac run it?
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.12 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAlB
OcsAnRWwmcSWGVs
=2dua
-----END PGP SIGNATURE-----
| Jeroen T. Vermeulen (jtv) wrote : | # |
That's been known to happen, but "make distclean" seems to fix it.
Having tarmac run it would be a good idea IMO.


As mentioned on IRC, the error message in InvalidConstraint seems misleading. As far as I can see, "Invalid 'X' constraint 'Y'" can mean that Y is not an acceptable value for constraint X. So far, so good. But it can also mean that X is not a known constraint in the first place! I would suggest either splitting these two meanings into separate exception classes, or differentiating the error message so that it will contain the constraint value only if relevant. (Presumably it wouldn't need to be a separate attribute on the exception class).
The AcquisitionCons trainer looks like it wants to be a module, not a class. Its self.nodes looks like it wants to be a parameter and return value to the various filter functions, rather than implicit mutable state. And looking up a computed method name can be a handy shorthand, but if you're duplicating the list of accepted constraints here anyway (you also had it in the constraints map), then you might as well make the lookup in an explicit dict:
def constrain_ identical( nodes, key, value): key=value)
"""Generic constraint: filter nodes by exact value match."""
return nodes.filter(
def constrain_ int_gte( nodes, key, value): nt(key, value, e) **{"%s_ _gte" % key: value})
"""Integer constraint: greater than or equal to `value`."""
try:
int_value = int(value)
except ValueError as e:
raise InvalidConstrai
return nodes.filter(
def constrain_ tags(nodes, key, tag_expression): replace( ",", " ").strip().split() Tag.objects. filter( name=tag_ name)) nt('tags' , tag_name, "No such tag.") tags=tag)
"""Tags match: restrict to nodes that have all of given tags list."""
# We support both comma-separated and space-separated tag lists.
tag_names = tag_expression.
for tag_name in tag_names:
tag = get_one(
if tag is None:
raise InvalidConstrai
nodes = nodes.filter(
return nodes
# How to filter each individual constraint. identical, identical,
constraint_filters = {
# This only supports an exact match on arch type. Using an i386 image
# on amd64 hardware will wait.
'architecture': constrain_
'name': constrain_
'cpu_count': constrain_int_gte,
'memory': constrain_int_gte,
'tags': constrain_tags,
}
def constrain_ nodes(nodes, constraints): items() : filters. get(constraint)
for constraint, value in constraints.
filter = constraint_
if filter is not None:
nodes = filter(nodes, constraint, value)
return nodes
I think that's easier to follow, and despite having more documentation it's fewer lines as well.
(By the way, does an equality comparison on a tag value really do an "is in" comparison!? The tests verify it, but )