Merge lp:~rvb/maas/zone-placement into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 1771
Proposed branch: lp:~rvb/maas/zone-placement
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 146 lines (+69/-2)
5 files modified
src/maasserver/api.py (+3/-0)
src/maasserver/node_constraint_filter_forms.py (+22/-1)
src/maasserver/static/css/components/search_box.css (+1/-1)
src/maasserver/tests/test_api_nodes.py (+21/-0)
src/maasserver/tests/test_node_constraint_filter_forms.py (+22/-0)
To merge this branch: bzr merge lp:~rvb/maas/zone-placement
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+198547@code.launchpad.net

Commit message

Add a constraint to acquire nodes located in a particular availability zone.

Description of the change

The CSS fix is a simple drive-by fix to reduce the size of the search box on the node listing page.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Not quite voting yet, because the diff isn't updating for some reason and I haven't seen the final branch yet.

As for the actual code I do see, two notes that apply in multiple places:

1. Where possible, please avoid implicit boolean conversions! Instead of “if zone:” say “if zone is not None:” or “if zone != '':” etc.

2. In several places you basically do:

    node = factory.make_node()
    zone = factory.make_zone(nodes=[node])

I think it would be just slightly more obvious to do...

    zone = factory.make_zone()
    node = factory.make_node(zone=zone)

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

OK, the CSS fix looks trivially fine. Approved, with the two notes above.

review: Approve
Revision history for this message
Raphaël Badin (rvb) wrote :

> 1. Where possible, please avoid implicit boolean conversions! Instead of “if
> zone:” say “if zone is not None:” or “if zone != '':” etc.

I'm doing "if zone" here because '' and None really mean the same thing. And both are possible.

>
> 2. In several places you basically do:
>
> node = factory.make_node()
> zone = factory.make_zone(nodes=[node])
>
> I think it would be just slightly more obvious to do...
>
> zone = factory.make_zone()
> node = factory.make_node(zone=zone)

All right, fixed.

