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