Merge ~ack/maas:1922569-discover-projects-rpc-error into maas:master

Proposed by Alberto Donato
Status: Merged
Approved by: Alberto Donato
Approved revision: e9d1d5e52c1c537c7708a43783d8b71d15c0b844
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ack/maas:1922569-discover-projects-rpc-error
Merge into: maas:master
Diff against target: 203 lines (+59/-51)
4 files modified
src/provisioningserver/drivers/pod/lxd.py (+5/-2)
src/provisioningserver/drivers/pod/tests/test_lxd.py (+15/-0)
src/provisioningserver/rpc/pods.py (+30/-49)
src/provisioningserver/rpc/tests/test_pods.py (+9/-0)
Reviewer Review Type Date Requested Status
Adam Collard (community) Approve
MAAS Lander Approve
Review via email: mp+400695@code.launchpad.net

Commit message

LP: #1922569 - wrap errors from discover projects call in RPC handler

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b 1922569-discover-projects-rpc-error lp:~ack/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/9691/console
COMMIT: e9d1d5e52c1c537c7708a43783d8b71d15c0b844

review: Needs Fixing
Revision history for this message
Alberto Donato (ack) wrote :

jenkins: !test

Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b 1922569-discover-projects-rpc-error lp:~ack/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: e9d1d5e52c1c537c7708a43783d8b71d15c0b844

review: Approve
Revision history for this message
Adam Collard (adam-collard) :
review: Approve