Thanks for the review!

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

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api.py'
2--- src/maasserver/api.py 2013-12-09 12:02:14 +0000
3+++ src/maasserver/api.py 2013-12-11 13:09:41 +0000
4@@ -812,6 +812,9 @@
5 :param not_connected_to: List of routers' MAC Addresses the returned
6 node must not be connected to.
7 :type connected_to: list of unicodes
8+ :param zone: An optional name for an availability zone the acquired
9+ node should be located in.
10+ :type zone: unicode
11 :param agent_name: An optional agent name to attach to the
12 acquired node.
13 :type agent_name: unicode
14
15=== modified file 'src/maasserver/node_constraint_filter_forms.py'
16--- src/maasserver/node_constraint_filter_forms.py 2013-10-07 09:12:40 +0000
17+++ src/maasserver/node_constraint_filter_forms.py 2013-12-11 13:09:41 +0000
18@@ -31,7 +31,10 @@
19 UnconstrainedMultipleChoiceField,
20 ValidatorMultipleChoiceField,
21 )
22-from maasserver.models import Tag
23+from maasserver.models import (
24+ Tag,
25+ Zone,
26+ )
27 from maasserver.utils.orm import (
28 macs_contain,
29 macs_do_not_contain,
30@@ -148,6 +151,8 @@
31 'invalid_list':
32 "Invalid parameter: list of MAC addresses required."})
33
34+ zone = forms.CharField(label="Availability zone", required=False)
35+
36 ignore_unknown_constraints = True
37
38 @classmethod
39@@ -189,6 +194,17 @@
40 return tag_names
41 return None
42
43+ def clean_zone(self):
44+ value = self.cleaned_data[self.get_field_name('zone')]
45+ if value:
46+ zone_names = Zone.objects.all().values_list('name', flat=True)
47+ if value not in zone_names:
48+ error_msg = "No such zone: '%s'." % value
49+ raise ValidationError(
50+ {self.get_field_name('zone'): [error_msg]})
51+ return value
52+ return None
53+
54 def clean(self):
55 if not self.ignore_unknown_constraints:
56 unknown_constraints = set(
57@@ -239,6 +255,11 @@
58 for tag in tags:
59 filtered_nodes = filtered_nodes.filter(tags__name=tag)
60
61+ # Filter by zone.
62+ zone = self.cleaned_data.get(self.get_field_name('zone'))
63+ if zone:
64+ filtered_nodes = filtered_nodes.filter(zone=zone)
65+
66 # Filter by connected_to.
67 connected_to = self.cleaned_data.get(
68 self.get_field_name('connected_to'))
69
70=== modified file 'src/maasserver/static/css/components/search_box.css'
71--- src/maasserver/static/css/components/search_box.css 2013-11-15 18:15:51 +0000
72+++ src/maasserver/static/css/components/search_box.css 2013-12-11 13:09:41 +0000
73@@ -14,7 +14,7 @@
74 position: absolute;
75 top: 0;
76 left: 0;
77- width: 450px;
78+ width: 420px;
79 padding-left: 22px;
80 background-color: #fff;
81 }
82
83=== modified file 'src/maasserver/tests/test_api_nodes.py'
84--- src/maasserver/tests/test_api_nodes.py 2013-12-03 17:46:05 +0000
85+++ src/maasserver/tests/test_api_nodes.py 2013-12-11 13:09:41 +0000
86@@ -622,6 +622,27 @@
87 response_json = json.loads(response.content)
88 self.assertEqual(node_tag_names, response_json['tag_names'])
89
90+ def test_POST_acquire_allocates_node_by_zone(self):
91+ factory.make_node(status=NODE_STATUS.READY)
92+ zone = factory.make_zone()
93+ node = factory.make_node(status=NODE_STATUS.READY, zone=zone)
94+ response = self.client.post(reverse('nodes_handler'), {
95+ 'op': 'acquire',
96+ 'zone': zone.name,
97+ })
98+ self.assertResponseCode(httplib.OK, response)
99+ response_json = json.loads(response.content)
100+ self.assertEqual(node.system_id, response_json['system_id'])
101+
102+ def test_POST_acquire_allocates_node_by_zone_fails_if_no_node(self):
103+ factory.make_node(status=NODE_STATUS.READY)
104+ zone = factory.make_zone()
105+ response = self.client.post(reverse('nodes_handler'), {
106+ 'op': 'acquire',
107+ 'zone': zone.name,
108+ })
109+ self.assertResponseCode(httplib.CONFLICT, response)
110+
111 def test_POST_acquire_allocates_node_by_tags_comma_separated(self):
112 node = factory.make_node(status=NODE_STATUS.READY)
113 node_tag_names = ["fast", "stable", "cute"]
114
115=== modified file 'src/maasserver/tests/test_node_constraint_filter_forms.py'
116--- src/maasserver/tests/test_node_constraint_filter_forms.py 2013-12-03 10:27:44 +0000
117+++ src/maasserver/tests/test_node_constraint_filter_forms.py 2013-12-11 13:09:41 +0000
118@@ -230,6 +230,28 @@
119 ["Invalid parameter: list of MAC addresses required."]}),
120 (form.is_valid(), form.errors))
121
122+ def test_zone(self):
123+ node1 = factory.make_node()
124+ node2 = factory.make_node()
125+ node3 = factory.make_node()
126+ zone1 = factory.make_zone(nodes=[node1, node2])
127+ zone2 = factory.make_zone()
128+
129+ self.assertConstrainedNodes(
130+ [node1, node2], {'zone': zone1.name})
131+ self.assertConstrainedNodes(
132+ [node1, node2, node3], {'zone': ''})
133+ self.assertConstrainedNodes(
134+ [node1, node2, node3], {})
135+ self.assertConstrainedNodes(
136+ [], {'zone': zone2.name})
137+
138+ def test_invalid_zone(self):
139+ form = AcquireNodeForm(data={'zone': 'unknown'})
140+ self.assertEquals(
141+ (False, {'zone': ["No such zone: 'unknown'."]}),
142+ (form.is_valid(), form.errors))
143+
144 def test_tags(self):
145 tag_big = factory.make_tag(name='big')
146 tag_burly = factory.make_tag(name='burly')