Merge ~bjornt/maas:bug-1772906-fix-pod-update-api into maas:master
- Git
- lp:~bjornt/maas
- bug-1772906-fix-pod-update-api
- Merge into master
Status: | Merged |
---|---|
Approved by: | Björn Tillenius |
Approved revision: | f1a1cdfde86f33481b60e7ef2dd9b92cf37a4018 |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~bjornt/maas:bug-1772906-fix-pod-update-api |
Merge into: | maas:master |
Diff against target: |
343 lines (+64/-119) 6 files modified
src/maasserver/api/tests/test_pods.py (+20/-0) src/maasserver/forms/pods.py (+5/-1) src/maasserver/forms/tests/test_pods.py (+23/-12) src/maasserver/models/tests/test_bmc.py (+2/-2) src/maasserver/testing/factory.py (+4/-1) src/maasserver/websockets/handlers/tests/test_pod.py (+10/-103) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
MAAS Lander | Needs Fixing | ||
Alberto Donato (community) | Approve | ||
Review via email: mp+346735@code.launchpad.net |
Commit message
LP: #1772906 - Pods can't be updated via the API without passing power_address
Description of the change
MAAS Lander (maas-lander) wrote : | # |
LANDING
-b bug-1772906-fix-pod-update-api lp:~bjornt/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED BUILD
LOG: http://
MAAS Lander (maas-lander) wrote : | # |
LANDING
-b bug-1772906-fix-pod-update-api lp:~bjornt/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED BUILD
LOG: http://
MAAS Lander (maas-lander) wrote : | # |
LANDING
-b bug-1772906-fix-pod-update-api lp:~bjornt/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED BUILD
LOG: http://
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b bug-1772906-fix-pod-update-api lp:~bjornt/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: aa1df742fc57ac9
- f1a1cdf... by Björn Tillenius
-
Fix test failure.
Preview Diff
1 | diff --git a/src/maasserver/api/tests/test_pods.py b/src/maasserver/api/tests/test_pods.py |
2 | index fed3570..863df22 100644 |
3 | --- a/src/maasserver/api/tests/test_pods.py |
4 | +++ b/src/maasserver/api/tests/test_pods.py |
5 | @@ -276,6 +276,26 @@ class TestPodAPI(APITestCase.ForUser, PodMixin): |
6 | self.assertEqual(new_name, parsed_output['name']) |
7 | self.assertEqual(discovered_pod.cores, parsed_output['total']['cores']) |
8 | |
9 | + def test_PUT_update_minimal(self): |
10 | + self.become_admin() |
11 | + pod_info = self.make_pod_info() |
12 | + power_parameters = { |
13 | + 'power_address': pod_info['power_address'], |
14 | + 'power_pass': pod_info['power_pass'], |
15 | + } |
16 | + pod = factory.make_Pod( |
17 | + pod_type=pod_info['type'], parameters=power_parameters) |
18 | + new_name = factory.make_name('pool') |
19 | + self.fake_pod_discovery() |
20 | + response = self.client.put(get_pod_uri(pod), { |
21 | + 'name': new_name, |
22 | + }) |
23 | + self.assertEqual( |
24 | + http.client.OK, response.status_code, response.content) |
25 | + pod.refresh_from_db() |
26 | + self.assertEqual(new_name, pod.name) |
27 | + self.assertEqual(power_parameters, pod.power_parameters) |
28 | + |
29 | def test_refresh_requires_admin(self): |
30 | pod = factory.make_Pod() |
31 | response = self.client.post(get_pod_uri(pod), { |
32 | diff --git a/src/maasserver/forms/pods.py b/src/maasserver/forms/pods.py |
33 | index 654f5f6..89e3b2b 100644 |
34 | --- a/src/maasserver/forms/pods.py |
35 | +++ b/src/maasserver/forms/pods.py |
36 | @@ -165,10 +165,14 @@ class PodForm(MAASModelForm): |
37 | # selected type for the pod. |
38 | if len(self.errors) == 0: |
39 | driver_fields = get_driver_parameters_from_json( |
40 | - self.drivers_orig, None, scope=SETTING_SCOPE.BMC) |
41 | + self.drivers_orig, scope=SETTING_SCOPE.BMC) |
42 | self.param_fields = ( |
43 | driver_fields[self.cleaned_data['type']].field_dict) |
44 | self.fields.update(self.param_fields) |
45 | + if not self.is_new: |
46 | + for key, value in self.instance.power_parameters.items(): |
47 | + if key not in self.data: |
48 | + self.data[key] = value |
49 | super(PodForm, self)._clean_fields() |
50 | |
51 | def clean(self): |
52 | diff --git a/src/maasserver/forms/tests/test_pods.py b/src/maasserver/forms/tests/test_pods.py |
53 | index 64bfae4..a567f31 100644 |
54 | --- a/src/maasserver/forms/tests/test_pods.py |
55 | +++ b/src/maasserver/forms/tests/test_pods.py |
56 | @@ -85,7 +85,7 @@ def make_pod_with_hints(): |
57 | pod = factory.make_Pod( |
58 | architectures=architectures, cores=random.randint(8, 16), |
59 | memory=random.randint(4096, 8192), |
60 | - cpu_speed=random.randint(2000, 3000)) |
61 | + cpu_speed=random.randint(2000, 3000), parameters={}) |
62 | for _ in range(3): |
63 | pool = factory.make_PodStoragePool(pod) |
64 | pod.default_storage_pool = pool |
65 | @@ -369,18 +369,22 @@ class TestPodForm(MAASTransactionServerTestCase): |
66 | |
67 | def test_updates_existing_pod_minimal(self): |
68 | self.fake_pod_discovery() |
69 | - pod_info = self.make_pod_info() |
70 | zone = factory.make_Zone() |
71 | pool = factory.make_ResourcePool() |
72 | cpu_over_commit = random.randint(0, 10) |
73 | memory_over_commit = random.randint(0, 10) |
74 | + power_parameters = { |
75 | + 'power_address': 'qemu+ssh://1.2.3.4/system', |
76 | + 'power_pass': 'pass', |
77 | + } |
78 | orig_pod = factory.make_Pod( |
79 | - pod_type=pod_info['type'], zone=zone, default_pool=pool, |
80 | + pod_type='virsh', zone=zone, default_pool=pool, |
81 | cpu_over_commit_ratio=cpu_over_commit, |
82 | - memory_over_commit_ratio=memory_over_commit) |
83 | + memory_over_commit_ratio=memory_over_commit, |
84 | + parameters=power_parameters) |
85 | new_name = factory.make_name("pod") |
86 | - pod_info['name'] = new_name |
87 | - form = PodForm(data=pod_info, request=self.request, instance=orig_pod) |
88 | + form = PodForm( |
89 | + data={'name': new_name}, request=self.request, instance=orig_pod) |
90 | self.assertTrue(form.is_valid(), form._errors) |
91 | pod = form.save() |
92 | self.assertEqual(new_name, pod.name) |
93 | @@ -388,6 +392,8 @@ class TestPodForm(MAASTransactionServerTestCase): |
94 | self.assertEqual(pool, pod.default_pool) |
95 | self.assertEqual(cpu_over_commit, pod.cpu_over_commit_ratio) |
96 | self.assertEqual(memory_over_commit, pod.memory_over_commit_ratio) |
97 | + self.assertEqual(memory_over_commit, pod.memory_over_commit_ratio) |
98 | + self.assertEqual(power_parameters, pod.power_parameters) |
99 | |
100 | def test_updates_existing_pod(self): |
101 | discovered_pod, discovered_racks, failed_racks = ( |
102 | @@ -533,7 +539,10 @@ class TestPodForm(MAASTransactionServerTestCase): |
103 | self.fake_pod_discovery()) |
104 | zone = factory.make_Zone() |
105 | pod_info = self.make_pod_info() |
106 | - orig_pod = factory.make_Pod(zone=zone, pod_type=pod_info['type']) |
107 | + power_parameters = {'power_address': pod_info['power_address']} |
108 | + orig_pod = factory.make_Pod( |
109 | + zone=zone, pod_type=pod_info['type'], |
110 | + parameters=power_parameters) |
111 | form = PodForm(data=pod_info, request=self.request, instance=orig_pod) |
112 | pod = form.discover_and_sync_pod() |
113 | self.assertThat(pod, MatchesStructure( |
114 | @@ -546,8 +555,8 @@ class TestPodForm(MAASTransactionServerTestCase): |
115 | cpu_speed=Equals(discovered_pod.cpu_speed), |
116 | zone=Equals(zone), |
117 | power_type=Equals(pod_info['type']), |
118 | - power_parameters=Equals({}), |
119 | - ip_address=Is(None), |
120 | + power_parameters=Equals(power_parameters), |
121 | + ip_address=MatchesStructure(ip=Equals(pod_info['ip_address'])), |
122 | )) |
123 | routable_racks = [ |
124 | relation.rack_controller |
125 | @@ -571,8 +580,10 @@ class TestPodForm(MAASTransactionServerTestCase): |
126 | pods_module.discover_pod.return_value) |
127 | zone = yield deferToDatabase(factory.make_Zone) |
128 | pod_info = yield deferToDatabase(self.make_pod_info) |
129 | + power_parameters = {'power_address': pod_info['power_address']} |
130 | orig_pod = yield deferToDatabase( |
131 | - factory.make_Pod, zone=zone, pod_type=pod_info['type']) |
132 | + factory.make_Pod, zone=zone, pod_type=pod_info['type'], |
133 | + parameters=power_parameters) |
134 | form = yield deferToDatabase( |
135 | PodForm, data=pod_info, request=self.request, instance=orig_pod) |
136 | pod = yield form.discover_and_sync_pod() |
137 | @@ -586,8 +597,8 @@ class TestPodForm(MAASTransactionServerTestCase): |
138 | cpu_speed=Equals(discovered_pod.cpu_speed), |
139 | zone=Equals(zone), |
140 | power_type=Equals(pod_info['type']), |
141 | - power_parameters=Equals({}), |
142 | - ip_address=Is(None), |
143 | + power_parameters=Equals(power_parameters), |
144 | + ip_address=MatchesStructure(ip=Equals(pod_info['ip_address'])), |
145 | )) |
146 | |
147 | def validate_rack_routes(): |
148 | diff --git a/src/maasserver/models/tests/test_bmc.py b/src/maasserver/models/tests/test_bmc.py |
149 | index 9bc3cfb..84db146 100644 |
150 | --- a/src/maasserver/models/tests/test_bmc.py |
151 | +++ b/src/maasserver/models/tests/test_bmc.py |
152 | @@ -1509,8 +1509,8 @@ class TestPodDelete(MAASTransactionServerTestCase): |
153 | yield pod.async_delete() |
154 | self.assertThat( |
155 | client, MockCalledOnceWith( |
156 | - DecomposeMachine, type=pod.power_type, context={}, |
157 | - pod_id=pod.id, name=pod.name)) |
158 | + DecomposeMachine, type=pod.power_type, |
159 | + context=pod.power_parameters, pod_id=pod.id, name=pod.name)) |
160 | decomposable_machine = yield deferToDatabase( |
161 | reload_object, decomposable_machine) |
162 | delete_machine = yield deferToDatabase(reload_object, delete_machine) |
163 | diff --git a/src/maasserver/testing/factory.py b/src/maasserver/testing/factory.py |
164 | index 0204588..e5bd2b7 100644 |
165 | --- a/src/maasserver/testing/factory.py |
166 | +++ b/src/maasserver/testing/factory.py |
167 | @@ -610,7 +610,10 @@ class Factory(maastesting.factory.Factory): |
168 | if pod_type is None: |
169 | pod_type = 'virsh' |
170 | if parameters is None: |
171 | - parameters = {} |
172 | + parameters = { |
173 | + 'power_address': 'qemu+ssh://{}/system'.format( |
174 | + self.make_ip_address()) |
175 | + } |
176 | pod = Pod( |
177 | power_type=pod_type, power_parameters=parameters, |
178 | ip_address=ip_address, **kwargs) |
179 | diff --git a/src/maasserver/websockets/handlers/tests/test_pod.py b/src/maasserver/websockets/handlers/tests/test_pod.py |
180 | index 7539a5a..d3a4621 100644 |
181 | --- a/src/maasserver/websockets/handlers/tests/test_pod.py |
182 | +++ b/src/maasserver/websockets/handlers/tests/test_pod.py |
183 | @@ -9,14 +9,12 @@ import random |
184 | from unittest.mock import MagicMock |
185 | |
186 | from crochet import wait_for |
187 | -from maasserver.enum import NODE_TYPE |
188 | from maasserver.forms import pods |
189 | from maasserver.forms.pods import PodForm |
190 | from maasserver.testing.factory import factory |
191 | from maasserver.testing.testcase import MAASTransactionServerTestCase |
192 | from maasserver.utils.orm import reload_object |
193 | from maasserver.utils.threads import deferToDatabase |
194 | -from maasserver.websockets.base import dehydrate_datetime |
195 | from maasserver.websockets.handlers.pod import ( |
196 | ComposeMachineForm, |
197 | PodHandler, |
198 | @@ -113,116 +111,22 @@ class TestPodHandler(MAASTransactionServerTestCase): |
199 | cpu_speed=random.randint(1000, 3000), local_storage=0) |
200 | return composed_machine, pod_hints |
201 | |
202 | - def dehydrate_storage_pool(self, pool): |
203 | - used = pool.get_used_storage() |
204 | - return { |
205 | - 'id': pool.pool_id, |
206 | - 'name': pool.name, |
207 | - 'type': pool.pool_type, |
208 | - 'path': pool.path, |
209 | - 'total': pool.storage, |
210 | - 'used': used, |
211 | - 'available': pool.storage - used, |
212 | - } |
213 | - |
214 | - def dehydrate_pod(self, pod, admin=True, for_list=True): |
215 | - data = { |
216 | - "id": pod.id, |
217 | - "name": pod.name, |
218 | - "cpu_speed": pod.cpu_speed, |
219 | - "type": pod.power_type, |
220 | - "ip_address": pod.ip_address, |
221 | - "updated": dehydrate_datetime(pod.updated), |
222 | - "created": dehydrate_datetime(pod.created), |
223 | - "composed_machines_count": pod.node_set.filter( |
224 | - node_type=NODE_TYPE.MACHINE).count(), |
225 | - "total": { |
226 | - "cores": pod.cores, |
227 | - "memory": pod.memory, |
228 | - 'memory_gb': '%.1f' % (pod.memory / 1024.0), |
229 | - "local_storage": pod.local_storage, |
230 | - 'local_storage_gb': '%.1f' % (pod.local_storage / (1024 ** 3)), |
231 | - }, |
232 | - "used": { |
233 | - "cores": pod.get_used_cores(), |
234 | - "memory": pod.get_used_memory(), |
235 | - 'memory_gb': '%.1f' % (pod.get_used_memory() / 1024.0), |
236 | - "local_storage": pod.get_used_local_storage(), |
237 | - 'local_storage_gb': '%.1f' % ( |
238 | - pod.get_used_local_storage() / (1024 ** 3)), |
239 | - }, |
240 | - "available": { |
241 | - "cores": pod.cores - pod.get_used_cores(), |
242 | - "memory": pod.memory - pod.get_used_memory(), |
243 | - 'memory_gb': '%.1f' % ( |
244 | - (pod.memory - pod.get_used_memory()) / 1024.0), |
245 | - "local_storage": ( |
246 | - pod.local_storage - pod.get_used_local_storage()), |
247 | - 'local_storage_gb': '%.1f' % ( |
248 | - (pod.local_storage - pod.get_used_local_storage()) / ( |
249 | - (1024 ** 3))), |
250 | - }, |
251 | - "default_pool": pod.default_pool.id, |
252 | - "capabilities": pod.capabilities, |
253 | - "architectures": pod.architectures, |
254 | - "hints": { |
255 | - 'cores': pod.hints.cores, |
256 | - 'cpu_speed': pod.hints.cpu_speed, |
257 | - 'memory': pod.hints.memory, |
258 | - 'memory_gb': '%.1f' % (pod.hints.memory / 1024.0), |
259 | - 'local_storage': pod.hints.local_storage, |
260 | - 'local_storage_gb': '%.1f' % ( |
261 | - pod.hints.local_storage / (1024 ** 3)), |
262 | - 'local_disks': pod.hints.local_disks, |
263 | - 'iscsi_storage': pod.hints.iscsi_storage, |
264 | - 'iscsi_storage_gb': '%.1f' % ( |
265 | - pod.hints.iscsi_storage / (1024 ** 3)), |
266 | - }, |
267 | - "tags": pod.tags, |
268 | - "zone": pod.zone.id, |
269 | - "cpu_over_commit_ratio": pod.cpu_over_commit_ratio, |
270 | - "memory_over_commit_ratio": pod.memory_over_commit_ratio |
271 | - } |
272 | - if Capabilities.FIXED_LOCAL_STORAGE in pod.capabilities: |
273 | - data['total']['local_disks'] = pod.local_disks |
274 | - data['used']['local_disks'] = pod.get_used_local_disks() |
275 | - data['available']['local_disks'] = ( |
276 | - pod.local_disks - pod.get_used_local_disks()) |
277 | - if Capabilities.ISCSI_STORAGE in pod.capabilities: |
278 | - data['total']['iscsi_storage'] = pod.iscsi_storage |
279 | - data['total']['iscsi_storage_gb'] = '%.1f' % ( |
280 | - pod.iscsi_storage / (1024 ** 3)) |
281 | - data['used']['iscsi_storage'] = pod.get_used_iscsi_storage() |
282 | - data['used']['iscsi_storage_gb'] = '%.1f' % ( |
283 | - pod.get_used_iscsi_storage() / (1024 ** 3)) |
284 | - data['available']['iscsi_storage'] = ( |
285 | - pod.iscsi_storage - pod.get_used_iscsi_storage()) |
286 | - data['available']['iscsi_storage_gb'] = '%.1f' % ( |
287 | - (pod.iscsi_storage - pod.get_used_iscsi_storage()) / ( |
288 | - 1024 ** 3)) |
289 | - if admin: |
290 | - data.update(pod.power_parameters) |
291 | - if not for_list and pod.storage_pools.all(): |
292 | - data["storage_pools"] = [ |
293 | - self.dehydrate_storage_pool(pool) |
294 | - for pool in pod.storage_pools.all() |
295 | - ] |
296 | - data["default_storage_pool"] = pod.default_storage_pool.pool_id |
297 | - return data |
298 | - |
299 | def test_get(self): |
300 | admin = factory.make_admin() |
301 | handler = PodHandler(admin, {}) |
302 | pod = self.make_pod_with_hints() |
303 | - expected_data = self.dehydrate_pod(pod, for_list=False) |
304 | + expected_data = handler.full_dehydrate(pod) |
305 | result = handler.get({"id": pod.id}) |
306 | + self.assertItemsEqual(expected_data.keys(), result.keys()) |
307 | + for key in expected_data: |
308 | + self.assertEqual(expected_data[key], result[key], key) |
309 | self.assertThat(result, Equals(expected_data)) |
310 | |
311 | def test_get_as_standard_user(self): |
312 | user = factory.make_User() |
313 | handler = PodHandler(user, {}) |
314 | pod = self.make_pod_with_hints() |
315 | - expected_data = self.dehydrate_pod(pod, admin=False, for_list=False) |
316 | + expected_data = handler.full_dehydrate(pod) |
317 | result = handler.get({"id": pod.id}) |
318 | self.assertThat(result, Equals(expected_data)) |
319 | |
320 | @@ -230,7 +134,7 @@ class TestPodHandler(MAASTransactionServerTestCase): |
321 | admin = factory.make_admin() |
322 | handler = PodHandler(admin, {}) |
323 | pod = self.make_pod_with_hints() |
324 | - expected_data = [self.dehydrate_pod(pod, for_list=True)] |
325 | + expected_data = [handler.full_dehydrate(pod, for_list=True)] |
326 | result = handler.list({"id": pod.id}) |
327 | self.assertThat(result, Equals(expected_data)) |
328 | |
329 | @@ -244,10 +148,13 @@ class TestPodHandler(MAASTransactionServerTestCase): |
330 | PodForm, 'discover_and_sync_pod') |
331 | mock_discover_and_sync_pod.return_value = succeed(pod) |
332 | expected_data = yield deferToDatabase( |
333 | - self.dehydrate_pod, pod, for_list=False) |
334 | + handler.full_dehydrate, pod, for_list=False) |
335 | observed_data = yield handler.refresh({"id": pod.id}) |
336 | self.assertThat( |
337 | mock_discover_and_sync_pod, MockCalledOnceWith()) |
338 | + self.assertItemsEqual(expected_data.keys(), observed_data.keys()) |
339 | + for key in expected_data: |
340 | + self.assertEqual(expected_data[key], observed_data[key], key) |
341 | self.assertEqual(expected_data, observed_data) |
342 | |
343 | @wait_for_reactor |
+1