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

Subscribers

People subscribed via source and target branches