Merge ~raharper/curtin:feature/write-pollinate-user-data into curtin:master
- Git
- lp:~raharper/curtin
- feature/write-pollinate-user-data
- Merge into master
Status: | Merged |
---|---|
Approved by: | Scott Moser |
Approved revision: | 7c2f63b7d0555f481115f4ed5200ed523bee865b |
Merge reported by: | Scott Moser |
Merged at revision: | bb2d120b490ab778b0929dfaf20341b682be98e1 |
Proposed branch: | ~raharper/curtin:feature/write-pollinate-user-data |
Merge into: | curtin:master |
Diff against target: |
499 lines (+395/-1) 8 files modified
curtin/commands/curthooks.py (+63/-0) curtin/url_helper.py (+60/-0) doc/topics/config.rst (+31/-0) examples/tests/pollinate-useragent.yaml (+4/-0) tests/unittests/test_curthooks.py (+72/-0) tests/unittests/test_url_helper.py (+98/-0) tests/vmtests/report_webhook_logger.py (+1/-1) tests/vmtests/test_pollinate_useragent.py (+66/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Chad Smith | Needs Fixing | ||
Server Team CI bot | continuous-integration | Approve | |
Review via email: mp+337367@code.launchpad.net |
Commit message
Add pollinate user-agent configuration support.
The pollinate client in Ubuntu images supports custom data to be
appended to the user-agent value sent when contacting the pollen
server. This patch allows users to add any key/value pairs which
will be added to the pollinate client configuration.
By default, curtin will configure pollinate with the curtin version
and if being deployed by MAAS, the MAAS version. Users can also
disable configuration of the pollinate client.
Additionally fix the vmtest webhook logger to encode writes when
handling GETs.
Description of the change
Add pollinate user-agent configuration support
The pollinate client in Ubuntu images supports custom data to be
appended to the user-agent value sent when contacting the pollen
server. This patch allows users to add any key/value pairs which
will be added to the pollinate client configuration.
By default, curtin will configure pollinate with the curtin version
and if being deployed by MAAS, the MAAS version. Users can also
disable configuration of the pollinate client.
Additional fixes
vmtest:
report_
Server Team CI bot (server-team-bot) wrote : | # |
Scott Moser (smoser) wrote : | # |
i don't love the pollinate daemon install.
is it guaranteed that pollinate daemon is running and available before pollinate runs ?
we really should change pollinate to log somewhere what its user-agent is, or provide 'pollinate --user-agent' and just print it.
Ryan Harper (raharper) wrote : | # |
1) we install pollen during the curtin install
2) we restart the service in our boot scripts
3) we manually invoke pollinate in boot scripts
So, yes, we ensure local pollen server is running and then force a pollinate client connection to the localhost service.
If that happens, we can modify the vmtest.
Scott Moser (smoser) wrote : | # |
Scott Moser (smoser) wrote : | # |
if we can get that into pollinate, then we should put this test into the base class, and always collect the output of 'pollinate --print-user-agent' and check it is either error or has what we expect.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:eb38070b9ea
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
i think this is good, but lets just use --print-user-agent.
one other inlinecomment.
Chad Smith (chad.smith) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:941e606e02b
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:3a38395ef70
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) wrote : | # |
Couple minor inlines remaining in case you haven't already fixed them.
Ryan Harper (raharper) wrote : | # |
Thanks Chad,
I've taken your suggestions and added additional exception handling in get_maas_version.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:c8e5b4faabb
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:82350f997cc
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
we suggested to change the maas version to something with 'vmtest' in it to not be confusin.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:7c2f63b7d05
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) wrote : | # |
LGTM! thanks for the changes to integration test data.
Chad Smith (chad.smith) wrote : | # |
Thank you for your merge proposal.
Your branch has been set to 'Work in progress'.
Please set the branch back to 'Needs Review' after resolving the issues below.
Thanks again,
Your friendly neighborhood cloud-init robot.
Please fix the following issues:
-------
No commit message in Launchpad
-------
For more information, see commit message guidelines at
https:/
Chad Smith (chad.smith) wrote : | # |
Thank you for your merge proposal.
Your branch has been set to 'Work in progress'.
Please set the branch back to 'Needs Review' after resolving the issues below.
Thanks again,
Your friendly neighborhood curtin robot.
Please fix the following issues:
-------
No commit message in Launchpad
-------
For more information, see commit message guidelines at
https:/
Preview Diff
1 | diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py |
2 | index 64e5180..9e51a65 100644 |
3 | --- a/curtin/commands/curthooks.py |
4 | +++ b/curtin/commands/curthooks.py |
5 | @@ -16,8 +16,10 @@ from curtin import futil |
6 | from curtin.log import LOG |
7 | from curtin import swap |
8 | from curtin import util |
9 | +from curtin import version as curtin_version |
10 | from curtin.reporter import events |
11 | from curtin.commands import apply_net, apt_config |
12 | +from curtin.url_helper import get_maas_version |
13 | |
14 | from . import populate_one_subcmd |
15 | |
16 | @@ -717,6 +719,61 @@ def system_upgrade(cfg, target): |
17 | util.system_upgrade(target=target) |
18 | |
19 | |
20 | +def inject_pollinate_user_agent_config(ua_cfg, target): |
21 | + """Write out user-agent config dictionary to pollinate's |
22 | + user-agent file (/etc/pollinate/add-user-agent) in target. |
23 | + """ |
24 | + if not isinstance(ua_cfg, dict): |
25 | + raise ValueError('ua_cfg is not a dictionary: %s', ua_cfg) |
26 | + |
27 | + pollinate_cfg = util.target_path(target, '/etc/pollinate/add-user-agent') |
28 | + comment = "# written by curtin" |
29 | + content = "\n".join(["%s/%s %s" % (ua_key, ua_val, comment) |
30 | + for ua_key, ua_val in ua_cfg.items()]) + "\n" |
31 | + util.write_file(pollinate_cfg, content=content) |
32 | + |
33 | + |
34 | +def handle_pollinate_user_agent(cfg, target): |
35 | + """Configure the pollinate user-agent if provided configuration |
36 | + |
37 | + pollinate: |
38 | + user_agent: false # disable writing out a user-agent string |
39 | + |
40 | + # custom agent key/value pairs |
41 | + pollinate: |
42 | + user_agent: |
43 | + key1: value1 |
44 | + key2: value2 |
45 | + |
46 | + No config will result in curtin fetching: |
47 | + curtin version |
48 | + maas version (via endpoint URL, if present) |
49 | + """ |
50 | + |
51 | + pcfg = cfg.get('pollinate') |
52 | + if not isinstance(pcfg, dict): |
53 | + pcfg = {'user_agent': {}} |
54 | + |
55 | + uacfg = pcfg.get('user_agent', {}) |
56 | + if uacfg is False: |
57 | + return |
58 | + |
59 | + # set curtin version |
60 | + uacfg['curtin'] = curtin_version.version_string() |
61 | + |
62 | + # maas configures a curtin reporting webhook handler with |
63 | + # an endpoint URL. This url is used to query the MAAS REST |
64 | + # api to extract the exact maas version. |
65 | + maas_reporting = cfg.get('reporting', {}).get('maas', None) |
66 | + if maas_reporting: |
67 | + endpoint = maas_reporting.get('endpoint') |
68 | + maas_version = get_maas_version(endpoint) |
69 | + if maas_version: |
70 | + uacfg['maas'] = maas_version['version'] |
71 | + |
72 | + inject_pollinate_user_agent_config(uacfg, target) |
73 | + |
74 | + |
75 | def handle_cloudconfig(cfg, base_dir=None): |
76 | """write cloud-init configuration files into base_dir. |
77 | |
78 | @@ -1012,6 +1069,12 @@ def curthooks(args): |
79 | description="updating packages on target system"): |
80 | system_upgrade(cfg, target) |
81 | |
82 | + with events.ReportEventStack( |
83 | + name=stack_prefix + '/pollinate-user-agent', |
84 | + reporting_enabled=True, level="INFO", |
85 | + description="configuring pollinate user-agent on target system"): |
86 | + handle_pollinate_user_agent(cfg, target) |
87 | + |
88 | # If a crypttab file was created by block_meta than it needs to be copied |
89 | # onto the target system, and update_initramfs() needs to be run, so that |
90 | # the cryptsetup hooks are properly configured on the installed system and |
91 | diff --git a/curtin/url_helper.py b/curtin/url_helper.py |
92 | index 8343ebe..d4d43a9 100644 |
93 | --- a/curtin/url_helper.py |
94 | +++ b/curtin/url_helper.py |
95 | @@ -117,6 +117,66 @@ def download(url, path, reporthook=None, data=None): |
96 | wfp.close() |
97 | |
98 | |
99 | +def get_maas_version(endpoint): |
100 | + """ Attempt to return the MAAS version via api calls to the specified |
101 | + endpoint. |
102 | + |
103 | + MAAS endpoint url looks like this: |
104 | + |
105 | + http://10.245.168.2/MAAS/metadata/status/node-f0462064-20f6-11e5-990a-d4bed9a84493 |
106 | + |
107 | + We need the MAAS_URL, which is http://10.245.168.2 |
108 | + |
109 | + Returns a maas version dictionary: |
110 | + {'subversion': '16.04.1', |
111 | + 'capabilities': ['networks-management', 'static-ipaddresses', |
112 | + 'ipv6-deployment-ubuntu', 'devices-management', |
113 | + 'storage-deployment-ubuntu', |
114 | + 'network-deployment-ubuntu', |
115 | + 'bridging-interface-ubuntu', |
116 | + 'bridging-automatic-ubuntu'], |
117 | + 'version': '2.1.5+bzr5596-0ubuntu1' |
118 | + } |
119 | + """ |
120 | + # https://docs.ubuntu.com/maas/devel/en/api indicates that |
121 | + # we leave 1.0 in here for maas 1.9 endpoints |
122 | + MAAS_API_SUPPORTED_VERSIONS = ["1.0", "2.0"] |
123 | + |
124 | + try: |
125 | + parsed = urlparse(endpoint) |
126 | + except AttributeError as e: |
127 | + LOG.warn('Failed to parse endpoint URL: %s', e) |
128 | + return None |
129 | + |
130 | + maas_host = "%s://%s" % (parsed.scheme, parsed.netloc) |
131 | + maas_api_version_url = "%s/MAAS/api/version/" % (maas_host) |
132 | + |
133 | + try: |
134 | + result = geturl(maas_api_version_url) |
135 | + except UrlError as e: |
136 | + LOG.warn('Failed to query MAAS API version URL: %s', e) |
137 | + return None |
138 | + |
139 | + api_version = result.decode('utf-8') |
140 | + if api_version not in MAAS_API_SUPPORTED_VERSIONS: |
141 | + LOG.warn('Endpoint "%s" API version "%s" not in MAAS supported' |
142 | + 'versions: "%s"', endpoint, api_version, |
143 | + MAAS_API_SUPPORTED_VERSIONS) |
144 | + return None |
145 | + |
146 | + maas_version_url = "%s/MAAS/api/%s/version/" % (maas_host, api_version) |
147 | + maas_version = None |
148 | + try: |
149 | + result = geturl(maas_version_url) |
150 | + maas_version = json.loads(result.decode('utf-8')) |
151 | + except UrlError as e: |
152 | + LOG.warn('Failed to query MAAS version via URL: %s', e) |
153 | + except (ValueError, TypeError): |
154 | + LOG.warn('Failed to load MAAS version result: %s', result) |
155 | + |
156 | + return maas_version |
157 | + |
158 | + |
159 | def _get_headers(headers=None): |
160 | allheaders = DEFAULT_HEADERS.copy() |
161 | if headers is not None: |
162 | diff --git a/doc/topics/config.rst b/doc/topics/config.rst |
163 | index fdc524f..76e520d 100644 |
164 | --- a/doc/topics/config.rst |
165 | +++ b/doc/topics/config.rst |
166 | @@ -23,6 +23,7 @@ Curtin's top level config keys are as follows: |
167 | - kexec (``kexec``) |
168 | - multipath (``multipath``) |
169 | - network (``network``) |
170 | +- pollinate (``pollinate``) |
171 | - power_state (``power_state``) |
172 | - proxy (``proxy``) |
173 | - reporting (``reporting``) |
174 | @@ -348,6 +349,36 @@ Configure networking (see Networking section for details). |
175 | - type: dhcp4 |
176 | |
177 | |
178 | +pollinate |
179 | +~~~~~~~~~ |
180 | +Configure pollinate user-agent |
181 | + |
182 | +Curtin will automatically include Curtin's version in the pollinate user-agent. |
183 | +If a MAAS server is being used, Curtin will query the MAAS version and include |
184 | +this value as well. |
185 | + |
186 | +**user_agent**: [*<mapping>* | *<boolean>*] |
187 | + |
188 | +Mapping is a dictionary of key value pairs which will result in the string |
189 | +'key/value' being present in the pollinate user-agent string sent to the |
190 | +pollen server. |
191 | + |
192 | +Setting the ``user_agent`` value to false will disable writting of the |
193 | +user-agent string. |
194 | + |
195 | +**Example**:: |
196 | + |
197 | + pollinate: |
198 | + user_agent: |
199 | + curtin: 17.1-33-g92fbc491 |
200 | + maas: 2.1.5+bzr5596-0ubuntu1 |
201 | + machine: bob27 |
202 | + app: 63.12 |
203 | + |
204 | + pollinate: |
205 | + user_agent: false |
206 | + |
207 | + |
208 | power_state |
209 | ~~~~~~~~~~~ |
210 | Curtin can configure the target machine into a specific power state after |
211 | diff --git a/examples/tests/pollinate-useragent.yaml b/examples/tests/pollinate-useragent.yaml |
212 | new file mode 100644 |
213 | index 0000000..dcf373c |
214 | --- /dev/null |
215 | +++ b/examples/tests/pollinate-useragent.yaml |
216 | @@ -0,0 +1,4 @@ |
217 | +pollinate: |
218 | + user_agent: |
219 | + curtin-vmtest-app1: 32.1.5+bzr5596-0ubuntu1 |
220 | + curtin-vmtest-app2: uc16.snap1/edge |
221 | diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py |
222 | index e4360e8..a8275c7 100644 |
223 | --- a/tests/unittests/test_curthooks.py |
224 | +++ b/tests/unittests/test_curthooks.py |
225 | @@ -808,4 +808,76 @@ class TestCurthooksWriteFiles(CiTestCase): |
226 | path=cc_target + expected_cfg['file1']['path'], |
227 | perms='0644') |
228 | |
229 | + |
230 | +class TestCurthooksPollinate(CiTestCase): |
231 | + def setUp(self): |
232 | + super(TestCurthooksPollinate, self).setUp() |
233 | + self.add_patch('curtin.version.version_string', 'mock_curtin_version') |
234 | + self.add_patch('curtin.util.write_file', 'mock_write') |
235 | + self.add_patch('curtin.commands.curthooks.get_maas_version', |
236 | + 'mock_maas_version') |
237 | + self.target = self.tmp_dir() |
238 | + |
239 | + def test_handle_pollinate_user_agent_disable(self): |
240 | + """ handle_pollinate_user_agent does nothing if disabled """ |
241 | + cfg = {'pollinate': {'user_agent': False}} |
242 | + curthooks.handle_pollinate_user_agent(cfg, self.target) |
243 | + self.assertEqual(0, self.mock_curtin_version.call_count) |
244 | + self.assertEqual(0, self.mock_maas_version.call_count) |
245 | + self.assertEqual(0, self.mock_write.call_count) |
246 | + |
247 | + def test_handle_pollinate_user_agent_default(self): |
248 | + """ handle_pollinate_user_agent checks curtin/maas version by default |
249 | + """ |
250 | + cfg = {'reporting': {'maas': {'endpoint': 'http://127.0.0.1/foo'}}} |
251 | + curthooks.handle_pollinate_user_agent(cfg, self.target) |
252 | + self.assertEqual(1, self.mock_curtin_version.call_count) |
253 | + self.assertEqual(1, self.mock_maas_version.call_count) |
254 | + self.assertEqual(1, self.mock_write.call_count) |
255 | + |
256 | + def test_handle_pollinate_user_agent_default_no_maas(self): |
257 | + """ handle_pollinate_user_agent checks curtin version, skips maas """ |
258 | + cfg = {} |
259 | + curthooks.handle_pollinate_user_agent(cfg, self.target) |
260 | + self.assertEqual(1, self.mock_curtin_version.call_count) |
261 | + self.assertEqual(0, self.mock_maas_version.call_count) |
262 | + self.assertEqual(1, self.mock_write.call_count) |
263 | + |
264 | + @patch('curtin.commands.curthooks.inject_pollinate_user_agent_config') |
265 | + def test_handle_pollinate_user_agent_custom(self, mock_inject): |
266 | + """ handle_pollinate_user_agent merges custom with default config """ |
267 | + self.mock_curtin_version.return_value = 'curtin-version' |
268 | + cfg = {'pollinate': {'user_agent': {'myapp': 'myversion'}}} |
269 | + curthooks.handle_pollinate_user_agent(cfg, self.target) |
270 | + self.assertEqual(1, self.mock_curtin_version.call_count) |
271 | + self.assertEqual(0, self.mock_maas_version.call_count) |
272 | + expected_cfg = { |
273 | + 'curtin': 'curtin-version', |
274 | + 'myapp': 'myversion', |
275 | + } |
276 | + mock_inject.assert_called_with(expected_cfg, self.target) |
277 | + |
278 | + |
279 | +class TestCurthooksInjectPollinate(CiTestCase): |
280 | + def setUp(self): |
281 | + super(TestCurthooksInjectPollinate, self).setUp() |
282 | + self.target = self.tmp_dir() |
283 | + self.user_agent = os.path.join(self.target, |
284 | + 'etc/pollinate/add-user-agent') |
285 | + |
286 | + def test_inject_ua_output(self): |
287 | + cfg = {'mykey': 'myvalue', 'foobar': 127} |
288 | + expected_content = [ |
289 | + "mykey/myvalue # written by curtin\n", |
290 | + "foobar/127 # written by curtin\n" |
291 | + ] |
292 | + curthooks.inject_pollinate_user_agent_config(cfg, self.target) |
293 | + content = open(self.user_agent).readlines() |
294 | + for line in expected_content: |
295 | + self.assertIn(line, content) |
296 | + |
297 | + def test_inject_ua_raises_exception(self): |
298 | + with self.assertRaises(ValueError): |
299 | + curthooks.inject_pollinate_user_agent_config(None, self.target) |
300 | + |
301 | # vi: ts=4 expandtab syntax=python |
302 | diff --git a/tests/unittests/test_url_helper.py b/tests/unittests/test_url_helper.py |
303 | index 8fb3565..9cc9a5d 100644 |
304 | --- a/tests/unittests/test_url_helper.py |
305 | +++ b/tests/unittests/test_url_helper.py |
306 | @@ -1,6 +1,8 @@ |
307 | # This file is part of curtin. See LICENSE file for copyright and license info. |
308 | |
309 | import filecmp |
310 | +import json |
311 | +import mock |
312 | |
313 | from curtin import url_helper |
314 | |
315 | @@ -22,3 +24,99 @@ class TestDownload(CiTestCase): |
316 | url_helper.download("file://" + src_file, target_file) |
317 | self.assertTrue(filecmp.cmp(src_file, target_file), |
318 | "Downloaded file differed from source file.") |
319 | + |
320 | + |
321 | +class TestGetMaasVersion(CiTestCase): |
322 | + @mock.patch('curtin.url_helper.geturl') |
323 | + def test_get_maas_version(self, mock_get_url): |
324 | + """verify we fetch maas version from api """ |
325 | + host = '127.0.0.1' |
326 | + endpoint = 'http://%s/MAAS/metadata/node-1230348484' % host |
327 | + maas_version = {'version': '2.1.5+bzr5596-0ubuntu1'} |
328 | + maas_api_version = '2.0' |
329 | + mock_get_url.side_effect = iter([ |
330 | + maas_api_version.encode('utf-8'), |
331 | + ("%s" % json.dumps(maas_version)).encode('utf-8'), |
332 | + ]) |
333 | + result = url_helper.get_maas_version(endpoint) |
334 | + self.assertEqual(maas_version, result) |
335 | + mock_get_url.assert_has_calls([ |
336 | + mock.call('http://%s/MAAS/api/version/' % host), |
337 | + mock.call('http://%s/MAAS/api/%s/version/' % (host, |
338 | + maas_api_version)) |
339 | + ]) |
340 | + |
341 | + @mock.patch('curtin.url_helper.LOG') |
342 | + @mock.patch('curtin.url_helper.geturl') |
343 | + def test_get_maas_version_unsupported(self, mock_get_url, mock_log): |
344 | + """return value is None if endpoint returns unsupported MAAS version |
345 | + """ |
346 | + host = '127.0.0.1' |
347 | + endpoint = 'http://%s/MAAS/metadata/node-1230348484' % host |
348 | + supported = ['1.0', '2.0'] |
349 | + maas_api_version = 'garbage' |
350 | + mock_get_url.side_effect = iter([ |
351 | + maas_api_version.encode('utf-8'), |
352 | + ]) |
353 | + result = url_helper.get_maas_version(endpoint) |
354 | + self.assertIsNone(result) |
355 | + self.assertTrue(mock_log.warn.called) |
356 | + mock_log.warn.assert_called_with( |
357 | + 'Endpoint "%s" API version "%s" not in MAAS supported' |
358 | + 'versions: "%s"', endpoint, maas_api_version, supported) |
359 | + mock_get_url.assert_has_calls([ |
360 | + mock.call('http://%s/MAAS/api/version/' % host), |
361 | + ]) |
362 | + |
363 | + def test_get_maas_version_no_endpoint(self): |
364 | + """verify we return None with invalid endpoint """ |
365 | + host = None |
366 | + result = url_helper.get_maas_version(host) |
367 | + self.assertEqual(None, result) |
368 | + |
369 | + @mock.patch('curtin.url_helper.LOG') |
370 | + @mock.patch('curtin.url_helper.geturl') |
371 | + def test_get_maas_version_bad_json(self, mock_get_url, mock_log): |
372 | + """return value is None if endpoint returns invalid json """ |
373 | + host = '127.0.0.1' |
374 | + endpoint = 'http://%s/MAAS/metadata/node-1230348484' % host |
375 | + maas_api_version = '2.0' |
376 | + bad_json = '{1237'.encode('utf-8') |
377 | + mock_get_url.side_effect = iter([ |
378 | + maas_api_version.encode('utf-8'), |
379 | + bad_json, |
380 | + ]) |
381 | + result = url_helper.get_maas_version(endpoint) |
382 | + self.assertIsNone(result) |
383 | + self.assertTrue(mock_log.warn.called) |
384 | + mock_log.warn.assert_called_with( |
385 | + 'Failed to load MAAS version result: %s', bad_json) |
386 | + mock_get_url.assert_has_calls([ |
387 | + mock.call('http://%s/MAAS/api/version/' % host), |
388 | + mock.call('http://%s/MAAS/api/%s/version/' % (host, |
389 | + maas_api_version)) |
390 | + ]) |
391 | + |
392 | + @mock.patch('curtin.url_helper.LOG') |
393 | + @mock.patch('curtin.url_helper.geturl') |
394 | + def test_get_maas_version_network_error_returns_none(self, mock_get_url, |
395 | + mock_log): |
396 | + """return value is None if endpoint fails to respond""" |
397 | + host = '127.0.0.1' |
398 | + endpoint = 'http://%s/MAAS/metadata/node-1230348484' % host |
399 | + maas_api_version = '2.0' |
400 | + exception = url_helper.UrlError('404') |
401 | + mock_get_url.side_effect = iter([ |
402 | + maas_api_version.encode('utf-8'), |
403 | + exception, |
404 | + ]) |
405 | + result = url_helper.get_maas_version(endpoint) |
406 | + self.assertIsNone(result) |
407 | + self.assertTrue(mock_log.warn.called) |
408 | + mock_log.warn.assert_called_with( |
409 | + 'Failed to query MAAS version via URL: %s', exception) |
410 | + mock_get_url.assert_has_calls([ |
411 | + mock.call('http://%s/MAAS/api/version/' % host), |
412 | + mock.call('http://%s/MAAS/api/%s/version/' % (host, |
413 | + maas_api_version)) |
414 | + ]) |
415 | diff --git a/tests/vmtests/report_webhook_logger.py b/tests/vmtests/report_webhook_logger.py |
416 | index 954fa12..e95397c 100755 |
417 | --- a/tests/vmtests/report_webhook_logger.py |
418 | +++ b/tests/vmtests/report_webhook_logger.py |
419 | @@ -76,7 +76,7 @@ class ServerHandler(http_server.SimpleHTTPRequestHandler): |
420 | self._message = None |
421 | self.send_response(200) |
422 | self.end_headers() |
423 | - self.wfile.write("content of %s\n" % self.path) |
424 | + self.wfile.write(("content of %s\n" % self.path).encode('utf-8')) |
425 | |
426 | def do_POST(self): |
427 | length = int(self.headers['Content-Length']) |
428 | diff --git a/tests/vmtests/test_pollinate_useragent.py b/tests/vmtests/test_pollinate_useragent.py |
429 | new file mode 100644 |
430 | index 0000000..e5ad64d |
431 | --- /dev/null |
432 | +++ b/tests/vmtests/test_pollinate_useragent.py |
433 | @@ -0,0 +1,66 @@ |
434 | +# This file is part of curtin. See LICENSE file for copyright and license info. |
435 | + |
436 | +from . import VMBaseClass |
437 | +from .releases import base_vm_classes as relbase |
438 | + |
439 | +import re |
440 | +import textwrap |
441 | +from unittest import SkipTest |
442 | + |
443 | + |
444 | +class TestPollinateUserAgent(VMBaseClass): |
445 | + # Test configuring pollinate useragent |
446 | + conf_file = "examples/tests/pollinate-useragent.yaml" |
447 | + extra_disks = [] |
448 | + extra_nics = [] |
449 | + collect_scripts = VMBaseClass.collect_scripts + [textwrap.dedent(""" |
450 | + cd OUTPUT_COLLECT_D |
451 | + find /etc/network/interfaces.d > find_interfacesd |
452 | + cp -a /etc/pollinate etc_pollinate |
453 | + pollinate --print-user-agent > pollinate_print_user_agent |
454 | + """)] |
455 | + |
456 | + def test_pollinate_user_agent(self): |
457 | + self.output_files_exist(["pollinate_print_user_agent"]) |
458 | + agent_values = self.load_collect_file("pollinate_print_user_agent") |
459 | + if len(agent_values) == 0: |
460 | + pollver = re.search('pollinate\s(?P<version>\S+)', |
461 | + self.load_collect_file("debian-packages.txt")) |
462 | + msg = ("pollinate client '%s' does not support " |
463 | + "--print-user-agent'" % pollver['version']) |
464 | + raise SkipTest(msg) |
465 | + |
466 | + # curtin version is always present |
467 | + curtin_ua = "curtin/%s #from curtin" % self.get_curtin_version() |
468 | + ua_values = self.load_collect_file("etc_pollinate/add-user-agent") |
469 | + for line in ua_values.splitlines() + [curtin_ua]: |
470 | + """Each line is: |
471 | + key/value # comment goes here |
472 | + |
473 | + or |
474 | + |
475 | + key/value |
476 | + |
477 | + So we break at the first space |
478 | + """ |
479 | + ua_val = line.split()[0] |
480 | + # escape + and . that are likely in maas/curtin version strings |
481 | + regex = r'%s' % ua_val.replace('+', '\+').replace('.', '\.') |
482 | + hit = re.search(regex, agent_values) |
483 | + self.assertIsNotNone(hit) |
484 | + self.assertEqual(ua_val, hit.group()) |
485 | + |
486 | + |
487 | +class TrustyTestPollinateUserAgent(relbase.trusty, TestPollinateUserAgent): |
488 | + __test__ = True |
489 | + |
490 | + |
491 | +class XenialTestPollinateUserAgent(relbase.xenial, TestPollinateUserAgent): |
492 | + __test__ = True |
493 | + |
494 | + |
495 | +class BionicTestPollinateUserAgent(relbase.bionic, TestPollinateUserAgent): |
496 | + __test__ = True |
497 | + |
498 | + |
499 | +# vi: ts=4 expandtab syntax=python |
PASSED: Continuous integration, rev:b732e66eb53 fbb2a494119f3d3 756ef63f3be7f8 /jenkins. ubuntu. com/server/ job/curtin- ci/829/ /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-amd64/ 829 /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-arm64/ 829 /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-ppc64el/ 829 /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-s390x/ 829
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/curtin- ci/829/ rebuild
https:/