Merge lp:~ltrager/maas/lp1593991_2.0 into lp:maas/2.0
- lp1593991_2.0
- Merge into 2.0
Proposed by
Lee Trager
Status: | Merged |
---|---|
Approved by: | Lee Trager |
Approved revision: | no longer in the source branch. |
Merged at revision: | 5199 |
Proposed branch: | lp:~ltrager/maas/lp1593991_2.0 |
Merge into: | lp:maas/2.0 |
Diff against target: |
1223 lines (+297/-141) 21 files modified
src/maasserver/api/machines.py (+0/-3) src/maasserver/api/tests/test_enlistment.py (+21/-12) src/maasserver/api/tests/test_machine.py (+35/-22) src/maasserver/api/tests/test_node.py (+1/-1) src/maasserver/api/tests/test_rackcontroller.py (+2/-1) src/maasserver/api/tests/test_utils.py (+20/-0) src/maasserver/api/utils.py (+14/-1) src/maasserver/clusterrpc/power_parameters.py (+24/-5) src/maasserver/clusterrpc/tests/test_power_parameters.py (+48/-0) src/maasserver/config_forms.py (+3/-0) src/maasserver/forms.py (+37/-6) src/maasserver/rpc/nodes.py (+0/-4) src/maasserver/rpc/tests/test_nodes.py (+18/-32) src/maasserver/testing/factory.py (+2/-3) src/maasserver/tests/test_config_forms.py (+9/-0) src/maasserver/tests/test_forms_controller.py (+4/-2) src/maasserver/tests/test_forms_machine.py (+4/-1) src/maasserver/websockets/handlers/machine.py (+2/-9) src/maasserver/websockets/handlers/tests/test_machine.py (+7/-4) src/maasserver/websockets/tests/test_base.py (+2/-2) src/provisioningserver/power/schema.py (+44/-33) |
To merge this branch: | bzr merge lp:~ltrager/maas/lp1593991_2.0 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mike Pontillo (community) | Approve | ||
Review via email: mp+309157@code.launchpad.net |
Commit message
Backport from 2.1.1 - Validate power parameters in the node form.
Description of the change
Backport from 2.1.1, reviewed in https:/
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maasserver/api/machines.py' |
2 | --- src/maasserver/api/machines.py 2016-08-18 12:30:42 +0000 |
3 | +++ src/maasserver/api/machines.py 2016-10-24 18:44:21 +0000 |
4 | @@ -30,7 +30,6 @@ |
5 | OwnerDataMixin, |
6 | PowerMixin, |
7 | PowersMixin, |
8 | - store_node_power_parameters, |
9 | ) |
10 | from maasserver.api.support import ( |
11 | admin_method, |
12 | @@ -737,8 +736,6 @@ |
13 | form = Form(data=altered_query_data, request=request) |
14 | if form.is_valid(): |
15 | machine = form.save() |
16 | - # Hack in the power parameters here. |
17 | - store_node_power_parameters(machine, request) |
18 | maaslog.info("%s: Enlisted new machine", machine.hostname) |
19 | return machine |
20 | else: |
21 | |
22 | === modified file 'src/maasserver/api/tests/test_enlistment.py' |
23 | --- src/maasserver/api/tests/test_enlistment.py 2016-06-14 18:31:07 +0000 |
24 | +++ src/maasserver/api/tests/test_enlistment.py 2016-10-24 18:44:21 +0000 |
25 | @@ -87,6 +87,7 @@ |
26 | architecture = make_usable_architecture(self) |
27 | power_type = 'ipmi' |
28 | power_parameters = { |
29 | + "power_address": factory.make_ip_address(), |
30 | "power_user": factory.make_name("power-user"), |
31 | "power_pass": factory.make_name("power-pass"), |
32 | } |
33 | @@ -95,14 +96,16 @@ |
34 | { |
35 | 'hostname': hostname, |
36 | 'architecture': architecture, |
37 | - 'power_type': 'manual', |
38 | + 'power_type': power_type, |
39 | 'mac_addresses': factory.make_mac_address(), |
40 | 'power_parameters': json.dumps(power_parameters), |
41 | - 'power_type': power_type, |
42 | }) |
43 | + # Add the default values. |
44 | + power_parameters['power_driver'] = 'LAN_2_0' |
45 | + power_parameters['mac_address'] = '' |
46 | self.assertEqual(http.client.OK, response.status_code) |
47 | [machine] = Machine.objects.filter(hostname=hostname) |
48 | - self.assertEqual(power_parameters, machine.power_parameters) |
49 | + self.assertItemsEqual(power_parameters, machine.power_parameters) |
50 | self.assertEqual(power_type, machine.power_type) |
51 | |
52 | def test_POST_create_creates_machine_with_arch_only(self): |
53 | @@ -454,23 +457,29 @@ |
54 | self.assertEqual([], machines_returned) |
55 | |
56 | def test_POST_simple_user_can_set_power_type_and_parameters(self): |
57 | - new_power_address = factory.make_string() |
58 | + new_power_address = factory.make_url() |
59 | + new_power_id = factory.make_name('power_id') |
60 | response = self.client.post( |
61 | reverse('machines_handler'), { |
62 | 'architecture': make_usable_architecture(self), |
63 | - 'power_type': 'manual', |
64 | + 'power_type': 'virsh', |
65 | 'power_parameters': json.dumps( |
66 | - {"power_address": new_power_address}), |
67 | + { |
68 | + "power_address": new_power_address, |
69 | + "power_id": new_power_id, |
70 | + }), |
71 | 'mac_addresses': ['AA:BB:CC:DD:EE:FF'], |
72 | }) |
73 | - |
74 | machine = Machine.objects.get( |
75 | system_id=json_load_bytes(response.content)['system_id']) |
76 | - self.assertEqual( |
77 | - (http.client.OK, {"power_address": new_power_address}, |
78 | - 'manual'), |
79 | - (response.status_code, machine.power_parameters, |
80 | - machine.power_type)) |
81 | + self.assertEqual(http.client.OK, response.status_code) |
82 | + self.assertEqual('virsh', machine.power_type) |
83 | + self.assertItemsEqual( |
84 | + { |
85 | + 'power_pass': '', |
86 | + 'power_id': new_power_id, |
87 | + 'power_address': new_power_address, |
88 | + }, machine.power_parameters) |
89 | |
90 | def test_POST_returns_limited_fields(self): |
91 | response = self.client.post( |
92 | |
93 | === modified file 'src/maasserver/api/tests/test_machine.py' |
94 | --- src/maasserver/api/tests/test_machine.py 2016-05-24 22:05:45 +0000 |
95 | +++ src/maasserver/api/tests/test_machine.py 2016-10-24 18:44:21 +0000 |
96 | @@ -808,7 +808,7 @@ |
97 | # The api allows the updating of a Machine. |
98 | machine = factory.make_Node( |
99 | hostname='diane', owner=self.user, |
100 | - architecture=make_usable_architecture(self)) |
101 | + architecture=make_usable_architecture(self), power_type='manual') |
102 | response = self.client.put( |
103 | self.get_machine_uri(machine), {'hostname': 'francis'}) |
104 | parsed_result = json_load_bytes(response.content) |
105 | @@ -825,7 +825,8 @@ |
106 | hostname = factory.make_name('hostname') |
107 | arch = make_usable_architecture(self) |
108 | machine = factory.make_Node( |
109 | - hostname=hostname, owner=self.user, architecture=arch) |
110 | + hostname=hostname, owner=self.user, architecture=arch, |
111 | + power_type='manual') |
112 | response = self.client.put( |
113 | self.get_machine_uri(machine), |
114 | {'architecture': arch}) |
115 | @@ -848,7 +849,8 @@ |
116 | self.become_admin() |
117 | machine = factory.make_Node( |
118 | owner=self.user, |
119 | - architecture=make_usable_architecture(self)) |
120 | + architecture=make_usable_architecture(self), |
121 | + power_type='manual') |
122 | field = factory.make_string() |
123 | response = self.client.put( |
124 | self.get_machine_uri(machine), |
125 | @@ -866,7 +868,11 @@ |
126 | power_type=original_power_type, |
127 | architecture=make_usable_architecture(self)) |
128 | response = self.client.put( |
129 | - self.get_machine_uri(machine), {'power_type': new_power_type}) |
130 | + self.get_machine_uri(machine), |
131 | + { |
132 | + 'power_type': new_power_type, |
133 | + 'power_parameters_skip_check': 'true', |
134 | + }) |
135 | |
136 | self.assertEqual(http.client.OK, response.status_code) |
137 | self.assertEqual( |
138 | @@ -890,7 +896,8 @@ |
139 | # provides the URI for this Machine. |
140 | machine = factory.make_Node( |
141 | hostname='diane', owner=self.user, |
142 | - architecture=make_usable_architecture(self)) |
143 | + architecture=make_usable_architecture(self), |
144 | + power_type='manual') |
145 | response = self.client.put( |
146 | self.get_machine_uri(machine), {'hostname': 'francis'}) |
147 | parsed_result = json_load_bytes(response.content) |
148 | @@ -906,7 +913,8 @@ |
149 | self.become_admin() |
150 | machine = factory.make_Node( |
151 | hostname='diane', owner=self.user, |
152 | - architecture=make_usable_architecture(self)) |
153 | + architecture=make_usable_architecture(self), |
154 | + power_type='manual') |
155 | response = self.client.put( |
156 | self.get_machine_uri(machine), {'hostname': '.'}) |
157 | parsed_result = json_load_bytes(response.content) |
158 | @@ -959,8 +967,8 @@ |
159 | self.become_admin() |
160 | machine = factory.make_Node( |
161 | owner=self.user, |
162 | - power_type=factory.pick_power_type(), |
163 | - architecture=make_usable_architecture(self)) |
164 | + architecture=make_usable_architecture(self), |
165 | + power_type='manual') |
166 | response = self.client.put( |
167 | self.get_machine_uri(machine), |
168 | {'cpu_count': 1, 'memory': 1024}) |
169 | @@ -1067,7 +1075,8 @@ |
170 | self.become_admin() |
171 | machine = factory.make_Node( |
172 | owner=self.user, |
173 | - architecture=make_usable_architecture(self)) |
174 | + architecture=make_usable_architecture(self), |
175 | + power_parameters={}) |
176 | new_param = factory.make_string() |
177 | new_value = factory.make_string() |
178 | response = self.client.put( |
179 | @@ -1083,7 +1092,11 @@ |
180 | |
181 | def test_PUT_updates_power_parameters_empty_string(self): |
182 | self.become_admin() |
183 | - power_parameters = {factory.make_string(): factory.make_string()} |
184 | + power_parameters = { |
185 | + 'power_address': factory.make_url(), |
186 | + 'power_id': factory.make_name('power_id'), |
187 | + 'power_pass': factory.make_name('power_pass'), |
188 | + } |
189 | machine = factory.make_Node( |
190 | owner=self.user, |
191 | power_type='virsh', |
192 | @@ -1091,22 +1104,17 @@ |
193 | architecture=make_usable_architecture(self)) |
194 | response = self.client.put( |
195 | self.get_machine_uri(machine), |
196 | - {'power_parameters_power_id': ''}) |
197 | + {'power_parameters_power_pass': ''}) |
198 | |
199 | self.assertEqual(http.client.OK, response.status_code) |
200 | self.assertEqual( |
201 | - { |
202 | - 'power_id': '', |
203 | - 'power_pass': '', |
204 | - 'power_address': '', |
205 | - }, |
206 | - reload_object(machine).power_parameters) |
207 | + '', reload_object(machine).power_parameters['power_pass']) |
208 | |
209 | def test_PUT_sets_zone(self): |
210 | self.become_admin() |
211 | new_zone = factory.make_Zone() |
212 | machine = factory.make_Node( |
213 | - architecture=make_usable_architecture(self)) |
214 | + architecture=make_usable_architecture(self), power_type='manual') |
215 | |
216 | response = self.client.put( |
217 | self.get_machine_uri(machine), {'zone': new_zone.name}) |
218 | @@ -1119,7 +1127,7 @@ |
219 | self.become_admin() |
220 | new_name = factory.make_name() |
221 | machine = factory.make_Node( |
222 | - architecture=make_usable_architecture(self)) |
223 | + architecture=make_usable_architecture(self), power_type='manual') |
224 | old_zone = machine.zone |
225 | |
226 | response = self.client.put( |
227 | @@ -1148,7 +1156,8 @@ |
228 | self.become_admin() |
229 | zone = factory.make_Zone() |
230 | machine = factory.make_Node( |
231 | - zone=zone, architecture=make_usable_architecture(self)) |
232 | + zone=zone, architecture=make_usable_architecture(self), |
233 | + power_type='manual') |
234 | |
235 | response = self.client.put(self.get_machine_uri(machine), {}) |
236 | |
237 | @@ -1186,6 +1195,7 @@ |
238 | machine = factory.make_Node( |
239 | owner=self.user, |
240 | architecture=make_usable_architecture(self), |
241 | + power_type='manual', |
242 | disable_ipv4=original_setting) |
243 | new_setting = not original_setting |
244 | |
245 | @@ -1202,6 +1212,7 @@ |
246 | machine = factory.make_Node( |
247 | owner=self.user, |
248 | architecture=make_usable_architecture(self), |
249 | + power_type='manual', |
250 | disable_ipv4=original_setting) |
251 | self.assertEqual(original_setting, machine.disable_ipv4) |
252 | |
253 | @@ -1216,7 +1227,8 @@ |
254 | self.become_admin() |
255 | machine = factory.make_Node( |
256 | owner=self.user, |
257 | - architecture=make_usable_architecture(self)) |
258 | + architecture=make_usable_architecture(self), |
259 | + power_type='manual') |
260 | response = self.client.put( |
261 | reverse('machine_handler', args=[machine.system_id]), |
262 | {'swap_size': 5 * 1000 ** 3}) # Making sure we overflow 32 bits |
263 | @@ -1229,7 +1241,8 @@ |
264 | self.become_admin() |
265 | machine = factory.make_Node( |
266 | owner=self.user, |
267 | - architecture=make_usable_architecture(self)) |
268 | + architecture=make_usable_architecture(self), |
269 | + power_type='manual') |
270 | |
271 | response = self.client.put( |
272 | reverse('machine_handler', args=[machine.system_id]), |
273 | |
274 | === modified file 'src/maasserver/api/tests/test_node.py' |
275 | --- src/maasserver/api/tests/test_node.py 2016-06-17 07:16:39 +0000 |
276 | +++ src/maasserver/api/tests/test_node.py 2016-10-24 18:44:21 +0000 |
277 | @@ -302,7 +302,7 @@ |
278 | self.assertEqual( |
279 | http.client.OK, response.status_code, response.content) |
280 | parsed_params = json_load_bytes(response.content) |
281 | - self.assertEqual({}, parsed_params) |
282 | + self.assertEqual(node.power_parameters, parsed_params) |
283 | |
284 | def test_power_parameters_requires_admin(self): |
285 | node = factory.make_Node() |
286 | |
287 | === modified file 'src/maasserver/api/tests/test_rackcontroller.py' |
288 | --- src/maasserver/api/tests/test_rackcontroller.py 2016-06-08 20:20:40 +0000 |
289 | +++ src/maasserver/api/tests/test_rackcontroller.py 2016-10-24 18:44:21 +0000 |
290 | @@ -36,7 +36,8 @@ |
291 | |
292 | def test_PUT_updates_rack_controller(self): |
293 | self.become_admin() |
294 | - rack = factory.make_RackController(owner=self.user) |
295 | + rack = factory.make_RackController( |
296 | + owner=self.user, power_type='manual') |
297 | zone = factory.make_zone() |
298 | response = self.client.put( |
299 | self.get_rack_uri(rack), {'zone': zone.name}) |
300 | |
301 | === modified file 'src/maasserver/api/tests/test_utils.py' |
302 | --- src/maasserver/api/tests/test_utils.py 2016-05-24 13:51:36 +0000 |
303 | +++ src/maasserver/api/tests/test_utils.py 2016-10-24 18:44:21 +0000 |
304 | @@ -7,6 +7,7 @@ |
305 | |
306 | from collections import namedtuple |
307 | |
308 | +from django.forms import CharField |
309 | from django.http import QueryDict |
310 | from maasserver.api.utils import ( |
311 | extract_bool, |
312 | @@ -15,6 +16,7 @@ |
313 | get_oauth_token, |
314 | get_overridden_query_dict, |
315 | ) |
316 | +from maasserver.config_forms import DictCharField |
317 | from maasserver.exceptions import Unauthorized |
318 | from maasserver.testing.factory import factory |
319 | from maasserver.testing.testcase import MAASServerTestCase |
320 | @@ -94,6 +96,24 @@ |
321 | results = get_overridden_query_dict(defaults, data, [key1]) |
322 | self.assertEqual([data_value2], results.getlist(key2)) |
323 | |
324 | + def test_expands_dict_fields(self): |
325 | + field_name = factory.make_name('field_name') |
326 | + sub_fields = { |
327 | + factory.make_name('sub_field'): CharField() for _ in range(3) |
328 | + } |
329 | + fields = { |
330 | + field_name: DictCharField(sub_fields) |
331 | + } |
332 | + defaults = { |
333 | + "%s_%s" % (field_name, field): factory.make_name('subfield') |
334 | + for field in sub_fields.keys() |
335 | + } |
336 | + data = {field_name: DictCharField(fields)} |
337 | + results = get_overridden_query_dict(defaults, data, fields) |
338 | + expected = {key: [value] for key, value in defaults.items()} |
339 | + expected.update(fields) |
340 | + self.assertItemsEqual(expected, results) |
341 | + |
342 | |
343 | def make_fake_request(auth_header): |
344 | """Create a very simple fake request, with just an auth header.""" |
345 | |
346 | === modified file 'src/maasserver/api/utils.py' |
347 | --- src/maasserver/api/utils.py 2016-03-28 13:54:47 +0000 |
348 | +++ src/maasserver/api/utils.py 2016-10-24 18:44:21 +0000 |
349 | @@ -16,6 +16,7 @@ |
350 | |
351 | from django.http import QueryDict |
352 | from formencode.validators import Invalid |
353 | +from maasserver.config_forms import DictCharField |
354 | from maasserver.exceptions import ( |
355 | MAASAPIValidationError, |
356 | Unauthorized, |
357 | @@ -149,11 +150,23 @@ |
358 | """ |
359 | # Create a writable query dict. |
360 | new_data = QueryDict('').copy() |
361 | + # If the fields are a dict of django Fields see if one is a DictCharField. |
362 | + # DictCharField must have their values prefixed with the DictField name in |
363 | + # the returned data or defaults don't get carried. |
364 | + if isinstance(fields, dict): |
365 | + acceptable_fields = [] |
366 | + for field_name, field in fields.items(): |
367 | + acceptable_fields.append(field_name) |
368 | + if isinstance(field, DictCharField): |
369 | + for sub_field in field.names: |
370 | + acceptable_fields.append("%s_%s" % (field_name, sub_field)) |
371 | + else: |
372 | + acceptable_fields = fields |
373 | # Missing fields will be taken from the node's current values. This |
374 | # is to circumvent Django's ModelForm (form created from a model) |
375 | # default behaviour that requires all the fields to be defined. |
376 | for k, v in defaults.items(): |
377 | - if k in fields: |
378 | + if k in acceptable_fields: |
379 | new_data.setlist(k, listify(v)) |
380 | # We can't use update here because data is a QueryDict and 'update' |
381 | # does not replaces the old values with the new as one would expect. |
382 | |
383 | === modified file 'src/maasserver/clusterrpc/power_parameters.py' |
384 | --- src/maasserver/clusterrpc/power_parameters.py 2016-03-28 13:54:47 +0000 |
385 | +++ src/maasserver/clusterrpc/power_parameters.py 2016-10-24 18:44:21 +0000 |
386 | @@ -65,12 +65,16 @@ |
387 | json_field['name'], json_field['choices']) |
388 | extra_parameters = { |
389 | 'choices': json_field['choices'], |
390 | - 'initial': json_field['default'], |
391 | 'error_messages': { |
392 | 'invalid_choice': invalid_choice_message}, |
393 | } |
394 | else: |
395 | extra_parameters = {} |
396 | + |
397 | + default = json_field.get('default') |
398 | + if default is not None: |
399 | + extra_parameters['initial'] = default |
400 | + |
401 | form_field = field_class( |
402 | label=json_field['label'], required=json_field['required'], |
403 | **extra_parameters) |
404 | @@ -111,13 +115,20 @@ |
405 | 'missing_packages': missing_packages}) |
406 | |
407 | |
408 | -def get_power_type_parameters_from_json(json_power_type_parameters): |
409 | +def get_power_type_parameters_from_json( |
410 | + json_power_type_parameters, initial_power_params=None, |
411 | + skip_check=False): |
412 | """Return power type parameters. |
413 | |
414 | :param json_power_type_parameters: Power type parameters expressed |
415 | as a JSON string or as set of JSONSchema-verifiable objects. |
416 | Will be validated using jsonschema.validate(). |
417 | :type json_power_type_parameters: JSON string or iterable. |
418 | + :param initial_power_params: Power paramaters that were already set, any |
419 | + field which matches will have its initial value set. |
420 | + :type initial_power_params: dict |
421 | + :param skip_check: Whether the field should be checked or not. |
422 | + :type skip_check: bool |
423 | :return: A dict of power parameters for all power types, indexed by |
424 | power type name. |
425 | """ |
426 | @@ -127,19 +138,27 @@ |
427 | '': DictCharField( |
428 | [], required=False, skip_check=True), |
429 | } |
430 | + if initial_power_params is None: |
431 | + initial_power_params = [] |
432 | for power_type in json_power_type_parameters: |
433 | fields = [] |
434 | + has_required_field = False |
435 | for json_field in power_type['fields']: |
436 | + field_name = json_field['name'] |
437 | + if field_name in initial_power_params: |
438 | + json_field['default'] = initial_power_params[field_name] |
439 | + has_required_field = has_required_field or json_field['required'] |
440 | fields.append(( |
441 | json_field['name'], make_form_field(json_field))) |
442 | - params = DictCharField(fields, required=False, skip_check=True) |
443 | + params = DictCharField( |
444 | + fields, required=has_required_field, skip_check=skip_check) |
445 | power_parameters[power_type['name']] = params |
446 | return power_parameters |
447 | |
448 | |
449 | -def get_power_type_parameters(): |
450 | +def get_power_type_parameters(initial_power_params=None, skip_check=False): |
451 | return get_power_type_parameters_from_json( |
452 | - get_all_power_types_from_clusters()) |
453 | + get_all_power_types_from_clusters(), initial_power_params, skip_check) |
454 | |
455 | |
456 | def get_power_type_choices(): |
457 | |
458 | === modified file 'src/maasserver/clusterrpc/tests/test_power_parameters.py' |
459 | --- src/maasserver/clusterrpc/tests/test_power_parameters.py 2016-05-12 19:07:37 +0000 |
460 | +++ src/maasserver/clusterrpc/tests/test_power_parameters.py 2016-10-24 18:44:21 +0000 |
461 | @@ -20,6 +20,7 @@ |
462 | ) |
463 | from maasserver.config_forms import DictCharField |
464 | from maasserver.fields import MACAddressFormField |
465 | +from maasserver.testing.factory import factory |
466 | from maasserver.testing.testcase import MAASServerTestCase |
467 | from maasserver.utils.forms import compose_invalid_choice_text |
468 | from maastesting.matchers import MockCalledOnceWith |
469 | @@ -70,6 +71,42 @@ |
470 | for name, field in power_type_parameters.items(): |
471 | self.assertIsInstance(field, DictCharField) |
472 | |
473 | + def test__overrides_defaults(self): |
474 | + name = factory.make_name('name') |
475 | + field_name = factory.make_name('field_name') |
476 | + new_default = factory.make_name('new default') |
477 | + json_parameters = [{ |
478 | + 'name': name, |
479 | + 'description': factory.make_name('description'), |
480 | + 'fields': [{ |
481 | + 'name': field_name, |
482 | + 'label': factory.make_name('field label'), |
483 | + 'field_type': factory.make_name('field type'), |
484 | + 'default': factory.make_name('field default'), |
485 | + 'required': False, |
486 | + }], |
487 | + }] |
488 | + power_type_parameters = get_power_type_parameters_from_json( |
489 | + json_parameters, {field_name: new_default}) |
490 | + self.assertEqual( |
491 | + new_default, power_type_parameters[name].fields[0].initial) |
492 | + |
493 | + def test__manual_does_not_require_power_params(self): |
494 | + json_parameters = [{ |
495 | + 'name': 'manual', |
496 | + 'description': factory.make_name('description'), |
497 | + 'fields': [{ |
498 | + 'name': factory.make_name('field name'), |
499 | + 'label': factory.make_name('field label'), |
500 | + 'field_type': factory.make_name('field type'), |
501 | + 'default': factory.make_name('field default'), |
502 | + 'required': False, |
503 | + }], |
504 | + }] |
505 | + power_type_parameters = get_power_type_parameters_from_json( |
506 | + json_parameters) |
507 | + self.assertFalse(power_type_parameters['manual'].required) |
508 | + |
509 | |
510 | class TestMakeFormField(MAASServerTestCase): |
511 | """Test that make_form_field() converts JSON fields to Django.""" |
512 | @@ -137,6 +174,17 @@ |
513 | (json_field['label'], json_field['required']), |
514 | (django_field.label, django_field.required)) |
515 | |
516 | + def test__sets_initial_to_default(self): |
517 | + json_field = { |
518 | + 'name': 'some_field', |
519 | + 'label': 'Some Field', |
520 | + 'field_type': 'string', |
521 | + 'required': False, |
522 | + 'default': 'some default', |
523 | + } |
524 | + django_field = make_form_field(json_field) |
525 | + self.assertEquals(json_field['default'], django_field.initial) |
526 | + |
527 | |
528 | class TestMakeJSONField(MAASServerTestCase): |
529 | """Test that make_json_field() creates JSON-verifiable fields.""" |
530 | |
531 | === modified file 'src/maasserver/config_forms.py' |
532 | --- src/maasserver/config_forms.py 2015-12-01 18:12:59 +0000 |
533 | +++ src/maasserver/config_forms.py 2016-10-24 18:44:21 +0000 |
534 | @@ -186,6 +186,9 @@ |
535 | field_value = value[index] |
536 | except IndexError: |
537 | field_value = None |
538 | + # Set the field_value to the default value if not set. |
539 | + if field_value is None and field.initial not in (None, ''): |
540 | + field_value = field.initial |
541 | # Check the field's 'required' field instead of the global |
542 | # 'required' field to allow subfields to be required or not. |
543 | if field.required and field_value in validators.EMPTY_VALUES: |
544 | |
545 | === modified file 'src/maasserver/forms.py' |
546 | --- src/maasserver/forms.py 2016-08-09 17:19:45 +0000 |
547 | +++ src/maasserver/forms.py 2016-10-24 18:44:21 +0000 |
548 | @@ -36,6 +36,7 @@ |
549 | |
550 | from collections import Counter |
551 | from functools import partial |
552 | +import json |
553 | import pipes |
554 | import re |
555 | |
556 | @@ -244,6 +245,27 @@ |
557 | return power_type |
558 | |
559 | @staticmethod |
560 | + def _get_power_parameters(form, data, machine): |
561 | + if data is None: |
562 | + data = {} |
563 | + |
564 | + power_parameters = data.get( |
565 | + 'power_parameters', form.initial.get('power_parameters', {})) |
566 | + |
567 | + if isinstance(power_parameters, str): |
568 | + try: |
569 | + power_parameters = json.loads(power_parameters) |
570 | + except json.JSONDecodeError: |
571 | + raise ValidationError("Failed to parse JSON power_parameters") |
572 | + |
573 | + # Integrate the machines existing power_parameters if unset by form. |
574 | + if machine: |
575 | + for key, value in machine.power_parameters.items(): |
576 | + if power_parameters.get(key) is None: |
577 | + power_parameters[key] = value |
578 | + return power_parameters |
579 | + |
580 | + @staticmethod |
581 | def set_up_power_type(form, data, machine=None): |
582 | """Set up the 'power_type' and 'power_parameters' fields. |
583 | |
584 | @@ -254,10 +276,18 @@ |
585 | choices = [BLANK_CHOICE] + get_power_type_choices() |
586 | form.fields['power_type'] = forms.ChoiceField( |
587 | required=False, choices=choices, initial=power_type) |
588 | - form.fields['power_parameters'] = get_power_type_parameters()[ |
589 | - power_type] |
590 | - if form.instance is not None and form.instance.power_type != '': |
591 | - form.initial['power_type'] = form.instance.power_type |
592 | + power_parameters = WithPowerMixin._get_power_parameters( |
593 | + form, data, machine) |
594 | + skip_check = ( |
595 | + form.data.get('power_parameters_%s' % SKIP_CHECK_NAME) == 'true') |
596 | + form.fields['power_parameters'] = get_power_type_parameters( |
597 | + power_parameters, skip_check=skip_check)[power_type] |
598 | + if form.instance is not None: |
599 | + if form.instance.power_type != '': |
600 | + form.initial['power_type'] = form.instance.power_type |
601 | + if form.instance.power_parameters != '': |
602 | + for key, value in power_parameters.items(): |
603 | + form.initial['power_parameters_%s' % key] = value |
604 | |
605 | @staticmethod |
606 | def check_power_type(form, cleaned_data): |
607 | @@ -280,8 +310,9 @@ |
608 | |
609 | # If power_type is not set and power_parameters_skip_check is not |
610 | # on, reset power_parameters (set it to the empty string). |
611 | - if cleaned_data.get('power_type', '') == '': |
612 | - cleaned_data['power_parameters'] = '' |
613 | + power_type = cleaned_data.get('power_type', '') |
614 | + if power_type == '': |
615 | + cleaned_data['power_parameters'] = {} |
616 | return cleaned_data |
617 | |
618 | @staticmethod |
619 | |
620 | === modified file 'src/maasserver/rpc/nodes.py' |
621 | --- src/maasserver/rpc/nodes.py 2016-07-25 23:25:53 +0000 |
622 | +++ src/maasserver/rpc/nodes.py 2016-10-24 18:44:21 +0000 |
623 | @@ -222,10 +222,6 @@ |
624 | form = AdminMachineWithMACAddressesForm(data_query_dict) |
625 | if form.is_valid(): |
626 | node = form.save() |
627 | - # We have to explicitly save the power parameters; the form |
628 | - # won't do it for us. |
629 | - node.power_parameters = json.loads(power_parameters) |
630 | - node.save() |
631 | return node |
632 | else: |
633 | raise ValidationError(form.errors) |
634 | |
635 | === modified file 'src/maasserver/rpc/tests/test_nodes.py' |
636 | --- src/maasserver/rpc/tests/test_nodes.py 2016-05-11 00:25:14 +0000 |
637 | +++ src/maasserver/rpc/tests/test_nodes.py 2016-10-24 18:44:21 +0000 |
638 | @@ -7,7 +7,6 @@ |
639 | |
640 | from datetime import timedelta |
641 | import json |
642 | -from json import dumps |
643 | from operator import attrgetter |
644 | import random |
645 | from random import randint |
646 | @@ -79,17 +78,13 @@ |
647 | mac_addresses = [ |
648 | factory.make_mac_address() for _ in range(3)] |
649 | architecture = make_usable_architecture(self) |
650 | - power_type = random.choice(self.power_types)['name'] |
651 | - power_parameters = dumps({}) |
652 | |
653 | - node = create_node( |
654 | - architecture, power_type, power_parameters, |
655 | - mac_addresses) |
656 | + node = create_node(architecture, 'manual', {}, mac_addresses) |
657 | |
658 | self.assertEqual( |
659 | ( |
660 | architecture, |
661 | - power_type, |
662 | + 'manual', |
663 | {}, |
664 | ), |
665 | ( |
666 | @@ -113,17 +108,14 @@ |
667 | factory.make_mac_address() for _ in range(3)] |
668 | architecture = make_usable_architecture(self) |
669 | hostname = factory.make_hostname() |
670 | - power_type = random.choice(self.power_types)['name'] |
671 | - power_parameters = dumps({}) |
672 | |
673 | node = create_node( |
674 | - architecture, power_type, power_parameters, |
675 | - mac_addresses, hostname=hostname) |
676 | + architecture, 'manual', {}, mac_addresses, hostname=hostname) |
677 | |
678 | self.assertEqual( |
679 | ( |
680 | architecture, |
681 | - power_type, |
682 | + 'manual', |
683 | {}, |
684 | hostname |
685 | ), |
686 | @@ -149,7 +141,7 @@ |
687 | "Microsoft Windows", |
688 | ]) |
689 | power_type = random.choice(self.power_types)['name'] |
690 | - power_parameters = dumps({}) |
691 | + power_parameters = {} |
692 | |
693 | with ExpectedException(ValidationError): |
694 | create_node( |
695 | @@ -164,17 +156,15 @@ |
696 | architecture = make_usable_architecture(self) |
697 | hostname = factory.make_hostname() |
698 | domain = factory.make_Domain() |
699 | - power_type = random.choice(self.power_types)['name'] |
700 | - power_parameters = dumps({}) |
701 | |
702 | node = create_node( |
703 | - architecture, power_type, power_parameters, |
704 | - mac_addresses, domain=domain.name, hostname=hostname) |
705 | + architecture, 'manual', {}, mac_addresses, domain=domain.name, |
706 | + hostname=hostname) |
707 | |
708 | self.assertEqual( |
709 | ( |
710 | architecture, |
711 | - power_type, |
712 | + 'manual', |
713 | {}, |
714 | domain.id, |
715 | hostname, |
716 | @@ -198,7 +188,7 @@ |
717 | factory.make_mac_address() for _ in range(3)] |
718 | architecture = make_usable_architecture(self) |
719 | power_type = random.choice(self.power_types)['name'] |
720 | - power_parameters = dumps({}) |
721 | + power_parameters = {} |
722 | |
723 | with ExpectedException(ValidationError): |
724 | create_node( |
725 | @@ -211,7 +201,7 @@ |
726 | self.assertRaises( |
727 | ValidationError, create_node, |
728 | architecture="spam/eggs", power_type="scrambled", |
729 | - power_parameters=dumps({}), |
730 | + power_parameters={}, |
731 | mac_addresses=[factory.make_mac_address()]) |
732 | |
733 | def test__raises_error_if_node_already_exists(self): |
734 | @@ -220,8 +210,8 @@ |
735 | mac_addresses = [ |
736 | factory.make_mac_address() for _ in range(3)] |
737 | architecture = make_usable_architecture(self) |
738 | - power_type = random.choice(self.power_types)['name'] |
739 | - power_parameters = dumps({}) |
740 | + power_type = 'manual' |
741 | + power_parameters = {} |
742 | |
743 | create_node( |
744 | architecture, power_type, power_parameters, |
745 | @@ -236,20 +226,20 @@ |
746 | mac_addresses = [ |
747 | factory.make_mac_address() for _ in range(3)] |
748 | architecture = make_usable_architecture(self) |
749 | - power_type = random.choice(self.power_types)['name'] |
750 | power_parameters = { |
751 | - factory.make_name('key'): factory.make_name('value') |
752 | - for _ in range(3) |
753 | + 'power_address': factory.make_url(), |
754 | + 'power_pass': factory.make_name('power_pass'), |
755 | + 'power_id': factory.make_name('power_id'), |
756 | } |
757 | |
758 | node = create_node( |
759 | - architecture, power_type, dumps(power_parameters), |
760 | + architecture, 'virsh', power_parameters, |
761 | mac_addresses) |
762 | |
763 | # Reload the object from the DB so that we're sure its power |
764 | # parameters are being persisted. |
765 | node = reload_object(node) |
766 | - self.assertEqual(power_parameters, node.power_parameters) |
767 | + self.assertItemsEqual(power_parameters, node.power_parameters) |
768 | |
769 | def test__forces_generic_subarchitecture_if_missing(self): |
770 | self.prepare_rack_rpc() |
771 | @@ -257,13 +247,9 @@ |
772 | mac_addresses = [ |
773 | factory.make_mac_address() for _ in range(3)] |
774 | architecture = make_usable_architecture(self, subarch_name='generic') |
775 | - power_type = random.choice(self.power_types)['name'] |
776 | - power_parameters = dumps({}) |
777 | |
778 | arch, subarch = architecture.split('/') |
779 | - node = create_node( |
780 | - arch, power_type, power_parameters, |
781 | - mac_addresses) |
782 | + node = create_node(arch, 'manual', {}, mac_addresses) |
783 | |
784 | self.assertEqual(architecture, node.architecture) |
785 | |
786 | |
787 | === modified file 'src/maasserver/testing/factory.py' |
788 | --- src/maasserver/testing/factory.py 2016-07-21 16:43:37 +0000 |
789 | +++ src/maasserver/testing/factory.py 2016-10-24 18:44:21 +0000 |
790 | @@ -367,8 +367,6 @@ |
791 | zone = self.make_Zone() |
792 | if power_type is None: |
793 | power_type = 'virsh' |
794 | - if power_parameters is None: |
795 | - power_parameters = {} |
796 | if power_state is None: |
797 | power_state = self.pick_enum(POWER_STATE) |
798 | if power_state_updated is undefined: |
799 | @@ -430,7 +428,8 @@ |
800 | ip_address = existing_static_ips[0] |
801 | bmc_ip_address = self.pick_ip_in_Subnet(ip_address.subnet) |
802 | node.power_parameters = { |
803 | - "power_address": "qemu+ssh://user@%s/system" % bmc_ip_address |
804 | + "power_address": "qemu+ssh://user@%s/system" % bmc_ip_address, |
805 | + "power_id": factory.make_name("power_id"), |
806 | } |
807 | node.save() |
808 | |
809 | |
810 | === modified file 'src/maasserver/tests/test_config_forms.py' |
811 | --- src/maasserver/tests/test_config_forms.py 2016-05-12 19:07:37 +0000 |
812 | +++ src/maasserver/tests/test_config_forms.py 2016-10-24 18:44:21 +0000 |
813 | @@ -165,6 +165,15 @@ |
814 | {'char_field': char_value, 'multi_field': None}, |
815 | form.cleaned_data) |
816 | |
817 | + def test_DictCharField_sets_default_value_for_subfields(self): |
818 | + default_value = factory.make_name('default_value') |
819 | + multi_field = DictCharField( |
820 | + [('field_a', forms.CharField( |
821 | + label='Field a', initial=default_value))], |
822 | + required=False) |
823 | + self.assertEquals( |
824 | + default_value, multi_field.clean_sub_fields('')['field_a']) |
825 | + |
826 | |
827 | class TestUtilities(MAASServerTestCase): |
828 | |
829 | |
830 | === modified file 'src/maasserver/tests/test_forms_controller.py' |
831 | --- src/maasserver/tests/test_forms_controller.py 2016-04-13 02:20:16 +0000 |
832 | +++ src/maasserver/tests/test_forms_controller.py 2016-10-24 18:44:21 +0000 |
833 | @@ -40,6 +40,7 @@ |
834 | form = ControllerForm( |
835 | data={ |
836 | 'power_type': power_type, |
837 | + 'power_parameters_skip_check': 'true', |
838 | }, |
839 | instance=rack) |
840 | rack = form.save() |
841 | @@ -51,12 +52,12 @@ |
842 | form = ControllerForm( |
843 | data={ |
844 | 'power_parameters_field': power_parameters_field, |
845 | - 'power_parameters_skip_check': True, |
846 | + 'power_parameters_skip_check': 'true', |
847 | }, |
848 | instance=rack) |
849 | rack = form.save() |
850 | self.assertEqual( |
851 | - {'field': power_parameters_field}, rack.power_parameters) |
852 | + power_parameters_field, rack.power_parameters['field']) |
853 | |
854 | def test__sets_zone(self): |
855 | rack = factory.make_RackController() |
856 | @@ -64,6 +65,7 @@ |
857 | form = ControllerForm( |
858 | data={ |
859 | 'zone': zone.name, |
860 | + 'power_parameters_skip_check': 'true', |
861 | }, |
862 | instance=rack) |
863 | rack = form.save() |
864 | |
865 | === modified file 'src/maasserver/tests/test_forms_machine.py' |
866 | --- src/maasserver/tests/test_forms_machine.py 2016-04-12 22:25:44 +0000 |
867 | +++ src/maasserver/tests/test_forms_machine.py 2016-10-24 18:44:21 +0000 |
868 | @@ -373,7 +373,7 @@ |
869 | 'architecture': arch, |
870 | 'power_type': power_type, |
871 | 'power_parameters_field': power_parameters_field, |
872 | - 'power_parameters_skip_check': True, |
873 | + 'power_parameters_skip_check': 'true', |
874 | }, |
875 | instance=node) |
876 | form.save() |
877 | @@ -393,6 +393,7 @@ |
878 | data={ |
879 | 'hostname': hostname, |
880 | 'architecture': arch, |
881 | + 'power_parameters_skip_check': 'true', |
882 | }, |
883 | instance=node) |
884 | node = form.save() |
885 | @@ -407,6 +408,7 @@ |
886 | data={ |
887 | 'hostname': hostname, |
888 | 'architecture': arch, |
889 | + 'power_parameters_skip_check': 'true', |
890 | }, |
891 | instance=node) |
892 | node = form.save() |
893 | @@ -422,6 +424,7 @@ |
894 | 'hostname': hostname, |
895 | 'architecture': arch, |
896 | 'power_type': power_type, |
897 | + 'power_parameters_skip_check': 'true', |
898 | }, |
899 | instance=node) |
900 | node = form.save() |
901 | |
902 | === modified file 'src/maasserver/websockets/handlers/machine.py' |
903 | --- src/maasserver/websockets/handlers/machine.py 2016-04-22 21:02:20 +0000 |
904 | +++ src/maasserver/websockets/handlers/machine.py 2016-10-24 18:44:21 +0000 |
905 | @@ -214,6 +214,7 @@ |
906 | new_params["hostname"] = params.get("hostname") |
907 | new_params["architecture"] = params.get("architecture") |
908 | new_params["power_type"] = params.get("power_type") |
909 | + new_params["power_parameters"] = params.get("power_parameters") |
910 | if "zone" in params: |
911 | new_params["zone"] = params["zone"]["name"] |
912 | if "domain" in params: |
913 | @@ -236,13 +237,8 @@ |
914 | if not self.user.is_superuser: |
915 | raise HandlerPermissionError() |
916 | |
917 | - # Create the object, then save the power parameters because the |
918 | - # form will not save this information. |
919 | data = super(NodeHandler, self).create(params) |
920 | node_obj = Node.objects.get(system_id=data['system_id']) |
921 | - node_obj.power_type = params.get("power_type", '') |
922 | - node_obj.power_parameters = params.get("power_parameters", {}) |
923 | - node_obj.save() |
924 | |
925 | # Start the commissioning process right away, which has the |
926 | # desired side effect of initializing the node's power state. |
927 | @@ -256,16 +252,13 @@ |
928 | if not self.user.is_superuser: |
929 | raise HandlerPermissionError() |
930 | |
931 | - # Update the node with the form. The form will not update the |
932 | - # power_type or power_parameters, so we perform that here. |
933 | data = super(NodeHandler, self).update(params) |
934 | node_obj = Node.objects.get(system_id=data['system_id']) |
935 | - node_obj.power_type = params.get("power_type", '') |
936 | - node_obj.power_parameters = params.get("power_parameters", {}) |
937 | |
938 | # Update the tags for the node and disks. |
939 | self.update_tags(node_obj, params['tags']) |
940 | node_obj.save() |
941 | + |
942 | return self.full_dehydrate(node_obj) |
943 | |
944 | def mount_special(self, params): |
945 | |
946 | === modified file 'src/maasserver/websockets/handlers/tests/test_machine.py' |
947 | --- src/maasserver/websockets/handlers/tests/test_machine.py 2016-05-12 19:07:37 +0000 |
948 | +++ src/maasserver/websockets/handlers/tests/test_machine.py 2016-10-24 18:44:21 +0000 |
949 | @@ -1094,7 +1094,7 @@ |
950 | def test_update_raises_validation_error_for_invalid_architecture(self): |
951 | user = factory.make_admin() |
952 | handler = MachineHandler(user, {}) |
953 | - node = factory.make_Node(interface=True) |
954 | + node = factory.make_Node(interface=True, power_type='manual') |
955 | node_data = self.dehydrate_node(node, handler) |
956 | arch = factory.make_name("arch") |
957 | node_data["architecture"] = arch |
958 | @@ -1144,7 +1144,8 @@ |
959 | user = factory.make_admin() |
960 | handler = MachineHandler(user, {}) |
961 | architecture = make_usable_architecture(self) |
962 | - node = factory.make_Node(interface=True, architecture=architecture) |
963 | + node = factory.make_Node( |
964 | + interface=True, architecture=architecture, power_type='manual') |
965 | tags = [ |
966 | factory.make_Tag(definition='').name |
967 | for _ in range(3) |
968 | @@ -1158,7 +1159,8 @@ |
969 | user = factory.make_admin() |
970 | handler = MachineHandler(user, {}) |
971 | architecture = make_usable_architecture(self) |
972 | - node = factory.make_Node(interface=True, architecture=architecture) |
973 | + node = factory.make_Node( |
974 | + interface=True, architecture=architecture, power_type='manual') |
975 | tags = [] |
976 | for _ in range(3): |
977 | tag = factory.make_Tag(definition='') |
978 | @@ -1175,7 +1177,8 @@ |
979 | user = factory.make_admin() |
980 | handler = MachineHandler(user, {}) |
981 | architecture = make_usable_architecture(self) |
982 | - node = factory.make_Node(interface=True, architecture=architecture) |
983 | + node = factory.make_Node( |
984 | + interface=True, architecture=architecture, power_type='manual') |
985 | tag_name = factory.make_name("tag") |
986 | node_data = self.dehydrate_node(node, handler) |
987 | node_data["tags"].append(tag_name) |
988 | |
989 | === modified file 'src/maasserver/websockets/tests/test_base.py' |
990 | --- src/maasserver/websockets/tests/test_base.py 2016-05-12 19:07:37 +0000 |
991 | +++ src/maasserver/websockets/tests/test_base.py 2016-10-24 18:44:21 +0000 |
992 | @@ -536,7 +536,7 @@ |
993 | |
994 | def test_update_with_form_updates_node(self): |
995 | arch = make_usable_architecture(self) |
996 | - node = factory.make_Node(architecture=arch) |
997 | + node = factory.make_Node(architecture=arch, power_type='manual') |
998 | hostname = factory.make_name("hostname") |
999 | handler = self.make_nodes_handler( |
1000 | fields=['hostname'], form=AdminMachineForm) |
1001 | @@ -552,7 +552,7 @@ |
1002 | |
1003 | def test_update_with_form_uses_form_from_get_form_class(self): |
1004 | arch = make_usable_architecture(self) |
1005 | - node = factory.make_Node(architecture=arch) |
1006 | + node = factory.make_Node(architecture=arch, power_type='manual') |
1007 | hostname = factory.make_name("hostname") |
1008 | handler = self.make_nodes_handler(fields=['hostname']) |
1009 | self.patch( |
1010 | |
1011 | === modified file 'src/provisioningserver/power/schema.py' |
1012 | --- src/provisioningserver/power/schema.py 2016-05-20 19:58:33 +0000 |
1013 | +++ src/provisioningserver/power/schema.py 2016-10-24 18:44:21 +0000 |
1014 | @@ -224,9 +224,10 @@ |
1015 | 'name': 'virsh', |
1016 | 'description': 'Virsh (virtual systems)', |
1017 | 'fields': [ |
1018 | - make_json_field('power_address', "Power address"), |
1019 | + make_json_field('power_address', "Power address", required=True), |
1020 | make_json_field( |
1021 | - 'power_id', "Power ID", scope=POWER_PARAMETER_SCOPE.NODE), |
1022 | + 'power_id', "Power ID", scope=POWER_PARAMETER_SCOPE.NODE, |
1023 | + required=True), |
1024 | make_json_field( |
1025 | 'power_pass', "Power password (optional)", |
1026 | required=False, field_type='password'), |
1027 | @@ -244,10 +245,11 @@ |
1028 | make_json_field( |
1029 | 'power_uuid', "VM UUID (if known)", required=False, |
1030 | scope=POWER_PARAMETER_SCOPE.NODE), |
1031 | - make_json_field('power_address', "VMware hostname"), |
1032 | - make_json_field('power_user', "VMware username"), |
1033 | + make_json_field('power_address', "VMware hostname", required=True), |
1034 | + make_json_field('power_user', "VMware username", required=True), |
1035 | make_json_field( |
1036 | - 'power_pass', "VMware password", field_type='password'), |
1037 | + 'power_pass', "VMware password", field_type='password', |
1038 | + required=True), |
1039 | make_json_field( |
1040 | 'power_port', "VMware API port (optional)", required=False), |
1041 | make_json_field( |
1042 | @@ -259,9 +261,10 @@ |
1043 | 'name': 'fence_cdu', |
1044 | 'description': 'Sentry Switch CDU', |
1045 | 'fields': [ |
1046 | - make_json_field('power_address', "Power address"), |
1047 | + make_json_field('power_address', "Power address", required=True), |
1048 | make_json_field( |
1049 | - 'power_id', "Power ID", scope=POWER_PARAMETER_SCOPE.NODE), |
1050 | + 'power_id', "Power ID", scope=POWER_PARAMETER_SCOPE.NODE, |
1051 | + required=True), |
1052 | make_json_field('power_user', "Power user"), |
1053 | make_json_field( |
1054 | 'power_pass', "Power password", field_type='password'), |
1055 | @@ -274,8 +277,9 @@ |
1056 | 'fields': [ |
1057 | make_json_field( |
1058 | 'power_driver', "Power driver", field_type='choice', |
1059 | - choices=IPMI_DRIVER_CHOICES, default=IPMI_DRIVER.LAN_2_0), |
1060 | - make_json_field('power_address', "IP address"), |
1061 | + choices=IPMI_DRIVER_CHOICES, default=IPMI_DRIVER.LAN_2_0, |
1062 | + required=True), |
1063 | + make_json_field('power_address', "IP address", required=True), |
1064 | make_json_field('power_user', "Power user"), |
1065 | make_json_field( |
1066 | 'power_pass', "Power password", field_type='password'), |
1067 | @@ -288,13 +292,13 @@ |
1068 | 'name': 'moonshot', |
1069 | 'description': 'HP Moonshot - iLO4 (IPMI)', |
1070 | 'fields': [ |
1071 | - make_json_field('power_address', "Power address"), |
1072 | + make_json_field('power_address', "Power address", required=True), |
1073 | make_json_field('power_user', "Power user"), |
1074 | make_json_field( |
1075 | 'power_pass', "Power password", field_type='password'), |
1076 | make_json_field( |
1077 | 'power_hwaddress', "Power hardware address", |
1078 | - scope=POWER_PARAMETER_SCOPE.NODE), |
1079 | + scope=POWER_PARAMETER_SCOPE.NODE, required=True), |
1080 | ], |
1081 | 'ip_extractor': make_ip_extractor('power_address'), |
1082 | }, |
1083 | @@ -303,14 +307,16 @@ |
1084 | 'description': 'SeaMicro 15000', |
1085 | 'fields': [ |
1086 | make_json_field( |
1087 | - 'system_id', "System ID", scope=POWER_PARAMETER_SCOPE.NODE), |
1088 | - make_json_field('power_address', "Power address"), |
1089 | + 'system_id', "System ID", scope=POWER_PARAMETER_SCOPE.NODE, |
1090 | + required=True), |
1091 | + make_json_field('power_address', "Power address", required=True), |
1092 | make_json_field('power_user', "Power user"), |
1093 | make_json_field( |
1094 | 'power_pass', "Power password", field_type='password'), |
1095 | make_json_field( |
1096 | 'power_control', "Power control type", field_type='choice', |
1097 | - choices=SM15K_POWER_CONTROL_CHOICES, default='ipmi'), |
1098 | + choices=SM15K_POWER_CONTROL_CHOICES, default='ipmi', |
1099 | + required=True), |
1100 | ], |
1101 | 'ip_extractor': make_ip_extractor('power_address'), |
1102 | }, |
1103 | @@ -320,7 +326,7 @@ |
1104 | 'fields': [ |
1105 | make_json_field( |
1106 | 'power_pass', "Power password", field_type='password'), |
1107 | - make_json_field('power_address', "Power address") |
1108 | + make_json_field('power_address', "Power address", required=True) |
1109 | ], |
1110 | 'ip_extractor': make_ip_extractor('power_address'), |
1111 | }, |
1112 | @@ -329,8 +335,9 @@ |
1113 | 'description': 'Digital Loggers, Inc. PDU', |
1114 | 'fields': [ |
1115 | make_json_field( |
1116 | - 'outlet_id', "Outlet ID", scope=POWER_PARAMETER_SCOPE.NODE), |
1117 | - make_json_field('power_address', "Power address"), |
1118 | + 'outlet_id', "Outlet ID", scope=POWER_PARAMETER_SCOPE.NODE, |
1119 | + required=True), |
1120 | + make_json_field('power_address', "Power address", required=True), |
1121 | make_json_field('power_user', "Power user"), |
1122 | make_json_field( |
1123 | 'power_pass', "Power password", field_type='password'), |
1124 | @@ -342,8 +349,9 @@ |
1125 | 'description': "Cisco UCS Manager", |
1126 | 'fields': [ |
1127 | make_json_field( |
1128 | - 'uuid', "Server UUID", scope=POWER_PARAMETER_SCOPE.NODE), |
1129 | - make_json_field('power_address', "URL for XML API"), |
1130 | + 'uuid', "Server UUID", scope=POWER_PARAMETER_SCOPE.NODE, |
1131 | + required=True), |
1132 | + make_json_field('power_address', "URL for XML API", required=True), |
1133 | make_json_field('power_user', "API user"), |
1134 | make_json_field( |
1135 | 'power_pass', "API password", field_type='password'), |
1136 | @@ -355,7 +363,8 @@ |
1137 | 'name': 'mscm', |
1138 | 'description': "HP Moonshot - iLO Chassis Manager", |
1139 | 'fields': [ |
1140 | - make_json_field('power_address', "IP for MSCM CLI API"), |
1141 | + make_json_field( |
1142 | + 'power_address', "IP for MSCM CLI API", required=True), |
1143 | make_json_field('power_user', "MSCM CLI API user"), |
1144 | make_json_field( |
1145 | 'power_pass', "MSCM CLI API password", field_type='password'), |
1146 | @@ -363,7 +372,7 @@ |
1147 | 'node_id', |
1148 | "Node ID - Must adhere to cXnY format " |
1149 | "(X=cartridge number, Y=node number).", |
1150 | - scope=POWER_PARAMETER_SCOPE.NODE), |
1151 | + scope=POWER_PARAMETER_SCOPE.NODE, required=True), |
1152 | ], |
1153 | 'ip_extractor': make_ip_extractor('power_address'), |
1154 | }, |
1155 | @@ -371,14 +380,14 @@ |
1156 | 'name': 'msftocs', |
1157 | 'description': "Microsoft OCS - Chassis Manager", |
1158 | 'fields': [ |
1159 | - make_json_field('power_address', "Power address"), |
1160 | + make_json_field('power_address', "Power address", required=True), |
1161 | make_json_field('power_port', "Power port"), |
1162 | make_json_field('power_user', "Power user"), |
1163 | make_json_field( |
1164 | 'power_pass', "Power password", field_type='password'), |
1165 | make_json_field( |
1166 | 'blade_id', "Blade ID (Typically 1-24)", |
1167 | - scope=POWER_PARAMETER_SCOPE.NODE), |
1168 | + scope=POWER_PARAMETER_SCOPE.NODE, required=True), |
1169 | ], |
1170 | 'ip_extractor': make_ip_extractor('power_address'), |
1171 | }, |
1172 | @@ -386,10 +395,10 @@ |
1173 | 'name': 'apc', |
1174 | 'description': "American Power Conversion (APC) PDU", |
1175 | 'fields': [ |
1176 | - make_json_field('power_address', "IP for APC PDU"), |
1177 | + make_json_field('power_address', "IP for APC PDU", required=True), |
1178 | make_json_field( |
1179 | 'node_outlet', "APC PDU node outlet number (1-16)", |
1180 | - scope=POWER_PARAMETER_SCOPE.NODE), |
1181 | + scope=POWER_PARAMETER_SCOPE.NODE, required=True), |
1182 | make_json_field( |
1183 | 'power_on_delay', "Power ON outlet delay (seconds)", |
1184 | default='5'), |
1185 | @@ -400,16 +409,16 @@ |
1186 | 'name': 'hmc', |
1187 | 'description': "IBM Hardware Management Console (HMC)", |
1188 | 'fields': [ |
1189 | - make_json_field('power_address', "IP for HMC"), |
1190 | + make_json_field('power_address', "IP for HMC", required=True), |
1191 | make_json_field('power_user', "HMC username"), |
1192 | make_json_field( |
1193 | 'power_pass', "HMC password", field_type='password'), |
1194 | make_json_field( |
1195 | 'server_name', "HMC Managed System server name", |
1196 | - scope=POWER_PARAMETER_SCOPE.NODE), |
1197 | + scope=POWER_PARAMETER_SCOPE.NODE, required=True), |
1198 | make_json_field( |
1199 | 'lpar', "HMC logical partition", |
1200 | - scope=POWER_PARAMETER_SCOPE.NODE), |
1201 | + scope=POWER_PARAMETER_SCOPE.NODE, required=True), |
1202 | ], |
1203 | 'ip_extractor': make_ip_extractor('power_address'), |
1204 | }, |
1205 | @@ -417,11 +426,13 @@ |
1206 | 'name': 'nova', |
1207 | 'description': 'OpenStack Nova', |
1208 | 'fields': [ |
1209 | - make_json_field('nova_id', "Host UUID"), |
1210 | - make_json_field('os_tenantname', "Tenant name"), |
1211 | - make_json_field('os_username', "Username"), |
1212 | - make_json_field('os_password', "Password", field_type='password'), |
1213 | - make_json_field('os_authurl', "Auth URL"), |
1214 | + make_json_field('nova_id', "Host UUID", required=True), |
1215 | + make_json_field('os_tenantname', "Tenant name", required=True), |
1216 | + make_json_field('os_username', "Username", required=True), |
1217 | + make_json_field( |
1218 | + 'os_password', "Password", field_type='password', |
1219 | + required=True), |
1220 | + make_json_field('os_authurl', "Auth URL", required=True), |
1221 | ], |
1222 | }, |
1223 | ] |
You can self-approve and immediately land backports, unless there was a merge conflict and you'd like to get another pair of eyes. I'm assuming that is the case here.