Merge ~bjornt/maas:bug-1845351-2.4 into maas:2.4

Proposed by Björn Tillenius
Status: Merged
Approved by: Björn Tillenius
Approved revision: 2058405835f46f2aee1a1f8cb7a0e46d8652c4ce
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~bjornt/maas:bug-1845351-2.4
Merge into: maas:2.4
Diff against target: 193 lines (+96/-11)
4 files modified
src/apiclient/maas_client.py (+11/-1)
src/apiclient/tests/test_maas_client.py (+67/-8)
src/provisioningserver/rpc/tags.py (+7/-2)
src/provisioningserver/rpc/tests/test_tags.py (+11/-0)
Reviewer Review Type Date Requested Status
MAAS Lander Needs Fixing
Björn Tillenius Approve
Review via email: mp+374284@code.launchpad.net

Commit message

LP #1845351: MAAS sometimes get in a state where machine tags aren't updated from their definitions

Don't use a proxy for the rack-region API communication.

We require the rack to have direct access to the region without going
through a proxy.

Using a proxy for the rack-region communication (HTTP API only) has
partly worked, but been broken.

This fixes the test_check_tag_applied_to_all_machines() failure that
fails intermittently in CI.

(cherry picked from commit 4eee730d67082731ed43fe1075a4ce883fcd3823)

To post a comment you must log in.
Revision history for this message
Björn Tillenius (bjornt) wrote :

Self-approve backport.

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 :

UNIT TESTS
-b bug-1845351-2.4 lp:~bjornt/maas/+git/maas into -b 2.4 lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/6804/console
COMMIT: 2058405835f46f2aee1a1f8cb7a0e46d8652c4ce

