Merge ~ltrager/maas:proxmox_papercuts_2.9 into maas:2.9
- Git
- lp:~ltrager/maas
- proxmox_papercuts_2.9
- Merge into 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) |
Related bugs: |
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
Description of the change
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 | diff --git a/src/provisioningserver/drivers/power/proxmox.py b/src/provisioningserver/drivers/power/proxmox.py | |||
2 | index 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 | 54 | field_type="password", | 54 | field_type="password", |
7 | 55 | ), | 55 | ), |
8 | 56 | make_setting_field( | 56 | make_setting_field( |
10 | 57 | "power_vm_name", "Node ID", scope=SETTING_SCOPE.NODE | 57 | "power_vm_name", "Node ID", scope=SETTING_SCOPE.NODE, required=True |
11 | 58 | ), | 58 | ), |
12 | 59 | make_setting_field( | 59 | make_setting_field( |
13 | 60 | "power_verify_ssl", | 60 | "power_verify_ssl", |
14 | @@ -157,7 +157,12 @@ class ProxmoxPowerDriver(WebhookPowerDriver): | |||
15 | 157 | 157 | ||
16 | 158 | def cb(response_data): | 158 | def cb(response_data): |
17 | 159 | parsed_data = json.loads(response_data) | 159 | parsed_data = json.loads(response_data) |
19 | 160 | for vm in parsed_data["data"]: | 160 | vms = parsed_data["data"] |
20 | 161 | if not vms: | ||
21 | 162 | raise PowerActionError( | ||
22 | 163 | "No VMs returned! Are permissions set correctly?" | ||
23 | 164 | ) | ||
24 | 165 | for vm in vms: | ||
25 | 161 | if power_vm_name in (str(vm.get("vmid")), vm.get("name")): | 166 | if power_vm_name in (str(vm.get("vmid")), vm.get("name")): |
26 | 162 | return vm | 167 | return vm |
27 | 163 | raise PowerActionError("Unable to find virtual machine") | 168 | raise PowerActionError("Unable to find virtual machine") |
28 | @@ -252,7 +257,6 @@ def probe_proxmox_and_enlist( | |||
29 | 252 | 257 | ||
30 | 253 | d = proxmox._login("", context) | 258 | d = proxmox._login("", context) |
31 | 254 | 259 | ||
32 | 255 | @asynchronous | ||
33 | 256 | @inlineCallbacks | 260 | @inlineCallbacks |
34 | 257 | def get_vms(extra_headers): | 261 | def get_vms(extra_headers): |
35 | 258 | vms = yield proxmox._webhook_request( | 262 | vms = yield proxmox._webhook_request( |
36 | @@ -265,11 +269,15 @@ def probe_proxmox_and_enlist( | |||
37 | 265 | 269 | ||
38 | 266 | d.addCallback(get_vms) | 270 | d.addCallback(get_vms) |
39 | 267 | 271 | ||
40 | 268 | @asynchronous | ||
41 | 269 | @inlineCallbacks | 272 | @inlineCallbacks |
42 | 270 | def process_vms(data): | 273 | def process_vms(data): |
43 | 271 | extra_headers, response_data = data | 274 | extra_headers, response_data = data |
45 | 272 | for vm in json.loads(response_data)["data"]: | 275 | vms = json.loads(response_data)["data"] |
46 | 276 | if not vms: | ||
47 | 277 | raise PowerActionError( | ||
48 | 278 | "No VMs returned! Are permissions set correctly?" | ||
49 | 279 | ) | ||
50 | 280 | for vm in vms: | ||
51 | 273 | if prefix_filter and not vm["name"].startswith(prefix_filter): | 281 | if prefix_filter and not vm["name"].startswith(prefix_filter): |
52 | 274 | continue | 282 | continue |
53 | 275 | # Proxmox doesn't have an easy way to get the MAC address, it | 283 | # Proxmox doesn't have an easy way to get the MAC address, it |
54 | diff --git a/src/provisioningserver/drivers/power/tests/test_proxmox.py b/src/provisioningserver/drivers/power/tests/test_proxmox.py | |||
55 | index 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 | 108 | }, | 108 | }, |
60 | 109 | extra_headers, | 109 | extra_headers, |
61 | 110 | ) | 110 | ) |
75 | 111 | self.assertThat( | 111 | self.mock_webhook_request.assert_called_once_with( |
76 | 112 | self.mock_webhook_request, | 112 | b"POST", |
77 | 113 | MockCalledOnceWith( | 113 | self.proxmox._get_url(context, "access/ticket"), |
78 | 114 | b"POST", | 114 | self.proxmox._make_auth_headers( |
79 | 115 | self.proxmox._get_url(context, "access/ticket"), | 115 | system_id, |
80 | 116 | self.proxmox._make_auth_headers( | 116 | {}, |
81 | 117 | system_id, | 117 | {b"Content-Type": [b"application/json; charset=utf-8"]}, |
69 | 118 | {}, | ||
70 | 119 | {b"Content-Type": [b"application/json; charset=utf-8"]}, | ||
71 | 120 | ), | ||
72 | 121 | False, | ||
73 | 122 | # unittest doesn't know how to compare FileBodyProducer | ||
74 | 123 | ANY, | ||
82 | 124 | ), | 118 | ), |
83 | 119 | False, | ||
84 | 120 | # unittest doesn't know how to compare FileBodyProducer | ||
85 | 121 | ANY, | ||
86 | 125 | ) | 122 | ) |
87 | 126 | 123 | ||
88 | 127 | @inlineCallbacks | 124 | @inlineCallbacks |
89 | @@ -197,16 +194,43 @@ class TestProxmoxPowerDriver(MAASTestCase): | |||
90 | 197 | ) | 194 | ) |
91 | 198 | 195 | ||
92 | 199 | self.assertEqual(vm, found_vm) | 196 | self.assertEqual(vm, found_vm) |
102 | 200 | self.assertThat( | 197 | self.mock_webhook_request.assert_called_once_with( |
103 | 201 | self.mock_webhook_request, | 198 | b"GET", |
104 | 202 | MockCalledOnceWith( | 199 | self.proxmox._get_url( |
105 | 203 | b"GET", | 200 | context, "cluster/resources", {"type": "vm"} |
97 | 204 | self.proxmox._get_url( | ||
98 | 205 | context, "cluster/resources", {"type": "vm"} | ||
99 | 206 | ), | ||
100 | 207 | self.proxmox._make_auth_headers(system_id, {}, extra_headers), | ||
101 | 208 | False, | ||
106 | 209 | ), | 201 | ), |
107 | 202 | self.proxmox._make_auth_headers(system_id, {}, extra_headers), | ||
108 | 203 | False, | ||
109 | 204 | ) | ||
110 | 205 | |||
111 | 206 | @inlineCallbacks | ||
112 | 207 | def test_find_vm_doesnt_find_any_vms(self): | ||
113 | 208 | system_id = factory.make_name("system_id") | ||
114 | 209 | context = { | ||
115 | 210 | "power_address": factory.make_name("power_address"), | ||
116 | 211 | "power_vm_name": factory.make_name("power_vm_name"), | ||
117 | 212 | } | ||
118 | 213 | extra_headers = { | ||
119 | 214 | factory.make_name("key").encode(): [ | ||
120 | 215 | factory.make_name("value").encode() | ||
121 | 216 | ] | ||
122 | 217 | for _ in range(3) | ||
123 | 218 | } | ||
124 | 219 | self.mock_webhook_request.return_value = succeed( | ||
125 | 220 | json.dumps({"data": []}) | ||
126 | 221 | ) | ||
127 | 222 | |||
128 | 223 | with ExpectedException( | ||
129 | 224 | PowerActionError, "No VMs returned! Are permissions set correctly?" | ||
130 | 225 | ): | ||
131 | 226 | yield self.proxmox._find_vm(system_id, context, extra_headers) | ||
132 | 227 | self.mock_webhook_request.assert_called_once_with( | ||
133 | 228 | b"GET", | ||
134 | 229 | self.proxmox._get_url( | ||
135 | 230 | context, "cluster/resources", {"type": "vm"} | ||
136 | 231 | ), | ||
137 | 232 | self.proxmox._make_auth_headers(system_id, {}, extra_headers), | ||
138 | 233 | False, | ||
139 | 210 | ) | 234 | ) |
140 | 211 | 235 | ||
141 | 212 | @inlineCallbacks | 236 | @inlineCallbacks |
142 | @@ -231,18 +255,17 @@ class TestProxmoxPowerDriver(MAASTestCase): | |||
143 | 231 | json.dumps({"data": [vm]}) | 255 | json.dumps({"data": [vm]}) |
144 | 232 | ) | 256 | ) |
145 | 233 | 257 | ||
147 | 234 | with ExpectedException(PowerActionError): | 258 | with ExpectedException( |
148 | 259 | PowerActionError, "Unable to find virtual machine" | ||
149 | 260 | ): | ||
150 | 235 | yield self.proxmox._find_vm(system_id, context, extra_headers) | 261 | yield self.proxmox._find_vm(system_id, context, extra_headers) |
160 | 236 | self.assertThat( | 262 | self.mock_webhook_request.assert_called_once_with( |
161 | 237 | self.mock_webhook_request, | 263 | b"GET", |
162 | 238 | MockCalledOnceWith( | 264 | self.proxmox._get_url( |
163 | 239 | b"GET", | 265 | context, "cluster/resources", {"type": "vm"} |
155 | 240 | self.proxmox._get_url( | ||
156 | 241 | context, "cluster/resources", {"type": "vm"} | ||
157 | 242 | ), | ||
158 | 243 | self.proxmox._make_auth_headers(system_id, {}, extra_headers), | ||
159 | 244 | False, | ||
164 | 245 | ), | 266 | ), |
165 | 267 | self.proxmox._make_auth_headers(system_id, {}, extra_headers), | ||
166 | 268 | False, | ||
167 | 246 | ) | 269 | ) |
168 | 247 | 270 | ||
169 | 248 | @inlineCallbacks | 271 | @inlineCallbacks |
170 | @@ -268,18 +291,15 @@ class TestProxmoxPowerDriver(MAASTestCase): | |||
171 | 268 | 291 | ||
172 | 269 | yield self.proxmox.power_on(system_id, context) | 292 | yield self.proxmox.power_on(system_id, context) |
173 | 270 | 293 | ||
185 | 271 | self.assertThat( | 294 | self.mock_webhook_request.assert_called_once_with( |
186 | 272 | self.mock_webhook_request, | 295 | b"POST", |
187 | 273 | MockCalledOnceWith( | 296 | self.proxmox._get_url( |
188 | 274 | b"POST", | 297 | context, |
189 | 275 | self.proxmox._get_url( | 298 | f"nodes/{vm['node']}/{vm['type']}/{vm['vmid']}/" |
190 | 276 | context, | 299 | "status/start", |
180 | 277 | f"nodes/{vm['node']}/{vm['type']}/{vm['vmid']}/" | ||
181 | 278 | "status/start", | ||
182 | 279 | ), | ||
183 | 280 | self.proxmox._make_auth_headers(system_id, {}, extra_headers), | ||
184 | 281 | False, | ||
191 | 282 | ), | 300 | ), |
192 | 301 | self.proxmox._make_auth_headers(system_id, {}, extra_headers), | ||
193 | 302 | False, | ||
194 | 283 | ) | 303 | ) |
195 | 284 | 304 | ||
196 | 285 | @inlineCallbacks | 305 | @inlineCallbacks |
197 | @@ -330,18 +350,14 @@ class TestProxmoxPowerDriver(MAASTestCase): | |||
198 | 330 | 350 | ||
199 | 331 | yield self.proxmox.power_off(system_id, context) | 351 | yield self.proxmox.power_off(system_id, context) |
200 | 332 | 352 | ||
212 | 333 | self.assertThat( | 353 | self.mock_webhook_request.assert_called_once_with( |
213 | 334 | self.mock_webhook_request, | 354 | b"POST", |
214 | 335 | MockCalledOnceWith( | 355 | self.proxmox._get_url( |
215 | 336 | b"POST", | 356 | context, |
216 | 337 | self.proxmox._get_url( | 357 | f"nodes/{vm['node']}/{vm['type']}/{vm['vmid']}/" "status/stop", |
206 | 338 | context, | ||
207 | 339 | f"nodes/{vm['node']}/{vm['type']}/{vm['vmid']}/" | ||
208 | 340 | "status/stop", | ||
209 | 341 | ), | ||
210 | 342 | self.proxmox._make_auth_headers(system_id, {}, extra_headers), | ||
211 | 343 | False, | ||
217 | 344 | ), | 358 | ), |
218 | 359 | self.proxmox._make_auth_headers(system_id, {}, extra_headers), | ||
219 | 360 | False, | ||
220 | 345 | ) | 361 | ) |
221 | 346 | 362 | ||
222 | 347 | @inlineCallbacks | 363 | @inlineCallbacks |
223 | @@ -573,6 +589,36 @@ class TestProxmoxProbeAndEnlist(MAASTestCase): | |||
224 | 573 | self.assertThat(self.mock_commission_node, MockNotCalled()) | 589 | self.assertThat(self.mock_commission_node, MockNotCalled()) |
225 | 574 | 590 | ||
226 | 575 | @inlineCallbacks | 591 | @inlineCallbacks |
227 | 592 | def test_probe_and_enlist_doesnt_find_any_vms(self): | ||
228 | 593 | user = factory.make_name("user") | ||
229 | 594 | hostname = factory.make_ipv4_address() | ||
230 | 595 | username = factory.make_name("username") | ||
231 | 596 | password = factory.make_name("password") | ||
232 | 597 | token_name = factory.make_name("token_name") | ||
233 | 598 | token_secret = factory.make_name("token_secret") | ||
234 | 599 | domain = factory.make_name("domain") | ||
235 | 600 | mock_webhook_request = self.patch( | ||
236 | 601 | proxmox_module.ProxmoxPowerDriver, "_webhook_request" | ||
237 | 602 | ) | ||
238 | 603 | mock_webhook_request.return_value = succeed(json.dumps({"data": []})) | ||
239 | 604 | |||
240 | 605 | with ExpectedException( | ||
241 | 606 | PowerActionError, "No VMs returned! Are permissions set correctly?" | ||
242 | 607 | ): | ||
243 | 608 | yield proxmox_module.probe_proxmox_and_enlist( | ||
244 | 609 | user, | ||
245 | 610 | hostname, | ||
246 | 611 | username, | ||
247 | 612 | password, | ||
248 | 613 | token_name, | ||
249 | 614 | token_secret, | ||
250 | 615 | False, | ||
251 | 616 | False, | ||
252 | 617 | domain, | ||
253 | 618 | None, | ||
254 | 619 | ) | ||
255 | 620 | |||
256 | 621 | @inlineCallbacks | ||
257 | 576 | def test_probe_and_enlist_filters(self): | 622 | def test_probe_and_enlist_filters(self): |
258 | 577 | user = factory.make_name("user") | 623 | user = factory.make_name("user") |
259 | 578 | hostname = factory.make_ipv4_address() | 624 | hostname = factory.make_ipv4_address() |
260 | diff --git a/src/provisioningserver/rpc/clusterservice.py b/src/provisioningserver/rpc/clusterservice.py | |||
261 | index 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 | 807 | ) | 807 | ) |
266 | 808 | d.addErrback(partial(catch_probe_and_enlist_error, "virsh")) | 808 | d.addErrback(partial(catch_probe_and_enlist_error, "virsh")) |
267 | 809 | elif chassis_type == "proxmox": | 809 | elif chassis_type == "proxmox": |
270 | 810 | d = deferToThread( | 810 | d = probe_proxmox_and_enlist( |
269 | 811 | probe_proxmox_and_enlist, | ||
271 | 812 | user, | 811 | user, |
272 | 813 | hostname, | 812 | hostname, |
273 | 814 | username, | 813 | username, |
274 | diff --git a/src/provisioningserver/rpc/tests/test_clusterservice.py b/src/provisioningserver/rpc/tests/test_clusterservice.py | |||
275 | index 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 | 3487 | ) | 3487 | ) |
280 | 3488 | 3488 | ||
281 | 3489 | def test_chassis_type_proxmox_calls_probe_proxmoxand_enlist(self): | 3489 | def test_chassis_type_proxmox_calls_probe_proxmoxand_enlist(self): |
284 | 3490 | mock_deferToThread = self.patch_autospec( | 3490 | mock_proxmox = self.patch_autospec( |
285 | 3491 | clusterservice, "deferToThread" | 3491 | clusterservice, "probe_proxmox_and_enlist" |
286 | 3492 | ) | 3492 | ) |
287 | 3493 | user = factory.make_name("user") | 3493 | user = factory.make_name("user") |
288 | 3494 | hostname = factory.make_hostname() | 3494 | hostname = factory.make_hostname() |
289 | @@ -3518,9 +3518,8 @@ class TestClusterProtocol_AddChassis(MAASTestCase): | |||
290 | 3518 | }, | 3518 | }, |
291 | 3519 | ) | 3519 | ) |
292 | 3520 | self.assertThat( | 3520 | self.assertThat( |
294 | 3521 | mock_deferToThread, | 3521 | mock_proxmox, |
295 | 3522 | MockCalledOnceWith( | 3522 | MockCalledOnceWith( |
296 | 3523 | clusterservice.probe_proxmox_and_enlist, | ||
297 | 3524 | user, | 3523 | user, |
298 | 3525 | hostname, | 3524 | hostname, |
299 | 3526 | username, | 3525 | username, |
300 | @@ -3537,10 +3536,10 @@ class TestClusterProtocol_AddChassis(MAASTestCase): | |||
301 | 3537 | def test_chassis_type_proxmox_logs_error_to_maaslog(self): | 3536 | def test_chassis_type_proxmox_logs_error_to_maaslog(self): |
302 | 3538 | fake_error = factory.make_name("error") | 3537 | fake_error = factory.make_name("error") |
303 | 3539 | self.patch(clusterservice, "maaslog") | 3538 | self.patch(clusterservice, "maaslog") |
306 | 3540 | mock_deferToThread = self.patch_autospec( | 3539 | mock_proxmox = self.patch_autospec( |
307 | 3541 | clusterservice, "deferToThread" | 3540 | clusterservice, "probe_proxmox_and_enlist" |
308 | 3542 | ) | 3541 | ) |
310 | 3543 | mock_deferToThread.return_value = fail(Exception(fake_error)) | 3542 | mock_proxmox.return_value = fail(Exception(fake_error)) |
311 | 3544 | user = factory.make_name("user") | 3543 | user = factory.make_name("user") |
312 | 3545 | hostname = factory.make_hostname() | 3544 | hostname = factory.make_hostname() |
313 | 3546 | username = factory.make_name("username") | 3545 | username = factory.make_name("username") |
Approved in https:/ /code.launchpad .net/~ltrager/ maas/+git/ maas/+merge/ 399245