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