Merge ~smoser/cloud-init:fix/read_file_or_url-contents-should-be-text into cloud-init:master
- Git
- lp:~smoser/cloud-init
- fix/read_file_or_url-contents-should-be-text
- Merge into master
Status: | Merged |
---|---|
Approved by: | Chad Smith |
Approved revision: | ab62be9aa53f1450c99aa5596aa2232b47f38030 |
Merge reported by: | Chad Smith |
Merged at revision: | 30e730f7ca111487d243ba9f40c66df6d7a49953 |
Proposed branch: | ~smoser/cloud-init:fix/read_file_or_url-contents-should-be-text |
Merge into: | cloud-init:master |
Diff against target: |
847 lines (+131/-141) 17 files modified
cloudinit/cmd/main.py (+1/-1) cloudinit/config/cc_phone_home.py (+4/-3) cloudinit/config/schema.py (+2/-2) cloudinit/ec2_utils.py (+6/-8) cloudinit/sources/DataSourceMAAS.py (+1/-1) cloudinit/sources/helpers/azure.py (+3/-2) cloudinit/tests/test_url_helper.py (+27/-1) cloudinit/url_helper.py (+28/-1) cloudinit/user_data.py (+3/-3) cloudinit/util.py (+4/-33) tests/unittests/test__init__.py (+4/-4) tests/unittests/test_datasource/test_azure_helper.py (+1/-1) tests/unittests/test_handler/test_handler_apt_conf_v1.py (+6/-10) tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py (+0/-7) tests/unittests/test_handler/test_handler_apt_source_v1.py (+10/-17) tests/unittests/test_handler/test_handler_apt_source_v3.py (+10/-17) tests/unittests/test_handler/test_handler_ntp.py (+21/-30) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Chad Smith | Approve | ||
Server Team CI bot | continuous-integration | Approve | |
Review via email: mp+343127@code.launchpad.net |
Commit message
read_file_or_url: move to url_helper, fix bug in its FileResponse.
The result of a read_file_or_url on a file and on a url would differ
in behavior.
str(UrlResponse) would return UrlResponse.
while
str(FileResponse) would return str(FileRespons
The difference being "b'foo'" versus "foo".
As part of the general goal of cleaning util, move read_file_or_url
into url_helper.
Description of the change
Server Team CI bot (server-team-bot) wrote : | # |
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:a6c448127b1
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
FAILED: Ubuntu LTS: Integration
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:162d2022c53
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:b9f5010e98c
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:ab62be9aa53
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) wrote : | # |
LGTM!
Was able to validate tracebacks without your changeset using the script below and python3.
Traceback on python3 (tip)
Traceback (most recent call last):
File "test_read.py", line 12, in <module>
print(f)
TypeError: __str__ returned non-string (type bytes)
With your changes things look good, FileResponse.
from cloudinit.
web = read_file_or_url('http://
print('----------- url -------------')
print(web.contents)
print(web)
print('----------- file -------------')
f = read_file_
print(f.contents)
print(f)
Chad Smith (chad.smith) wrote : | # |
An upstream commit landed for this bug.
To view that commit see the following URL:
https:/
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
1 | diff --git a/cloudinit/cmd/main.py b/cloudinit/cmd/main.py |
2 | index 3f2dbb9..d6ba90f 100644 |
3 | --- a/cloudinit/cmd/main.py |
4 | +++ b/cloudinit/cmd/main.py |
5 | @@ -187,7 +187,7 @@ def attempt_cmdline_url(path, network=True, cmdline=None): |
6 | data = None |
7 | header = b'#cloud-config' |
8 | try: |
9 | - resp = util.read_file_or_url(**kwargs) |
10 | + resp = url_helper.read_file_or_url(**kwargs) |
11 | if resp.ok(): |
12 | data = resp.contents |
13 | if not resp.contents.startswith(header): |
14 | diff --git a/cloudinit/config/cc_phone_home.py b/cloudinit/config/cc_phone_home.py |
15 | index 878069b..3be0d1c 100644 |
16 | --- a/cloudinit/config/cc_phone_home.py |
17 | +++ b/cloudinit/config/cc_phone_home.py |
18 | @@ -41,6 +41,7 @@ keys to post. Available keys are: |
19 | """ |
20 | |
21 | from cloudinit import templater |
22 | +from cloudinit import url_helper |
23 | from cloudinit import util |
24 | |
25 | from cloudinit.settings import PER_INSTANCE |
26 | @@ -136,9 +137,9 @@ def handle(name, cfg, cloud, log, args): |
27 | } |
28 | url = templater.render_string(url, url_params) |
29 | try: |
30 | - util.read_file_or_url(url, data=real_submit_keys, |
31 | - retries=tries, sec_between=3, |
32 | - ssl_details=util.fetch_ssl_details(cloud.paths)) |
33 | + url_helper.read_file_or_url( |
34 | + url, data=real_submit_keys, retries=tries, sec_between=3, |
35 | + ssl_details=util.fetch_ssl_details(cloud.paths)) |
36 | except Exception: |
37 | util.logexc(log, "Failed to post phone home data to %s in %s tries", |
38 | url, tries) |
39 | diff --git a/cloudinit/config/schema.py b/cloudinit/config/schema.py |
40 | index 76826e0..3bad8e2 100644 |
41 | --- a/cloudinit/config/schema.py |
42 | +++ b/cloudinit/config/schema.py |
43 | @@ -4,7 +4,7 @@ |
44 | from __future__ import print_function |
45 | |
46 | from cloudinit import importer |
47 | -from cloudinit.util import find_modules, read_file_or_url |
48 | +from cloudinit.util import find_modules, load_file |
49 | |
50 | import argparse |
51 | from collections import defaultdict |
52 | @@ -139,7 +139,7 @@ def validate_cloudconfig_file(config_path, schema, annotate=False): |
53 | """ |
54 | if not os.path.exists(config_path): |
55 | raise RuntimeError('Configfile {0} does not exist'.format(config_path)) |
56 | - content = read_file_or_url('file://{0}'.format(config_path)).contents |
57 | + content = load_file(config_path, decode=False) |
58 | if not content.startswith(CLOUD_CONFIG_HEADER): |
59 | errors = ( |
60 | ('header', 'File {0} needs to begin with "{1}"'.format( |
61 | diff --git a/cloudinit/ec2_utils.py b/cloudinit/ec2_utils.py |
62 | index dc3f0fc..3b7b17f 100644 |
63 | --- a/cloudinit/ec2_utils.py |
64 | +++ b/cloudinit/ec2_utils.py |
65 | @@ -150,11 +150,9 @@ def get_instance_userdata(api_version='latest', |
66 | # NOT_FOUND occurs) and just in that case returning an empty string. |
67 | exception_cb = functools.partial(_skip_retry_on_codes, |
68 | SKIP_USERDATA_CODES) |
69 | - response = util.read_file_or_url(ud_url, |
70 | - ssl_details=ssl_details, |
71 | - timeout=timeout, |
72 | - retries=retries, |
73 | - exception_cb=exception_cb) |
74 | + response = url_helper.read_file_or_url( |
75 | + ud_url, ssl_details=ssl_details, timeout=timeout, |
76 | + retries=retries, exception_cb=exception_cb) |
77 | user_data = response.contents |
78 | except url_helper.UrlError as e: |
79 | if e.code not in SKIP_USERDATA_CODES: |
80 | @@ -169,9 +167,9 @@ def _get_instance_metadata(tree, api_version='latest', |
81 | ssl_details=None, timeout=5, retries=5, |
82 | leaf_decoder=None): |
83 | md_url = url_helper.combine_url(metadata_address, api_version, tree) |
84 | - caller = functools.partial(util.read_file_or_url, |
85 | - ssl_details=ssl_details, timeout=timeout, |
86 | - retries=retries) |
87 | + caller = functools.partial( |
88 | + url_helper.read_file_or_url, ssl_details=ssl_details, |
89 | + timeout=timeout, retries=retries) |
90 | |
91 | def mcaller(url): |
92 | return caller(url).contents |
93 | diff --git a/cloudinit/sources/DataSourceMAAS.py b/cloudinit/sources/DataSourceMAAS.py |
94 | index aa56add..bcb3854 100644 |
95 | --- a/cloudinit/sources/DataSourceMAAS.py |
96 | +++ b/cloudinit/sources/DataSourceMAAS.py |
97 | @@ -198,7 +198,7 @@ def read_maas_seed_url(seed_url, read_file_or_url=None, timeout=None, |
98 | If version is None, then <version>/ will not be used. |
99 | """ |
100 | if read_file_or_url is None: |
101 | - read_file_or_url = util.read_file_or_url |
102 | + read_file_or_url = url_helper.read_file_or_url |
103 | |
104 | if seed_url.endswith("/"): |
105 | seed_url = seed_url[:-1] |
106 | diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py |
107 | index 90c12df..e5696b1 100644 |
108 | --- a/cloudinit/sources/helpers/azure.py |
109 | +++ b/cloudinit/sources/helpers/azure.py |
110 | @@ -14,6 +14,7 @@ from cloudinit import temp_utils |
111 | from contextlib import contextmanager |
112 | from xml.etree import ElementTree |
113 | |
114 | +from cloudinit import url_helper |
115 | from cloudinit import util |
116 | |
117 | LOG = logging.getLogger(__name__) |
118 | @@ -55,14 +56,14 @@ class AzureEndpointHttpClient(object): |
119 | if secure: |
120 | headers = self.headers.copy() |
121 | headers.update(self.extra_secure_headers) |
122 | - return util.read_file_or_url(url, headers=headers) |
123 | + return url_helper.read_file_or_url(url, headers=headers) |
124 | |
125 | def post(self, url, data=None, extra_headers=None): |
126 | headers = self.headers |
127 | if extra_headers is not None: |
128 | headers = self.headers.copy() |
129 | headers.update(extra_headers) |
130 | - return util.read_file_or_url(url, data=data, headers=headers) |
131 | + return url_helper.read_file_or_url(url, data=data, headers=headers) |
132 | |
133 | |
134 | class GoalState(object): |
135 | diff --git a/cloudinit/tests/test_url_helper.py b/cloudinit/tests/test_url_helper.py |
136 | index b778a3a..113249d 100644 |
137 | --- a/cloudinit/tests/test_url_helper.py |
138 | +++ b/cloudinit/tests/test_url_helper.py |
139 | @@ -1,7 +1,10 @@ |
140 | # This file is part of cloud-init. See LICENSE file for license information. |
141 | |
142 | -from cloudinit.url_helper import oauth_headers |
143 | +from cloudinit.url_helper import oauth_headers, read_file_or_url |
144 | from cloudinit.tests.helpers import CiTestCase, mock, skipIf |
145 | +from cloudinit import util |
146 | + |
147 | +import httpretty |
148 | |
149 | |
150 | try: |
151 | @@ -38,3 +41,26 @@ class TestOAuthHeaders(CiTestCase): |
152 | 'url', 'consumer_key', 'token_key', 'token_secret', |
153 | 'consumer_secret') |
154 | self.assertEqual('url', return_value) |
155 | + |
156 | + |
157 | +class TestReadFileOrUrl(CiTestCase): |
158 | + def test_read_file_or_url_str_from_file(self): |
159 | + """Test that str(result.contents) on file is text version of contents. |
160 | + It should not be "b'data'", but just "'data'" """ |
161 | + tmpf = self.tmp_path("myfile1") |
162 | + data = b'This is my file content\n' |
163 | + util.write_file(tmpf, data, omode="wb") |
164 | + result = read_file_or_url("file://%s" % tmpf) |
165 | + self.assertEqual(result.contents, data) |
166 | + self.assertEqual(str(result), data.decode('utf-8')) |
167 | + |
168 | + @httpretty.activate |
169 | + def test_read_file_or_url_str_from_url(self): |
170 | + """Test that str(result.contents) on url is text version of contents. |
171 | + It should not be "b'data'", but just "'data'" """ |
172 | + url = 'http://hostname/path' |
173 | + data = b'This is my url content\n' |
174 | + httpretty.register_uri(httpretty.GET, url, data) |
175 | + result = read_file_or_url(url) |
176 | + self.assertEqual(result.contents, data) |
177 | + self.assertEqual(str(result), data.decode('utf-8')) |
178 | diff --git a/cloudinit/url_helper.py b/cloudinit/url_helper.py |
179 | index 1de07b1..8067979 100644 |
180 | --- a/cloudinit/url_helper.py |
181 | +++ b/cloudinit/url_helper.py |
182 | @@ -15,6 +15,7 @@ import six |
183 | import time |
184 | |
185 | from email.utils import parsedate |
186 | +from errno import ENOENT |
187 | from functools import partial |
188 | from itertools import count |
189 | from requests import exceptions |
190 | @@ -80,6 +81,32 @@ def combine_url(base, *add_ons): |
191 | return url |
192 | |
193 | |
194 | +def read_file_or_url(url, timeout=5, retries=10, |
195 | + headers=None, data=None, sec_between=1, ssl_details=None, |
196 | + headers_cb=None, exception_cb=None): |
197 | + url = url.lstrip() |
198 | + if url.startswith("/"): |
199 | + url = "file://%s" % url |
200 | + if url.lower().startswith("file://"): |
201 | + if data: |
202 | + LOG.warning("Unable to post data to file resource %s", url) |
203 | + file_path = url[len("file://"):] |
204 | + try: |
205 | + with open(file_path, "rb") as fp: |
206 | + contents = fp.read() |
207 | + except IOError as e: |
208 | + code = e.errno |
209 | + if e.errno == ENOENT: |
210 | + code = NOT_FOUND |
211 | + raise UrlError(cause=e, code=code, headers=None, url=url) |
212 | + return FileResponse(file_path, contents=contents) |
213 | + else: |
214 | + return readurl(url, timeout=timeout, retries=retries, headers=headers, |
215 | + headers_cb=headers_cb, data=data, |
216 | + sec_between=sec_between, ssl_details=ssl_details, |
217 | + exception_cb=exception_cb) |
218 | + |
219 | + |
220 | # Made to have same accessors as UrlResponse so that the |
221 | # read_file_or_url can return this or that object and the |
222 | # 'user' of those objects will not need to know the difference. |
223 | @@ -96,7 +123,7 @@ class StringResponse(object): |
224 | return True |
225 | |
226 | def __str__(self): |
227 | - return self.contents |
228 | + return self.contents.decode('utf-8') |
229 | |
230 | |
231 | class FileResponse(StringResponse): |
232 | diff --git a/cloudinit/user_data.py b/cloudinit/user_data.py |
233 | index cc55daf..8f6aba1 100644 |
234 | --- a/cloudinit/user_data.py |
235 | +++ b/cloudinit/user_data.py |
236 | @@ -19,7 +19,7 @@ import six |
237 | |
238 | from cloudinit import handlers |
239 | from cloudinit import log as logging |
240 | -from cloudinit.url_helper import UrlError |
241 | +from cloudinit.url_helper import read_file_or_url, UrlError |
242 | from cloudinit import util |
243 | |
244 | LOG = logging.getLogger(__name__) |
245 | @@ -224,8 +224,8 @@ class UserDataProcessor(object): |
246 | content = util.load_file(include_once_fn) |
247 | else: |
248 | try: |
249 | - resp = util.read_file_or_url(include_url, |
250 | - ssl_details=self.ssl_details) |
251 | + resp = read_file_or_url(include_url, |
252 | + ssl_details=self.ssl_details) |
253 | if include_once_on and resp.ok(): |
254 | util.write_file(include_once_fn, resp.contents, |
255 | mode=0o600) |
256 | diff --git a/cloudinit/util.py b/cloudinit/util.py |
257 | index 310758d..efcbf9f 100644 |
258 | --- a/cloudinit/util.py |
259 | +++ b/cloudinit/util.py |
260 | @@ -857,37 +857,6 @@ def fetch_ssl_details(paths=None): |
261 | return ssl_details |
262 | |
263 | |
264 | -def read_file_or_url(url, timeout=5, retries=10, |
265 | - headers=None, data=None, sec_between=1, ssl_details=None, |
266 | - headers_cb=None, exception_cb=None): |
267 | - url = url.lstrip() |
268 | - if url.startswith("/"): |
269 | - url = "file://%s" % url |
270 | - if url.lower().startswith("file://"): |
271 | - if data: |
272 | - LOG.warning("Unable to post data to file resource %s", url) |
273 | - file_path = url[len("file://"):] |
274 | - try: |
275 | - contents = load_file(file_path, decode=False) |
276 | - except IOError as e: |
277 | - code = e.errno |
278 | - if e.errno == ENOENT: |
279 | - code = url_helper.NOT_FOUND |
280 | - raise url_helper.UrlError(cause=e, code=code, headers=None, |
281 | - url=url) |
282 | - return url_helper.FileResponse(file_path, contents=contents) |
283 | - else: |
284 | - return url_helper.readurl(url, |
285 | - timeout=timeout, |
286 | - retries=retries, |
287 | - headers=headers, |
288 | - headers_cb=headers_cb, |
289 | - data=data, |
290 | - sec_between=sec_between, |
291 | - ssl_details=ssl_details, |
292 | - exception_cb=exception_cb) |
293 | - |
294 | - |
295 | def load_yaml(blob, default=None, allowed=(dict,)): |
296 | loaded = default |
297 | blob = decode_binary(blob) |
298 | @@ -925,12 +894,14 @@ def read_seeded(base="", ext="", timeout=5, retries=10, file_retries=0): |
299 | ud_url = "%s%s%s" % (base, "user-data", ext) |
300 | md_url = "%s%s%s" % (base, "meta-data", ext) |
301 | |
302 | - md_resp = read_file_or_url(md_url, timeout, retries, file_retries) |
303 | + md_resp = url_helper.read_file_or_url(md_url, timeout, retries, |
304 | + file_retries) |
305 | md = None |
306 | if md_resp.ok(): |
307 | md = load_yaml(decode_binary(md_resp.contents), default={}) |
308 | |
309 | - ud_resp = read_file_or_url(ud_url, timeout, retries, file_retries) |
310 | + ud_resp = url_helper.read_file_or_url(ud_url, timeout, retries, |
311 | + file_retries) |
312 | ud = None |
313 | if ud_resp.ok(): |
314 | ud = ud_resp.contents |
315 | diff --git a/tests/unittests/test__init__.py b/tests/unittests/test__init__.py |
316 | index f1ab02e..739bbeb 100644 |
317 | --- a/tests/unittests/test__init__.py |
318 | +++ b/tests/unittests/test__init__.py |
319 | @@ -182,7 +182,7 @@ class TestCmdlineUrl(CiTestCase): |
320 | self.assertEqual( |
321 | ('url', 'http://example.com'), main.parse_cmdline_url(cmdline)) |
322 | |
323 | - @mock.patch('cloudinit.cmd.main.util.read_file_or_url') |
324 | + @mock.patch('cloudinit.cmd.main.url_helper.read_file_or_url') |
325 | def test_invalid_content(self, m_read): |
326 | key = "cloud-config-url" |
327 | url = 'http://example.com/foo' |
328 | @@ -196,7 +196,7 @@ class TestCmdlineUrl(CiTestCase): |
329 | self.assertIn(url, msg) |
330 | self.assertFalse(os.path.exists(fpath)) |
331 | |
332 | - @mock.patch('cloudinit.cmd.main.util.read_file_or_url') |
333 | + @mock.patch('cloudinit.cmd.main.url_helper.read_file_or_url') |
334 | def test_valid_content(self, m_read): |
335 | url = "http://example.com/foo" |
336 | payload = b"#cloud-config\nmydata: foo\nbar: wark\n" |
337 | @@ -210,7 +210,7 @@ class TestCmdlineUrl(CiTestCase): |
338 | self.assertEqual(logging.INFO, lvl) |
339 | self.assertIn(url, msg) |
340 | |
341 | - @mock.patch('cloudinit.cmd.main.util.read_file_or_url') |
342 | + @mock.patch('cloudinit.cmd.main.url_helper.read_file_or_url') |
343 | def test_no_key_found(self, m_read): |
344 | cmdline = "ro mykey=http://example.com/foo root=foo" |
345 | fpath = self.tmp_path("ccpath") |
346 | @@ -221,7 +221,7 @@ class TestCmdlineUrl(CiTestCase): |
347 | self.assertFalse(os.path.exists(fpath)) |
348 | self.assertEqual(logging.DEBUG, lvl) |
349 | |
350 | - @mock.patch('cloudinit.cmd.main.util.read_file_or_url') |
351 | + @mock.patch('cloudinit.cmd.main.url_helper.read_file_or_url') |
352 | def test_exception_warns(self, m_read): |
353 | url = "http://example.com/foo" |
354 | cmdline = "ro cloud-config-url=%s root=LABEL=bar" % url |
355 | diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py |
356 | index b42b073..af9d3e1 100644 |
357 | --- a/tests/unittests/test_datasource/test_azure_helper.py |
358 | +++ b/tests/unittests/test_datasource/test_azure_helper.py |
359 | @@ -195,7 +195,7 @@ class TestAzureEndpointHttpClient(CiTestCase): |
360 | self.addCleanup(patches.close) |
361 | |
362 | self.read_file_or_url = patches.enter_context( |
363 | - mock.patch.object(azure_helper.util, 'read_file_or_url')) |
364 | + mock.patch.object(azure_helper.url_helper, 'read_file_or_url')) |
365 | |
366 | def test_non_secure_get(self): |
367 | client = azure_helper.AzureEndpointHttpClient(mock.MagicMock()) |
368 | diff --git a/tests/unittests/test_handler/test_handler_apt_conf_v1.py b/tests/unittests/test_handler/test_handler_apt_conf_v1.py |
369 | index 83f962a..6a4b03e 100644 |
370 | --- a/tests/unittests/test_handler/test_handler_apt_conf_v1.py |
371 | +++ b/tests/unittests/test_handler/test_handler_apt_conf_v1.py |
372 | @@ -12,10 +12,6 @@ import shutil |
373 | import tempfile |
374 | |
375 | |
376 | -def load_tfile_or_url(*args, **kwargs): |
377 | - return(util.decode_binary(util.read_file_or_url(*args, **kwargs).contents)) |
378 | - |
379 | - |
380 | class TestAptProxyConfig(TestCase): |
381 | def setUp(self): |
382 | super(TestAptProxyConfig, self).setUp() |
383 | @@ -36,7 +32,7 @@ class TestAptProxyConfig(TestCase): |
384 | self.assertTrue(os.path.isfile(self.pfile)) |
385 | self.assertFalse(os.path.isfile(self.cfile)) |
386 | |
387 | - contents = load_tfile_or_url(self.pfile) |
388 | + contents = util.load_file(self.pfile) |
389 | self.assertTrue(self._search_apt_config(contents, "http", "myproxy")) |
390 | |
391 | def test_apt_http_proxy_written(self): |
392 | @@ -46,7 +42,7 @@ class TestAptProxyConfig(TestCase): |
393 | self.assertTrue(os.path.isfile(self.pfile)) |
394 | self.assertFalse(os.path.isfile(self.cfile)) |
395 | |
396 | - contents = load_tfile_or_url(self.pfile) |
397 | + contents = util.load_file(self.pfile) |
398 | self.assertTrue(self._search_apt_config(contents, "http", "myproxy")) |
399 | |
400 | def test_apt_all_proxy_written(self): |
401 | @@ -64,7 +60,7 @@ class TestAptProxyConfig(TestCase): |
402 | self.assertTrue(os.path.isfile(self.pfile)) |
403 | self.assertFalse(os.path.isfile(self.cfile)) |
404 | |
405 | - contents = load_tfile_or_url(self.pfile) |
406 | + contents = util.load_file(self.pfile) |
407 | |
408 | for ptype, pval in values.items(): |
409 | self.assertTrue(self._search_apt_config(contents, ptype, pval)) |
410 | @@ -80,7 +76,7 @@ class TestAptProxyConfig(TestCase): |
411 | cc_apt_configure.apply_apt_config({'proxy': "foo"}, |
412 | self.pfile, self.cfile) |
413 | self.assertTrue(os.path.isfile(self.pfile)) |
414 | - contents = load_tfile_or_url(self.pfile) |
415 | + contents = util.load_file(self.pfile) |
416 | self.assertTrue(self._search_apt_config(contents, "http", "foo")) |
417 | |
418 | def test_config_written(self): |
419 | @@ -92,14 +88,14 @@ class TestAptProxyConfig(TestCase): |
420 | self.assertTrue(os.path.isfile(self.cfile)) |
421 | self.assertFalse(os.path.isfile(self.pfile)) |
422 | |
423 | - self.assertEqual(load_tfile_or_url(self.cfile), payload) |
424 | + self.assertEqual(util.load_file(self.cfile), payload) |
425 | |
426 | def test_config_replaced(self): |
427 | util.write_file(self.pfile, "content doesnt matter") |
428 | cc_apt_configure.apply_apt_config({'conf': "foo"}, |
429 | self.pfile, self.cfile) |
430 | self.assertTrue(os.path.isfile(self.cfile)) |
431 | - self.assertEqual(load_tfile_or_url(self.cfile), "foo") |
432 | + self.assertEqual(util.load_file(self.cfile), "foo") |
433 | |
434 | def test_config_deleted(self): |
435 | # if no 'conf' is provided, delete any previously written file |
436 | diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py |
437 | index d2b96f0..23bd6e1 100644 |
438 | --- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py |
439 | +++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py |
440 | @@ -64,13 +64,6 @@ deb-src http://archive.ubuntu.com/ubuntu/ fakerelease main restricted |
441 | """) |
442 | |
443 | |
444 | -def load_tfile_or_url(*args, **kwargs): |
445 | - """load_tfile_or_url |
446 | - load file and return content after decoding |
447 | - """ |
448 | - return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) |
449 | - |
450 | - |
451 | class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): |
452 | """TestAptSourceConfigSourceList |
453 | Main Class to test sources list rendering |
454 | diff --git a/tests/unittests/test_handler/test_handler_apt_source_v1.py b/tests/unittests/test_handler/test_handler_apt_source_v1.py |
455 | index 46ca4ce..a3132fb 100644 |
456 | --- a/tests/unittests/test_handler/test_handler_apt_source_v1.py |
457 | +++ b/tests/unittests/test_handler/test_handler_apt_source_v1.py |
458 | @@ -39,13 +39,6 @@ S0ORP6HXET3+jC8BMG4tBWCTK/XEZw== |
459 | ADD_APT_REPO_MATCH = r"^[\w-]+:\w" |
460 | |
461 | |
462 | -def load_tfile_or_url(*args, **kwargs): |
463 | - """load_tfile_or_url |
464 | - load file and return content after decoding |
465 | - """ |
466 | - return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) |
467 | - |
468 | - |
469 | class FakeDistro(object): |
470 | """Fake Distro helper object""" |
471 | def update_package_sources(self): |
472 | @@ -125,7 +118,7 @@ class TestAptSourceConfig(TestCase): |
473 | |
474 | self.assertTrue(os.path.isfile(filename)) |
475 | |
476 | - contents = load_tfile_or_url(filename) |
477 | + contents = util.load_file(filename) |
478 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
479 | ("deb", "http://archive.ubuntu.com/ubuntu", |
480 | "karmic-backports", |
481 | @@ -157,13 +150,13 @@ class TestAptSourceConfig(TestCase): |
482 | self.apt_src_basic(self.aptlistfile, cfg) |
483 | |
484 | # extra verify on two extra files of this test |
485 | - contents = load_tfile_or_url(self.aptlistfile2) |
486 | + contents = util.load_file(self.aptlistfile2) |
487 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
488 | ("deb", "http://archive.ubuntu.com/ubuntu", |
489 | "precise-backports", |
490 | "main universe multiverse restricted"), |
491 | contents, flags=re.IGNORECASE)) |
492 | - contents = load_tfile_or_url(self.aptlistfile3) |
493 | + contents = util.load_file(self.aptlistfile3) |
494 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
495 | ("deb", "http://archive.ubuntu.com/ubuntu", |
496 | "lucid-backports", |
497 | @@ -220,7 +213,7 @@ class TestAptSourceConfig(TestCase): |
498 | |
499 | self.assertTrue(os.path.isfile(filename)) |
500 | |
501 | - contents = load_tfile_or_url(filename) |
502 | + contents = util.load_file(filename) |
503 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
504 | ("deb", params['MIRROR'], params['RELEASE'], |
505 | "multiverse"), |
506 | @@ -241,12 +234,12 @@ class TestAptSourceConfig(TestCase): |
507 | |
508 | # extra verify on two extra files of this test |
509 | params = self._get_default_params() |
510 | - contents = load_tfile_or_url(self.aptlistfile2) |
511 | + contents = util.load_file(self.aptlistfile2) |
512 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
513 | ("deb", params['MIRROR'], params['RELEASE'], |
514 | "main"), |
515 | contents, flags=re.IGNORECASE)) |
516 | - contents = load_tfile_or_url(self.aptlistfile3) |
517 | + contents = util.load_file(self.aptlistfile3) |
518 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
519 | ("deb", params['MIRROR'], params['RELEASE'], |
520 | "universe"), |
521 | @@ -296,7 +289,7 @@ class TestAptSourceConfig(TestCase): |
522 | |
523 | self.assertTrue(os.path.isfile(filename)) |
524 | |
525 | - contents = load_tfile_or_url(filename) |
526 | + contents = util.load_file(filename) |
527 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
528 | ("deb", |
529 | ('http://ppa.launchpad.net/smoser/' |
530 | @@ -336,14 +329,14 @@ class TestAptSourceConfig(TestCase): |
531 | 'filename': self.aptlistfile3} |
532 | |
533 | self.apt_src_keyid(self.aptlistfile, [cfg1, cfg2, cfg3], 3) |
534 | - contents = load_tfile_or_url(self.aptlistfile2) |
535 | + contents = util.load_file(self.aptlistfile2) |
536 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
537 | ("deb", |
538 | ('http://ppa.launchpad.net/smoser/' |
539 | 'cloud-init-test/ubuntu'), |
540 | "xenial", "universe"), |
541 | contents, flags=re.IGNORECASE)) |
542 | - contents = load_tfile_or_url(self.aptlistfile3) |
543 | + contents = util.load_file(self.aptlistfile3) |
544 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
545 | ("deb", |
546 | ('http://ppa.launchpad.net/smoser/' |
547 | @@ -375,7 +368,7 @@ class TestAptSourceConfig(TestCase): |
548 | |
549 | self.assertTrue(os.path.isfile(filename)) |
550 | |
551 | - contents = load_tfile_or_url(filename) |
552 | + contents = util.load_file(filename) |
553 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
554 | ("deb", |
555 | ('http://ppa.launchpad.net/smoser/' |
556 | diff --git a/tests/unittests/test_handler/test_handler_apt_source_v3.py b/tests/unittests/test_handler/test_handler_apt_source_v3.py |
557 | index e486862..7a64c23 100644 |
558 | --- a/tests/unittests/test_handler/test_handler_apt_source_v3.py |
559 | +++ b/tests/unittests/test_handler/test_handler_apt_source_v3.py |
560 | @@ -49,13 +49,6 @@ ADD_APT_REPO_MATCH = r"^[\w-]+:\w" |
561 | TARGET = None |
562 | |
563 | |
564 | -def load_tfile(*args, **kwargs): |
565 | - """load_tfile_or_url |
566 | - load file and return content after decoding |
567 | - """ |
568 | - return util.decode_binary(util.read_file_or_url(*args, **kwargs).contents) |
569 | - |
570 | - |
571 | class TestAptSourceConfig(t_help.FilesystemMockingTestCase): |
572 | """TestAptSourceConfig |
573 | Main Class to test apt configs |
574 | @@ -119,7 +112,7 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): |
575 | |
576 | self.assertTrue(os.path.isfile(filename)) |
577 | |
578 | - contents = load_tfile(filename) |
579 | + contents = util.load_file(filename) |
580 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
581 | ("deb", "http://test.ubuntu.com/ubuntu", |
582 | "karmic-backports", |
583 | @@ -151,13 +144,13 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): |
584 | self._apt_src_basic(self.aptlistfile, cfg) |
585 | |
586 | # extra verify on two extra files of this test |
587 | - contents = load_tfile(self.aptlistfile2) |
588 | + contents = util.load_file(self.aptlistfile2) |
589 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
590 | ("deb", "http://test.ubuntu.com/ubuntu", |
591 | "precise-backports", |
592 | "main universe multiverse restricted"), |
593 | contents, flags=re.IGNORECASE)) |
594 | - contents = load_tfile(self.aptlistfile3) |
595 | + contents = util.load_file(self.aptlistfile3) |
596 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
597 | ("deb", "http://test.ubuntu.com/ubuntu", |
598 | "lucid-backports", |
599 | @@ -174,7 +167,7 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): |
600 | |
601 | self.assertTrue(os.path.isfile(filename)) |
602 | |
603 | - contents = load_tfile(filename) |
604 | + contents = util.load_file(filename) |
605 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
606 | ("deb", params['MIRROR'], params['RELEASE'], |
607 | "multiverse"), |
608 | @@ -201,12 +194,12 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): |
609 | |
610 | # extra verify on two extra files of this test |
611 | params = self._get_default_params() |
612 | - contents = load_tfile(self.aptlistfile2) |
613 | + contents = util.load_file(self.aptlistfile2) |
614 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
615 | ("deb", params['MIRROR'], params['RELEASE'], |
616 | "main"), |
617 | contents, flags=re.IGNORECASE)) |
618 | - contents = load_tfile(self.aptlistfile3) |
619 | + contents = util.load_file(self.aptlistfile3) |
620 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
621 | ("deb", params['MIRROR'], params['RELEASE'], |
622 | "universe"), |
623 | @@ -240,7 +233,7 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): |
624 | |
625 | self.assertTrue(os.path.isfile(filename)) |
626 | |
627 | - contents = load_tfile(filename) |
628 | + contents = util.load_file(filename) |
629 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
630 | ("deb", |
631 | ('http://ppa.launchpad.net/smoser/' |
632 | @@ -277,14 +270,14 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): |
633 | 'keyid': "03683F77"}} |
634 | |
635 | self._apt_src_keyid(self.aptlistfile, cfg, 3) |
636 | - contents = load_tfile(self.aptlistfile2) |
637 | + contents = util.load_file(self.aptlistfile2) |
638 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
639 | ("deb", |
640 | ('http://ppa.launchpad.net/smoser/' |
641 | 'cloud-init-test/ubuntu'), |
642 | "xenial", "universe"), |
643 | contents, flags=re.IGNORECASE)) |
644 | - contents = load_tfile(self.aptlistfile3) |
645 | + contents = util.load_file(self.aptlistfile3) |
646 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
647 | ("deb", |
648 | ('http://ppa.launchpad.net/smoser/' |
649 | @@ -310,7 +303,7 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): |
650 | |
651 | self.assertTrue(os.path.isfile(self.aptlistfile)) |
652 | |
653 | - contents = load_tfile(self.aptlistfile) |
654 | + contents = util.load_file(self.aptlistfile) |
655 | self.assertTrue(re.search(r"%s %s %s %s\n" % |
656 | ("deb", |
657 | ('http://ppa.launchpad.net/smoser/' |
658 | diff --git a/tests/unittests/test_handler/test_handler_ntp.py b/tests/unittests/test_handler/test_handler_ntp.py |
659 | index 17c5355..f109c0e 100644 |
660 | --- a/tests/unittests/test_handler/test_handler_ntp.py |
661 | +++ b/tests/unittests/test_handler/test_handler_ntp.py |
662 | @@ -155,9 +155,9 @@ class TestNtp(FilesystemMockingTestCase): |
663 | path=confpath, |
664 | template_fn=template_fn, |
665 | template=None) |
666 | - content = util.read_file_or_url('file://' + confpath).contents |
667 | self.assertEqual( |
668 | - "servers []\npools ['10.0.0.1', '10.0.0.2']\n", content.decode()) |
669 | + "servers []\npools ['10.0.0.1', '10.0.0.2']\n", |
670 | + util.load_file(confpath)) |
671 | |
672 | def test_write_ntp_config_template_defaults_pools_w_empty_lists(self): |
673 | """write_ntp_config_template defaults pools servers upon empty config. |
674 | @@ -176,10 +176,9 @@ class TestNtp(FilesystemMockingTestCase): |
675 | path=confpath, |
676 | template_fn=template_fn, |
677 | template=None) |
678 | - content = util.read_file_or_url('file://' + confpath).contents |
679 | self.assertEqual( |
680 | "servers []\npools {0}\n".format(pools), |
681 | - content.decode()) |
682 | + util.load_file(confpath)) |
683 | |
684 | def test_defaults_pools_empty_lists_sles(self): |
685 | """write_ntp_config_template defaults opensuse pools upon empty config. |
686 | @@ -196,11 +195,11 @@ class TestNtp(FilesystemMockingTestCase): |
687 | path=confpath, |
688 | template_fn=template_fn, |
689 | template=None) |
690 | - content = util.read_file_or_url('file://' + confpath).contents |
691 | for pool in default_pools: |
692 | self.assertIn('opensuse', pool) |
693 | self.assertEqual( |
694 | - "servers []\npools {0}\n".format(default_pools), content.decode()) |
695 | + "servers []\npools {0}\n".format(default_pools), |
696 | + util.load_file(confpath)) |
697 | self.assertIn( |
698 | "Adding distro default ntp pool servers: {0}".format( |
699 | ",".join(default_pools)), |
700 | @@ -217,10 +216,9 @@ class TestNtp(FilesystemMockingTestCase): |
701 | path=confpath, |
702 | template_fn=template_fn, |
703 | template=None) |
704 | - content = util.read_file_or_url('file://' + confpath).contents |
705 | self.assertEqual( |
706 | "[Time]\nNTP=%s %s \n" % (" ".join(servers), " ".join(pools)), |
707 | - content.decode()) |
708 | + util.load_file(confpath)) |
709 | |
710 | def test_distro_ntp_client_configs(self): |
711 | """Test we have updated ntp client configs on different distros""" |
712 | @@ -267,17 +265,17 @@ class TestNtp(FilesystemMockingTestCase): |
713 | cc_ntp.write_ntp_config_template(distro, servers=servers, |
714 | pools=pools, path=confpath, |
715 | template_fn=template_fn) |
716 | - content = util.read_file_or_url('file://' + confpath).contents |
717 | + content = util.load_file(confpath) |
718 | if client in ['ntp', 'chrony']: |
719 | expected_servers = '\n'.join([ |
720 | 'server {0} iburst'.format(srv) for srv in servers]) |
721 | print('distro=%s client=%s' % (distro, client)) |
722 | - self.assertIn(expected_servers, content.decode('utf-8'), |
723 | + self.assertIn(expected_servers, content, |
724 | ('failed to render {0} conf' |
725 | ' for distro:{1}'.format(client, distro))) |
726 | expected_pools = '\n'.join([ |
727 | 'pool {0} iburst'.format(pool) for pool in pools]) |
728 | - self.assertIn(expected_pools, content.decode('utf-8'), |
729 | + self.assertIn(expected_pools, content, |
730 | ('failed to render {0} conf' |
731 | ' for distro:{1}'.format(client, distro))) |
732 | elif client == 'systemd-timesyncd': |
733 | @@ -286,7 +284,7 @@ class TestNtp(FilesystemMockingTestCase): |
734 | "# See timesyncd.conf(5) for details.\n\n" + |
735 | "[Time]\nNTP=%s %s \n" % (" ".join(servers), |
736 | " ".join(pools))) |
737 | - self.assertEqual(expected_content, content.decode()) |
738 | + self.assertEqual(expected_content, content) |
739 | |
740 | def test_no_ntpcfg_does_nothing(self): |
741 | """When no ntp section is defined handler logs a warning and noops.""" |
742 | @@ -308,10 +306,10 @@ class TestNtp(FilesystemMockingTestCase): |
743 | confpath = ntpconfig['confpath'] |
744 | m_select.return_value = ntpconfig |
745 | cc_ntp.handle('cc_ntp', valid_empty_config, mycloud, None, []) |
746 | - content = util.read_file_or_url('file://' + confpath).contents |
747 | pools = cc_ntp.generate_server_names(mycloud.distro.name) |
748 | self.assertEqual( |
749 | - "servers []\npools {0}\n".format(pools), content.decode()) |
750 | + "servers []\npools {0}\n".format(pools), |
751 | + util.load_file(confpath)) |
752 | self.assertNotIn('Invalid config:', self.logs.getvalue()) |
753 | |
754 | @skipUnlessJsonSchema() |
755 | @@ -333,9 +331,8 @@ class TestNtp(FilesystemMockingTestCase): |
756 | "Invalid config:\nntp.pools.0: 123 is not of type 'string'\n" |
757 | "ntp.servers.1: None is not of type 'string'", |
758 | self.logs.getvalue()) |
759 | - content = util.read_file_or_url('file://' + confpath).contents |
760 | self.assertEqual("servers ['valid', None]\npools [123]\n", |
761 | - content.decode()) |
762 | + util.load_file(confpath)) |
763 | |
764 | @skipUnlessJsonSchema() |
765 | @mock.patch('cloudinit.config.cc_ntp.select_ntp_client') |
766 | @@ -357,9 +354,8 @@ class TestNtp(FilesystemMockingTestCase): |
767 | "Invalid config:\nntp.pools: 123 is not of type 'array'\n" |
768 | "ntp.servers: 'non-array' is not of type 'array'", |
769 | self.logs.getvalue()) |
770 | - content = util.read_file_or_url('file://' + confpath).contents |
771 | self.assertEqual("servers non-array\npools 123\n", |
772 | - content.decode()) |
773 | + util.load_file(confpath)) |
774 | |
775 | @skipUnlessJsonSchema() |
776 | @mock.patch('cloudinit.config.cc_ntp.select_ntp_client') |
777 | @@ -381,10 +377,9 @@ class TestNtp(FilesystemMockingTestCase): |
778 | "Invalid config:\nntp: Additional properties are not allowed " |
779 | "('invalidkey' was unexpected)", |
780 | self.logs.getvalue()) |
781 | - content = util.read_file_or_url('file://' + confpath).contents |
782 | self.assertEqual( |
783 | "servers []\npools ['0.mycompany.pool.ntp.org']\n", |
784 | - content.decode()) |
785 | + util.load_file(confpath)) |
786 | |
787 | @skipUnlessJsonSchema() |
788 | @mock.patch('cloudinit.config.cc_ntp.select_ntp_client') |
789 | @@ -407,10 +402,10 @@ class TestNtp(FilesystemMockingTestCase): |
790 | " has non-unique elements\nntp.servers: " |
791 | "['10.0.0.1', '10.0.0.1'] has non-unique elements", |
792 | self.logs.getvalue()) |
793 | - content = util.read_file_or_url('file://' + confpath).contents |
794 | self.assertEqual( |
795 | "servers ['10.0.0.1', '10.0.0.1']\n" |
796 | - "pools ['0.mypool.org', '0.mypool.org']\n", content.decode()) |
797 | + "pools ['0.mypool.org', '0.mypool.org']\n", |
798 | + util.load_file(confpath)) |
799 | |
800 | @mock.patch('cloudinit.config.cc_ntp.select_ntp_client') |
801 | def test_ntp_handler_timesyncd(self, m_select): |
802 | @@ -426,10 +421,9 @@ class TestNtp(FilesystemMockingTestCase): |
803 | confpath = ntpconfig['confpath'] |
804 | m_select.return_value = ntpconfig |
805 | cc_ntp.handle('cc_ntp', cfg, mycloud, None, []) |
806 | - content = util.read_file_or_url('file://' + confpath).contents |
807 | self.assertEqual( |
808 | "[Time]\nNTP=192.168.2.1 192.168.2.2 0.mypool.org \n", |
809 | - content.decode()) |
810 | + util.load_file(confpath)) |
811 | |
812 | @mock.patch('cloudinit.config.cc_ntp.select_ntp_client') |
813 | def test_ntp_handler_enabled_false(self, m_select): |
814 | @@ -466,10 +460,9 @@ class TestNtp(FilesystemMockingTestCase): |
815 | m_util.subp.assert_called_with( |
816 | ['systemctl', 'reload-or-restart', |
817 | service_name], capture=True) |
818 | - content = util.read_file_or_url('file://' + confpath).contents |
819 | self.assertEqual( |
820 | "servers []\npools {0}\n".format(pools), |
821 | - content.decode()) |
822 | + util.load_file(confpath)) |
823 | |
824 | def test_opensuse_picks_chrony(self): |
825 | """Test opensuse picks chrony or ntp on certain distro versions""" |
826 | @@ -638,10 +631,9 @@ class TestNtp(FilesystemMockingTestCase): |
827 | mock_path = 'cloudinit.config.cc_ntp.temp_utils._TMPDIR' |
828 | with mock.patch(mock_path, self.new_root): |
829 | cc_ntp.handle('notimportant', cfg, mycloud, None, None) |
830 | - content = util.read_file_or_url('file://' + confpath).contents |
831 | self.assertEqual( |
832 | "servers []\npools ['mypool.org']\n%s" % custom, |
833 | - content.decode()) |
834 | + util.load_file(confpath)) |
835 | |
836 | @mock.patch('cloudinit.config.cc_ntp.supplemental_schema_validation') |
837 | @mock.patch('cloudinit.config.cc_ntp.reload_ntp') |
838 | @@ -675,10 +667,9 @@ class TestNtp(FilesystemMockingTestCase): |
839 | with mock.patch(mock_path, self.new_root): |
840 | cc_ntp.handle('notimportant', |
841 | {'ntp': cfg}, mycloud, None, None) |
842 | - content = util.read_file_or_url('file://' + confpath).contents |
843 | self.assertEqual( |
844 | "servers []\npools ['mypool.org']\n%s" % custom, |
845 | - content.decode()) |
846 | + util.load_file(confpath)) |
847 | m_schema.assert_called_with(expected_merged_cfg) |
848 | |
849 |
PASSED: Continuous integration, rev:d2de560b2ea 0471d9730ab16cd 9fa6334be1ffcc /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 999/
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 999/rebuild
https:/