Merge ~bjornt/maas:bug-1845351-update-tags-no-proxy into maas:master

Proposed by Björn Tillenius
Status: Merged
Approved by: Björn Tillenius
Approved revision: 226898c23ee945f9d843ea888ca5b9cc3a0932e2
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~bjornt/maas:bug-1845351-update-tags-no-proxy
Merge into: maas:master
Diff against target: 231 lines (+98/-15)
4 files modified
src/apiclient/maas_client.py (+11/-1)
src/apiclient/tests/test_maas_client.py (+67/-13)
src/provisioningserver/rpc/tags.py (+3/-1)
src/provisioningserver/rpc/tests/test_tags.py (+17/-0)
Reviewer Review Type Date Requested Status
MAAS Lander Needs Fixing
Alberto Donato (community) Approve
Review via email: mp+374214@code.launchpad.net

Commit message

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.

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

LGTM, +1

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

UNIT TESTS
-b bug-1845351-update-tags-no-proxy 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/6771/console
COMMIT: 1523767a1851eca9202edbea70d553679a429d51

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

Fix test failure.

Revision history for this message
Björn Tillenius (bjornt) :
226898c... by Björn Tillenius

Typo.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/apiclient/maas_client.py b/src/apiclient/maas_client.py
index d0607e7..95d98e1 100644
--- a/src/apiclient/maas_client.py
+++ b/src/apiclient/maas_client.py
@@ -87,8 +87,14 @@ class MAASDispatcher:
87 Be careful when changing its API: this class is designed so that it87 Be careful when changing its API: this class is designed so that it
88 can be replaced with a Twisted-enabled alternative. See the MAAS88 can be replaced with a Twisted-enabled alternative. See the MAAS
89 provider in Juju for the code this would require.89 provider in Juju for the code this would require.
90
91 @ivar autodetect_proxies: Extract proxy information from the
92 environment variables (http_proxy, no_proxy). Default True
90 """93 """
9194
95 def __init__(self, autodetect_proxies=True):
96 self.autodetect_proxies = autodetect_proxies
97
92 def dispatch_query(self, request_url, headers, method="GET", data=None):98 def dispatch_query(self, request_url, headers, method="GET", data=None):
93 """Synchronously dispatch an OAuth-signed request to L{request_url}.99 """Synchronously dispatch an OAuth-signed request to L{request_url}.
94100
@@ -118,9 +124,13 @@ class MAASDispatcher:
118 data = bytes(data, "utf-8")124 data = bytes(data, "utf-8")
119 req = RequestWithMethod(request_url, data, headers, method=method)125 req = RequestWithMethod(request_url, data, headers, method=method)
120 # Retry the request maximum of 3 times.126 # Retry the request maximum of 3 times.
127 handlers = []
128 if not self.autodetect_proxies:
129 handlers.append(urllib.request.ProxyHandler({}))
121 for try_count in range(3):130 for try_count in range(3):
131 opener = urllib.request.build_opener(*handlers)
122 try:132 try:
123 res = urllib.request.urlopen(req)133 res = opener.open(req)
124 except urllib.error.HTTPError as exc:134 except urllib.error.HTTPError as exc:
125 if exc.code == 503:135 if exc.code == 503:
126 # HTTP 503 Service Unavailable - MAAS might still be136 # HTTP 503 Service Unavailable - MAAS might still be
diff --git a/src/apiclient/tests/test_maas_client.py b/src/apiclient/tests/test_maas_client.py
index 36701d1..6e06581 100644
--- a/src/apiclient/tests/test_maas_client.py
+++ b/src/apiclient/tests/test_maas_client.py
@@ -8,8 +8,9 @@ __all__ = []
8import gzip8import gzip
9from io import BytesIO9from io import BytesIO
10import json10import json
11import os
11from random import randint12from random import randint
12from unittest.mock import ANY13from unittest.mock import ANY, MagicMock, patch
13import urllib.error14import urllib.error
14import urllib.parse15import urllib.parse
15from urllib.parse import parse_qs, urljoin, urlparse16from urllib.parse import parse_qs, urljoin, urlparse
@@ -33,6 +34,23 @@ class TestMAASOAuth(MAASTestCase):
3334
3435
35class TestMAASDispatcher(MAASTestCase):36class TestMAASDispatcher(MAASTestCase):
37 def setUp(self):
38 super().setUp()
39 self.patch_urllib()
40
41 def patch_urllib(self):
42 def patch_build_opener(*args, **kwargs):
43 self.opener = real_build_opener(*args, **kwargs)
44 self.orig_open_func = self.opener.open
45 if self.open_func is not None:
46 self.opener.open = self.open_func
47 return self.opener
48
49 real_build_opener = urllib.request.build_opener
50 build_opener_mock = self.patch(urllib.request, "build_opener")
51 build_opener_mock.side_effect = patch_build_opener
52 self.open_func = None
53
36 def test_dispatch_query_makes_direct_call(self):54 def test_dispatch_query_makes_direct_call(self):
37 contents = factory.make_string().encode("ascii")55 contents = factory.make_string().encode("ascii")
38 url = "file://%s" % self.make_file(contents=contents)56 url = "file://%s" % self.make_file(contents=contents)
@@ -44,12 +62,12 @@ class TestMAASDispatcher(MAASTestCase):
44 # urllib, used by MAASDispatcher, requires data encoded into bytes. We62 # urllib, used by MAASDispatcher, requires data encoded into bytes. We
45 # encode into utf-8 in dispatch_query if necessary.63 # encode into utf-8 in dispatch_query if necessary.
46 request = self.patch(urllib.request.Request, "__init__")64 request = self.patch(urllib.request.Request, "__init__")
47 urlopen = self.patch(urllib.request, "urlopen")65 self.patch_urllib()
66 self.open_func = lambda *args: MagicMock()
48 url = factory.make_url()67 url = factory.make_url()
49 data = factory.make_string(300, spaces=True)68 data = factory.make_string(300, spaces=True)
50 MAASDispatcher().dispatch_query(url, {}, method="POST", data=data)69 MAASDispatcher().dispatch_query(url, {}, method="POST", data=data)
51 request.assert_called_once_with(ANY, url, bytes(data, "utf-8"), ANY)70 request.assert_called_once_with(ANY, url, bytes(data, "utf-8"), ANY)
52 urlopen.assert_called_once_with(ANY)
5371
54 def test_request_from_http(self):72 def test_request_from_http(self):
55 # We can't just call self.make_file because HTTPServerFixture will only73 # We can't just call self.make_file because HTTPServerFixture will only
@@ -97,13 +115,12 @@ class TestMAASDispatcher(MAASTestCase):
97 content = factory.make_string(300).encode("ascii")115 content = factory.make_string(300).encode("ascii")
98 factory.make_file(location=".", name=name, contents=content)116 factory.make_file(location=".", name=name, contents=content)
99 called = []117 called = []
100 orig_urllib = urllib.request.urlopen
101118
102 def logging_urlopen(*args, **kwargs):119 def logging_open(*args, **kwargs):
103 called.append((args, kwargs))120 called.append((args, kwargs))
104 return orig_urllib(*args, **kwargs)121 return self.orig_open_func(*args, **kwargs)
105122
106 self.patch(urllib.request, "urlopen", logging_urlopen)123 self.open_func = logging_open
107 with HTTPServerFixture() as httpd:124 with HTTPServerFixture() as httpd:
108 url = urljoin(httpd.url, name)125 url = urljoin(httpd.url, name)
109 res = MAASDispatcher().dispatch_query(url, {})126 res = MAASDispatcher().dispatch_query(url, {})
@@ -139,20 +156,19 @@ class TestMAASDispatcher(MAASTestCase):
139 factory.make_file(location=".", name=name, contents=content)156 factory.make_file(location=".", name=name, contents=content)
140 with HTTPServerFixture() as httpd:157 with HTTPServerFixture() as httpd:
141 url = urljoin(httpd.url, name)158 url = urljoin(httpd.url, name)
142 original_urlopen = urllib.request.urlopen
143159
144 counter = {"count": 0}160 counter = {"count": 0}
145161
146 def _wrap_urlopen(*args, **kwargs):162 def _wrap_open(*args, **kwargs):
147 if counter["count"] < 2:163 if counter["count"] < 2:
148 counter["count"] += 1164 counter["count"] += 1
149 raise urllib.error.HTTPError(165 raise urllib.error.HTTPError(
150 url, 503, "service unavailable", {}, None166 url, 503, "service unavailable", {}, None
151 )167 )
152 else:168 else:
153 return original_urlopen(*args, **kwargs)169 return self.orig_open_func(*args, **kwargs)
154170
155 self.patch(urllib.request, "urlopen").side_effect = _wrap_urlopen171 self.open_func = _wrap_open
156 response = MAASDispatcher().dispatch_query(url, {})172 response = MAASDispatcher().dispatch_query(url, {})
157 self.assertEqual(200, response.code)173 self.assertEqual(200, response.code)
158 self.assertEqual(content, response.read())174 self.assertEqual(content, response.read())
@@ -165,12 +181,12 @@ class TestMAASDispatcher(MAASTestCase):
165 with HTTPServerFixture() as httpd:181 with HTTPServerFixture() as httpd:
166 url = urljoin(httpd.url, name)182 url = urljoin(httpd.url, name)
167183
168 def _wrap_urlopen(*args, **kwargs):184 def _wrap_open(*args, **kwargs):
169 raise urllib.error.HTTPError(185 raise urllib.error.HTTPError(
170 url, 503, "service unavailable", {}, None186 url, 503, "service unavailable", {}, None
171 )187 )
172188
173 self.patch(urllib.request, "urlopen").side_effect = _wrap_urlopen189 self.open_func = _wrap_open
174 err = self.assertRaises(190 err = self.assertRaises(
175 urllib.error.HTTPError,191 urllib.error.HTTPError,
176 MAASDispatcher().dispatch_query,192 MAASDispatcher().dispatch_query,
@@ -179,6 +195,44 @@ class TestMAASDispatcher(MAASTestCase):
179 )195 )
180 self.assertEqual(503, err.code)196 self.assertEqual(503, err.code)
181197
198 def test_autodetects_proxies(self):
199 self.open_func = lambda *args: MagicMock()
200 url = factory.make_url()
201 proxy_variables = {
202 "http_proxy": "http://proxy.example.com",
203 "https_proxy": "https://proxy.example.com",
204 "no_proxy": "noproxy.example.com",
205 }
206 with patch.dict(os.environ, proxy_variables):
207 MAASDispatcher().dispatch_query(url, {}, method="GET")
208 for handler in self.opener.handle_open["http"]:
209 if isinstance(handler, urllib.request.ProxyHandler):
210 break
211 else:
212 raise AssertionError("No ProxyHandler installed")
213 expected = {
214 "http": proxy_variables["http_proxy"],
215 "https": proxy_variables["https_proxy"],
216 "no": proxy_variables["no_proxy"],
217 }
218 for key, value in expected.items():
219 self.assertEqual(value, handler.proxies[key])
220
221 def test_no_autodetects_proxies(self):
222 self.open_func = lambda *args: MagicMock()
223 url = factory.make_url()
224 proxy_variables = {
225 "http_proxy": "http://proxy.example.com",
226 "https_proxy": "https://proxy.example.com",
227 "no_proxy": "noproxy.example.com",
228 }
229 with patch.dict(os.environ, proxy_variables):
230 dispatcher = MAASDispatcher(autodetect_proxies=False)
231 dispatcher.dispatch_query(url, {}, method="GET")
232 for handler in self.opener.handle_open["http"]:
233 if isinstance(handler, urllib.request.ProxyHandler):
234 raise AssertionError("ProxyHandler shouldn't be there")
235
182236
183def make_path():237def make_path():
184 """Create an arbitrary resource path."""238 """Create an arbitrary resource path."""
diff --git a/src/provisioningserver/rpc/tags.py b/src/provisioningserver/rpc/tags.py
index 3b436f6..653e777 100644
--- a/src/provisioningserver/rpc/tags.py
+++ b/src/provisioningserver/rpc/tags.py
@@ -30,9 +30,11 @@ def evaluate_tag(
30 :param credentials: A 3-tuple of OAuth credentials.30 :param credentials: A 3-tuple of OAuth credentials.
31 :param maas_url: URL of the MAAS API.31 :param maas_url: URL of the MAAS API.
32 """32 """
33 # Turn off proxy detection, since the rack should talk directly to
34 # the region, even if a system-wide proxy is configured.
33 client = MAASClient(35 client = MAASClient(
34 auth=MAASOAuth(*credentials),36 auth=MAASOAuth(*credentials),
35 dispatcher=MAASDispatcher(),37 dispatcher=MAASDispatcher(autodetect_proxies=False),
36 base_url=maas_url,38 base_url=maas_url,
37 )39 )
38 process_node_tags(40 process_node_tags(
diff --git a/src/provisioningserver/rpc/tests/test_tags.py b/src/provisioningserver/rpc/tests/test_tags.py
index 7a7d0b3..a3493e5 100644
--- a/src/provisioningserver/rpc/tests/test_tags.py
+++ b/src/provisioningserver/rpc/tests/test_tags.py
@@ -73,3 +73,20 @@ class TestEvaluateTag(MAASTestCase):
73 tags.MAASOAuth,73 tags.MAASOAuth,
74 MockCalledOnceWith(consumer_key, resource_token, resource_secret),74 MockCalledOnceWith(consumer_key, resource_token, resource_secret),
75 )75 )
76
77 def test__constructs_client_with_no_proxies(self):
78 credentials = "aaa", "bbb", "ccc"
79 rack_id = factory.make_name("rack")
80 process_node_tags = self.patch_autospec(tags, "process_node_tags")
81 tags.evaluate_tag(
82 rack_id,
83 [],
84 sentinel.tag_name,
85 sentinel.tag_definition,
86 sentinel.tag_nsmap,
87 credentials,
88 self.mock_url,
89 )
90
91 client = process_node_tags.call_args[1]["client"]
92 self.assertFalse(client.dispatcher.autodetect_proxies)

Subscribers

People subscribed via source and target branches