Merge ~ltrager/maas:proxmox_papercuts_2.9 into maas:2.9

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: f66b4a8bf2b92fc7178188dd471d0759c698b8b3
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ltrager/maas:proxmox_papercuts_2.9
Merge into: maas:2.9
Diff against target: 313 lines (+120/-68)
4 files modified
src/provisioningserver/drivers/power/proxmox.py (+13/-5)
src/provisioningserver/drivers/power/tests/test_proxmox.py (+100/-54)
src/provisioningserver/rpc/clusterservice.py (+1/-2)
src/provisioningserver/rpc/tests/test_clusterservice.py (+6/-7)
Reviewer Review Type Date Requested Status
Lee Trager (community) Approve
Review via email: mp+399318@code.launchpad.net

Commit message

Fix various Proxmox papercuts

* power_vm_name is required
* Fix warnings that a synchronous thread was returning a Deferred() object
* Add log message when no VMs are returned. Proxmox forces you to define
  permissions for a token, if you don't nothing gets returned. Users
  thought this was a MAAS bug.

Backport of c19697f

To post a comment you must log in.
Revision history for this message
Lee Trager (ltrager) wrote :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/provisioningserver/drivers/power/proxmox.py b/src/provisioningserver/drivers/power/proxmox.py
2index cf7a8db..cc0b720 100644
3--- a/src/provisioningserver/drivers/power/proxmox.py
4+++ b/src/provisioningserver/drivers/power/proxmox.py
5@@ -54,7 +54,7 @@ class ProxmoxPowerDriver(WebhookPowerDriver):
6 field_type="password",
7 ),
8 make_setting_field(
9- "power_vm_name", "Node ID", scope=SETTING_SCOPE.NODE
10+ "power_vm_name", "Node ID", scope=SETTING_SCOPE.NODE, required=True
11 ),
12 make_setting_field(
13 "power_verify_ssl",
14@@ -157,7 +157,12 @@ class ProxmoxPowerDriver(WebhookPowerDriver):
15
16 def cb(response_data):
17 parsed_data = json.loads(response_data)
18- for vm in parsed_data["data"]:
19+ vms = parsed_data["data"]
20+ if not vms:
21+ raise PowerActionError(
22+ "No VMs returned! Are permissions set correctly?"
23+ )
24+ for vm in vms:
25 if power_vm_name in (str(vm.get("vmid")), vm.get("name")):
26 return vm
27 raise PowerActionError("Unable to find virtual machine")
28@@ -252,7 +257,6 @@ def probe_proxmox_and_enlist(
29
30 d = proxmox._login("", context)
31
32- @asynchronous
33 @inlineCallbacks
34 def get_vms(extra_headers):
35 vms = yield proxmox._webhook_request(
36@@ -265,11 +269,15 @@ def probe_proxmox_and_enlist(
37
38 d.addCallback(get_vms)
39
40- @asynchronous
41 @inlineCallbacks
42 def process_vms(data):
43 extra_headers, response_data = data
44- for vm in json.loads(response_data)["data"]:
45+ vms = json.loads(response_data)["data"]
46+ if not vms:
47+ raise PowerActionError(
48+ "No VMs returned! Are permissions set correctly?"
49+ )
50+ for vm in vms:
51 if prefix_filter and not vm["name"].startswith(prefix_filter):
52 continue
53 # Proxmox doesn't have an easy way to get the MAC address, it
54diff --git a/src/provisioningserver/drivers/power/tests/test_proxmox.py b/src/provisioningserver/drivers/power/tests/test_proxmox.py
55index 6292eae..2903e8c 100644
56--- a/src/provisioningserver/drivers/power/tests/test_proxmox.py
57+++ b/src/provisioningserver/drivers/power/tests/test_proxmox.py
58@@ -108,20 +108,17 @@ class TestProxmoxPowerDriver(MAASTestCase):
59 },
60 extra_headers,
61 )
62- self.assertThat(
63- self.mock_webhook_request,
64- MockCalledOnceWith(
65- b"POST",
66- self.proxmox._get_url(context, "access/ticket"),
67- self.proxmox._make_auth_headers(
68- system_id,
69- {},
70- {b"Content-Type": [b"application/json; charset=utf-8"]},
71- ),
72- False,
73- # unittest doesn't know how to compare FileBodyProducer
74- ANY,
75+ self.mock_webhook_request.assert_called_once_with(
76+ b"POST",
77+ self.proxmox._get_url(context, "access/ticket"),
78+ self.proxmox._make_auth_headers(
79+ system_id,
80+ {},
81+ {b"Content-Type": [b"application/json; charset=utf-8"]},
82 ),
83+ False,
84+ # unittest doesn't know how to compare FileBodyProducer
85+ ANY,
86 )
87
88 @inlineCallbacks
89@@ -197,16 +194,43 @@ class TestProxmoxPowerDriver(MAASTestCase):
90 )
91
92 self.assertEqual(vm, found_vm)
93- self.assertThat(
94- self.mock_webhook_request,
95- MockCalledOnceWith(
96- b"GET",
97- self.proxmox._get_url(
98- context, "cluster/resources", {"type": "vm"}
99- ),
100- self.proxmox._make_auth_headers(system_id, {}, extra_headers),
101- False,
102+ self.mock_webhook_request.assert_called_once_with(
103+ b"GET",
104+ self.proxmox._get_url(
105+ context, "cluster/resources", {"type": "vm"}
106 ),
107+ self.proxmox._make_auth_headers(system_id, {}, extra_headers),
108+ False,
109+ )
110+
111+ @inlineCallbacks
112+ def test_find_vm_doesnt_find_any_vms(self):
113+ system_id = factory.make_name("system_id")
114+ context = {
115+ "power_address": factory.make_name("power_address"),
116+ "power_vm_name": factory.make_name("power_vm_name"),
117+ }
118+ extra_headers = {
119+ factory.make_name("key").encode(): [
120+ factory.make_name("value").encode()
121+ ]
122+ for _ in range(3)
123+ }
124+ self.mock_webhook_request.return_value = succeed(
125+ json.dumps({"data": []})
126+ )
127+
128+ with ExpectedException(
129+ PowerActionError, "No VMs returned! Are permissions set correctly?"
130+ ):
131+ yield self.proxmox._find_vm(system_id, context, extra_headers)
132+ self.mock_webhook_request.assert_called_once_with(
133+ b"GET",
134+ self.proxmox._get_url(
135+ context, "cluster/resources", {"type": "vm"}
136+ ),
137+ self.proxmox._make_auth_headers(system_id, {}, extra_headers),
138+ False,
139 )
140
141 @inlineCallbacks
142@@ -231,18 +255,17 @@ class TestProxmoxPowerDriver(MAASTestCase):
143 json.dumps({"data": [vm]})
144 )
145
146- with ExpectedException(PowerActionError):
147+ with ExpectedException(
148+ PowerActionError, "Unable to find virtual machine"
149+ ):
150 yield self.proxmox._find_vm(system_id, context, extra_headers)
151- self.assertThat(
152- self.mock_webhook_request,
153- MockCalledOnceWith(
154- b"GET",
155- self.proxmox._get_url(
156- context, "cluster/resources", {"type": "vm"}
157- ),
158- self.proxmox._make_auth_headers(system_id, {}, extra_headers),
159- False,
160+ self.mock_webhook_request.assert_called_once_with(
161+ b"GET",
162+ self.proxmox._get_url(
163+ context, "cluster/resources", {"type": "vm"}
164 ),
165+ self.proxmox._make_auth_headers(system_id, {}, extra_headers),
166+ False,
167 )
168
169 @inlineCallbacks
170@@ -268,18 +291,15 @@ class TestProxmoxPowerDriver(MAASTestCase):
171
172 yield self.proxmox.power_on(system_id, context)
173
174- self.assertThat(
175- self.mock_webhook_request,
176- MockCalledOnceWith(
177- b"POST",
178- self.proxmox._get_url(
179- context,
180- f"nodes/{vm['node']}/{vm['type']}/{vm['vmid']}/"
181- "status/start",
182- ),
183- self.proxmox._make_auth_headers(system_id, {}, extra_headers),
184- False,
185+ self.mock_webhook_request.assert_called_once_with(
186+ b"POST",
187+ self.proxmox._get_url(
188+ context,
189+ f"nodes/{vm['node']}/{vm['type']}/{vm['vmid']}/"
190+ "status/start",
191 ),
192+ self.proxmox._make_auth_headers(system_id, {}, extra_headers),
193+ False,
194 )
195
196 @inlineCallbacks
197@@ -330,18 +350,14 @@ class TestProxmoxPowerDriver(MAASTestCase):
198
199 yield self.proxmox.power_off(system_id, context)
200
201- self.assertThat(
202- self.mock_webhook_request,
203- MockCalledOnceWith(
204- b"POST",
205- self.proxmox._get_url(
206- context,
207- f"nodes/{vm['node']}/{vm['type']}/{vm['vmid']}/"
208- "status/stop",
209- ),
210- self.proxmox._make_auth_headers(system_id, {}, extra_headers),
211- False,
212+ self.mock_webhook_request.assert_called_once_with(
213+ b"POST",
214+ self.proxmox._get_url(
215+ context,
216+ f"nodes/{vm['node']}/{vm['type']}/{vm['vmid']}/" "status/stop",
217 ),
218+ self.proxmox._make_auth_headers(system_id, {}, extra_headers),
219+ False,
220 )
221
222 @inlineCallbacks
223@@ -573,6 +589,36 @@ class TestProxmoxProbeAndEnlist(MAASTestCase):
224 self.assertThat(self.mock_commission_node, MockNotCalled())
225
226 @inlineCallbacks
227+ def test_probe_and_enlist_doesnt_find_any_vms(self):
228+ user = factory.make_name("user")
229+ hostname = factory.make_ipv4_address()
230+ username = factory.make_name("username")
231+ password = factory.make_name("password")
232+ token_name = factory.make_name("token_name")
233+ token_secret = factory.make_name("token_secret")
234+ domain = factory.make_name("domain")
235+ mock_webhook_request = self.patch(
236+ proxmox_module.ProxmoxPowerDriver, "_webhook_request"
237+ )
238+ mock_webhook_request.return_value = succeed(json.dumps({"data": []}))
239+
240+ with ExpectedException(
241+ PowerActionError, "No VMs returned! Are permissions set correctly?"
242+ ):
243+ yield proxmox_module.probe_proxmox_and_enlist(
244+ user,
245+ hostname,
246+ username,
247+ password,
248+ token_name,
249+ token_secret,
250+ False,
251+ False,
252+ domain,
253+ None,
254+ )
255+
256+ @inlineCallbacks
257 def test_probe_and_enlist_filters(self):
258 user = factory.make_name("user")
259 hostname = factory.make_ipv4_address()
260diff --git a/src/provisioningserver/rpc/clusterservice.py b/src/provisioningserver/rpc/clusterservice.py
261index d8b269d..279a804 100644
262--- a/src/provisioningserver/rpc/clusterservice.py
263+++ b/src/provisioningserver/rpc/clusterservice.py
264@@ -807,8 +807,7 @@ class Cluster(RPCProtocol):
265 )
266 d.addErrback(partial(catch_probe_and_enlist_error, "virsh"))
267 elif chassis_type == "proxmox":
268- d = deferToThread(
269- probe_proxmox_and_enlist,
270+ d = probe_proxmox_and_enlist(
271 user,
272 hostname,
273 username,
274diff --git a/src/provisioningserver/rpc/tests/test_clusterservice.py b/src/provisioningserver/rpc/tests/test_clusterservice.py
275index 4485515..3512523 100644
276--- a/src/provisioningserver/rpc/tests/test_clusterservice.py
277+++ b/src/provisioningserver/rpc/tests/test_clusterservice.py
278@@ -3487,8 +3487,8 @@ class TestClusterProtocol_AddChassis(MAASTestCase):
279 )
280
281 def test_chassis_type_proxmox_calls_probe_proxmoxand_enlist(self):
282- mock_deferToThread = self.patch_autospec(
283- clusterservice, "deferToThread"
284+ mock_proxmox = self.patch_autospec(
285+ clusterservice, "probe_proxmox_and_enlist"
286 )
287 user = factory.make_name("user")
288 hostname = factory.make_hostname()
289@@ -3518,9 +3518,8 @@ class TestClusterProtocol_AddChassis(MAASTestCase):
290 },
291 )
292 self.assertThat(
293- mock_deferToThread,
294+ mock_proxmox,
295 MockCalledOnceWith(
296- clusterservice.probe_proxmox_and_enlist,
297 user,
298 hostname,
299 username,
300@@ -3537,10 +3536,10 @@ class TestClusterProtocol_AddChassis(MAASTestCase):
301 def test_chassis_type_proxmox_logs_error_to_maaslog(self):
302 fake_error = factory.make_name("error")
303 self.patch(clusterservice, "maaslog")
304- mock_deferToThread = self.patch_autospec(
305- clusterservice, "deferToThread"
306+ mock_proxmox = self.patch_autospec(
307+ clusterservice, "probe_proxmox_and_enlist"
308 )
309- mock_deferToThread.return_value = fail(Exception(fake_error))
310+ mock_proxmox.return_value = fail(Exception(fake_error))
311 user = factory.make_name("user")
312 hostname = factory.make_hostname()
313 username = factory.make_name("username")

Subscribers

People subscribed via source and target branches