Merge lp:~mpontillo/maas/l2-spaces--juju-bug-workaround into lp:~maas-committers/maas/trunk

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 5654
Proposed branch: lp:~mpontillo/maas/l2-spaces--juju-bug-workaround
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 391 lines (+142/-43)
9 files modified
src/maasserver/api/subnets.py (+5/-2)
src/maasserver/api/tests/test_vlans.py (+59/-2)
src/maasserver/api/vlans.py (+15/-23)
src/maasserver/forms_subnet.py (+5/-11)
src/maasserver/forms_vlan.py (+9/-3)
src/maasserver/models/space.py (+7/-1)
src/maasserver/models/tests/test_space.py (+6/-0)
src/maasserver/tests/test_forms_subnet.py (+14/-1)
src/maasserver/tests/test_forms_vlan.py (+22/-0)
To merge this branch: bzr merge lp:~mpontillo/maas/l2-spaces--juju-bug-workaround
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+315291@code.launchpad.net

Commit message

Spaces API cleanup.

 * Make spaces API more backward compatible with older Juju versions.
 * Always return a string for the space name. The string "undefined" will be used for null spaces.
 * "undefined" is now a reserved space name.
 * A specifier (in practice, the name or the ID) can now be used to specify a VLAN's space.
 * Remove dead code.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good. Only have a question about the dead code you removed.