There was an error fetching revisions from git servers. Please try again in a few minutes. If the problem persists, contact Launchpad support.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/provisioningserver/drivers/pod/lxd.py b/src/provisioningserver/drivers/pod/lxd.py
2index 3683fe2..4095e85 100644
3--- a/src/provisioningserver/drivers/pod/lxd.py
4+++ b/src/provisioningserver/drivers/pod/lxd.py
5@@ -11,7 +11,7 @@ from urllib.parse import urlparse
6 import uuid
7
8 from pylxd import Client
9-from pylxd.exceptions import ClientConnectionFailed, NotFound
10+from pylxd.exceptions import ClientConnectionFailed, LXDAPIException, NotFound
11 import urllib3
12
13 from provisioningserver.drivers import (
14@@ -757,7 +757,10 @@ class LXDPodDriver(PodDriver):
15 if not client.trusted:
16 password = context.get("password")
17 if password:
18- client.authenticate(password)
19+ try:
20+ client.authenticate(password)
21+ except LXDAPIException as e:
22+ raise LXDPodError(f"Authentication failed: {e}")
23 else:
24 raise LXDPodError(
25 f"Pod {pod_id}: Certificate is not trusted and no password was given."
26diff --git a/src/provisioningserver/drivers/pod/tests/test_lxd.py b/src/provisioningserver/drivers/pod/tests/test_lxd.py
27index 0976151..0b41f13 100644
28--- a/src/provisioningserver/drivers/pod/tests/test_lxd.py
29+++ b/src/provisioningserver/drivers/pod/tests/test_lxd.py
30@@ -5,6 +5,7 @@ from os.path import join
31 import random
32 from unittest.mock import ANY, Mock, PropertyMock, sentinel
33
34+from pylxd.exceptions import LXDAPIException
35 from testtools.matchers import Equals, IsInstance, MatchesAll, MatchesStructure
36 from testtools.testcase import ExpectedException
37 from twisted.internet.defer import inlineCallbacks
38@@ -237,6 +238,20 @@ class TestLXDPodDriver(MAASTestCase):
39 with ExpectedException(lxd_module.LXDPodError, error_msg):
40 driver._get_client(pod_id, context)
41
42+ def test_get_client_raises_error_when_authenticate_fails(self):
43+ context = self.make_parameters_context()
44+ pod_id = factory.make_name("pod_id")
45+ Client = self.patch(lxd_module, "Client")
46+ client = Client.return_value
47+ client.trusted = False
48+ mock_response = Mock(status_code=403)
49+ mock_response.json.return_value = {"error": "auth failed"}
50+ client.authenticate.side_effect = LXDAPIException(mock_response)
51+ driver = lxd_module.LXDPodDriver()
52+ error_msg = "Authentication failed: auth failed"
53+ with ExpectedException(lxd_module.LXDPodError, error_msg):
54+ driver._get_client(pod_id, context)
55+
56 def test_get_machine(self):
57 context = self.make_parameters_context()
58 driver = lxd_module.LXDPodDriver()
59diff --git a/src/provisioningserver/rpc/pods.py b/src/provisioningserver/rpc/pods.py
60index bbc422e..6ac985c 100644
61--- a/src/provisioningserver/rpc/pods.py
62+++ b/src/provisioningserver/rpc/pods.py
63@@ -52,6 +52,9 @@ def discover_pod_projects(pod_type, context):
64 }
65
66 d.addCallback(convert)
67+ d.addErrback(
68+ convert_errors, log_message="Failed to discover VM host projects."
69+ )
70 return d
71
72
73@@ -86,18 +89,8 @@ def discover_pod(pod_type, context, pod_id=None, name=None):
74 else:
75 return {"pod": result}
76
77- def catch_all(failure):
78- """Convert all failures into `PodActionFail` unless already a
79- `PodActionFail` or `NotImplementedError`."""
80- # Log locally to help debugging.
81- log.err(failure, "Failed to discover pod.")
82- if failure.check(NotImplementedError, PodActionFail):
83- return failure
84- else:
85- raise PodActionFail(get_error_message(failure.value))
86-
87 d.addCallback(convert)
88- d.addErrback(catch_all)
89+ d.addErrback(convert_errors, log_message="Failed to discover VM host.")
90 return d
91
92
93@@ -141,23 +134,12 @@ def compose_machine(pod_type, context, request, pod_id, name):
94 "invalid result." % pod_type
95 )
96
97- def catch_all(failure):
98- """Convert all failures into `PodActionFail` unless already a
99- `PodActionFail`, `PodInvalidResources` or `NotImplementedError`."""
100- if failure.check(PodInvalidResources):
101- # Driver returned its own invalid resource exception instead of
102- # None. Just pass this onto the region.
103- return failure
104-
105- # Log locally to help debugging.
106- log.err(failure, "%s: Failed to compose machine: %s" % (name, request))
107- if failure.check(NotImplementedError, PodActionFail):
108- return failure
109- else:
110- raise PodActionFail(get_error_message(failure.value))
111-
112 d.addCallback(convert)
113- d.addErrback(catch_all)
114+ d.addErrback(
115+ convert_errors,
116+ log_message=f"{name}: Failed to compose machine: {request}",
117+ keep_failures=[PodInvalidResources],
118+ )
119 return d
120
121
122@@ -217,23 +199,15 @@ def send_pod_commissioning_results(
123 f"Unable to send Pod commissioning information for {name}({system_id}): {e.error}"
124 )
125
126- def catch_all(failure):
127- """Convert all failures into `PodActionFail` unless already a
128- `PodActionFail` or `NotImplementedError`."""
129- # Log locally to help debugging.
130- log.err(failure, "Failed to send_pod_commissioning_results.")
131- if failure.check(NotImplementedError, PodActionFail):
132- return failure
133- else:
134- raise PodActionFail(get_error_message(failure.value))
135-
136 d.addCallback(
137 lambda commissioning_results: deferToThread(
138 send_items, commissioning_results
139 )
140 )
141 d.addCallback(lambda _: {})
142- d.addErrback(catch_all)
143+ d.addErrback(
144+ convert_errors, log_message="Failed to send_pod_commissioning_results."
145+ )
146 return d
147
148
149@@ -261,16 +235,23 @@ def decompose_machine(pod_type, context, pod_id, name):
150 else:
151 return {"hints": result}
152
153- def catch_all(failure):
154- """Convert all failures into `PodActionFail` unless already a
155- `PodActionFail` or `NotImplementedError`."""
156- # Log locally to help debugging.
157- log.err(failure, "Failed to decompose machine.")
158- if failure.check(NotImplementedError, PodActionFail):
159- return failure
160- else:
161- raise PodActionFail(get_error_message(failure.value))
162-
163 d.addCallback(convert)
164- d.addErrback(catch_all)
165+ d.addErrback(convert_errors, log_message="Failed to decompose machine.")
166 return d
167+
168+
169+def convert_errors(failure, log_message=None, keep_failures=None):
170+ """Convert all failures into PodActionFail unless already a
171+ PodActionFail or NotImplementedError.
172+
173+ Optionally, also log a failure message.
174+ """
175+ valid_failures = [NotImplementedError, PodActionFail]
176+ if keep_failures:
177+ valid_failures.extend(keep_failures)
178+ if log_message:
179+ log.err(failure, log_message)
180+ if failure.check(*valid_failures):
181+ return failure
182+ else:
183+ raise PodActionFail(get_error_message(failure.value))
184diff --git a/src/provisioningserver/rpc/tests/test_pods.py b/src/provisioningserver/rpc/tests/test_pods.py
185index aa8f547..0f8bb01 100644
186--- a/src/provisioningserver/rpc/tests/test_pods.py
187+++ b/src/provisioningserver/rpc/tests/test_pods.py
188@@ -41,6 +41,15 @@ class TestDiscoverPodProjects(MAASTestCase):
189 yield pods.discover_pod_projects(unknown_type, {})
190
191 @inlineCallbacks
192+ def test_converts_exceptions(self):
193+ fake_driver = MagicMock()
194+ fake_driver.name = factory.make_name("pod")
195+ fake_driver.discover_projects.return_value = fail(Exception("fail!"))
196+ self.patch(PodDriverRegistry, "get_item").return_value = fake_driver
197+ with ExpectedException(exceptions.PodActionFail):
198+ yield pods.discover_pod_projects(fake_driver.name, {})
199+
200+ @inlineCallbacks
201 def test_return_projects(self):
202 fake_driver = MagicMock()
203 fake_driver.name = factory.make_name("pod")

Subscribers

People subscribed via source and target branches