Merge ~bjornt/maas:bug-1772906-fix-pod-update-api into maas:master

Proposed by Björn Tillenius
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)
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

To post a comment you must log in.
Revision history for this message
Alberto Donato (ack) wrote :

+1

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Revision history for this message
MAAS Lander (maas-lander) wrote :
Revision history for this message
MAAS Lander (maas-lander) wrote :
Revision history for this message
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://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/2911/console
COMMIT: aa1df742fc57ac9acb2b9254284ce89fda6ef091

review: Needs Fixing
f1a1cdf... by Björn Tillenius

Fix test failure.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/api/tests/test_pods.py b/src/maasserver/api/tests/test_pods.py
2index 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), {
32diff --git a/src/maasserver/forms/pods.py b/src/maasserver/forms/pods.py
33index 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):
52diff --git a/src/maasserver/forms/tests/test_pods.py b/src/maasserver/forms/tests/test_pods.py
53index 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():
148diff --git a/src/maasserver/models/tests/test_bmc.py b/src/maasserver/models/tests/test_bmc.py
149index 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)
163diff --git a/src/maasserver/testing/factory.py b/src/maasserver/testing/factory.py
164index 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)
179diff --git a/src/maasserver/websockets/handlers/tests/test_pod.py b/src/maasserver/websockets/handlers/tests/test_pod.py
180index 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

Subscribers

People subscribed via source and target branches