review: Needs Information
Revision history for this message
Mike Pontillo (mpontillo) :
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Okay thanks fore verifying.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/subnets.py'
2--- src/maasserver/api/subnets.py 2016-12-20 20:16:37 +0000
3+++ src/maasserver/api/subnets.py 2017-01-22 22:55:49 +0000
4@@ -13,7 +13,10 @@
5 from maasserver.enum import NODE_PERMISSION
6 from maasserver.exceptions import MAASAPIValidationError
7 from maasserver.forms_subnet import SubnetForm
8-from maasserver.models import Subnet
9+from maasserver.models import (
10+ Space,
11+ Subnet,
12+)
13 from piston3.utils import rc
14 from provisioningserver.utils.network import IPRangeStatistics
15
16@@ -147,7 +150,7 @@
17 def space(cls, subnet):
18 """Return the name of the space, or None if the space is undefined."""
19 if subnet.space is None:
20- return None
21+ return Space.UNDEFINED
22 return subnet.space.get_name()
23
24 def read(self, request, id):
25
26=== modified file 'src/maasserver/api/tests/test_vlans.py'
27--- src/maasserver/api/tests/test_vlans.py 2016-12-08 23:02:30 +0000
28+++ src/maasserver/api/tests/test_vlans.py 2017-01-22 22:55:49 +0000
29@@ -11,12 +11,18 @@
30
31 from django.conf import settings
32 from django.core.urlresolvers import reverse
33+from maasserver.models import Space
34 from maasserver.testing.api import APITestCase
35-from maasserver.testing.factory import factory
36+from maasserver.testing.factory import (
37+ factory,
38+ RANDOM,
39+)
40 from maasserver.utils.orm import reload_object
41 from testtools.matchers import (
42 ContainsDict,
43 Equals,
44+ Is,
45+ Not,
46 )
47
48
49@@ -126,7 +132,7 @@
50 self.assertEqual(vlan_name, response_data['name'])
51 self.assertEqual(vid, response_data['vid'])
52 self.assertEqual(mtu, response_data['mtu'])
53- self.assertEqual(None, response_data['space'])
54+ self.assertEqual(Space.UNDEFINED, response_data['space'])
55
56 def test_create_with_space(self):
57 self.become_admin()
58@@ -239,6 +245,23 @@
59 "resource_uri": Equals(get_vlan_uri(vlan)),
60 }))
61
62+ def test_read_without_space_returns_undefined_space(self):
63+ vlan = factory.make_VLAN(space=None)
64+ uri = get_vlan_uri(vlan, vlan.fabric)
65+ response = self.client.get(uri)
66+
67+ self.assertEqual(
68+ http.client.OK, response.status_code, response.content)
69+ parsed_vlan = json.loads(
70+ response.content.decode(settings.DEFAULT_CHARSET))
71+ self.assertThat(parsed_vlan, ContainsDict({
72+ "id": Equals(vlan.id),
73+ "name": Equals(vlan.get_name()),
74+ "vid": Equals(vlan.vid),
75+ "space": Equals(Space.UNDEFINED),
76+ "resource_uri": Equals(get_vlan_uri(vlan)),
77+ }))
78+
79 def test_read_404_when_bad_id(self):
80 fabric = factory.make_Fabric()
81 uri = reverse(
82@@ -306,6 +329,40 @@
83 self.assertEqual(new_vid, parsed_vlan['vid'])
84 self.assertEqual(new_vid, vlan.vid)
85
86+ def test_update_with_empty_space_clears_space(self):
87+ self.become_admin()
88+ fabric = factory.make_Fabric()
89+ vlan = factory.make_VLAN(fabric=fabric, space=RANDOM)
90+ self.assertThat(vlan.space, Not(Is(None)))
91+ uri = get_vlan_uri(vlan, fabric)
92+ response = self.client.put(uri, {
93+ "space": '',
94+ })
95+ self.assertEqual(
96+ http.client.OK, response.status_code, response.content)
97+ parsed_vlan = json.loads(
98+ response.content.decode(settings.DEFAULT_CHARSET))
99+ vlan = reload_object(vlan)
100+ self.assertThat(vlan.space, Is(None))
101+ self.assertThat(parsed_vlan['space'], Equals(Space.UNDEFINED))
102+
103+ def test_update_with_undefined_space_clears_space(self):
104+ self.become_admin()
105+ fabric = factory.make_Fabric()
106+ vlan = factory.make_VLAN(fabric=fabric, space=RANDOM)
107+ self.assertThat(vlan.space, Not(Is(None)))
108+ uri = get_vlan_uri(vlan, fabric)
109+ response = self.client.put(uri, {
110+ "space": Space.UNDEFINED,
111+ })
112+ self.assertEqual(
113+ http.client.OK, response.status_code, response.content)
114+ parsed_vlan = json.loads(
115+ response.content.decode(settings.DEFAULT_CHARSET))
116+ vlan = reload_object(vlan)
117+ self.assertThat(vlan.space, Is(None))
118+ self.assertThat(parsed_vlan['space'], Equals(Space.UNDEFINED))
119+
120 def test_update_admin_only(self):
121 fabric = factory.make_Fabric()
122 vlan = factory.make_VLAN(fabric=fabric)
123
124=== modified file 'src/maasserver/api/vlans.py'
125--- src/maasserver/api/vlans.py 2016-12-08 23:02:30 +0000
126+++ src/maasserver/api/vlans.py 2017-01-22 22:55:49 +0000
127@@ -11,6 +11,7 @@
128 from maasserver.forms_vlan import VLANForm
129 from maasserver.models import (
130 Fabric,
131+ Space,
132 VLAN,
133 )
134 from piston3.utils import rc
135@@ -43,27 +44,6 @@
136 # See the comment in NodeHandler.resource_uri.
137 return ('vlans_handler', ["fabric_id"])
138
139- @classmethod
140- def primary_rack(handler, vlan):
141- if vlan.primary_rack:
142- return vlan.primary_rack.system_id
143- else:
144- return None
145-
146- @classmethod
147- def secondary_rack(handler, vlan):
148- if vlan.secondary_rack:
149- return vlan.secondary_rack.system_id
150- else:
151- return None
152-
153- @classmethod
154- def space(handler, vlan):
155- if vlan.space:
156- return vlan.space.get_name()
157- else:
158- return None
159-
160 def read(self, request, fabric_id):
161 """List all VLANs belonging to fabric.
162
163@@ -126,7 +106,7 @@
164 if vlan.space:
165 return vlan.space.get_name()
166 else:
167- return None
168+ return Space.UNDEFINED
169
170 @classmethod
171 def fabric(cls, vlan):
172@@ -194,11 +174,23 @@
173 be configured to proxy reqests to the primary and/or secondary
174 rack controller interfaces for the VLAN specified in this field.
175 :type relay_vlan: ID of VLAN
176+ :param space: The space this VLAN should be placed in. Passing in an
177+ empty string (or the string 'undefined') will cause the VLAN to be
178+ placed in the 'undefined' space.
179+ :type space: unicode
180
181 Returns 404 if the fabric or VLAN is not found.
182 """
183 vlan = self._get_vlan(request.user, NODE_PERMISSION.ADMIN, **kwargs)
184- form = VLANForm(instance=vlan, data=request.data)
185+ data = {}
186+ # If the user passed in a space, make the undefined space name a
187+ # synonym for the empty space. But the Django request data object is
188+ # immutable, so we must first copy its contents into our own dict.
189+ for k, v in request.data.items():
190+ data[k] = v
191+ if 'space' in data and data['space'] == Space.UNDEFINED:
192+ data['space'] = ''
193+ form = VLANForm(instance=vlan, data=data)
194 if form.is_valid():
195 return form.save()
196 else:
197
198=== modified file 'src/maasserver/forms_subnet.py'
199--- src/maasserver/forms_subnet.py 2016-12-17 03:08:28 +0000
200+++ src/maasserver/forms_subnet.py 2017-01-22 22:55:49 +0000
201@@ -52,7 +52,6 @@
202 'name',
203 'description',
204 'vlan',
205- 'space',
206 'cidr',
207 'gateway_ip',
208 'dns_servers',
209@@ -68,6 +67,11 @@
210
211 def clean(self):
212 cleaned_data = super(SubnetForm, self).clean()
213+ if 'space' in self.data:
214+ set_form_error(
215+ self, "space",
216+ "Spaces may no longer be set on subnets. Set the space on the "
217+ "underlying VLAN.")
218 # The default value for 'allow_proxy' is True.
219 if 'allow_proxy' not in self.data:
220 cleaned_data['allow_proxy'] = True
221@@ -84,7 +88,6 @@
222 # the VLAN specifically. This is because we cannot make a correct
223 # determination on what should be done in this case.
224 cleaned_data = self._clean_vlan(cleaned_data)
225- cleaned_data = self._clean_space(cleaned_data)
226 return cleaned_data
227
228 def _clean_name(self, cleaned_data):
229@@ -139,15 +142,6 @@
230 "VLAN %s is not in fabric %s." % (vlan, fabric))
231 return cleaned_data
232
233- def _clean_space(self, cleaned_data):
234- space = cleaned_data.get("space", None)
235- if space is not None:
236- set_form_error(
237- self, "space",
238- "Spaces may no longer be set on subnets. Set the space on the "
239- "underlying VLAN.")
240- return cleaned_data
241-
242 def _clean_dns_servers(self, cleaned_data):
243 dns_servers = cleaned_data.get("dns_servers", None)
244 if dns_servers is None:
245
246=== modified file 'src/maasserver/forms_vlan.py'
247--- src/maasserver/forms_vlan.py 2016-12-07 16:40:35 +0000
248+++ src/maasserver/forms_vlan.py 2017-01-22 22:55:49 +0000
249@@ -9,7 +9,10 @@
250
251 from django import forms
252 from django.core.exceptions import ValidationError
253-from maasserver.fields import NodeChoiceField
254+from maasserver.fields import (
255+ NodeChoiceField,
256+ SpecifierOrModelChoiceField,
257+)
258 from maasserver.forms import MAASModelForm
259 from maasserver.models import (
260 RackController,
261@@ -24,8 +27,8 @@
262 # Linux doesn't allow lower than 552 for the MTU.
263 mtu = forms.IntegerField(min_value=552, required=False)
264
265- space = forms.ModelChoiceField(
266- queryset=Space.objects.all(), required=False)
267+ space = SpecifierOrModelChoiceField(
268+ queryset=Space.objects.all(), required=False, empty_label="")
269
270 class Meta:
271 model = VLAN
272@@ -128,6 +131,9 @@
273 if (cleaned_data.get('secondary_rack') is None and
274 self.instance.secondary_rack is not None):
275 self.instance.secondary_rack = None
276+ if (cleaned_data.get('space') is "" and
277+ self.instance.space is not None):
278+ self.instance.space = None
279 return cleaned_data
280
281 def clean_dhcp_on(self):
282
283=== modified file 'src/maasserver/models/space.py'
284--- src/maasserver/models/space.py 2017-01-04 17:45:19 +0000
285+++ src/maasserver/models/space.py 2017-01-22 22:55:49 +0000
286@@ -119,6 +119,9 @@
287 :ivar objects: An instance of the class :class:`SpaceManager`.
288 """
289
290+ # Name of the undefined space.
291+ UNDEFINED = "undefined"
292+
293 class Meta(DefaultMeta):
294 """Needed for South to recognize this model."""
295 verbose_name = "Space"
296@@ -151,8 +154,11 @@
297 def clean_name(self):
298 reserved = re.compile('^space-\d+$')
299 if self.name is not None and self.name != '':
300+ if self.name == Space.UNDEFINED:
301+ raise ValidationError(
302+ {'name': ["Reserved space name."]})
303 if reserved.search(self.name):
304- if (self.id is None or self.name != 'space-%d' % self.id):
305+ if self.id is None or self.name != 'space-%d' % self.id:
306 raise ValidationError(
307 {'name': ["Reserved space name."]})
308 elif self.id is not None:
309
310=== modified file 'src/maasserver/models/tests/test_space.py'
311--- src/maasserver/models/tests/test_space.py 2016-12-20 06:52:13 +0000
312+++ src/maasserver/models/tests/test_space.py 2017-01-22 22:55:49 +0000
313@@ -155,6 +155,12 @@
314 factory.make_Space,
315 name='space-1999')
316
317+ def test_undefined_name_raises_exception(self):
318+ self.assertRaises(
319+ ValidationError,
320+ factory.make_Space,
321+ name=Space.UNDEFINED)
322+
323 def test_create_sets_name(self):
324 space = Space.objects.create(name=None)
325 self.assertEqual("space-%d" % space.id, space.name)
326
327=== modified file 'src/maasserver/tests/test_forms_subnet.py'
328--- src/maasserver/tests/test_forms_subnet.py 2016-12-17 03:08:28 +0000
329+++ src/maasserver/tests/test_forms_subnet.py 2017-01-22 22:55:49 +0000
330@@ -24,7 +24,20 @@
331 "cidr": ["This field is required."],
332 }, form.errors)
333
334- def test__rejects_provided_space(self):
335+ def test__rejects_provided_space_on_update(self):
336+ space = factory.make_Space()
337+ subnet = factory.make_Subnet()
338+ form = SubnetForm(instance=subnet, data={
339+ "space": space.id
340+ })
341+ self.assertFalse(form.is_valid(), form.errors)
342+ self.assertEqual({
343+ "space": [
344+ "Spaces may no longer be set on subnets. Set the space on the "
345+ "underlying VLAN."
346+ ]}, form.errors)
347+
348+ def test__rejects_space_on_create(self):
349 space = factory.make_Space()
350 form = SubnetForm({
351 "space": space.id, "cidr": factory._make_random_network()
352
353=== modified file 'src/maasserver/tests/test_forms_vlan.py'
354--- src/maasserver/tests/test_forms_vlan.py 2016-12-07 16:40:35 +0000
355+++ src/maasserver/tests/test_forms_vlan.py 2017-01-22 22:55:49 +0000
356@@ -277,6 +277,17 @@
357 vlan = reload_object(vlan)
358 self.assertEquals(space.id, vlan.space.id)
359
360+ def test_update_sets_space_by_specifier(self):
361+ vlan = factory.make_VLAN()
362+ space = factory.make_Space()
363+ form = VLANForm(instance=vlan, data={
364+ "space": "name:" + space.name,
365+ })
366+ self.assertTrue(form.is_valid(), form.errors)
367+ form.save()
368+ vlan = reload_object(vlan)
369+ self.assertEquals(space.id, vlan.space.id)
370+
371 def test_update_clears_space_when_None(self):
372 space = factory.make_Space()
373 vlan = factory.make_VLAN(space=space)
374@@ -288,6 +299,17 @@
375 vlan = reload_object(vlan)
376 self.assertIsNone(vlan.space)
377
378+ def test_update_clears_space_when_empty(self):
379+ space = factory.make_Space()
380+ vlan = factory.make_VLAN(space=space)
381+ form = VLANForm(instance=vlan, data={
382+ "space": '',
383+ })
384+ self.assertTrue(form.is_valid(), form.errors)
385+ form.save()
386+ vlan = reload_object(vlan)
387+ self.assertIsNone(vlan.space)
388+
389 def test_update_clears_space_vlan_when_empty(self):
390 space = factory.make_Space()
391 vlan = factory.make_VLAN(space=space)