Merge lp:~julian-edwards/maas/enlist-nodegroup-bug-1274926 into lp:~maas-committers/maas/trunk

Proposed by Julian Edwards
Status: Merged
Approved by: Julian Edwards
Approved revision: no longer in the source branch.
Merged at revision: 1928
Proposed branch: lp:~julian-edwards/maas/enlist-nodegroup-bug-1274926
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 304 lines (+62/-0)
3 files modified
src/maasserver/api.py (+12/-0)
src/maasserver/tests/test_api_enlistment.py (+47/-0)
src/maasserver/tests/test_api_nodes.py (+3/-0)
To merge this branch: bzr merge lp:~julian-edwards/maas/enlist-nodegroup-bug-1274926
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+205681@code.launchpad.net

Commit message

Add a new parameter for node enlisting called "autodetect_nodegroup", which will avoid an error being returned if the nodegroup is not manually specified. Previously, non-node API users calling this would cause the nodegroup detection to be based on the caller's IP, which is wrong.

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

Sensible change. I assume autodetection itself is already tested, in a test that needed no change. One thing that's a bit awkward (and I do see the XXX about it) is that the semantics of the autodetect_nodegroup parameter are only explained in the test. I think the representation of booleans is not very highly standardised, so it may be worth a note in the docstring even if the docstring does not cover all parameters.

For the error message in api.py, saying that the nodegroup parameter is missing may be too strong. There's a choice to be made. How about "one of nodegroup or autodetect_nodegroup must be specified"?

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

Thanks for reviewing.

On Tuesday 11 Feb 2014 07:00:06 you wrote:
> Review: Approve
>
> Sensible change. I assume autodetection itself is already tested, in a test
> that needed no change. One thing that's a bit awkward (and I do see the
> XXX about it) is that the semantics of the autodetect_nodegroup parameter
> are only explained in the test. I think the representation of booleans is
> not very highly standardised, so it may be worth a note in the docstring
> even if the docstring does not cover all parameters.

I considered it but it felt daft to add just that one param and none of the
others, which is why I XXXed it for later! I'll stick it in the comment for
later inclusion in a decent docstring.

> For the error message in api.py, saying that the nodegroup parameter is
> missing may be too strong. There's a choice to be made. How about "one of
> nodegroup or autodetect_nodegroup must be specified"?

The code doesn't enforce that though, it just enforces the former without the
latter.

I'll land this now and we can discuss a different message later, I gotta run.

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (25.7 KiB)