review: Needs Fixing
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 :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/apiclient/maas_client.py b/src/apiclient/maas_client.py
2index a719a29..625b2ad 100644
3--- a/src/apiclient/maas_client.py
4+++ b/src/apiclient/maas_client.py
5@@ -79,8 +79,14 @@ class MAASDispatcher:
6 Be careful when changing its API: this class is designed so that it
7 can be replaced with a Twisted-enabled alternative. See the MAAS
8 provider in Juju for the code this would require.
9+
10+ @ivar autodetect_proxies: Extract proxy information from the
11+ environment variables (http_proxy, no_proxy). Default True
12 """
13
14+ def __init__(self, autodetect_proxies=True):
15+ self.autodetect_proxies = autodetect_proxies
16+
17 def dispatch_query(self, request_url, headers, method="GET", data=None):
18 """Synchronously dispatch an OAuth-signed request to L{request_url}.
19
20@@ -109,7 +115,11 @@ class MAASDispatcher:
21 if data is not None and not isinstance(data, bytes):
22 data = bytes(data, 'utf-8')
23 req = RequestWithMethod(request_url, data, headers, method=method)
24- res = urllib.request.urlopen(req)
25+ handlers = []
26+ if not self.autodetect_proxies:
27+ handlers.append(urllib.request.ProxyHandler({}))
28+ opener = urllib.request.build_opener(*handlers)
29+ res = opener.open(req)
30 # If we set the Accept-encoding header, then we decode the header for
31 # the caller.
32 is_gzip = (
33diff --git a/src/apiclient/tests/test_maas_client.py b/src/apiclient/tests/test_maas_client.py
34index 5b126b3..da4b654 100644
35--- a/src/apiclient/tests/test_maas_client.py
36+++ b/src/apiclient/tests/test_maas_client.py
37@@ -8,8 +8,13 @@ __all__ = []
38 import gzip
39 from io import BytesIO
40 import json
41+import os
42 from random import randint
43-from unittest.mock import ANY
44+from unittest.mock import (
45+ ANY,
46+ MagicMock,
47+ patch,
48+)
49 import urllib.error
50 import urllib.parse
51 from urllib.parse import (
52@@ -46,6 +51,22 @@ class TestMAASOAuth(MAASTestCase):
53
54
55 class TestMAASDispatcher(MAASTestCase):
56+ def setUp(self):
57+ super().setUp()
58+ self.patch_urllib()
59+
60+ def patch_urllib(self):
61+ def patch_build_opener(*args, **kwargs):
62+ self.opener = real_build_opener(*args, **kwargs)
63+ self.orig_open_func = self.opener.open
64+ if self.open_func is not None:
65+ self.opener.open = self.open_func
66+ return self.opener
67+
68+ real_build_opener = urllib.request.build_opener
69+ build_opener_mock = self.patch(urllib.request, "build_opener")
70+ build_opener_mock.side_effect = patch_build_opener
71+ self.open_func = None
72
73 def test_dispatch_query_makes_direct_call(self):
74 contents = factory.make_string().encode("ascii")
75@@ -56,13 +77,13 @@ class TestMAASDispatcher(MAASTestCase):
76 def test_dispatch_query_encodes_string_data(self):
77 # urllib, used by MAASDispatcher, requires data encoded into bytes. We
78 # encode into utf-8 in dispatch_query if necessary.
79- request = self.patch(urllib.request.Request, '__init__')
80- urlopen = self.patch(urllib.request, 'urlopen')
81+ request = self.patch(urllib.request.Request, "__init__")
82+ self.patch_urllib()
83+ self.open_func = lambda *args: MagicMock()
84 url = factory.make_url()
85 data = factory.make_string(300, spaces=True)
86 MAASDispatcher().dispatch_query(url, {}, method="POST", data=data)
87 request.assert_called_once_with(ANY, url, bytes(data, "utf-8"), ANY)
88- urlopen.assert_called_once_with(ANY)
89
90 def test_request_from_http(self):
91 # We can't just call self.make_file because HTTPServerFixture will only
92@@ -106,12 +127,12 @@ class TestMAASDispatcher(MAASTestCase):
93 content = factory.make_string(300).encode('ascii')
94 factory.make_file(location='.', name=name, contents=content)
95 called = []
96- orig_urllib = urllib.request.urlopen
97
98- def logging_urlopen(*args, **kwargs):
99+ def logging_open(*args, **kwargs):
100 called.append((args, kwargs))
101- return orig_urllib(*args, **kwargs)
102- self.patch(urllib.request, 'urlopen', logging_urlopen)
103+ return self.orig_open_func(*args, **kwargs)
104+
105+ self.open_func = logging_open
106 with HTTPServerFixture() as httpd:
107 url = urljoin(httpd.url, name)
108 res = MAASDispatcher().dispatch_query(url, {})
109@@ -139,6 +160,44 @@ class TestMAASDispatcher(MAASTestCase):
110 mode='rb', fileobj=BytesIO(raw_content)).read()
111 self.assertEqual(content, read_content)
112
113+ def test_autodetects_proxies(self):
114+ self.open_func = lambda *args: MagicMock()
115+ url = factory.make_url()
116+ proxy_variables = {
117+ "http_proxy": "http://proxy.example.com",
118+ "https_proxy": "https://proxy.example.com",
119+ "no_proxy": "noproxy.example.com",
120+ }
121+ with patch.dict(os.environ, proxy_variables):
122+ MAASDispatcher().dispatch_query(url, {}, method="GET")
123+ for handler in self.opener.handle_open["http"]:
124+ if isinstance(handler, urllib.request.ProxyHandler):
125+ break
126+ else:
127+ raise AssertionError("No ProxyHandler installed")
128+ expected = {
129+ "http": proxy_variables["http_proxy"],
130+ "https": proxy_variables["https_proxy"],
131+ "no": proxy_variables["no_proxy"],
132+ }
133+ for key, value in expected.items():
134+ self.assertEqual(value, handler.proxies[key])
135+
136+ def test_no_autodetects_proxies(self):
137+ self.open_func = lambda *args: MagicMock()
138+ url = factory.make_url()
139+ proxy_variables = {
140+ "http_proxy": "http://proxy.example.com",
141+ "https_proxy": "https://proxy.example.com",
142+ "no_proxy": "noproxy.example.com",
143+ }
144+ with patch.dict(os.environ, proxy_variables):
145+ dispatcher = MAASDispatcher(autodetect_proxies=False)
146+ dispatcher.dispatch_query(url, {}, method="GET")
147+ for handler in self.opener.handle_open["http"]:
148+ if isinstance(handler, urllib.request.ProxyHandler):
149+ raise AssertionError("ProxyHandler shouldn't be there")
150+
151
152 def make_path():
153 """Create an arbitrary resource path."""
154diff --git a/src/provisioningserver/rpc/tags.py b/src/provisioningserver/rpc/tags.py
155index 12a4c53..c72dd3e 100644
156--- a/src/provisioningserver/rpc/tags.py
157+++ b/src/provisioningserver/rpc/tags.py
158@@ -31,9 +31,14 @@ def evaluate_tag(
159 """
160 with ClusterConfiguration.open() as config:
161 maas_url = config.maas_url
162+
163+ # Turn off proxy detection, since the rack should talk directly to
164+ # the region, even if a system-wide proxy is configured.
165 client = MAASClient(
166- auth=MAASOAuth(*credentials), dispatcher=MAASDispatcher(),
167- base_url=maas_url)
168+ auth=MAASOAuth(*credentials),
169+ dispatcher=MAASDispatcher(autodetect_proxies=False),
170+ base_url=maas_url,
171+ )
172 process_node_tags(
173 rack_id=system_id, nodes=nodes,
174 tag_name=tag_name, tag_definition=tag_definition,
175diff --git a/src/provisioningserver/rpc/tests/test_tags.py b/src/provisioningserver/rpc/tests/test_tags.py
176index 4e9558b..cd40376 100644
177--- a/src/provisioningserver/rpc/tests/test_tags.py
178+++ b/src/provisioningserver/rpc/tests/test_tags.py
179@@ -64,3 +64,14 @@ class TestEvaluateTag(MAASTestCase):
180 self.assertIsInstance(client.auth, MAASOAuth)
181 self.assertThat(tags.MAASOAuth, MockCalledOnceWith(
182 consumer_key, resource_token, resource_secret))
183+
184+ def test__constructs_client_with_no_proxies(self):
185+ credentials = "aaa", "bbb", "ccc"
186+ rack_id = factory.make_name("rack")
187+ process_node_tags = self.patch_autospec(tags, "process_node_tags")
188+ tags.evaluate_tag(
189+ rack_id, [], sentinel.tag_name, sentinel.tag_definition,
190+ sentinel.tag_nsmap, credentials)
191+
192+ client = process_node_tags.call_args[1]["client"]
193+ self.assertFalse(client.dispatcher.autodetect_proxies)

Subscribers

People subscribed via source and target branches