Merge lp:~gmb/maas/json-schema-part-1 into lp:~maas-committers/maas/trunk
- json-schema-part-1
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Graham Binns |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1921 |
Proposed branch: | lp:~gmb/maas/json-schema-part-1 |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
531 lines (+379/-98) 4 files modified
required-packages/base (+1/-0) src/maasserver/power_parameters.py (+227/-89) src/maasserver/tests/test_power_parameters.py (+146/-4) src/provisioningserver/enum.py (+5/-5) |
To merge this branch: | bzr merge lp:~gmb/maas/json-schema-part-1 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gavin Panella (community) | Approve | ||
Julian Edwards (community) | Approve | ||
Review via email: mp+205305@code.launchpad.net |
Commit message
Convert the definition of power type parameters from straight Python to something that's JSON-schema-
Description of the change
After a lot of dancing around, this is the first part of the work to making power type parameters specifiable in JSON.
In order to do this I've:
- Defined a JSON schema to describe how power type parameters should look
(this is split up so that we can validate separate parts of it individually)
- Created a JSON-validatable version of the existing POWER_TYPE_
- Created a function, get_power_
JSON power type parameters to something similar to the existing
POWER_
- Use get_power_
This is done once at the bottom of maasserver.
not parsing and validating JSON all the time.
Julian Edwards (julian-edwards) wrote : | # |
Julian Edwards (julian-edwards) : | # |
Gavin Panella (allenap) wrote : | # |
Looks grand :)
[1]
Couple of minor things:
+def make_json_
+ required=False):
Dosnae matter much, but try using the following form for indentation:
def make_json_field(
name, label, field_type=None, choices=None, default=None,
... code here ...
+ """Helper function for build a JSON power type parameters field."""
s/for/to/
Graham Binns (gmb) wrote : | # |
On 10 February 2014 02:24, Julian Edwards <email address hidden> wrote:
> On Friday 07 Feb 2014 09:20:37 you wrote:
>> Commit message:
>> Convert the definition of power type parameters from straight Python to
>> something that's JSON-schema-
>
> Can you add something about why this is happening please.
Yeah, sorry. I've gotten into a laziness habit with commit messages...
>> After a lot of dancing around, this is the first part of the work to making
>> power type parameters specifiable in JSON.
>
> \o/
>
> This is mostly looking *great*. I'd like to get Gavin to take a peek to make
> sure it matches his grand vision, but it looks like it's on the right track to
> me.
+1. Will bug Gavin.
> Some nits:
>
> [1] You're missing :param:, :type: and :return: info on the docstrings for the
> new functions.
>
> (FWIW My vim plugin reads the :type: and offers better auto-completions :)
Which vim plugin loveliness is it of which you speak? Fixed.
>
> [2] I realise it's early days, but the FIELD_TYPE_MAPPINGS are going to form
> part of the API with the drivers. I think putting these in their own module
> would be better (like errno), so we can do something like
> powerfield.
> with the driver, which consumes the API. (It also makes it easier to install
> for packaging and is something we should have done for enums so that maas api
> clients can use them)
Okay, I'm a bit lost as to what you mean here... I was with you up
until "their own module" but then you started talking about packaging
and I got confused. Are you talking about just adding a module under
src/maasserver (which is what I think you mean)?
Anyway, in the interests of getting this landed I'll do that in a
followup branch once I've got clarity.
> [3] jsonschema. MY EYES!
> OK this ones not a nit :)
DEAR GOD YES.
> [4] Missing tests for make_json_field.
>
Fixed. Cheers for the review.
Graham Binns (gmb) wrote : | # |
On 10 February 2014 10:25, Gavin Panella <email address hidden> wrote:
> Couple of minor things:
>
> +def make_json_
> + required=False):
>
> Dosnae matter much, but try using the following form for indentation:
Done. Is that the standard form for MAAS? I was doing it the LP way
but this looks better.
> + """Helper function for build a JSON power type parameters field."""
Actually should have been s/build/building/. Fixed.
Raphaël Badin (rvb) wrote : | # |
This introduces a new dependency: python-jsonschema. This means the packaging branch needs to be updated.
Next time, let's try to do this before landing the branch on lp:maas please. Otherwise it breaks the package, causes the integration tests to fail, and whoever ends up looking into it is wasting his time.
Julian Edwards (julian-edwards) wrote : | # |
On Monday 10 Feb 2014 12:06:29 you wrote:
> > (FWIW My vim plugin reads the :type: and offers better auto-completions :)
>
> Which vim plugin loveliness is it of which you speak? Fixed.
https:/
It is seriously *awesome*.
> > [2] I realise it's early days, but the FIELD_TYPE_MAPPINGS are going to
> > form part of the API with the drivers. I think putting these in their
> > own module would be better (like errno), so we can do something like
> > powerfield.
> > with the driver, which consumes the API. (It also makes it easier to
> > install for packaging and is something we should have done for enums so
> > that maas api clients can use them)
>
> Okay, I'm a bit lost as to what you mean here... I was with you up
> until "their own module" but then you started talking about packaging
> and I got confused. Are you talking about just adding a module under
> src/maasserver (which is what I think you mean)?
Ok so these are a bit like the enums in that third party code (drivers) are
going to need access. Putting it in its own module will help.
For packaging, I was thinking more about maas-cli because we could install an
enums.py (for example) along with the cli itself. This doesn't translate too
well for the drivers since they are going to be part and parcel of maas
itself; it's just that we want to make life as easy as possible for driver
writers so I'd prefer if we had all things that drivers will need to import in
a separate and convenience place.
Hope that makes sense :)
> Anyway, in the interests of getting this landed I'll do that in a
> followup branch once I've got clarity.
Naw worries.
It could probably even wait until Gavin starts writing the API layer.
> Fixed. Cheers for the review.
Pleasure.
Julian Edwards (julian-edwards) wrote : | # |
On Monday 10 Feb 2014 13:17:31 you wrote:
> This introduces a new dependency: python-jsonschema. This means the
> packaging branch needs to be updated.
>
> Next time, let's try to do this before landing the branch on lp:maas please.
> Otherwise it breaks the package, causes the integration tests to fail, and
> whoever ends up looking into it is wasting his time.
Argh, I meant to say something about that when I read the branch and totally
forgot.
Preview Diff
1 | === modified file 'required-packages/base' |
2 | --- required-packages/base 2014-01-20 09:39:00 +0000 |
3 | +++ required-packages/base 2014-02-10 12:07:12 +0000 |
4 | @@ -24,6 +24,7 @@ |
5 | python-docutils |
6 | python-formencode |
7 | python-httplib2 |
8 | +python-jsonschema |
9 | python-lockfile |
10 | python-netaddr |
11 | python-netifaces |
12 | |
13 | === modified file 'src/maasserver/power_parameters.py' |
14 | --- src/maasserver/power_parameters.py 2014-01-21 06:08:12 +0000 |
15 | +++ src/maasserver/power_parameters.py 2014-02-10 12:07:12 +0000 |
16 | @@ -33,6 +33,7 @@ |
17 | |
18 | |
19 | from django import forms |
20 | +from jsonschema import validate |
21 | from maasserver.config_forms import DictCharField |
22 | from maasserver.fields import MACAddressFormField |
23 | from provisioningserver.enum import ( |
24 | @@ -40,92 +41,229 @@ |
25 | IPMI_DRIVER_CHOICES, |
26 | ) |
27 | |
28 | -# FIXME: These should come from a hardware driver. |
29 | -POWER_TYPE_PARAMETERS = { |
30 | - # Empty type, for the case where nothing is entered in the form yet. |
31 | - '': DictCharField( |
32 | - [], required=False, skip_check=True), |
33 | - 'ether_wake': DictCharField( |
34 | - [ |
35 | - ('mac_address', |
36 | - MACAddressFormField(label="MAC Address", required=False)), |
37 | - ], |
38 | - required=False, |
39 | - skip_check=True), |
40 | - 'virsh': DictCharField( |
41 | - [ |
42 | - ('power_address', |
43 | - forms.CharField(label="Address", required=False)), |
44 | - ('power_id', |
45 | - forms.CharField(label="Power ID", required=False)), |
46 | - ], |
47 | - required=False, |
48 | - skip_check=True), |
49 | - 'fence_cdu': DictCharField( |
50 | - [ |
51 | - ('power_id', |
52 | - forms.CharField(label="Power ID", required=False)), |
53 | - ('power_address', |
54 | - forms.CharField(label="IP Address or Hostname", required=False)), |
55 | - ('power_user', |
56 | - forms.CharField(label="Username", required=False)), |
57 | - ('power_pass', |
58 | - forms.CharField(label="Password", required=False)), |
59 | - ], |
60 | - required=False, |
61 | - skip_check=True), |
62 | - 'ipmi': DictCharField( |
63 | - [ |
64 | - ('power_driver', |
65 | - forms.ChoiceField( |
66 | - label="Driver", required=False, |
67 | - choices=IPMI_DRIVER_CHOICES, |
68 | - initial=IPMI_DRIVER.DEFAULT)), |
69 | - ('power_address', |
70 | - forms.CharField(label="IP Address or Hostname", required=False)), |
71 | - ('power_user', |
72 | - forms.CharField(label="Username", required=False)), |
73 | - ('power_pass', |
74 | - forms.CharField(label="Password", required=False)), |
75 | - ], |
76 | - required=False, |
77 | - skip_check=True), |
78 | - 'moonshot': DictCharField( |
79 | - [ |
80 | - ('power_address', |
81 | - forms.CharField( |
82 | - label="IP Address or Hostname", required=False)), |
83 | - ('power_user', |
84 | - forms.CharField(label="Username", required=False)), |
85 | - ('power_pass', |
86 | - forms.CharField(label="Password", required=False)), |
87 | - ('power_hwaddress', |
88 | - forms.CharField(label="Hardware Address", required=False)), |
89 | - ], |
90 | - required=False, |
91 | - skip_check=True), |
92 | - 'sm15k': DictCharField( |
93 | - [ |
94 | - ('power_address', |
95 | - forms.CharField( |
96 | - label="IP Address or Hostname", required=False)), |
97 | - ('power_user', |
98 | - forms.CharField(label="Username", required=False)), |
99 | - ('power_pass', |
100 | - forms.CharField(label="Password", required=False)), |
101 | - ('system_id', |
102 | - forms.CharField(label="System ID", required=False)), |
103 | - ], |
104 | - required=False, |
105 | - skip_check=True), |
106 | - 'amt': DictCharField( |
107 | - [ |
108 | - ('power_address', |
109 | - forms.CharField( |
110 | - label="IP Address or Hostname", required=False)), |
111 | - ('power_pass', |
112 | - forms.CharField(label="Password", required=False)), |
113 | - ], |
114 | - required=False, |
115 | - skip_check=True), |
116 | -} |
117 | + |
118 | +# Represent the Django choices format as JSON; an array of 2-item |
119 | +# arrays. |
120 | +CHOICE_FIELD_SCHEMA = { |
121 | + 'type': 'array', |
122 | + 'items': { |
123 | + 'title': "Power type paramter field choice", |
124 | + 'type': 'array', |
125 | + 'minItems': 2, |
126 | + 'maxItems': 2, |
127 | + 'uniqueItems': True, |
128 | + 'items': { |
129 | + 'type': 'string', |
130 | + } |
131 | + }, |
132 | +} |
133 | + |
134 | + |
135 | +POWER_TYPE_PARAMETER_FIELD_SCHEMA = { |
136 | + 'title': "Power type parameter field", |
137 | + 'type': 'object', |
138 | + 'properties': { |
139 | + 'name': { |
140 | + 'type': 'string', |
141 | + }, |
142 | + 'field_type': { |
143 | + 'type': 'string', |
144 | + }, |
145 | + 'label': { |
146 | + 'type': 'string', |
147 | + }, |
148 | + 'required': { |
149 | + 'type': 'boolean', |
150 | + }, |
151 | + 'choices': CHOICE_FIELD_SCHEMA, |
152 | + 'default': { |
153 | + 'type': 'string', |
154 | + }, |
155 | + }, |
156 | + 'required': ['field_type', 'label', 'required'], |
157 | +} |
158 | + |
159 | + |
160 | +# A basic JSON schema for what power type parameters should look like. |
161 | +JSON_POWER_TYPE_PARAMETERS_SCHEMA = { |
162 | + 'title': "Power parameters set", |
163 | + 'type': 'array', |
164 | + 'items': { |
165 | + 'title': "Power type parameters", |
166 | + 'type': 'object', |
167 | + 'properties': { |
168 | + 'fields': { |
169 | + 'type': 'array', |
170 | + 'items': POWER_TYPE_PARAMETER_FIELD_SCHEMA, |
171 | + }, |
172 | + }, |
173 | + 'required': ['fields'], |
174 | + }, |
175 | +} |
176 | + |
177 | + |
178 | +def make_json_field( |
179 | + name, label, field_type=None, choices=None, default=None, |
180 | + required=False): |
181 | + """Helper function for building a JSON power type parameters field. |
182 | + |
183 | + :param name: The name of the field. |
184 | + :type name: string |
185 | + :param label: The label to be presented to the user for this field. |
186 | + :type label: string |
187 | + :param field_type: The type of field to create. Can be one of |
188 | + (string, choice, mac_address). Defaults to string. |
189 | + :type field_type: string. |
190 | + :param choices: The collection of choices to present to the user. |
191 | + Needs to be structured as a list of lists, otherwise |
192 | + make_json_field() will raise a ValidationError. |
193 | + :type list: |
194 | + :param default: The default value for the field. |
195 | + :type default: string |
196 | + :param required: Whether or not a value for the field is required. |
197 | + :type required: boolean |
198 | + """ |
199 | + if field_type not in ('string', 'mac_address', 'choice'): |
200 | + field_type = 'string' |
201 | + if choices is None: |
202 | + choices = [] |
203 | + validate(choices, CHOICE_FIELD_SCHEMA) |
204 | + if default is None: |
205 | + default = "" |
206 | + field = { |
207 | + 'name': name, |
208 | + 'label': label, |
209 | + 'required': required, |
210 | + 'field_type': field_type, |
211 | + 'choices': choices, |
212 | + 'default': default, |
213 | + } |
214 | + return field |
215 | + |
216 | + |
217 | +# FIXME this should all be produced by hardware drivers, not defined statically |
218 | +# like this. |
219 | +JSON_POWER_TYPE_PARAMETERS = [ |
220 | + { |
221 | + 'name': 'ether_wake', |
222 | + 'fields': [ |
223 | + make_json_field( |
224 | + 'mac_address', "MAC Address", field_type='mac_address'), |
225 | + ], |
226 | + }, |
227 | + { |
228 | + 'name': 'virsh', |
229 | + 'fields': [ |
230 | + make_json_field('power_address', "Power address"), |
231 | + make_json_field('power_id', "Power ID"), |
232 | + ], |
233 | + }, |
234 | + { |
235 | + 'name': 'fence_cdu', |
236 | + 'fields': [ |
237 | + make_json_field('power_address', "Power address"), |
238 | + make_json_field('power_id', "Power ID"), |
239 | + make_json_field('power_user', "Power user"), |
240 | + make_json_field('power_pass', "Power password"), |
241 | + ], |
242 | + }, |
243 | + { |
244 | + 'name': 'ipmi', |
245 | + 'fields': [ |
246 | + make_json_field( |
247 | + 'power_driver', "Power driver", field_type='choice', |
248 | + choices=IPMI_DRIVER_CHOICES, default=IPMI_DRIVER.DEFAULT), |
249 | + make_json_field('power_address', "Power address"), |
250 | + make_json_field('power_user', "Power user"), |
251 | + make_json_field('power_pass', "Power password"), |
252 | + ], |
253 | + }, |
254 | + { |
255 | + 'name': 'moonshot', |
256 | + 'fields': [ |
257 | + make_json_field('power_address', "Power address"), |
258 | + make_json_field('power_user', "Power user"), |
259 | + make_json_field('power_pass', "Power password"), |
260 | + make_json_field('power_hwaddress', "Power hardware address"), |
261 | + ], |
262 | + }, |
263 | + { |
264 | + 'name': 'sm15k', |
265 | + 'fields': [ |
266 | + make_json_field('system_id', "System ID"), |
267 | + make_json_field('power_address', "Power address"), |
268 | + make_json_field('power_user', "Power user"), |
269 | + make_json_field('power_pass', "Power password"), |
270 | + ], |
271 | + }, |
272 | + { |
273 | + 'name': 'amt', |
274 | + 'fields': [ |
275 | + make_json_field('power_address', "Power address"), |
276 | + make_json_field('power_pass', "Power password"), |
277 | + ], |
278 | + }, |
279 | +] |
280 | + |
281 | + |
282 | +FIELD_TYPE_MAPPINGS = { |
283 | + 'string': forms.CharField, |
284 | + 'mac_address': MACAddressFormField, |
285 | + 'choice': forms.ChoiceField, |
286 | +} |
287 | + |
288 | + |
289 | +def make_form_field(json_field): |
290 | + """Build a Django form field based on the JSON spec. |
291 | + |
292 | + :param json_field: The JSON-specified field to convert into a valid |
293 | + Djangoism. |
294 | + :type json_field: dict |
295 | + :return: The correct Django form field for the field type, as |
296 | + specified in FIELD_TYPE_MAPPINGS. |
297 | + """ |
298 | + field_class = FIELD_TYPE_MAPPINGS.get( |
299 | + json_field['field_type'], forms.CharField) |
300 | + if json_field['field_type'] == 'choice': |
301 | + extra_parameters = { |
302 | + 'choices': json_field['choices'], |
303 | + 'initial': json_field['default'], |
304 | + } |
305 | + else: |
306 | + extra_parameters = {} |
307 | + form_field = field_class( |
308 | + label=json_field['label'], required=json_field['required'], |
309 | + **extra_parameters) |
310 | + return form_field |
311 | + |
312 | + |
313 | +def get_power_type_parameters_from_json(json_power_type_parameters): |
314 | + """Return power type parameters. |
315 | + |
316 | + :param json_power_type_parameters: Power type parameters expressed |
317 | + as a JSON string or as set of JSONSchema-verifiable objects. |
318 | + Will be validated using jsonschema.validate(). |
319 | + :type json_power_type_parameters: JSON string or iterable. |
320 | + :return: A dict of power parameters for all power types, indexed by |
321 | + power type name. |
322 | + """ |
323 | + validate(json_power_type_parameters, JSON_POWER_TYPE_PARAMETERS_SCHEMA) |
324 | + power_parameters = { |
325 | + # Empty type, for the case where nothing is entered in the form yet. |
326 | + '': DictCharField( |
327 | + [], required=False, skip_check=True), |
328 | + } |
329 | + for power_type in json_power_type_parameters: |
330 | + fields = [] |
331 | + for json_field in power_type['fields']: |
332 | + fields.append(( |
333 | + json_field['name'], make_form_field(json_field))) |
334 | + params = DictCharField(fields, required=False, skip_check=True) |
335 | + power_parameters[power_type['name']] = params |
336 | + return power_parameters |
337 | + |
338 | + |
339 | +# We do this once because there's no point re-parsing the JSON every |
340 | +# time we need to look up the power type parameters code. |
341 | +POWER_TYPE_PARAMETERS = get_power_type_parameters_from_json( |
342 | + JSON_POWER_TYPE_PARAMETERS) |
343 | |
344 | === modified file 'src/maasserver/tests/test_power_parameters.py' |
345 | --- src/maasserver/tests/test_power_parameters.py 2014-01-21 06:08:12 +0000 |
346 | +++ src/maasserver/tests/test_power_parameters.py 2014-02-10 12:07:12 +0000 |
347 | @@ -14,13 +14,20 @@ |
348 | __metaclass__ = type |
349 | __all__ = [] |
350 | |
351 | +from django import forms |
352 | +import jsonschema |
353 | from maasserver.config_forms import DictCharField |
354 | -from maasserver.power_parameters import POWER_TYPE_PARAMETERS |
355 | +from maasserver.fields import MACAddressFormField |
356 | +from maasserver.power_parameters import ( |
357 | + get_power_type_parameters_from_json, |
358 | + make_form_field, |
359 | + make_json_field, |
360 | + POWER_TYPE_PARAMETERS, |
361 | + POWER_TYPE_PARAMETER_FIELD_SCHEMA, |
362 | + ) |
363 | from maasserver.testing.factory import factory |
364 | from maasserver.testing.testcase import MAASServerTestCase |
365 | -from provisioningserver.enum import ( |
366 | - get_power_types, |
367 | - ) |
368 | +from provisioningserver.enum import get_power_types |
369 | from provisioningserver.power.poweraction import PowerAction |
370 | from testtools.matchers import ( |
371 | AllMatch, |
372 | @@ -76,3 +83,138 @@ |
373 | script = action.render_template(action.get_template(), **params) |
374 | # The real check is that the rendering went fine. |
375 | self.assertIsInstance(script, bytes) |
376 | + |
377 | + |
378 | +class TestGetPowerTypeParametersFromJSON(MAASServerTestCase): |
379 | + """Test that get_power_type_parametrs_from_json.""" |
380 | + |
381 | + def test_validates_json_power_type_parameters(self): |
382 | + invalid_parameters = [{ |
383 | + 'name': 'invalid_power_type', |
384 | + 'fields': 'nothing to see here', |
385 | + }] |
386 | + self.assertRaises( |
387 | + jsonschema.ValidationError, get_power_type_parameters_from_json, |
388 | + invalid_parameters) |
389 | + |
390 | + def test_includes_empty_power_type(self): |
391 | + json_parameters = [{ |
392 | + 'name': 'something', |
393 | + 'fields': [{ |
394 | + 'name': 'some_field', |
395 | + 'label': 'Some Field', |
396 | + 'field_type': 'string', |
397 | + 'required': False, |
398 | + }], |
399 | + }] |
400 | + power_type_parameters = get_power_type_parameters_from_json( |
401 | + json_parameters) |
402 | + self.assertEqual(['', 'something'], power_type_parameters.keys()) |
403 | + |
404 | + def test_creates_dict_char_fields(self): |
405 | + json_parameters = [{ |
406 | + 'name': 'something', |
407 | + 'fields': [{ |
408 | + 'name': 'some_field', |
409 | + 'label': 'Some Field', |
410 | + 'field_type': 'string', |
411 | + 'required': False, |
412 | + }], |
413 | + }] |
414 | + power_type_parameters = get_power_type_parameters_from_json( |
415 | + json_parameters) |
416 | + for name, field in power_type_parameters.items(): |
417 | + self.assertIsInstance(field, DictCharField) |
418 | + |
419 | + |
420 | +class TestMakeFormField(MAASServerTestCase): |
421 | + """Test that make_form_field() converts JSON fields to Django.""" |
422 | + |
423 | + def test_creates_char_field_for_strings(self): |
424 | + json_field = { |
425 | + 'name': 'some_field', |
426 | + 'label': 'Some Field', |
427 | + 'field_type': 'string', |
428 | + 'required': False, |
429 | + } |
430 | + django_field = make_form_field(json_field) |
431 | + self.assertIsInstance(django_field, forms.CharField) |
432 | + |
433 | + def test_creates_choice_field_for_choices(self): |
434 | + json_field = { |
435 | + 'name': 'some_field', |
436 | + 'label': 'Some Field', |
437 | + 'field_type': 'choice', |
438 | + 'choices': [ |
439 | + ['choice-one', 'Choice One'], |
440 | + ['choice-two', 'Choice Two'], |
441 | + ], |
442 | + 'default': 'choice-one', |
443 | + 'required': False, |
444 | + } |
445 | + django_field = make_form_field(json_field) |
446 | + self.assertIsInstance(django_field, forms.ChoiceField) |
447 | + self.assertEqual(json_field['choices'], django_field.choices) |
448 | + self.assertEqual(json_field['default'], django_field.initial) |
449 | + |
450 | + def test_creates_mac_address_field_for_mac_addresses(self): |
451 | + json_field = { |
452 | + 'name': 'some_field', |
453 | + 'label': 'Some Field', |
454 | + 'field_type': 'mac_address', |
455 | + 'required': False, |
456 | + } |
457 | + django_field = make_form_field(json_field) |
458 | + self.assertIsInstance(django_field, MACAddressFormField) |
459 | + |
460 | + def test_sets_properties_on_form_field(self): |
461 | + json_field = { |
462 | + 'name': 'some_field', |
463 | + 'label': 'Some Field', |
464 | + 'field_type': 'string', |
465 | + 'required': False, |
466 | + } |
467 | + django_field = make_form_field(json_field) |
468 | + self.assertEqual( |
469 | + (json_field['label'], json_field['required']), |
470 | + (django_field.label, django_field.required)) |
471 | + |
472 | + |
473 | +class TestMakeJSONField(MAASServerTestCase): |
474 | + """Test that make_json_field() creates JSON-verifiable fields.""" |
475 | + |
476 | + def test_returns_json_verifiable_dict(self): |
477 | + json_field = make_json_field('some_field', 'Some Label') |
478 | + jsonschema.validate(json_field, POWER_TYPE_PARAMETER_FIELD_SCHEMA) |
479 | + |
480 | + def test_provides_sane_default_values(self): |
481 | + json_field = make_json_field('some_field', 'Some Label') |
482 | + expected_field = { |
483 | + 'name': 'some_field', |
484 | + 'label': 'Some Label', |
485 | + 'required': False, |
486 | + 'field_type': 'string', |
487 | + 'choices': [], |
488 | + 'default': '', |
489 | + } |
490 | + self.assertEqual(expected_field, json_field) |
491 | + |
492 | + def test_sets_field_values(self): |
493 | + expected_field = { |
494 | + 'name': 'yet_another_field', |
495 | + 'label': 'Can I stop writing tests now?', |
496 | + 'required': True, |
497 | + 'field_type': 'string', |
498 | + 'choices': [ |
499 | + ['spam', 'Spam'], |
500 | + ['eggs', 'Eggs'], |
501 | + ], |
502 | + 'default': 'spam', |
503 | + } |
504 | + json_field = make_json_field(**expected_field) |
505 | + self.assertEqual(expected_field, json_field) |
506 | + |
507 | + def test_validates_choices(self): |
508 | + self.assertRaises( |
509 | + jsonschema.ValidationError, make_json_field, |
510 | + 'some_field', 'Some Label', choices="Nonsense") |
511 | |
512 | === modified file 'src/provisioningserver/enum.py' |
513 | --- src/provisioningserver/enum.py 2014-01-31 10:46:57 +0000 |
514 | +++ src/provisioningserver/enum.py 2014-02-10 12:07:12 +0000 |
515 | @@ -92,11 +92,11 @@ |
516 | LAN_2_0 = 'LAN_2_0' |
517 | |
518 | |
519 | -IPMI_DRIVER_CHOICES = ( |
520 | - (IPMI_DRIVER.DEFAULT, "Auto-detect"), |
521 | - (IPMI_DRIVER.LAN, "LAN (IPMI 1.5)"), |
522 | - (IPMI_DRIVER.LAN_2_0, "LAN_2_0 (IPMI 2.0)"), |
523 | - ) |
524 | +IPMI_DRIVER_CHOICES = [ |
525 | + [IPMI_DRIVER.DEFAULT, "Auto-detect"], |
526 | + [IPMI_DRIVER.LAN, "LAN [IPMI 1.5]"], |
527 | + [IPMI_DRIVER.LAN_2_0, "LAN_2_0 [IPMI 2.0]"], |
528 | + ] |
529 | |
530 | |
531 | class ARP_HTYPE: |
On Friday 07 Feb 2014 09:20:37 you wrote: validatable.
> Commit message:
> Convert the definition of power type parameters from straight Python to
> something that's JSON-schema-
Can you add something about why this is happening please.
> After a lot of dancing around, this is the first part of the work to making
> power type parameters specifiable in JSON.
\o/
> In order to do this I've: PARAMETERS dict - Created a function, type_parameters _from_json( ) to convert the JSON power type PARAMETERS dict. type_parameters _from_json( ) to create PARAMETERS. This is done once at the bottom of power_parameter s so that we're not parsing and validating JSON
>
> - Defined a JSON schema to describe how power type parameters should look
> (this is split up so that we can validate separate parts of it
> individually) - Created a JSON-validatable version of the existing
> POWER_TYPE_
> get_power_
> parameters to something similar to the existing POWER_TYPE_
> - Use get_power_
> POWER_TYPE_
> maasserver.
> all the time.
This is mostly looking *great*. I'd like to get Gavin to take a peek to make
sure it matches his grand vision, but it looks like it's on the right track to
me.
Some nits:
[1] You're missing :param:, :type: and :return: info on the docstrings for the
new functions.
(FWIW My vim plugin reads the :type: and offers better auto-completions :)
[2] I realise it's early days, but the FIELD_TYPE_MAPPINGS are going to form mac_address, for example. This also then forms a firm contract
part of the API with the drivers. I think putting these in their own module
would be better (like errno), so we can do something like
powerfield.
with the driver, which consumes the API. (It also makes it easier to install
for packaging and is something we should have done for enums so that maas api
clients can use them)
[3] jsonschema. MY EYES!
OK this ones not a nit :)
[4] Missing tests for make_json_field.
cheers.