The attempt to merge lp:~julian-edwards/maas/enlist-nodegroup-bug-1274926 into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Hit http://security.ubuntu.com trusty-security Release.gpg
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Hit http://security.ubuntu.com trusty-security Release
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Get:1 http://nova.clouds.archive.ubuntu.com trusty Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg
Get:2 http://nova.clouds.archive.ubuntu.com trusty Release [58.5 kB]
Hit http://security.ubuntu.com trusty-security/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release
Hit http://security.ubuntu.com trusty-security/universe Sources
Hit http://security.ubuntu.com trusty-security/main amd64 Packages
Hit http://security.ubuntu.com trusty-security/universe amd64 Packages
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Get:3 http://nova.clouds.archive.ubuntu.com trusty/main Sources [1,057 kB]
Ign http://security.ubuntu.com trusty-security/main Translation-en_US
Ign http://security.ubuntu.com trusty-security/universe Translation-en_US
Get:4 http://nova.clouds.archive.ubuntu.com trusty/universe Sources [6,414 kB]
Get:5 http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages [1,315 kB]
Get:6 http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages [5,878 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en_US
Fetched 14.7 MB in 5s (2,680 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 bind9 bind9utils build-essential curl daemontools distro-info dnsutils firefox freeipmi-tools ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make postgresql python-amqplib python-bzrlib python-celery python-convoy python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-formencode python-httplib2 python-jinja2 python-jsonschema python-lockfile python-lxml python-netaddr python-netifaces python-oauth python-oops py...

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

So it looks like tests for creating new nodes on the api are split between
test_api_enlistment.py and test_api_nodes.py.

:/

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

As discussed this morning, this change means earlier versions of maas-enlist will fail to register nodes (because the API call will not contain the 'autodetect_nodegroup' parameter). This means we won't be able to enlist/commission nodes using pre-Trusty series. In conjunction with bug 1279310, this restricts the number of series that can be used for enlistment down to zero :). (Note that we have a manual workaround (see the bug description)).

Revision history for this message
Jorge Niedbalski (niedbalski) wrote :

Hello guys,

Any way to workaround this ( using maasclient api) with pre-trusty versions?

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 2014-02-11 08:23:05 +0000
3+++ src/maasserver/api.py 2014-02-11 23:37:14 +0000
4@@ -514,6 +514,13 @@
5 # If 'nodegroup' is not explicitely specified, get the origin of the
6 # request to figure out which nodegroup the new node should be
7 # attached to.
8+ if request.data.get('autodetect_nodegroup', None) is None:
9+ # We insist on this to protect command-line API users who
10+ # are manually enlisting nodes. You can't use the origin's
11+ # IP address to indicate in which nodegroup the new node belongs.
12+ raise ValidationError(
13+ "'autodetect_nodegroup' must be specified if 'nodegroup' "
14+ "parameter missing")
15 nodegroup = find_nodegroup(request)
16 if nodegroup is not None:
17 altered_query_data['nodegroup'] = nodegroup
18@@ -550,6 +557,11 @@
19 the enlistment is done by a non-admin, the node is held in the
20 "Declared" state for approval by a MAAS admin.
21 """
22+ # XXX 2014-02-11 bug=1278685
23+ # There's no documentation here on what parameters can be passed!
24+
25+ # Note that request.autodetect_nodegroup is treated as a
26+ # boolean; its presence indicates True.
27 return create_node(request)
28
29 @operation(idempotent=True)
30
31=== modified file 'src/maasserver/tests/test_api_enlistment.py'
32--- src/maasserver/tests/test_api_enlistment.py 2014-02-01 04:04:37 +0000
33+++ src/maasserver/tests/test_api_enlistment.py 2014-02-11 23:37:14 +0000
34@@ -54,6 +54,7 @@
35 reverse('nodes_handler'),
36 {
37 'op': 'new',
38+ 'autodetect_nodegroup': '1',
39 'hostname': 'diane',
40 'architecture': architecture,
41 'power_type': 'ether_wake',
42@@ -76,6 +77,7 @@
43 reverse('nodes_handler'),
44 {
45 'op': 'new',
46+ 'autodetect_nodegroup': '1',
47 'hostname': hostname,
48 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
49 'power_type': 'ether_wake',
50@@ -105,6 +107,7 @@
51 reverse('nodes_handler'),
52 {
53 'op': 'new',
54+ 'autodetect_nodegroup': '1',
55 'hostname': hostname,
56 'architecture': architecture,
57 'power_type': 'ether_wake',
58@@ -125,6 +128,7 @@
59 reverse('nodes_handler'),
60 {
61 'op': 'new',
62+ 'autodetect_nodegroup': '1',
63 'hostname': 'diane',
64 'architecture': architecture.split('/')[0],
65 'power_type': 'ether_wake',
66@@ -148,6 +152,7 @@
67 reverse('nodes_handler'),
68 {
69 'op': 'new',
70+ 'autodetect_nodegroup': '1',
71 'hostname': 'diane',
72 'architecture': architecture.split('/')[0],
73 'subarchitecture': architecture.split('/')[1],
74@@ -171,6 +176,7 @@
75 reverse('nodes_handler'),
76 {
77 'op': 'new',
78+ 'autodetect_nodegroup': '1',
79 'hostname': 'diane',
80 'architecture': architecture,
81 'subarchitecture': architecture.split('/')[1],
82@@ -192,6 +198,7 @@
83 reverse('nodes_handler'),
84 {
85 'op': 'new',
86+ 'autodetect_nodegroup': '1',
87 'hostname': 'diane',
88 'architecture': architecture,
89 'after_commissioning_action': (
90@@ -209,6 +216,7 @@
91 reverse('nodes_handler'),
92 {
93 'op': 'new',
94+ 'autodetect_nodegroup': '1',
95 'hostname': hostname,
96 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
97 'mac_addresses': [factory.getRandomMACAddress()],
98@@ -223,6 +231,7 @@
99 reverse('nodes_handler'),
100 {
101 'op': 'new',
102+ 'autodetect_nodegroup': '1',
103 'architecture': architecture,
104 'power_type': 'ether_wake',
105 'mac_addresses': [factory.getRandomMACAddress()],
106@@ -247,6 +256,26 @@
107 "Unrecognised signature: POST None",
108 response.content)
109
110+ def test_POST_new_fails_if_autodetect_nodegroup_required(self):
111+ # If new() is called with no nodegroup, we require the client to
112+ # explicitly also supply autodetect_nodegroup (with any value)
113+ # to force the autodetection. If it's not supplied then an error
114+ # is raised.
115+ architecture = factory.getRandomChoice(ARCHITECTURE_CHOICES)
116+ response = self.client.post(
117+ reverse('nodes_handler'),
118+ {
119+ 'op': 'new',
120+ 'architecture': architecture,
121+ 'power_type': 'ether_wake',
122+ 'mac_addresses': [factory.getRandomMACAddress()],
123+ })
124+ self.assertEqual(httplib.BAD_REQUEST, response.status_code)
125+ self.assertIn('text/plain', response['Content-Type'])
126+ self.assertEqual(
127+ "'autodetect_nodegroup' must be specified if 'nodegroup' "
128+ "parameter missing", response.content)
129+
130 def test_POST_fails_if_mac_duplicated(self):
131 # Mac Addresses should be unique.
132 mac = 'aa:bb:cc:dd:ee:ff'
133@@ -256,6 +285,7 @@
134 reverse('nodes_handler'),
135 {
136 'op': 'new',
137+ 'autodetect_nodegroup': '1',
138 'architecture': architecture,
139 'hostname': factory.getRandomString(),
140 'mac_addresses': [mac],
141@@ -275,6 +305,7 @@
142 reverse('nodes_handler'),
143 {
144 'op': 'invalid_operation',
145+ 'autodetect_nodegroup': '1',
146 'hostname': 'diane',
147 'mac_addresses': ['aa:bb:cc:dd:ee:ff', 'invalid'],
148 })
149@@ -291,6 +322,7 @@
150 reverse('nodes_handler'),
151 {
152 'op': 'new',
153+ 'autodetect_nodegroup': '1',
154 'hostname': 'diane',
155 'mac_addresses': ['aa:bb:cc:dd:ee:ff', 'invalid'],
156 })
157@@ -309,6 +341,7 @@
158 reverse('nodes_handler'),
159 {
160 'op': 'new',
161+ 'autodetect_nodegroup': '1',
162 'hostname': 'diane',
163 'mac_addresses': ['aa:bb:cc:dd:ee:ff'],
164 'architecture': 'invalid-architecture',
165@@ -342,6 +375,7 @@
166 reverse('nodes_handler'),
167 {
168 'op': 'new',
169+ 'autodetect_nodegroup': '1',
170 'hostname': hostname_with_domain,
171 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
172 'power_type': 'ether_wake',
173@@ -366,6 +400,7 @@
174 reverse('nodes_handler'),
175 {
176 'op': 'new',
177+ 'autodetect_nodegroup': '1',
178 'hostname': hostname_without_domain,
179 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
180 'power_type': 'ether_wake',
181@@ -388,6 +423,7 @@
182 reverse('nodes_handler'),
183 data={
184 'op': 'new',
185+ 'autodetect_nodegroup': '1',
186 'hostname': factory.make_name('hostname'),
187 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
188 'power_type': 'ether_wake',
189@@ -407,6 +443,7 @@
190 reverse('nodes_handler'),
191 data={
192 'op': 'new',
193+ 'autodetect_nodegroup': '1',
194 'hostname': factory.make_name('hostname'),
195 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
196 'power_type': 'ether_wake',
197@@ -438,6 +475,7 @@
198 reverse('nodes_handler'),
199 {
200 'op': 'new',
201+ 'autodetect_nodegroup': '1',
202 'hostname': factory.getRandomString(),
203 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
204 'after_commissioning_action': (
205@@ -470,6 +508,7 @@
206 reverse('nodes_handler'),
207 {
208 'op': 'new',
209+ 'autodetect_nodegroup': '1',
210 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
211 'hostname': factory.getRandomString(),
212 'after_commissioning_action': (
213@@ -535,6 +574,7 @@
214 response = self.client.post(
215 reverse('nodes_handler'), {
216 'op': 'new',
217+ 'autodetect_nodegroup': '1',
218 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
219 'power_type': 'ether_wake',
220 'power_parameters': json.dumps(
221@@ -556,6 +596,7 @@
222 reverse('nodes_handler'),
223 {
224 'op': 'new',
225+ 'autodetect_nodegroup': '1',
226 'hostname': factory.getRandomString(),
227 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
228 'after_commissioning_action': (
229@@ -592,6 +633,7 @@
230 response = self.client.post(
231 reverse('nodes_handler'), {
232 'op': 'new',
233+ 'autodetect_nodegroup': '1',
234 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
235 'power_type': 'ether_wake',
236 'mac_addresses': ['00:11:22:33:44:55'],
237@@ -609,6 +651,7 @@
238 response = self.client.post(
239 reverse('nodes_handler'), {
240 'op': 'new',
241+ 'autodetect_nodegroup': '1',
242 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
243 'power_type': 'ether_wake',
244 'power_parameters_mac_address': new_mac_address,
245@@ -628,6 +671,7 @@
246 response = self.client.post(
247 reverse('nodes_handler'), {
248 'op': 'new',
249+ 'autodetect_nodegroup': '1',
250 'hostname': hostname,
251 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
252 'power_type': 'ether_wake',
253@@ -651,6 +695,7 @@
254 response = self.client.post(
255 reverse('nodes_handler'), {
256 'op': 'new',
257+ 'autodetect_nodegroup': '1',
258 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
259 'power_type': 'ether_wake',
260 'power_parameters_param': param,
261@@ -673,6 +718,7 @@
262 reverse('nodes_handler'),
263 {
264 'op': 'new',
265+ 'autodetect_nodegroup': '1',
266 'hostname': factory.getRandomString(),
267 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
268 'power_type': 'ether_wake',
269@@ -692,6 +738,7 @@
270 reverse('nodes_handler'),
271 {
272 'op': 'new',
273+ 'autodetect_nodegroup': '1',
274 'hostname': factory.getRandomString(),
275 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
276 'power_type': 'ether_wake',
277
278=== modified file 'src/maasserver/tests/test_api_nodes.py'
279--- src/maasserver/tests/test_api_nodes.py 2014-02-10 11:34:47 +0000
280+++ src/maasserver/tests/test_api_nodes.py 2014-02-11 23:37:14 +0000
281@@ -152,6 +152,7 @@
282 reverse('nodes_handler'),
283 {
284 'op': 'new',
285+ 'autodetect_nodegroup': '1',
286 'hostname': factory.getRandomString(),
287 'architecture': architecture,
288 'after_commissioning_action': (
289@@ -168,6 +169,7 @@
290 reverse('nodes_handler'),
291 {
292 'op': 'new',
293+ 'autodetect_nodegroup': '1',
294 'hostname': factory.getRandomString(),
295 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
296 'after_commissioning_action': (
297@@ -1085,6 +1087,7 @@
298 '/api/1.0/nodes/MAAS/api/1.0/nodes/',
299 {
300 'op': 'new',
301+ 'autodetect_nodegroup': '1',
302 'hostname': factory.getRandomString(),
303 'architecture': architecture,
304 'mac_addresses': ['aa:bb:cc:dd:ee:ff'],