Merge ~raharper/cloud-init:ubuntu-devel-new-artful-release-v5 into cloud-init:ubuntu/devel
- Git
- lp:~raharper/cloud-init
- ubuntu-devel-new-artful-release-v5
- Merge into ubuntu/devel
Proposed by
Scott Moser
Status: | Merged | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 39513265b4e37280f4a56040ae1029634444da19 | ||||||||||||
Proposed branch: | ~raharper/cloud-init:ubuntu-devel-new-artful-release-v5 | ||||||||||||
Merge into: | cloud-init:ubuntu/devel | ||||||||||||
Diff against target: |
2252 lines (+1196/-148) 97 files modified
cloudinit/config/cc_ssh_authkey_fingerprints.py (+2/-2) cloudinit/config/cc_zypper_add_repo.py (+218/-0) cloudinit/net/dhcp.py (+42/-0) cloudinit/net/tests/test_dhcp.py (+111/-2) cloudinit/netinfo.py (+4/-4) cloudinit/simpletable.py (+62/-0) cloudinit/sources/DataSourceAltCloud.py (+2/-2) cloudinit/sources/DataSourceCloudStack.py (+13/-4) cloudinit/sources/DataSourceOVF.py (+47/-27) cloudinit/sources/helpers/azure.py (+14/-6) cloudinit/tests/helpers.py (+10/-0) cloudinit/tests/test_simpletable.py (+100/-0) config/cloud.cfg.tmpl (+3/-0) debian/changelog (+25/-3) debian/control (+0/-1) packages/debian/copyright (+10/-15) packages/pkg-deps.json (+0/-3) requirements.txt (+0/-3) systemd/cloud-final.service.tmpl (+3/-1) tests/cloud_tests/__init__.py (+1/-1) tests/cloud_tests/instances/nocloudkvm.py (+16/-15) tests/cloud_tests/testcases/bugs/README.md (+0/-0) tests/cloud_tests/testcases/bugs/lp1511485.yaml (+0/-0) tests/cloud_tests/testcases/bugs/lp1611074.yaml (+0/-0) tests/cloud_tests/testcases/bugs/lp1628337.yaml (+0/-0) tests/cloud_tests/testcases/examples/README.md (+0/-0) tests/cloud_tests/testcases/examples/TODO.md (+0/-0) tests/cloud_tests/testcases/examples/add_apt_repositories.yaml (+0/-0) tests/cloud_tests/testcases/examples/alter_completion_message.yaml (+0/-0) tests/cloud_tests/testcases/examples/configure_instance_trusted_ca_certificates.yaml (+0/-0) tests/cloud_tests/testcases/examples/configure_instances_ssh_keys.yaml (+0/-0) tests/cloud_tests/testcases/examples/including_user_groups.yaml (+0/-0) tests/cloud_tests/testcases/examples/install_arbitrary_packages.yaml (+0/-0) tests/cloud_tests/testcases/examples/install_run_chef_recipes.yaml (+0/-0) tests/cloud_tests/testcases/examples/run_apt_upgrade.yaml (+0/-0) tests/cloud_tests/testcases/examples/run_commands.yaml (+0/-0) tests/cloud_tests/testcases/examples/run_commands_first_boot.yaml (+0/-0) tests/cloud_tests/testcases/examples/setup_run_puppet.yaml (+0/-0) tests/cloud_tests/testcases/examples/writing_out_arbitrary_files.yaml (+0/-0) tests/cloud_tests/testcases/main/README.md (+0/-0) tests/cloud_tests/testcases/main/command_output_simple.yaml (+0/-0) tests/cloud_tests/testcases/modules/README.md (+0/-0) tests/cloud_tests/testcases/modules/TODO.md (+0/-0) tests/cloud_tests/testcases/modules/apt_configure_conf.yaml (+0/-0) tests/cloud_tests/testcases/modules/apt_configure_disable_suites.yaml (+0/-0) tests/cloud_tests/testcases/modules/apt_configure_primary.yaml (+0/-0) tests/cloud_tests/testcases/modules/apt_configure_proxy.yaml (+0/-0) tests/cloud_tests/testcases/modules/apt_configure_security.yaml (+0/-0) tests/cloud_tests/testcases/modules/apt_configure_sources_key.yaml (+0/-0) tests/cloud_tests/testcases/modules/apt_configure_sources_keyserver.yaml (+0/-0) tests/cloud_tests/testcases/modules/apt_configure_sources_list.yaml (+0/-0) tests/cloud_tests/testcases/modules/apt_configure_sources_ppa.yaml (+0/-0) tests/cloud_tests/testcases/modules/apt_pipelining_disable.yaml (+0/-0) tests/cloud_tests/testcases/modules/apt_pipelining_os.yaml (+0/-0) tests/cloud_tests/testcases/modules/bootcmd.yaml (+0/-0) tests/cloud_tests/testcases/modules/byobu.yaml (+0/-0) tests/cloud_tests/testcases/modules/ca_certs.yaml (+0/-0) tests/cloud_tests/testcases/modules/debug_disable.yaml (+0/-0) tests/cloud_tests/testcases/modules/debug_enable.yaml (+0/-0) tests/cloud_tests/testcases/modules/final_message.yaml (+0/-0) tests/cloud_tests/testcases/modules/keys_to_console.yaml (+0/-0) tests/cloud_tests/testcases/modules/landscape.yaml (+0/-0) tests/cloud_tests/testcases/modules/locale.yaml (+0/-0) tests/cloud_tests/testcases/modules/lxd_bridge.yaml (+0/-0) tests/cloud_tests/testcases/modules/lxd_dir.yaml (+0/-0) tests/cloud_tests/testcases/modules/ntp.yaml (+0/-0) tests/cloud_tests/testcases/modules/ntp_pools.yaml (+0/-0) tests/cloud_tests/testcases/modules/ntp_servers.yaml (+0/-0) tests/cloud_tests/testcases/modules/package_update_upgrade_install.yaml (+0/-0) tests/cloud_tests/testcases/modules/runcmd.yaml (+0/-0) tests/cloud_tests/testcases/modules/salt_minion.yaml (+0/-0) tests/cloud_tests/testcases/modules/seed_random_command.yaml (+0/-0) tests/cloud_tests/testcases/modules/seed_random_data.yaml (+0/-0) tests/cloud_tests/testcases/modules/set_hostname.yaml (+0/-0) tests/cloud_tests/testcases/modules/set_hostname_fqdn.yaml (+0/-0) tests/cloud_tests/testcases/modules/set_password.yaml (+0/-0) tests/cloud_tests/testcases/modules/set_password_expire.yaml (+0/-0) tests/cloud_tests/testcases/modules/set_password_list.yaml (+0/-0) tests/cloud_tests/testcases/modules/set_password_list_string.yaml (+0/-0) tests/cloud_tests/testcases/modules/snappy.yaml (+0/-0) tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_disable.yaml (+0/-0) tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_enable.yaml (+0/-0) tests/cloud_tests/testcases/modules/ssh_import_id.yaml (+0/-0) tests/cloud_tests/testcases/modules/ssh_keys_generate.yaml (+0/-0) tests/cloud_tests/testcases/modules/ssh_keys_provided.yaml (+0/-0) tests/cloud_tests/testcases/modules/timezone.yaml (+0/-0) tests/cloud_tests/testcases/modules/user_groups.yaml (+0/-0) tests/cloud_tests/testcases/modules/write_files.yaml (+0/-0) tests/unittests/test_datasource/test_altcloud.py (+2/-2) tests/unittests/test_datasource/test_azure_helper.py (+95/-48) tests/unittests/test_datasource/test_cloudstack.py (+7/-4) tests/unittests/test_datasource/test_ovf.py (+164/-0) tests/unittests/test_handler/test_handler_bootcmd.py (+1/-0) tests/unittests/test_handler/test_handler_zypper_add_repo.py (+237/-0) tests/unittests/test_handler/test_schema.py (+7/-1) tools/build-on-freebsd (+0/-1) tox.ini (+0/-3) |
||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Scott Moser | Approve | ||
Review via email: mp+331723@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Scott Moser (smoser) wrote : | # |
I am going to make a single change to the number of characters in the hash.
for some reason (probably git-version related), Ryan's git-describe created a 7 digit hash, mine did/does an 8 digit.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/cloudinit/config/cc_ssh_authkey_fingerprints.py b/cloudinit/config/cc_ssh_authkey_fingerprints.py |
2 | index 0066e97..35d8c57 100755 |
3 | --- a/cloudinit/config/cc_ssh_authkey_fingerprints.py |
4 | +++ b/cloudinit/config/cc_ssh_authkey_fingerprints.py |
5 | @@ -28,7 +28,7 @@ the keys can be specified, but defaults to ``md5``. |
6 | import base64 |
7 | import hashlib |
8 | |
9 | -from prettytable import PrettyTable |
10 | +from cloudinit.simpletable import SimpleTable |
11 | |
12 | from cloudinit.distros import ug_util |
13 | from cloudinit import ssh_util |
14 | @@ -74,7 +74,7 @@ def _pprint_key_entries(user, key_fn, key_entries, hash_meth='md5', |
15 | return |
16 | tbl_fields = ['Keytype', 'Fingerprint (%s)' % (hash_meth), 'Options', |
17 | 'Comment'] |
18 | - tbl = PrettyTable(tbl_fields) |
19 | + tbl = SimpleTable(tbl_fields) |
20 | for entry in key_entries: |
21 | if _is_printable_key(entry): |
22 | row = [] |
23 | diff --git a/cloudinit/config/cc_zypper_add_repo.py b/cloudinit/config/cc_zypper_add_repo.py |
24 | new file mode 100644 |
25 | index 0000000..aba2695 |
26 | --- /dev/null |
27 | +++ b/cloudinit/config/cc_zypper_add_repo.py |
28 | @@ -0,0 +1,218 @@ |
29 | +# |
30 | +# Copyright (C) 2017 SUSE LLC. |
31 | +# |
32 | +# This file is part of cloud-init. See LICENSE file for license information. |
33 | + |
34 | +"""zypper_add_repo: Add zyper repositories to the system""" |
35 | + |
36 | +import configobj |
37 | +import os |
38 | +from six import string_types |
39 | +from textwrap import dedent |
40 | + |
41 | +from cloudinit.config.schema import get_schema_doc |
42 | +from cloudinit import log as logging |
43 | +from cloudinit.settings import PER_ALWAYS |
44 | +from cloudinit import util |
45 | + |
46 | +distros = ['opensuse', 'sles'] |
47 | + |
48 | +schema = { |
49 | + 'id': 'cc_zypper_add_repo', |
50 | + 'name': 'ZypperAddRepo', |
51 | + 'title': 'Configure zypper behavior and add zypper repositories', |
52 | + 'description': dedent("""\ |
53 | + Configure zypper behavior by modifying /etc/zypp/zypp.conf. The |
54 | + configuration writer is "dumb" and will simply append the provided |
55 | + configuration options to the configuration file. Option settings |
56 | + that may be duplicate will be resolved by the way the zypp.conf file |
57 | + is parsed. The file is in INI format. |
58 | + Add repositories to the system. No validation is performed on the |
59 | + repository file entries, it is assumed the user is familiar with |
60 | + the zypper repository file format."""), |
61 | + 'distros': distros, |
62 | + 'examples': [dedent("""\ |
63 | + zypper: |
64 | + repos: |
65 | + - id: opensuse-oss |
66 | + name: os-oss |
67 | + baseurl: http://dl.opensuse.org/dist/leap/v/repo/oss/ |
68 | + enabled: 1 |
69 | + autorefresh: 1 |
70 | + - id: opensuse-oss-update |
71 | + name: os-oss-up |
72 | + baseurl: http://dl.opensuse.org/dist/leap/v/update |
73 | + # any setting per |
74 | + # https://en.opensuse.org/openSUSE:Standards_RepoInfo |
75 | + # enable and autorefresh are on by default |
76 | + config: |
77 | + reposdir: /etc/zypp/repos.dir |
78 | + servicesdir: /etc/zypp/services.d |
79 | + download.use_deltarpm: true |
80 | + # any setting in /etc/zypp/zypp.conf |
81 | + """)], |
82 | + 'frequency': PER_ALWAYS, |
83 | + 'type': 'object', |
84 | + 'properties': { |
85 | + 'zypper': { |
86 | + 'type': 'object', |
87 | + 'properties': { |
88 | + 'repos': { |
89 | + 'type': 'array', |
90 | + 'items': { |
91 | + 'type': 'object', |
92 | + 'properties': { |
93 | + 'id': { |
94 | + 'type': 'string', |
95 | + 'description': dedent("""\ |
96 | + The unique id of the repo, used when |
97 | + writing |
98 | + /etc/zypp/repos.d/<id>.repo.""") |
99 | + }, |
100 | + 'baseurl': { |
101 | + 'type': 'string', |
102 | + 'format': 'uri', # built-in format type |
103 | + 'description': 'The base repositoy URL' |
104 | + } |
105 | + }, |
106 | + 'required': ['id', 'baseurl'], |
107 | + 'additionalProperties': True |
108 | + }, |
109 | + 'minItems': 1 |
110 | + }, |
111 | + 'config': { |
112 | + 'type': 'object', |
113 | + 'description': dedent("""\ |
114 | + Any supported zypo.conf key is written to |
115 | + /etc/zypp/zypp.conf'""") |
116 | + } |
117 | + }, |
118 | + 'required': [], |
119 | + 'minProperties': 1, # Either config or repo must be provided |
120 | + 'additionalProperties': False, # only repos and config allowed |
121 | + } |
122 | + } |
123 | +} |
124 | + |
125 | +__doc__ = get_schema_doc(schema) # Supplement python help() |
126 | + |
127 | +LOG = logging.getLogger(__name__) |
128 | + |
129 | + |
130 | +def _canonicalize_id(repo_id): |
131 | + repo_id = repo_id.replace(" ", "_") |
132 | + return repo_id |
133 | + |
134 | + |
135 | +def _format_repo_value(val): |
136 | + if isinstance(val, bool): |
137 | + # zypp prefers 1/0 |
138 | + return 1 if val else 0 |
139 | + if isinstance(val, (list, tuple)): |
140 | + return "\n ".join([_format_repo_value(v) for v in val]) |
141 | + if not isinstance(val, string_types): |
142 | + return str(val) |
143 | + return val |
144 | + |
145 | + |
146 | +def _format_repository_config(repo_id, repo_config): |
147 | + to_be = configobj.ConfigObj() |
148 | + to_be[repo_id] = {} |
149 | + # Do basic translation of the items -> values |
150 | + for (k, v) in repo_config.items(): |
151 | + # For now assume that people using this know the format |
152 | + # of zypper repos and don't verify keys/values further |
153 | + to_be[repo_id][k] = _format_repo_value(v) |
154 | + lines = to_be.write() |
155 | + return "\n".join(lines) |
156 | + |
157 | + |
158 | +def _write_repos(repos, repo_base_path): |
159 | + """Write the user-provided repo definition files |
160 | + @param repos: A list of repo dictionary objects provided by the user's |
161 | + cloud config. |
162 | + @param repo_base_path: The directory path to which repo definitions are |
163 | + written. |
164 | + """ |
165 | + |
166 | + if not repos: |
167 | + return |
168 | + valid_repos = {} |
169 | + for index, user_repo_config in enumerate(repos): |
170 | + # Skip on absent required keys |
171 | + missing_keys = set(['id', 'baseurl']).difference(set(user_repo_config)) |
172 | + if missing_keys: |
173 | + LOG.warning( |
174 | + "Repo config at index %d is missing required config keys: %s", |
175 | + index, ",".join(missing_keys)) |
176 | + continue |
177 | + repo_id = user_repo_config.get('id') |
178 | + canon_repo_id = _canonicalize_id(repo_id) |
179 | + repo_fn_pth = os.path.join(repo_base_path, "%s.repo" % (canon_repo_id)) |
180 | + if os.path.exists(repo_fn_pth): |
181 | + LOG.info("Skipping repo %s, file %s already exists!", |
182 | + repo_id, repo_fn_pth) |
183 | + continue |
184 | + elif repo_id in valid_repos: |
185 | + LOG.info("Skipping repo %s, file %s already pending!", |
186 | + repo_id, repo_fn_pth) |
187 | + continue |
188 | + |
189 | + # Do some basic key formatting |
190 | + repo_config = dict( |
191 | + (k.lower().strip().replace("-", "_"), v) |
192 | + for k, v in user_repo_config.items() |
193 | + if k and k != 'id') |
194 | + |
195 | + # Set defaults if not present |
196 | + for field in ['enabled', 'autorefresh']: |
197 | + if field not in repo_config: |
198 | + repo_config[field] = '1' |
199 | + |
200 | + valid_repos[repo_id] = (repo_fn_pth, repo_config) |
201 | + |
202 | + for (repo_id, repo_data) in valid_repos.items(): |
203 | + repo_blob = _format_repository_config(repo_id, repo_data[-1]) |
204 | + util.write_file(repo_data[0], repo_blob) |
205 | + |
206 | + |
207 | +def _write_zypp_config(zypper_config): |
208 | + """Write to the default zypp configuration file /etc/zypp/zypp.conf""" |
209 | + if not zypper_config: |
210 | + return |
211 | + zypp_config = '/etc/zypp/zypp.conf' |
212 | + zypp_conf_content = util.load_file(zypp_config) |
213 | + new_settings = ['# Added via cloud.cfg'] |
214 | + for setting, value in zypper_config.items(): |
215 | + if setting == 'configdir': |
216 | + msg = 'Changing the location of the zypper configuration is ' |
217 | + msg += 'not supported, skipping "configdir" setting' |
218 | + LOG.warning(msg) |
219 | + continue |
220 | + if value: |
221 | + new_settings.append('%s=%s' % (setting, value)) |
222 | + if len(new_settings) > 1: |
223 | + new_config = zypp_conf_content + '\n'.join(new_settings) |
224 | + else: |
225 | + new_config = zypp_conf_content |
226 | + util.write_file(zypp_config, new_config) |
227 | + |
228 | + |
229 | +def handle(name, cfg, _cloud, log, _args): |
230 | + zypper_section = cfg.get('zypper') |
231 | + if not zypper_section: |
232 | + LOG.debug(("Skipping module named %s," |
233 | + " no 'zypper' relevant configuration found"), name) |
234 | + return |
235 | + repos = zypper_section.get('repos') |
236 | + if not repos: |
237 | + LOG.debug(("Skipping module named %s," |
238 | + " no 'repos' configuration found"), name) |
239 | + return |
240 | + zypper_config = zypper_section.get('config', {}) |
241 | + repo_base_path = zypper_config.get('reposdir', '/etc/zypp/repos.d/') |
242 | + |
243 | + _write_zypp_config(zypper_config) |
244 | + _write_repos(repos, repo_base_path) |
245 | + |
246 | +# vi: ts=4 expandtab |
247 | diff --git a/cloudinit/net/dhcp.py b/cloudinit/net/dhcp.py |
248 | index 0535063..0cba703 100644 |
249 | --- a/cloudinit/net/dhcp.py |
250 | +++ b/cloudinit/net/dhcp.py |
251 | @@ -4,6 +4,7 @@ |
252 | # |
253 | # This file is part of cloud-init. See LICENSE file for license information. |
254 | |
255 | +import configobj |
256 | import logging |
257 | import os |
258 | import re |
259 | @@ -11,9 +12,12 @@ import re |
260 | from cloudinit.net import find_fallback_nic, get_devicelist |
261 | from cloudinit import temp_utils |
262 | from cloudinit import util |
263 | +from six import StringIO |
264 | |
265 | LOG = logging.getLogger(__name__) |
266 | |
267 | +NETWORKD_LEASES_DIR = '/run/systemd/netif/leases' |
268 | + |
269 | |
270 | class InvalidDHCPLeaseFileError(Exception): |
271 | """Raised when parsing an empty or invalid dhcp.leases file. |
272 | @@ -118,4 +122,42 @@ def dhcp_discovery(dhclient_cmd_path, interface, cleandir): |
273 | return parse_dhcp_lease_file(lease_file) |
274 | |
275 | |
276 | +def networkd_parse_lease(content): |
277 | + """Parse a systemd lease file content as in /run/systemd/netif/leases/ |
278 | + |
279 | + Parse this (almost) ini style file even though it says: |
280 | + # This is private data. Do not parse. |
281 | + |
282 | + Simply return a dictionary of key/values.""" |
283 | + |
284 | + return dict(configobj.ConfigObj(StringIO(content), list_values=False)) |
285 | + |
286 | + |
287 | +def networkd_load_leases(leases_d=None): |
288 | + """Return a dictionary of dictionaries representing each lease |
289 | + found in lease_d.i |
290 | + |
291 | + The top level key will be the filename, which is typically the ifindex.""" |
292 | + |
293 | + if leases_d is None: |
294 | + leases_d = NETWORKD_LEASES_DIR |
295 | + |
296 | + ret = {} |
297 | + if not os.path.isdir(leases_d): |
298 | + return ret |
299 | + for lfile in os.listdir(leases_d): |
300 | + ret[lfile] = networkd_parse_lease( |
301 | + util.load_file(os.path.join(leases_d, lfile))) |
302 | + return ret |
303 | + |
304 | + |
305 | +def networkd_get_option_from_leases(keyname, leases_d=None): |
306 | + if leases_d is None: |
307 | + leases_d = NETWORKD_LEASES_DIR |
308 | + leases = networkd_load_leases(leases_d=leases_d) |
309 | + for ifindex, data in sorted(leases.items()): |
310 | + if data.get(keyname): |
311 | + return data[keyname] |
312 | + return None |
313 | + |
314 | # vi: ts=4 expandtab |
315 | diff --git a/cloudinit/net/tests/test_dhcp.py b/cloudinit/net/tests/test_dhcp.py |
316 | index a38edae..1c1f504 100644 |
317 | --- a/cloudinit/net/tests/test_dhcp.py |
318 | +++ b/cloudinit/net/tests/test_dhcp.py |
319 | @@ -6,9 +6,9 @@ from textwrap import dedent |
320 | |
321 | from cloudinit.net.dhcp import ( |
322 | InvalidDHCPLeaseFileError, maybe_perform_dhcp_discovery, |
323 | - parse_dhcp_lease_file, dhcp_discovery) |
324 | + parse_dhcp_lease_file, dhcp_discovery, networkd_load_leases) |
325 | from cloudinit.util import ensure_file, write_file |
326 | -from cloudinit.tests.helpers import CiTestCase, wrap_and_call |
327 | +from cloudinit.tests.helpers import CiTestCase, wrap_and_call, populate_dir |
328 | |
329 | |
330 | class TestParseDHCPLeasesFile(CiTestCase): |
331 | @@ -149,3 +149,112 @@ class TestDHCPDiscoveryClean(CiTestCase): |
332 | [os.path.join(tmpdir, 'dhclient'), '-1', '-v', '-lf', |
333 | lease_file, '-pf', os.path.join(tmpdir, 'dhclient.pid'), |
334 | 'eth9', '-sf', '/bin/true'], capture=True)]) |
335 | + |
336 | + |
337 | +class TestSystemdParseLeases(CiTestCase): |
338 | + |
339 | + lxd_lease = dedent("""\ |
340 | + # This is private data. Do not parse. |
341 | + ADDRESS=10.75.205.242 |
342 | + NETMASK=255.255.255.0 |
343 | + ROUTER=10.75.205.1 |
344 | + SERVER_ADDRESS=10.75.205.1 |
345 | + NEXT_SERVER=10.75.205.1 |
346 | + BROADCAST=10.75.205.255 |
347 | + T1=1580 |
348 | + T2=2930 |
349 | + LIFETIME=3600 |
350 | + DNS=10.75.205.1 |
351 | + DOMAINNAME=lxd |
352 | + HOSTNAME=a1 |
353 | + CLIENTID=ffe617693400020000ab110c65a6a0866931c2 |
354 | + """) |
355 | + |
356 | + lxd_parsed = { |
357 | + 'ADDRESS': '10.75.205.242', |
358 | + 'NETMASK': '255.255.255.0', |
359 | + 'ROUTER': '10.75.205.1', |
360 | + 'SERVER_ADDRESS': '10.75.205.1', |
361 | + 'NEXT_SERVER': '10.75.205.1', |
362 | + 'BROADCAST': '10.75.205.255', |
363 | + 'T1': '1580', |
364 | + 'T2': '2930', |
365 | + 'LIFETIME': '3600', |
366 | + 'DNS': '10.75.205.1', |
367 | + 'DOMAINNAME': 'lxd', |
368 | + 'HOSTNAME': 'a1', |
369 | + 'CLIENTID': 'ffe617693400020000ab110c65a6a0866931c2', |
370 | + } |
371 | + |
372 | + azure_lease = dedent("""\ |
373 | + # This is private data. Do not parse. |
374 | + ADDRESS=10.132.0.5 |
375 | + NETMASK=255.255.255.255 |
376 | + ROUTER=10.132.0.1 |
377 | + SERVER_ADDRESS=169.254.169.254 |
378 | + NEXT_SERVER=10.132.0.1 |
379 | + MTU=1460 |
380 | + T1=43200 |
381 | + T2=75600 |
382 | + LIFETIME=86400 |
383 | + DNS=169.254.169.254 |
384 | + NTP=169.254.169.254 |
385 | + DOMAINNAME=c.ubuntu-foundations.internal |
386 | + DOMAIN_SEARCH_LIST=c.ubuntu-foundations.internal google.internal |
387 | + HOSTNAME=tribaal-test-171002-1349.c.ubuntu-foundations.internal |
388 | + ROUTES=10.132.0.1/32,0.0.0.0 0.0.0.0/0,10.132.0.1 |
389 | + CLIENTID=ff405663a200020000ab11332859494d7a8b4c |
390 | + OPTION_245=624c3620 |
391 | + """) |
392 | + |
393 | + azure_parsed = { |
394 | + 'ADDRESS': '10.132.0.5', |
395 | + 'NETMASK': '255.255.255.255', |
396 | + 'ROUTER': '10.132.0.1', |
397 | + 'SERVER_ADDRESS': '169.254.169.254', |
398 | + 'NEXT_SERVER': '10.132.0.1', |
399 | + 'MTU': '1460', |
400 | + 'T1': '43200', |
401 | + 'T2': '75600', |
402 | + 'LIFETIME': '86400', |
403 | + 'DNS': '169.254.169.254', |
404 | + 'NTP': '169.254.169.254', |
405 | + 'DOMAINNAME': 'c.ubuntu-foundations.internal', |
406 | + 'DOMAIN_SEARCH_LIST': 'c.ubuntu-foundations.internal google.internal', |
407 | + 'HOSTNAME': 'tribaal-test-171002-1349.c.ubuntu-foundations.internal', |
408 | + 'ROUTES': '10.132.0.1/32,0.0.0.0 0.0.0.0/0,10.132.0.1', |
409 | + 'CLIENTID': 'ff405663a200020000ab11332859494d7a8b4c', |
410 | + 'OPTION_245': '624c3620'} |
411 | + |
412 | + def setUp(self): |
413 | + super(TestSystemdParseLeases, self).setUp() |
414 | + self.lease_d = self.tmp_dir() |
415 | + |
416 | + def test_no_leases_returns_empty_dict(self): |
417 | + """A leases dir with no lease files should return empty dictionary.""" |
418 | + self.assertEqual({}, networkd_load_leases(self.lease_d)) |
419 | + |
420 | + def test_no_leases_dir_returns_empty_dict(self): |
421 | + """A non-existing leases dir should return empty dict.""" |
422 | + enodir = os.path.join(self.lease_d, 'does-not-exist') |
423 | + self.assertEqual({}, networkd_load_leases(enodir)) |
424 | + |
425 | + def test_single_leases_file(self): |
426 | + """A leases dir with one leases file.""" |
427 | + populate_dir(self.lease_d, {'2': self.lxd_lease}) |
428 | + self.assertEqual( |
429 | + {'2': self.lxd_parsed}, networkd_load_leases(self.lease_d)) |
430 | + |
431 | + def test_single_azure_leases_file(self): |
432 | + """On Azure, option 245 should be present, verify it specifically.""" |
433 | + populate_dir(self.lease_d, {'1': self.azure_lease}) |
434 | + self.assertEqual( |
435 | + {'1': self.azure_parsed}, networkd_load_leases(self.lease_d)) |
436 | + |
437 | + def test_multiple_files(self): |
438 | + """Multiple leases files on azure with one found return that value.""" |
439 | + self.maxDiff = None |
440 | + populate_dir(self.lease_d, {'1': self.azure_lease, |
441 | + '9': self.lxd_lease}) |
442 | + self.assertEqual({'1': self.azure_parsed, '9': self.lxd_parsed}, |
443 | + networkd_load_leases(self.lease_d)) |
444 | diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py |
445 | index 39c79de..8f99d99 100644 |
446 | --- a/cloudinit/netinfo.py |
447 | +++ b/cloudinit/netinfo.py |
448 | @@ -13,7 +13,7 @@ import re |
449 | from cloudinit import log as logging |
450 | from cloudinit import util |
451 | |
452 | -from prettytable import PrettyTable |
453 | +from cloudinit.simpletable import SimpleTable |
454 | |
455 | LOG = logging.getLogger() |
456 | |
457 | @@ -170,7 +170,7 @@ def netdev_pformat(): |
458 | lines.append(util.center("Net device info failed", '!', 80)) |
459 | else: |
460 | fields = ['Device', 'Up', 'Address', 'Mask', 'Scope', 'Hw-Address'] |
461 | - tbl = PrettyTable(fields) |
462 | + tbl = SimpleTable(fields) |
463 | for (dev, d) in netdev.items(): |
464 | tbl.add_row([dev, d["up"], d["addr"], d["mask"], ".", d["hwaddr"]]) |
465 | if d.get('addr6'): |
466 | @@ -194,7 +194,7 @@ def route_pformat(): |
467 | if routes.get('ipv4'): |
468 | fields_v4 = ['Route', 'Destination', 'Gateway', |
469 | 'Genmask', 'Interface', 'Flags'] |
470 | - tbl_v4 = PrettyTable(fields_v4) |
471 | + tbl_v4 = SimpleTable(fields_v4) |
472 | for (n, r) in enumerate(routes.get('ipv4')): |
473 | route_id = str(n) |
474 | tbl_v4.add_row([route_id, r['destination'], |
475 | @@ -207,7 +207,7 @@ def route_pformat(): |
476 | if routes.get('ipv6'): |
477 | fields_v6 = ['Route', 'Proto', 'Recv-Q', 'Send-Q', |
478 | 'Local Address', 'Foreign Address', 'State'] |
479 | - tbl_v6 = PrettyTable(fields_v6) |
480 | + tbl_v6 = SimpleTable(fields_v6) |
481 | for (n, r) in enumerate(routes.get('ipv6')): |
482 | route_id = str(n) |
483 | tbl_v6.add_row([route_id, r['proto'], |
484 | diff --git a/cloudinit/simpletable.py b/cloudinit/simpletable.py |
485 | new file mode 100644 |
486 | index 0000000..9060322 |
487 | --- /dev/null |
488 | +++ b/cloudinit/simpletable.py |
489 | @@ -0,0 +1,62 @@ |
490 | +# Copyright (C) 2017 Amazon.com, Inc. or its affiliates |
491 | +# |
492 | +# Author: Ethan Faust <efaust@amazon.com> |
493 | +# Author: Andrew Jorgensen <ajorgens@amazon.com> |
494 | +# |
495 | +# This file is part of cloud-init. See LICENSE file for license information. |
496 | + |
497 | + |
498 | +class SimpleTable(object): |
499 | + """A minimal implementation of PrettyTable |
500 | + for distribution with cloud-init. |
501 | + """ |
502 | + |
503 | + def __init__(self, fields): |
504 | + self.fields = fields |
505 | + self.rows = [] |
506 | + |
507 | + # initialize list of 0s the same length |
508 | + # as the number of fields |
509 | + self.column_widths = [0] * len(self.fields) |
510 | + self.update_column_widths(fields) |
511 | + |
512 | + def update_column_widths(self, values): |
513 | + for i, value in enumerate(values): |
514 | + self.column_widths[i] = max( |
515 | + len(value), |
516 | + self.column_widths[i]) |
517 | + |
518 | + def add_row(self, values): |
519 | + if len(values) > len(self.fields): |
520 | + raise TypeError('too many values') |
521 | + values = [str(value) for value in values] |
522 | + self.rows.append(values) |
523 | + self.update_column_widths(values) |
524 | + |
525 | + def _hdiv(self): |
526 | + """Returns a horizontal divider for the table.""" |
527 | + return '+' + '+'.join( |
528 | + ['-' * (w + 2) for w in self.column_widths]) + '+' |
529 | + |
530 | + def _row(self, row): |
531 | + """Returns a formatted row.""" |
532 | + return '|' + '|'.join( |
533 | + [col.center(self.column_widths[i] + 2) |
534 | + for i, col in enumerate(row)]) + '|' |
535 | + |
536 | + def __str__(self): |
537 | + """Returns a string representation of the table with lines around. |
538 | + |
539 | + +-----+-----+ |
540 | + | one | two | |
541 | + +-----+-----+ |
542 | + | 1 | 2 | |
543 | + | 01 | 10 | |
544 | + +-----+-----+ |
545 | + """ |
546 | + lines = [self._hdiv(), self._row(self.fields), self._hdiv()] |
547 | + lines += [self._row(r) for r in self.rows] + [self._hdiv()] |
548 | + return '\n'.join(lines) |
549 | + |
550 | + def get_string(self): |
551 | + return repr(self) |
552 | diff --git a/cloudinit/sources/DataSourceAltCloud.py b/cloudinit/sources/DataSourceAltCloud.py |
553 | index ed1d691..c78ad9e 100644 |
554 | --- a/cloudinit/sources/DataSourceAltCloud.py |
555 | +++ b/cloudinit/sources/DataSourceAltCloud.py |
556 | @@ -28,8 +28,8 @@ LOG = logging.getLogger(__name__) |
557 | CLOUD_INFO_FILE = '/etc/sysconfig/cloud-info' |
558 | |
559 | # Shell command lists |
560 | -CMD_PROBE_FLOPPY = ['/sbin/modprobe', 'floppy'] |
561 | -CMD_UDEVADM_SETTLE = ['/sbin/udevadm', 'settle', '--timeout=5'] |
562 | +CMD_PROBE_FLOPPY = ['modprobe', 'floppy'] |
563 | +CMD_UDEVADM_SETTLE = ['udevadm', 'settle', '--timeout=5'] |
564 | |
565 | META_DATA_NOT_SUPPORTED = { |
566 | 'block-device-mapping': {}, |
567 | diff --git a/cloudinit/sources/DataSourceCloudStack.py b/cloudinit/sources/DataSourceCloudStack.py |
568 | index 7e0f9bb..9dc473f 100644 |
569 | --- a/cloudinit/sources/DataSourceCloudStack.py |
570 | +++ b/cloudinit/sources/DataSourceCloudStack.py |
571 | @@ -19,6 +19,7 @@ import time |
572 | |
573 | from cloudinit import ec2_utils as ec2 |
574 | from cloudinit import log as logging |
575 | +from cloudinit.net import dhcp |
576 | from cloudinit import sources |
577 | from cloudinit import url_helper as uhelp |
578 | from cloudinit import util |
579 | @@ -224,20 +225,28 @@ def get_vr_address(): |
580 | # Get the address of the virtual router via dhcp leases |
581 | # If no virtual router is detected, fallback on default gateway. |
582 | # See http://docs.cloudstack.apache.org/projects/cloudstack-administration/en/4.8/virtual_machines/user-data.html # noqa |
583 | + |
584 | + # Try networkd first... |
585 | + latest_address = dhcp.networkd_get_option_from_leases('SERVER_ADDRESS') |
586 | + if latest_address: |
587 | + LOG.debug("Found SERVER_ADDRESS '%s' via networkd_leases", |
588 | + latest_address) |
589 | + return latest_address |
590 | + |
591 | + # Try dhcp lease files next... |
592 | lease_file = get_latest_lease() |
593 | if not lease_file: |
594 | LOG.debug("No lease file found, using default gateway") |
595 | return get_default_gateway() |
596 | |
597 | - latest_address = None |
598 | with open(lease_file, "r") as fd: |
599 | for line in fd: |
600 | if "dhcp-server-identifier" in line: |
601 | words = line.strip(" ;\r\n").split(" ") |
602 | if len(words) > 2: |
603 | - dhcp = words[2] |
604 | - LOG.debug("Found DHCP identifier %s", dhcp) |
605 | - latest_address = dhcp |
606 | + dhcptok = words[2] |
607 | + LOG.debug("Found DHCP identifier %s", dhcptok) |
608 | + latest_address = dhcptok |
609 | if not latest_address: |
610 | # No virtual router found, fallback on default gateway |
611 | LOG.debug("No DHCP found, using default gateway") |
612 | diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py |
613 | index 24b45d5..ccebf11 100644 |
614 | --- a/cloudinit/sources/DataSourceOVF.py |
615 | +++ b/cloudinit/sources/DataSourceOVF.py |
616 | @@ -375,26 +375,56 @@ def get_ovf_env(dirname): |
617 | return (None, False) |
618 | |
619 | |
620 | -# Transport functions take no input and return |
621 | -# a 3 tuple of content, path, filename |
622 | -def transport_iso9660(require_iso=True): |
623 | +def maybe_cdrom_device(devname): |
624 | + """Test if devname matches known list of devices which may contain iso9660 |
625 | + filesystems. |
626 | |
627 | - # default_regex matches values in |
628 | - # /lib/udev/rules.d/60-cdrom_id.rules |
629 | - # KERNEL!="sr[0-9]*|hd[a-z]|xvd*", GOTO="cdrom_end" |
630 | - envname = "CLOUD_INIT_CDROM_DEV_REGEX" |
631 | - default_regex = "^(sr[0-9]+|hd[a-z]|xvd.*)" |
632 | + Be helpful in accepting either knames (with no leading /dev/) or full path |
633 | + names, but do not allow paths outside of /dev/, like /dev/foo/bar/xxx. |
634 | + """ |
635 | + if not devname: |
636 | + return False |
637 | + elif not isinstance(devname, util.string_types): |
638 | + raise ValueError("Unexpected input for devname: %s" % devname) |
639 | + |
640 | + # resolve '..' and multi '/' elements |
641 | + devname = os.path.normpath(devname) |
642 | |
643 | - devname_regex = os.environ.get(envname, default_regex) |
644 | + # drop leading '/dev/' |
645 | + if devname.startswith("/dev/"): |
646 | + # partition returns tuple (before, partition, after) |
647 | + devname = devname.partition("/dev/")[-1] |
648 | + |
649 | + # ignore leading slash (/sr0), else fail on / in name (foo/bar/xvdc) |
650 | + if devname.startswith("/"): |
651 | + devname = devname.split("/")[-1] |
652 | + elif devname.count("/") > 0: |
653 | + return False |
654 | + |
655 | + # if empty string |
656 | + if not devname: |
657 | + return False |
658 | + |
659 | + # default_regex matches values in /lib/udev/rules.d/60-cdrom_id.rules |
660 | + # KERNEL!="sr[0-9]*|hd[a-z]|xvd*", GOTO="cdrom_end" |
661 | + default_regex = r"^(sr[0-9]+|hd[a-z]|xvd.*)" |
662 | + devname_regex = os.environ.get("CLOUD_INIT_CDROM_DEV_REGEX", default_regex) |
663 | cdmatch = re.compile(devname_regex) |
664 | |
665 | + return cdmatch.match(devname) is not None |
666 | + |
667 | + |
668 | +# Transport functions take no input and return |
669 | +# a 3 tuple of content, path, filename |
670 | +def transport_iso9660(require_iso=True): |
671 | + |
672 | # Go through mounts to see if it was already mounted |
673 | mounts = util.mounts() |
674 | for (dev, info) in mounts.items(): |
675 | fstype = info['fstype'] |
676 | if fstype != "iso9660" and require_iso: |
677 | continue |
678 | - if cdmatch.match(dev[5:]) is None: # take off '/dev/' |
679 | + if not maybe_cdrom_device(dev): |
680 | continue |
681 | mp = info['mountpoint'] |
682 | (fname, contents) = get_ovf_env(mp) |
683 | @@ -406,29 +436,19 @@ def transport_iso9660(require_iso=True): |
684 | else: |
685 | mtype = None |
686 | |
687 | - devs = os.listdir("/dev/") |
688 | - devs.sort() |
689 | + # generate a list of devices with mtype filesystem, filter by regex |
690 | + devs = [dev for dev in |
691 | + util.find_devs_with("TYPE=%s" % mtype if mtype else None) |
692 | + if maybe_cdrom_device(dev)] |
693 | for dev in devs: |
694 | - fullp = os.path.join("/dev/", dev) |
695 | - |
696 | - if (fullp in mounts or |
697 | - not cdmatch.match(dev) or os.path.isdir(fullp)): |
698 | - continue |
699 | - |
700 | - try: |
701 | - # See if we can read anything at all...?? |
702 | - util.peek_file(fullp, 512) |
703 | - except IOError: |
704 | - continue |
705 | - |
706 | try: |
707 | - (fname, contents) = util.mount_cb(fullp, get_ovf_env, mtype=mtype) |
708 | + (fname, contents) = util.mount_cb(dev, get_ovf_env, mtype=mtype) |
709 | except util.MountFailedError: |
710 | - LOG.debug("%s not mountable as iso9660", fullp) |
711 | + LOG.debug("%s not mountable as iso9660", dev) |
712 | continue |
713 | |
714 | if contents is not False: |
715 | - return (contents, fullp, fname) |
716 | + return (contents, dev, fname) |
717 | |
718 | return (False, None, None) |
719 | |
720 | diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py |
721 | index 28ed0ae..959b1bd 100644 |
722 | --- a/cloudinit/sources/helpers/azure.py |
723 | +++ b/cloudinit/sources/helpers/azure.py |
724 | @@ -8,6 +8,7 @@ import socket |
725 | import struct |
726 | import time |
727 | |
728 | +from cloudinit.net import dhcp |
729 | from cloudinit import stages |
730 | from cloudinit import temp_utils |
731 | from contextlib import contextmanager |
732 | @@ -15,7 +16,6 @@ from xml.etree import ElementTree |
733 | |
734 | from cloudinit import util |
735 | |
736 | - |
737 | LOG = logging.getLogger(__name__) |
738 | |
739 | |
740 | @@ -239,6 +239,11 @@ class WALinuxAgentShim(object): |
741 | return socket.inet_ntoa(packed_bytes) |
742 | |
743 | @staticmethod |
744 | + def _networkd_get_value_from_leases(leases_d=None): |
745 | + return dhcp.networkd_get_option_from_leases( |
746 | + 'OPTION_245', leases_d=leases_d) |
747 | + |
748 | + @staticmethod |
749 | def _get_value_from_leases_file(fallback_lease_file): |
750 | leases = [] |
751 | content = util.load_file(fallback_lease_file) |
752 | @@ -287,12 +292,15 @@ class WALinuxAgentShim(object): |
753 | |
754 | @staticmethod |
755 | def find_endpoint(fallback_lease_file=None): |
756 | - LOG.debug('Finding Azure endpoint...') |
757 | value = None |
758 | - # Option-245 stored in /run/cloud-init/dhclient.hooks/<ifc>.json |
759 | - # a dhclient exit hook that calls cloud-init-dhclient-hook |
760 | - dhcp_options = WALinuxAgentShim._load_dhclient_json() |
761 | - value = WALinuxAgentShim._get_value_from_dhcpoptions(dhcp_options) |
762 | + LOG.debug('Finding Azure endpoint from networkd...') |
763 | + value = WALinuxAgentShim._networkd_get_value_from_leases() |
764 | + if value is None: |
765 | + # Option-245 stored in /run/cloud-init/dhclient.hooks/<ifc>.json |
766 | + # a dhclient exit hook that calls cloud-init-dhclient-hook |
767 | + LOG.debug('Finding Azure endpoint from hook json...') |
768 | + dhcp_options = WALinuxAgentShim._load_dhclient_json() |
769 | + value = WALinuxAgentShim._get_value_from_dhcpoptions(dhcp_options) |
770 | if value is None: |
771 | # Fallback and check the leases file if unsuccessful |
772 | LOG.debug("Unable to find endpoint in dhclient logs. " |
773 | diff --git a/cloudinit/tests/helpers.py b/cloudinit/tests/helpers.py |
774 | index 28e2662..6f88a5b 100644 |
775 | --- a/cloudinit/tests/helpers.py |
776 | +++ b/cloudinit/tests/helpers.py |
777 | @@ -104,6 +104,16 @@ class TestCase(unittest2.TestCase): |
778 | super(TestCase, self).setUp() |
779 | self.reset_global_state() |
780 | |
781 | + def add_patch(self, target, attr, **kwargs): |
782 | + """Patches specified target object and sets it as attr on test |
783 | + instance also schedules cleanup""" |
784 | + if 'autospec' not in kwargs: |
785 | + kwargs['autospec'] = True |
786 | + m = mock.patch(target, **kwargs) |
787 | + p = m.start() |
788 | + self.addCleanup(m.stop) |
789 | + setattr(self, attr, p) |
790 | + |
791 | |
792 | class CiTestCase(TestCase): |
793 | """This is the preferred test case base class unless user |
794 | diff --git a/cloudinit/tests/test_simpletable.py b/cloudinit/tests/test_simpletable.py |
795 | new file mode 100644 |
796 | index 0000000..96bc24c |
797 | --- /dev/null |
798 | +++ b/cloudinit/tests/test_simpletable.py |
799 | @@ -0,0 +1,100 @@ |
800 | +# Copyright (C) 2017 Amazon.com, Inc. or its affiliates |
801 | +# |
802 | +# Author: Andrew Jorgensen <ajorgens@amazon.com> |
803 | +# |
804 | +# This file is part of cloud-init. See LICENSE file for license information. |
805 | +"""Tests that SimpleTable works just like PrettyTable for cloud-init. |
806 | + |
807 | +Not all possible PrettyTable cases are tested because we're not trying to |
808 | +reimplement the entire library, only the minimal parts we actually use. |
809 | +""" |
810 | + |
811 | +from cloudinit.simpletable import SimpleTable |
812 | +from cloudinit.tests.helpers import CiTestCase |
813 | + |
814 | +# Examples rendered by cloud-init using PrettyTable |
815 | +NET_DEVICE_FIELDS = ( |
816 | + 'Device', 'Up', 'Address', 'Mask', 'Scope', 'Hw-Address') |
817 | +NET_DEVICE_ROWS = ( |
818 | + ('ens3', True, '172.31.4.203', '255.255.240.0', '.', '0a:1f:07:15:98:70'), |
819 | + ('ens3', True, 'fe80::81f:7ff:fe15:9870/64', '.', 'link', |
820 | + '0a:1f:07:15:98:70'), |
821 | + ('lo', True, '127.0.0.1', '255.0.0.0', '.', '.'), |
822 | + ('lo', True, '::1/128', '.', 'host', '.'), |
823 | +) |
824 | +NET_DEVICE_TABLE = """\ |
825 | ++--------+------+----------------------------+---------------+-------+-------------------+ |
826 | +| Device | Up | Address | Mask | Scope | Hw-Address | |
827 | ++--------+------+----------------------------+---------------+-------+-------------------+ |
828 | +| ens3 | True | 172.31.4.203 | 255.255.240.0 | . | 0a:1f:07:15:98:70 | |
829 | +| ens3 | True | fe80::81f:7ff:fe15:9870/64 | . | link | 0a:1f:07:15:98:70 | |
830 | +| lo | True | 127.0.0.1 | 255.0.0.0 | . | . | |
831 | +| lo | True | ::1/128 | . | host | . | |
832 | ++--------+------+----------------------------+---------------+-------+-------------------+""" # noqa: E501 |
833 | +ROUTE_IPV4_FIELDS = ( |
834 | + 'Route', 'Destination', 'Gateway', 'Genmask', 'Interface', 'Flags') |
835 | +ROUTE_IPV4_ROWS = ( |
836 | + ('0', '0.0.0.0', '172.31.0.1', '0.0.0.0', 'ens3', 'UG'), |
837 | + ('1', '169.254.0.0', '0.0.0.0', '255.255.0.0', 'ens3', 'U'), |
838 | + ('2', '172.31.0.0', '0.0.0.0', '255.255.240.0', 'ens3', 'U'), |
839 | +) |
840 | +ROUTE_IPV4_TABLE = """\ |
841 | ++-------+-------------+------------+---------------+-----------+-------+ |
842 | +| Route | Destination | Gateway | Genmask | Interface | Flags | |
843 | ++-------+-------------+------------+---------------+-----------+-------+ |
844 | +| 0 | 0.0.0.0 | 172.31.0.1 | 0.0.0.0 | ens3 | UG | |
845 | +| 1 | 169.254.0.0 | 0.0.0.0 | 255.255.0.0 | ens3 | U | |
846 | +| 2 | 172.31.0.0 | 0.0.0.0 | 255.255.240.0 | ens3 | U | |
847 | ++-------+-------------+------------+---------------+-----------+-------+""" |
848 | + |
849 | +AUTHORIZED_KEYS_FIELDS = ( |
850 | + 'Keytype', 'Fingerprint (md5)', 'Options', 'Comment') |
851 | +AUTHORIZED_KEYS_ROWS = ( |
852 | + ('ssh-rsa', '24:c7:41:49:47:12:31:a0:de:6f:62:79:9b:13:06:36', '-', |
853 | + 'ajorgens'), |
854 | +) |
855 | +AUTHORIZED_KEYS_TABLE = """\ |
856 | ++---------+-------------------------------------------------+---------+----------+ |
857 | +| Keytype | Fingerprint (md5) | Options | Comment | |
858 | ++---------+-------------------------------------------------+---------+----------+ |
859 | +| ssh-rsa | 24:c7:41:49:47:12:31:a0:de:6f:62:79:9b:13:06:36 | - | ajorgens | |
860 | ++---------+-------------------------------------------------+---------+----------+""" # noqa: E501 |
861 | + |
862 | +# from prettytable import PrettyTable |
863 | +# pt = PrettyTable(('HEADER',)) |
864 | +# print(pt) |
865 | +NO_ROWS_FIELDS = ('HEADER',) |
866 | +NO_ROWS_TABLE = """\ |
867 | ++--------+ |
868 | +| HEADER | |
869 | ++--------+ |
870 | ++--------+""" |
871 | + |
872 | + |
873 | +class TestSimpleTable(CiTestCase): |
874 | + |
875 | + def test_no_rows(self): |
876 | + """An empty table is rendered as PrettyTable would have done it.""" |
877 | + table = SimpleTable(NO_ROWS_FIELDS) |
878 | + self.assertEqual(str(table), NO_ROWS_TABLE) |
879 | + |
880 | + def test_net_dev(self): |
881 | + """Net device info is rendered as it was with PrettyTable.""" |
882 | + table = SimpleTable(NET_DEVICE_FIELDS) |
883 | + for row in NET_DEVICE_ROWS: |
884 | + table.add_row(row) |
885 | + self.assertEqual(str(table), NET_DEVICE_TABLE) |
886 | + |
887 | + def test_route_ipv4(self): |
888 | + """Route IPv4 info is rendered as it was with PrettyTable.""" |
889 | + table = SimpleTable(ROUTE_IPV4_FIELDS) |
890 | + for row in ROUTE_IPV4_ROWS: |
891 | + table.add_row(row) |
892 | + self.assertEqual(str(table), ROUTE_IPV4_TABLE) |
893 | + |
894 | + def test_authorized_keys(self): |
895 | + """SSH authorized keys are rendered as they were with PrettyTable.""" |
896 | + table = SimpleTable(AUTHORIZED_KEYS_FIELDS) |
897 | + for row in AUTHORIZED_KEYS_ROWS: |
898 | + table.add_row(row) |
899 | + self.assertEqual(str(table), AUTHORIZED_KEYS_TABLE) |
900 | diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl |
901 | index 50e3bd8..32de9c9 100644 |
902 | --- a/config/cloud.cfg.tmpl |
903 | +++ b/config/cloud.cfg.tmpl |
904 | @@ -84,6 +84,9 @@ cloud_config_modules: |
905 | - apt-pipelining |
906 | - apt-configure |
907 | {% endif %} |
908 | +{% if variant in ["suse"] %} |
909 | + - zypper-add-repo |
910 | +{% endif %} |
911 | {% if variant not in ["freebsd"] %} |
912 | - ntp |
913 | {% endif %} |
914 | diff --git a/debian/changelog b/debian/changelog |
915 | index 2080def..1e1007d 100644 |
916 | --- a/debian/changelog |
917 | +++ b/debian/changelog |
918 | @@ -1,9 +1,31 @@ |
919 | -cloud-init (17.1-0ubuntu2) UNRELEASED; urgency=medium |
920 | +cloud-init (17.1-13-g7fd0425-0ubuntu1) artful; urgency=medium |
921 | |
922 | * debian/copyright: dep5 updates, reorganize, add Apache 2.0 license. |
923 | (LP: #1718681) |
924 | - |
925 | - -- Scott Moser <smoser@brickies.net> Fri, 29 Sep 2017 08:44:15 -0400 |
926 | + * debian/control: drop dependency on python3-prettytable |
927 | + * New upstream snapshot. |
928 | + - systemd: remove limit on tasks created by cloud-init-final.service. |
929 | + [Robert Schweikert] (LP: #1717969) |
930 | + - suse: Support addition of zypper repos via cloud-config. |
931 | + [Robert Schweikert] (LP: #1718675) |
932 | + - tests: Combine integration configs and testcases [Joshua Powers] |
933 | + - Azure, CloudStack: Support reading dhcp options from systemd-networkd. |
934 | + [Dimitri John Ledkov] (LP: #1718029) |
935 | + - packages/debian/copyright: remove mention of boto and MIT license |
936 | + - systemd: only mention Before=apt-daily.service on debian based distros. |
937 | + [Robert Schweikert] |
938 | + - Add missing simpletable and simpletable tests for failed merge |
939 | + [Chad Smith] |
940 | + - Remove prettytable dependency, introduce simpletable [Andrew Jorgensen] |
941 | + - debian/copyright: dep5 updates, reorganize, add Apache 2.0 license. |
942 | + [Joshua Powers] (LP: #1718681) |
943 | + - tests: remove dependency on shlex [Joshua Powers] |
944 | + - AltCloud: Trust PATH for udevadm and modprobe. |
945 | + - DataSourceOVF: use util.find_devs_with(TYPE=iso9660) |
946 | + [Ryan Harper] (LP: #1718287) |
947 | + - tests: remove a temp file used in bootcmd tests. |
948 | + |
949 | + -- Ryan Harper <ryan.harper@canonical.com> Tue, 03 Oct 2017 10:59:52 -0500 |
950 | |
951 | cloud-init (17.1-0ubuntu1) artful; urgency=medium |
952 | |
953 | diff --git a/debian/control b/debian/control |
954 | index 731821e..3f46d7b 100644 |
955 | --- a/debian/control |
956 | +++ b/debian/control |
957 | @@ -19,7 +19,6 @@ Build-Depends: debhelper (>= 9), |
958 | python3-nose, |
959 | python3-oauthlib, |
960 | python3-pep8, |
961 | - python3-prettytable, |
962 | python3-pyflakes | pyflakes (<< 1.1.0-2), |
963 | python3-requests, |
964 | python3-serial, |
965 | diff --git a/packages/debian/copyright b/packages/debian/copyright |
966 | index c9c7d23..598cda1 100644 |
967 | --- a/packages/debian/copyright |
968 | +++ b/packages/debian/copyright |
969 | @@ -1,33 +1,28 @@ |
970 | -Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135 |
971 | -Name: cloud-init |
972 | -Maintainer: Scott Moser <scott.moser@canonical.com> |
973 | +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ |
974 | +Upstream-Name: cloud-init |
975 | +Upstream-Contact: cloud-init-dev@lists.launchpad.net |
976 | Source: https://launchpad.net/cloud-init |
977 | |
978 | -This package was debianized by Soren Hansen <soren@ubuntu.com> on |
979 | -Thu, 04 Sep 2008 12:49:15 +0200 as ec2-init. It was later renamed to |
980 | -cloud-init by Scott Moser <scott.moser@canonical.com> |
981 | - |
982 | -Upstream Author: Scott Moser <smoser@canonical.com> |
983 | - Soren Hansen <soren@canonical.com> |
984 | - Chuck Short <chuck.short@canonical.com> |
985 | - |
986 | -Copyright: 2010, Canonical Ltd. |
987 | +Files: * |
988 | +Copyright: 2010, Canonical Ltd. |
989 | License: GPL-3 or Apache-2.0 |
990 | + |
991 | License: GPL-3 |
992 | This program is free software: you can redistribute it and/or modify |
993 | it under the terms of the GNU General Public License version 3, as |
994 | published by the Free Software Foundation. |
995 | - |
996 | + . |
997 | This program is distributed in the hope that it will be useful, |
998 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
999 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1000 | GNU General Public License for more details. |
1001 | - |
1002 | + . |
1003 | You should have received a copy of the GNU General Public License |
1004 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
1005 | - |
1006 | + . |
1007 | The complete text of the GPL version 3 can be seen in |
1008 | /usr/share/common-licenses/GPL-3. |
1009 | + |
1010 | License: Apache-2.0 |
1011 | Licensed under the Apache License, Version 2.0 (the "License"); |
1012 | you may not use this file except in compliance with the License. |
1013 | diff --git a/packages/pkg-deps.json b/packages/pkg-deps.json |
1014 | index 822d29d..72409dd 100644 |
1015 | --- a/packages/pkg-deps.json |
1016 | +++ b/packages/pkg-deps.json |
1017 | @@ -34,9 +34,6 @@ |
1018 | "jsonschema" : { |
1019 | "3" : "python34-jsonschema" |
1020 | }, |
1021 | - "prettytable" : { |
1022 | - "3" : "python34-prettytable" |
1023 | - }, |
1024 | "pyflakes" : { |
1025 | "2" : "pyflakes", |
1026 | "3" : "python34-pyflakes" |
1027 | diff --git a/requirements.txt b/requirements.txt |
1028 | index 61d1e90..dd10d85 100644 |
1029 | --- a/requirements.txt |
1030 | +++ b/requirements.txt |
1031 | @@ -3,9 +3,6 @@ |
1032 | # Used for untemplating any files or strings with parameters. |
1033 | jinja2 |
1034 | |
1035 | -# This is used for any pretty printing of tabular data. |
1036 | -PrettyTable |
1037 | - |
1038 | # This one is currently only used by the MAAS datasource. If that |
1039 | # datasource is removed, this is no longer needed |
1040 | oauthlib |
1041 | diff --git a/systemd/cloud-final.service.tmpl b/systemd/cloud-final.service.tmpl |
1042 | index fc01b89..8207b18 100644 |
1043 | --- a/systemd/cloud-final.service.tmpl |
1044 | +++ b/systemd/cloud-final.service.tmpl |
1045 | @@ -4,9 +4,10 @@ Description=Execute cloud user/final scripts |
1046 | After=network-online.target cloud-config.service rc-local.service |
1047 | {% if variant in ["ubuntu", "unknown", "debian"] %} |
1048 | After=multi-user.target |
1049 | +Before=apt-daily.service |
1050 | {% endif %} |
1051 | Wants=network-online.target cloud-config.service |
1052 | -Before=apt-daily.service |
1053 | + |
1054 | |
1055 | [Service] |
1056 | Type=oneshot |
1057 | @@ -14,6 +15,7 @@ ExecStart=/usr/bin/cloud-init modules --mode=final |
1058 | RemainAfterExit=yes |
1059 | TimeoutSec=0 |
1060 | KillMode=process |
1061 | +TasksMax=infinity |
1062 | |
1063 | # Output needs to appear in instance console output |
1064 | StandardOutput=journal+console |
1065 | diff --git a/tests/cloud_tests/__init__.py b/tests/cloud_tests/__init__.py |
1066 | index 07148c1..98c1d6c 100644 |
1067 | --- a/tests/cloud_tests/__init__.py |
1068 | +++ b/tests/cloud_tests/__init__.py |
1069 | @@ -7,7 +7,7 @@ import os |
1070 | |
1071 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
1072 | TESTCASES_DIR = os.path.join(BASE_DIR, 'testcases') |
1073 | -TEST_CONF_DIR = os.path.join(BASE_DIR, 'configs') |
1074 | +TEST_CONF_DIR = os.path.join(BASE_DIR, 'testcases') |
1075 | TREE_BASE = os.sep.join(BASE_DIR.split(os.sep)[:-2]) |
1076 | |
1077 | |
1078 | diff --git a/tests/cloud_tests/instances/nocloudkvm.py b/tests/cloud_tests/instances/nocloudkvm.py |
1079 | index 7abfe73..8a0e531 100644 |
1080 | --- a/tests/cloud_tests/instances/nocloudkvm.py |
1081 | +++ b/tests/cloud_tests/instances/nocloudkvm.py |
1082 | @@ -4,7 +4,6 @@ |
1083 | |
1084 | import os |
1085 | import paramiko |
1086 | -import shlex |
1087 | import socket |
1088 | import subprocess |
1089 | import time |
1090 | @@ -83,10 +82,10 @@ class NoCloudKVMInstance(base.Instance): |
1091 | |
1092 | def mount_image_callback(self, cmd): |
1093 | """Run mount-image-callback.""" |
1094 | - mic = ('sudo mount-image-callback --system-mounts --system-resolvconf ' |
1095 | - '%s -- chroot _MOUNTPOINT_ ' % self.name) |
1096 | - |
1097 | - out, err = c_util.subp(shlex.split(mic) + cmd) |
1098 | + out, err = c_util.subp(['sudo', 'mount-image-callback', |
1099 | + '--system-mounts', '--system-resolvconf', |
1100 | + self.name, '--', 'chroot', |
1101 | + '_MOUNTPOINT_'] + cmd) |
1102 | |
1103 | return out, err |
1104 | |
1105 | @@ -122,11 +121,11 @@ class NoCloudKVMInstance(base.Instance): |
1106 | if self.pid: |
1107 | super(NoCloudKVMInstance, self).push_file() |
1108 | else: |
1109 | - cmd = ("sudo mount-image-callback --system-mounts " |
1110 | - "--system-resolvconf %s -- chroot _MOUNTPOINT_ " |
1111 | - "/bin/sh -c 'cat - > %s'" % (self.name, remote_path)) |
1112 | local_file = open(local_path) |
1113 | - p = subprocess.Popen(shlex.split(cmd), |
1114 | + p = subprocess.Popen(['sudo', 'mount-image-callback', |
1115 | + '--system-mounts', '--system-resolvconf', |
1116 | + self.name, '--', 'chroot', '_MOUNTPOINT_', |
1117 | + '/bin/sh', '-c', 'cat - > %s' % remote_path], |
1118 | stdin=local_file, |
1119 | stdout=subprocess.PIPE, |
1120 | stderr=subprocess.PIPE) |
1121 | @@ -186,12 +185,14 @@ class NoCloudKVMInstance(base.Instance): |
1122 | self.pid_file = os.path.join(tmpdir, '%s.pid' % self.name) |
1123 | self.ssh_port = self.get_free_port() |
1124 | |
1125 | - cmd = ('./tools/xkvm --disk %s,cache=unsafe --disk %s,cache=unsafe ' |
1126 | - '--netdev user,hostfwd=tcp::%s-:22 ' |
1127 | - '-- -pidfile %s -vnc none -m 2G -smp 2' |
1128 | - % (self.name, seed, self.ssh_port, self.pid_file)) |
1129 | - |
1130 | - subprocess.Popen(shlex.split(cmd), close_fds=True, |
1131 | + subprocess.Popen(['./tools/xkvm', |
1132 | + '--disk', '%s,cache=unsafe' % self.name, |
1133 | + '--disk', '%s,cache=unsafe' % seed, |
1134 | + '--netdev', |
1135 | + 'user,hostfwd=tcp::%s-:22' % self.ssh_port, |
1136 | + '--', '-pidfile', self.pid_file, '-vnc', 'none', |
1137 | + '-m', '2G', '-smp', '2'], |
1138 | + close_fds=True, |
1139 | stdin=subprocess.PIPE, |
1140 | stdout=subprocess.PIPE, |
1141 | stderr=subprocess.PIPE) |
1142 | diff --git a/tests/cloud_tests/configs/bugs/README.md b/tests/cloud_tests/testcases/bugs/README.md |
1143 | index 09ce076..09ce076 100644 |
1144 | --- a/tests/cloud_tests/configs/bugs/README.md |
1145 | +++ b/tests/cloud_tests/testcases/bugs/README.md |
1146 | diff --git a/tests/cloud_tests/configs/bugs/lp1511485.yaml b/tests/cloud_tests/testcases/bugs/lp1511485.yaml |
1147 | index ebf9763..ebf9763 100644 |
1148 | --- a/tests/cloud_tests/configs/bugs/lp1511485.yaml |
1149 | +++ b/tests/cloud_tests/testcases/bugs/lp1511485.yaml |
1150 | diff --git a/tests/cloud_tests/configs/bugs/lp1611074.yaml b/tests/cloud_tests/testcases/bugs/lp1611074.yaml |
1151 | index 960679d..960679d 100644 |
1152 | --- a/tests/cloud_tests/configs/bugs/lp1611074.yaml |
1153 | +++ b/tests/cloud_tests/testcases/bugs/lp1611074.yaml |
1154 | diff --git a/tests/cloud_tests/configs/bugs/lp1628337.yaml b/tests/cloud_tests/testcases/bugs/lp1628337.yaml |
1155 | index e39b3cd..e39b3cd 100644 |
1156 | --- a/tests/cloud_tests/configs/bugs/lp1628337.yaml |
1157 | +++ b/tests/cloud_tests/testcases/bugs/lp1628337.yaml |
1158 | diff --git a/tests/cloud_tests/configs/examples/README.md b/tests/cloud_tests/testcases/examples/README.md |
1159 | index 110a223..110a223 100644 |
1160 | --- a/tests/cloud_tests/configs/examples/README.md |
1161 | +++ b/tests/cloud_tests/testcases/examples/README.md |
1162 | diff --git a/tests/cloud_tests/configs/examples/TODO.md b/tests/cloud_tests/testcases/examples/TODO.md |
1163 | index 8db0e98..8db0e98 100644 |
1164 | --- a/tests/cloud_tests/configs/examples/TODO.md |
1165 | +++ b/tests/cloud_tests/testcases/examples/TODO.md |
1166 | diff --git a/tests/cloud_tests/configs/examples/add_apt_repositories.yaml b/tests/cloud_tests/testcases/examples/add_apt_repositories.yaml |
1167 | index 4b8575f..4b8575f 100644 |
1168 | --- a/tests/cloud_tests/configs/examples/add_apt_repositories.yaml |
1169 | +++ b/tests/cloud_tests/testcases/examples/add_apt_repositories.yaml |
1170 | diff --git a/tests/cloud_tests/configs/examples/alter_completion_message.yaml b/tests/cloud_tests/testcases/examples/alter_completion_message.yaml |
1171 | index 9e154f8..9e154f8 100644 |
1172 | --- a/tests/cloud_tests/configs/examples/alter_completion_message.yaml |
1173 | +++ b/tests/cloud_tests/testcases/examples/alter_completion_message.yaml |
1174 | diff --git a/tests/cloud_tests/configs/examples/configure_instance_trusted_ca_certificates.yaml b/tests/cloud_tests/testcases/examples/configure_instance_trusted_ca_certificates.yaml |
1175 | index ad32b08..ad32b08 100644 |
1176 | --- a/tests/cloud_tests/configs/examples/configure_instance_trusted_ca_certificates.yaml |
1177 | +++ b/tests/cloud_tests/testcases/examples/configure_instance_trusted_ca_certificates.yaml |
1178 | diff --git a/tests/cloud_tests/configs/examples/configure_instances_ssh_keys.yaml b/tests/cloud_tests/testcases/examples/configure_instances_ssh_keys.yaml |
1179 | index f3eaf3c..f3eaf3c 100644 |
1180 | --- a/tests/cloud_tests/configs/examples/configure_instances_ssh_keys.yaml |
1181 | +++ b/tests/cloud_tests/testcases/examples/configure_instances_ssh_keys.yaml |
1182 | diff --git a/tests/cloud_tests/configs/examples/including_user_groups.yaml b/tests/cloud_tests/testcases/examples/including_user_groups.yaml |
1183 | index 0aa7ad2..0aa7ad2 100644 |
1184 | --- a/tests/cloud_tests/configs/examples/including_user_groups.yaml |
1185 | +++ b/tests/cloud_tests/testcases/examples/including_user_groups.yaml |
1186 | diff --git a/tests/cloud_tests/configs/examples/install_arbitrary_packages.yaml b/tests/cloud_tests/testcases/examples/install_arbitrary_packages.yaml |
1187 | index d398022..d398022 100644 |
1188 | --- a/tests/cloud_tests/configs/examples/install_arbitrary_packages.yaml |
1189 | +++ b/tests/cloud_tests/testcases/examples/install_arbitrary_packages.yaml |
1190 | diff --git a/tests/cloud_tests/configs/examples/install_run_chef_recipes.yaml b/tests/cloud_tests/testcases/examples/install_run_chef_recipes.yaml |
1191 | index 0bec305..0bec305 100644 |
1192 | --- a/tests/cloud_tests/configs/examples/install_run_chef_recipes.yaml |
1193 | +++ b/tests/cloud_tests/testcases/examples/install_run_chef_recipes.yaml |
1194 | diff --git a/tests/cloud_tests/configs/examples/run_apt_upgrade.yaml b/tests/cloud_tests/testcases/examples/run_apt_upgrade.yaml |
1195 | index 2b7eae4..2b7eae4 100644 |
1196 | --- a/tests/cloud_tests/configs/examples/run_apt_upgrade.yaml |
1197 | +++ b/tests/cloud_tests/testcases/examples/run_apt_upgrade.yaml |
1198 | diff --git a/tests/cloud_tests/configs/examples/run_commands.yaml b/tests/cloud_tests/testcases/examples/run_commands.yaml |
1199 | index b0e311b..b0e311b 100644 |
1200 | --- a/tests/cloud_tests/configs/examples/run_commands.yaml |
1201 | +++ b/tests/cloud_tests/testcases/examples/run_commands.yaml |
1202 | diff --git a/tests/cloud_tests/configs/examples/run_commands_first_boot.yaml b/tests/cloud_tests/testcases/examples/run_commands_first_boot.yaml |
1203 | index 7bd803d..7bd803d 100644 |
1204 | --- a/tests/cloud_tests/configs/examples/run_commands_first_boot.yaml |
1205 | +++ b/tests/cloud_tests/testcases/examples/run_commands_first_boot.yaml |
1206 | diff --git a/tests/cloud_tests/configs/examples/setup_run_puppet.yaml b/tests/cloud_tests/testcases/examples/setup_run_puppet.yaml |
1207 | index e366c04..e366c04 100644 |
1208 | --- a/tests/cloud_tests/configs/examples/setup_run_puppet.yaml |
1209 | +++ b/tests/cloud_tests/testcases/examples/setup_run_puppet.yaml |
1210 | diff --git a/tests/cloud_tests/configs/examples/writing_out_arbitrary_files.yaml b/tests/cloud_tests/testcases/examples/writing_out_arbitrary_files.yaml |
1211 | index 6f78f99..6f78f99 100644 |
1212 | --- a/tests/cloud_tests/configs/examples/writing_out_arbitrary_files.yaml |
1213 | +++ b/tests/cloud_tests/testcases/examples/writing_out_arbitrary_files.yaml |
1214 | diff --git a/tests/cloud_tests/configs/main/README.md b/tests/cloud_tests/testcases/main/README.md |
1215 | index 6034606..6034606 100644 |
1216 | --- a/tests/cloud_tests/configs/main/README.md |
1217 | +++ b/tests/cloud_tests/testcases/main/README.md |
1218 | diff --git a/tests/cloud_tests/configs/main/command_output_simple.yaml b/tests/cloud_tests/testcases/main/command_output_simple.yaml |
1219 | index 08ca894..08ca894 100644 |
1220 | --- a/tests/cloud_tests/configs/main/command_output_simple.yaml |
1221 | +++ b/tests/cloud_tests/testcases/main/command_output_simple.yaml |
1222 | diff --git a/tests/cloud_tests/configs/modules/README.md b/tests/cloud_tests/testcases/modules/README.md |
1223 | index d66101f..d66101f 100644 |
1224 | --- a/tests/cloud_tests/configs/modules/README.md |
1225 | +++ b/tests/cloud_tests/testcases/modules/README.md |
1226 | diff --git a/tests/cloud_tests/configs/modules/TODO.md b/tests/cloud_tests/testcases/modules/TODO.md |
1227 | index 0b933b3..0b933b3 100644 |
1228 | --- a/tests/cloud_tests/configs/modules/TODO.md |
1229 | +++ b/tests/cloud_tests/testcases/modules/TODO.md |
1230 | diff --git a/tests/cloud_tests/configs/modules/apt_configure_conf.yaml b/tests/cloud_tests/testcases/modules/apt_configure_conf.yaml |
1231 | index de45300..de45300 100644 |
1232 | --- a/tests/cloud_tests/configs/modules/apt_configure_conf.yaml |
1233 | +++ b/tests/cloud_tests/testcases/modules/apt_configure_conf.yaml |
1234 | diff --git a/tests/cloud_tests/configs/modules/apt_configure_disable_suites.yaml b/tests/cloud_tests/testcases/modules/apt_configure_disable_suites.yaml |
1235 | index 9880067..9880067 100644 |
1236 | --- a/tests/cloud_tests/configs/modules/apt_configure_disable_suites.yaml |
1237 | +++ b/tests/cloud_tests/testcases/modules/apt_configure_disable_suites.yaml |
1238 | diff --git a/tests/cloud_tests/configs/modules/apt_configure_primary.yaml b/tests/cloud_tests/testcases/modules/apt_configure_primary.yaml |
1239 | index 41bcf2f..41bcf2f 100644 |
1240 | --- a/tests/cloud_tests/configs/modules/apt_configure_primary.yaml |
1241 | +++ b/tests/cloud_tests/testcases/modules/apt_configure_primary.yaml |
1242 | diff --git a/tests/cloud_tests/configs/modules/apt_configure_proxy.yaml b/tests/cloud_tests/testcases/modules/apt_configure_proxy.yaml |
1243 | index be6c6f8..be6c6f8 100644 |
1244 | --- a/tests/cloud_tests/configs/modules/apt_configure_proxy.yaml |
1245 | +++ b/tests/cloud_tests/testcases/modules/apt_configure_proxy.yaml |
1246 | diff --git a/tests/cloud_tests/configs/modules/apt_configure_security.yaml b/tests/cloud_tests/testcases/modules/apt_configure_security.yaml |
1247 | index 83dd51d..83dd51d 100644 |
1248 | --- a/tests/cloud_tests/configs/modules/apt_configure_security.yaml |
1249 | +++ b/tests/cloud_tests/testcases/modules/apt_configure_security.yaml |
1250 | diff --git a/tests/cloud_tests/configs/modules/apt_configure_sources_key.yaml b/tests/cloud_tests/testcases/modules/apt_configure_sources_key.yaml |
1251 | index bde9398..bde9398 100644 |
1252 | --- a/tests/cloud_tests/configs/modules/apt_configure_sources_key.yaml |
1253 | +++ b/tests/cloud_tests/testcases/modules/apt_configure_sources_key.yaml |
1254 | diff --git a/tests/cloud_tests/configs/modules/apt_configure_sources_keyserver.yaml b/tests/cloud_tests/testcases/modules/apt_configure_sources_keyserver.yaml |
1255 | index 2508813..2508813 100644 |
1256 | --- a/tests/cloud_tests/configs/modules/apt_configure_sources_keyserver.yaml |
1257 | +++ b/tests/cloud_tests/testcases/modules/apt_configure_sources_keyserver.yaml |
1258 | diff --git a/tests/cloud_tests/configs/modules/apt_configure_sources_list.yaml b/tests/cloud_tests/testcases/modules/apt_configure_sources_list.yaml |
1259 | index 143cb08..143cb08 100644 |
1260 | --- a/tests/cloud_tests/configs/modules/apt_configure_sources_list.yaml |
1261 | +++ b/tests/cloud_tests/testcases/modules/apt_configure_sources_list.yaml |
1262 | diff --git a/tests/cloud_tests/configs/modules/apt_configure_sources_ppa.yaml b/tests/cloud_tests/testcases/modules/apt_configure_sources_ppa.yaml |
1263 | index 9efdae5..9efdae5 100644 |
1264 | --- a/tests/cloud_tests/configs/modules/apt_configure_sources_ppa.yaml |
1265 | +++ b/tests/cloud_tests/testcases/modules/apt_configure_sources_ppa.yaml |
1266 | diff --git a/tests/cloud_tests/configs/modules/apt_pipelining_disable.yaml b/tests/cloud_tests/testcases/modules/apt_pipelining_disable.yaml |
1267 | index bd9b5d0..bd9b5d0 100644 |
1268 | --- a/tests/cloud_tests/configs/modules/apt_pipelining_disable.yaml |
1269 | +++ b/tests/cloud_tests/testcases/modules/apt_pipelining_disable.yaml |
1270 | diff --git a/tests/cloud_tests/configs/modules/apt_pipelining_os.yaml b/tests/cloud_tests/testcases/modules/apt_pipelining_os.yaml |
1271 | index cbed3ba..cbed3ba 100644 |
1272 | --- a/tests/cloud_tests/configs/modules/apt_pipelining_os.yaml |
1273 | +++ b/tests/cloud_tests/testcases/modules/apt_pipelining_os.yaml |
1274 | diff --git a/tests/cloud_tests/configs/modules/bootcmd.yaml b/tests/cloud_tests/testcases/modules/bootcmd.yaml |
1275 | index 3a73994..3a73994 100644 |
1276 | --- a/tests/cloud_tests/configs/modules/bootcmd.yaml |
1277 | +++ b/tests/cloud_tests/testcases/modules/bootcmd.yaml |
1278 | diff --git a/tests/cloud_tests/configs/modules/byobu.yaml b/tests/cloud_tests/testcases/modules/byobu.yaml |
1279 | index a9aa1f3..a9aa1f3 100644 |
1280 | --- a/tests/cloud_tests/configs/modules/byobu.yaml |
1281 | +++ b/tests/cloud_tests/testcases/modules/byobu.yaml |
1282 | diff --git a/tests/cloud_tests/configs/modules/ca_certs.yaml b/tests/cloud_tests/testcases/modules/ca_certs.yaml |
1283 | index d939f43..d939f43 100644 |
1284 | --- a/tests/cloud_tests/configs/modules/ca_certs.yaml |
1285 | +++ b/tests/cloud_tests/testcases/modules/ca_certs.yaml |
1286 | diff --git a/tests/cloud_tests/configs/modules/debug_disable.yaml b/tests/cloud_tests/testcases/modules/debug_disable.yaml |
1287 | index 63218b1..63218b1 100644 |
1288 | --- a/tests/cloud_tests/configs/modules/debug_disable.yaml |
1289 | +++ b/tests/cloud_tests/testcases/modules/debug_disable.yaml |
1290 | diff --git a/tests/cloud_tests/configs/modules/debug_enable.yaml b/tests/cloud_tests/testcases/modules/debug_enable.yaml |
1291 | index d44147d..d44147d 100644 |
1292 | --- a/tests/cloud_tests/configs/modules/debug_enable.yaml |
1293 | +++ b/tests/cloud_tests/testcases/modules/debug_enable.yaml |
1294 | diff --git a/tests/cloud_tests/configs/modules/final_message.yaml b/tests/cloud_tests/testcases/modules/final_message.yaml |
1295 | index c9ed611..c9ed611 100644 |
1296 | --- a/tests/cloud_tests/configs/modules/final_message.yaml |
1297 | +++ b/tests/cloud_tests/testcases/modules/final_message.yaml |
1298 | diff --git a/tests/cloud_tests/configs/modules/keys_to_console.yaml b/tests/cloud_tests/testcases/modules/keys_to_console.yaml |
1299 | index 5d86e73..5d86e73 100644 |
1300 | --- a/tests/cloud_tests/configs/modules/keys_to_console.yaml |
1301 | +++ b/tests/cloud_tests/testcases/modules/keys_to_console.yaml |
1302 | diff --git a/tests/cloud_tests/configs/modules/landscape.yaml b/tests/cloud_tests/testcases/modules/landscape.yaml |
1303 | index ed2c37c..ed2c37c 100644 |
1304 | --- a/tests/cloud_tests/configs/modules/landscape.yaml |
1305 | +++ b/tests/cloud_tests/testcases/modules/landscape.yaml |
1306 | diff --git a/tests/cloud_tests/configs/modules/locale.yaml b/tests/cloud_tests/testcases/modules/locale.yaml |
1307 | index e01518a..e01518a 100644 |
1308 | --- a/tests/cloud_tests/configs/modules/locale.yaml |
1309 | +++ b/tests/cloud_tests/testcases/modules/locale.yaml |
1310 | diff --git a/tests/cloud_tests/configs/modules/lxd_bridge.yaml b/tests/cloud_tests/testcases/modules/lxd_bridge.yaml |
1311 | index e6b7e76..e6b7e76 100644 |
1312 | --- a/tests/cloud_tests/configs/modules/lxd_bridge.yaml |
1313 | +++ b/tests/cloud_tests/testcases/modules/lxd_bridge.yaml |
1314 | diff --git a/tests/cloud_tests/configs/modules/lxd_dir.yaml b/tests/cloud_tests/testcases/modules/lxd_dir.yaml |
1315 | index f93a3fa..f93a3fa 100644 |
1316 | --- a/tests/cloud_tests/configs/modules/lxd_dir.yaml |
1317 | +++ b/tests/cloud_tests/testcases/modules/lxd_dir.yaml |
1318 | diff --git a/tests/cloud_tests/configs/modules/ntp.yaml b/tests/cloud_tests/testcases/modules/ntp.yaml |
1319 | index fbef431..fbef431 100644 |
1320 | --- a/tests/cloud_tests/configs/modules/ntp.yaml |
1321 | +++ b/tests/cloud_tests/testcases/modules/ntp.yaml |
1322 | diff --git a/tests/cloud_tests/configs/modules/ntp_pools.yaml b/tests/cloud_tests/testcases/modules/ntp_pools.yaml |
1323 | index 3a93faa..3a93faa 100644 |
1324 | --- a/tests/cloud_tests/configs/modules/ntp_pools.yaml |
1325 | +++ b/tests/cloud_tests/testcases/modules/ntp_pools.yaml |
1326 | diff --git a/tests/cloud_tests/configs/modules/ntp_servers.yaml b/tests/cloud_tests/testcases/modules/ntp_servers.yaml |
1327 | index d59d45a..d59d45a 100644 |
1328 | --- a/tests/cloud_tests/configs/modules/ntp_servers.yaml |
1329 | +++ b/tests/cloud_tests/testcases/modules/ntp_servers.yaml |
1330 | diff --git a/tests/cloud_tests/configs/modules/package_update_upgrade_install.yaml b/tests/cloud_tests/testcases/modules/package_update_upgrade_install.yaml |
1331 | index 71d24b8..71d24b8 100644 |
1332 | --- a/tests/cloud_tests/configs/modules/package_update_upgrade_install.yaml |
1333 | +++ b/tests/cloud_tests/testcases/modules/package_update_upgrade_install.yaml |
1334 | diff --git a/tests/cloud_tests/configs/modules/runcmd.yaml b/tests/cloud_tests/testcases/modules/runcmd.yaml |
1335 | index 04e5a05..04e5a05 100644 |
1336 | --- a/tests/cloud_tests/configs/modules/runcmd.yaml |
1337 | +++ b/tests/cloud_tests/testcases/modules/runcmd.yaml |
1338 | diff --git a/tests/cloud_tests/configs/modules/salt_minion.yaml b/tests/cloud_tests/testcases/modules/salt_minion.yaml |
1339 | index f20d24f..f20d24f 100644 |
1340 | --- a/tests/cloud_tests/configs/modules/salt_minion.yaml |
1341 | +++ b/tests/cloud_tests/testcases/modules/salt_minion.yaml |
1342 | diff --git a/tests/cloud_tests/configs/modules/seed_random_command.yaml b/tests/cloud_tests/testcases/modules/seed_random_command.yaml |
1343 | index 6a9157e..6a9157e 100644 |
1344 | --- a/tests/cloud_tests/configs/modules/seed_random_command.yaml |
1345 | +++ b/tests/cloud_tests/testcases/modules/seed_random_command.yaml |
1346 | diff --git a/tests/cloud_tests/configs/modules/seed_random_data.yaml b/tests/cloud_tests/testcases/modules/seed_random_data.yaml |
1347 | index a9b2c88..a9b2c88 100644 |
1348 | --- a/tests/cloud_tests/configs/modules/seed_random_data.yaml |
1349 | +++ b/tests/cloud_tests/testcases/modules/seed_random_data.yaml |
1350 | diff --git a/tests/cloud_tests/configs/modules/set_hostname.yaml b/tests/cloud_tests/testcases/modules/set_hostname.yaml |
1351 | index c96344c..c96344c 100644 |
1352 | --- a/tests/cloud_tests/configs/modules/set_hostname.yaml |
1353 | +++ b/tests/cloud_tests/testcases/modules/set_hostname.yaml |
1354 | diff --git a/tests/cloud_tests/configs/modules/set_hostname_fqdn.yaml b/tests/cloud_tests/testcases/modules/set_hostname_fqdn.yaml |
1355 | index daf7593..daf7593 100644 |
1356 | --- a/tests/cloud_tests/configs/modules/set_hostname_fqdn.yaml |
1357 | +++ b/tests/cloud_tests/testcases/modules/set_hostname_fqdn.yaml |
1358 | diff --git a/tests/cloud_tests/configs/modules/set_password.yaml b/tests/cloud_tests/testcases/modules/set_password.yaml |
1359 | index 04d7c58..04d7c58 100644 |
1360 | --- a/tests/cloud_tests/configs/modules/set_password.yaml |
1361 | +++ b/tests/cloud_tests/testcases/modules/set_password.yaml |
1362 | diff --git a/tests/cloud_tests/configs/modules/set_password_expire.yaml b/tests/cloud_tests/testcases/modules/set_password_expire.yaml |
1363 | index 789604b..789604b 100644 |
1364 | --- a/tests/cloud_tests/configs/modules/set_password_expire.yaml |
1365 | +++ b/tests/cloud_tests/testcases/modules/set_password_expire.yaml |
1366 | diff --git a/tests/cloud_tests/configs/modules/set_password_list.yaml b/tests/cloud_tests/testcases/modules/set_password_list.yaml |
1367 | index a2a89c9..a2a89c9 100644 |
1368 | --- a/tests/cloud_tests/configs/modules/set_password_list.yaml |
1369 | +++ b/tests/cloud_tests/testcases/modules/set_password_list.yaml |
1370 | diff --git a/tests/cloud_tests/configs/modules/set_password_list_string.yaml b/tests/cloud_tests/testcases/modules/set_password_list_string.yaml |
1371 | index c2a0f63..c2a0f63 100644 |
1372 | --- a/tests/cloud_tests/configs/modules/set_password_list_string.yaml |
1373 | +++ b/tests/cloud_tests/testcases/modules/set_password_list_string.yaml |
1374 | diff --git a/tests/cloud_tests/configs/modules/snappy.yaml b/tests/cloud_tests/testcases/modules/snappy.yaml |
1375 | index 43f9329..43f9329 100644 |
1376 | --- a/tests/cloud_tests/configs/modules/snappy.yaml |
1377 | +++ b/tests/cloud_tests/testcases/modules/snappy.yaml |
1378 | diff --git a/tests/cloud_tests/configs/modules/ssh_auth_key_fingerprints_disable.yaml b/tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_disable.yaml |
1379 | index 746653e..746653e 100644 |
1380 | --- a/tests/cloud_tests/configs/modules/ssh_auth_key_fingerprints_disable.yaml |
1381 | +++ b/tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_disable.yaml |
1382 | diff --git a/tests/cloud_tests/configs/modules/ssh_auth_key_fingerprints_enable.yaml b/tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_enable.yaml |
1383 | index 9f5dc34..9f5dc34 100644 |
1384 | --- a/tests/cloud_tests/configs/modules/ssh_auth_key_fingerprints_enable.yaml |
1385 | +++ b/tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_enable.yaml |
1386 | diff --git a/tests/cloud_tests/configs/modules/ssh_import_id.yaml b/tests/cloud_tests/testcases/modules/ssh_import_id.yaml |
1387 | index b62d3f6..b62d3f6 100644 |
1388 | --- a/tests/cloud_tests/configs/modules/ssh_import_id.yaml |
1389 | +++ b/tests/cloud_tests/testcases/modules/ssh_import_id.yaml |
1390 | diff --git a/tests/cloud_tests/configs/modules/ssh_keys_generate.yaml b/tests/cloud_tests/testcases/modules/ssh_keys_generate.yaml |
1391 | index 659fd93..659fd93 100644 |
1392 | --- a/tests/cloud_tests/configs/modules/ssh_keys_generate.yaml |
1393 | +++ b/tests/cloud_tests/testcases/modules/ssh_keys_generate.yaml |
1394 | diff --git a/tests/cloud_tests/configs/modules/ssh_keys_provided.yaml b/tests/cloud_tests/testcases/modules/ssh_keys_provided.yaml |
1395 | index 5ceb362..5ceb362 100644 |
1396 | --- a/tests/cloud_tests/configs/modules/ssh_keys_provided.yaml |
1397 | +++ b/tests/cloud_tests/testcases/modules/ssh_keys_provided.yaml |
1398 | diff --git a/tests/cloud_tests/configs/modules/timezone.yaml b/tests/cloud_tests/testcases/modules/timezone.yaml |
1399 | index 5112aa9..5112aa9 100644 |
1400 | --- a/tests/cloud_tests/configs/modules/timezone.yaml |
1401 | +++ b/tests/cloud_tests/testcases/modules/timezone.yaml |
1402 | diff --git a/tests/cloud_tests/configs/modules/user_groups.yaml b/tests/cloud_tests/testcases/modules/user_groups.yaml |
1403 | index 71cc9da..71cc9da 100644 |
1404 | --- a/tests/cloud_tests/configs/modules/user_groups.yaml |
1405 | +++ b/tests/cloud_tests/testcases/modules/user_groups.yaml |
1406 | diff --git a/tests/cloud_tests/configs/modules/write_files.yaml b/tests/cloud_tests/testcases/modules/write_files.yaml |
1407 | index ce936b7..ce936b7 100644 |
1408 | --- a/tests/cloud_tests/configs/modules/write_files.yaml |
1409 | +++ b/tests/cloud_tests/testcases/modules/write_files.yaml |
1410 | diff --git a/tests/unittests/test_datasource/test_altcloud.py b/tests/unittests/test_datasource/test_altcloud.py |
1411 | index 3b274d9..a4dfb54 100644 |
1412 | --- a/tests/unittests/test_datasource/test_altcloud.py |
1413 | +++ b/tests/unittests/test_datasource/test_altcloud.py |
1414 | @@ -280,8 +280,8 @@ class TestUserDataRhevm(TestCase): |
1415 | pass |
1416 | |
1417 | dsac.CLOUD_INFO_FILE = '/etc/sysconfig/cloud-info' |
1418 | - dsac.CMD_PROBE_FLOPPY = ['/sbin/modprobe', 'floppy'] |
1419 | - dsac.CMD_UDEVADM_SETTLE = ['/sbin/udevadm', 'settle', |
1420 | + dsac.CMD_PROBE_FLOPPY = ['modprobe', 'floppy'] |
1421 | + dsac.CMD_UDEVADM_SETTLE = ['udevadm', 'settle', |
1422 | '--quiet', '--timeout=5'] |
1423 | |
1424 | def test_mount_cb_fails(self): |
1425 | diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py |
1426 | index 44b99ec..b42b073 100644 |
1427 | --- a/tests/unittests/test_datasource/test_azure_helper.py |
1428 | +++ b/tests/unittests/test_datasource/test_azure_helper.py |
1429 | @@ -1,10 +1,12 @@ |
1430 | # This file is part of cloud-init. See LICENSE file for license information. |
1431 | |
1432 | import os |
1433 | +from textwrap import dedent |
1434 | |
1435 | from cloudinit.sources.helpers import azure as azure_helper |
1436 | -from cloudinit.tests.helpers import ExitStack, mock, TestCase |
1437 | +from cloudinit.tests.helpers import CiTestCase, ExitStack, mock, populate_dir |
1438 | |
1439 | +from cloudinit.sources.helpers.azure import WALinuxAgentShim as wa_shim |
1440 | |
1441 | GOAL_STATE_TEMPLATE = """\ |
1442 | <?xml version="1.0" encoding="utf-8"?> |
1443 | @@ -45,7 +47,7 @@ GOAL_STATE_TEMPLATE = """\ |
1444 | """ |
1445 | |
1446 | |
1447 | -class TestFindEndpoint(TestCase): |
1448 | +class TestFindEndpoint(CiTestCase): |
1449 | |
1450 | def setUp(self): |
1451 | super(TestFindEndpoint, self).setUp() |
1452 | @@ -56,18 +58,19 @@ class TestFindEndpoint(TestCase): |
1453 | mock.patch.object(azure_helper.util, 'load_file')) |
1454 | |
1455 | self.dhcp_options = patches.enter_context( |
1456 | - mock.patch.object(azure_helper.WALinuxAgentShim, |
1457 | - '_load_dhclient_json')) |
1458 | + mock.patch.object(wa_shim, '_load_dhclient_json')) |
1459 | + |
1460 | + self.networkd_leases = patches.enter_context( |
1461 | + mock.patch.object(wa_shim, '_networkd_get_value_from_leases')) |
1462 | + self.networkd_leases.return_value = None |
1463 | |
1464 | def test_missing_file(self): |
1465 | - self.assertRaises(ValueError, |
1466 | - azure_helper.WALinuxAgentShim.find_endpoint) |
1467 | + self.assertRaises(ValueError, wa_shim.find_endpoint) |
1468 | |
1469 | def test_missing_special_azure_line(self): |
1470 | self.load_file.return_value = '' |
1471 | self.dhcp_options.return_value = {'eth0': {'key': 'value'}} |
1472 | - self.assertRaises(ValueError, |
1473 | - azure_helper.WALinuxAgentShim.find_endpoint) |
1474 | + self.assertRaises(ValueError, wa_shim.find_endpoint) |
1475 | |
1476 | @staticmethod |
1477 | def _build_lease_content(encoded_address): |
1478 | @@ -80,8 +83,7 @@ class TestFindEndpoint(TestCase): |
1479 | |
1480 | def test_from_dhcp_client(self): |
1481 | self.dhcp_options.return_value = {"eth0": {"unknown_245": "5:4:3:2"}} |
1482 | - self.assertEqual('5.4.3.2', |
1483 | - azure_helper.WALinuxAgentShim.find_endpoint(None)) |
1484 | + self.assertEqual('5.4.3.2', wa_shim.find_endpoint(None)) |
1485 | |
1486 | def test_latest_lease_used(self): |
1487 | encoded_addresses = ['5:4:3:2', '4:3:2:1'] |
1488 | @@ -89,53 +91,38 @@ class TestFindEndpoint(TestCase): |
1489 | for encoded_address in encoded_addresses]) |
1490 | self.load_file.return_value = file_content |
1491 | self.assertEqual(encoded_addresses[-1].replace(':', '.'), |
1492 | - azure_helper.WALinuxAgentShim.find_endpoint("foobar")) |
1493 | + wa_shim.find_endpoint("foobar")) |
1494 | |
1495 | |
1496 | -class TestExtractIpAddressFromLeaseValue(TestCase): |
1497 | +class TestExtractIpAddressFromLeaseValue(CiTestCase): |
1498 | |
1499 | def test_hex_string(self): |
1500 | ip_address, encoded_address = '98.76.54.32', '62:4c:36:20' |
1501 | self.assertEqual( |
1502 | - ip_address, |
1503 | - azure_helper.WALinuxAgentShim.get_ip_from_lease_value( |
1504 | - encoded_address |
1505 | - )) |
1506 | + ip_address, wa_shim.get_ip_from_lease_value(encoded_address)) |
1507 | |
1508 | def test_hex_string_with_single_character_part(self): |
1509 | ip_address, encoded_address = '4.3.2.1', '4:3:2:1' |
1510 | self.assertEqual( |
1511 | - ip_address, |
1512 | - azure_helper.WALinuxAgentShim.get_ip_from_lease_value( |
1513 | - encoded_address |
1514 | - )) |
1515 | + ip_address, wa_shim.get_ip_from_lease_value(encoded_address)) |
1516 | |
1517 | def test_packed_string(self): |
1518 | ip_address, encoded_address = '98.76.54.32', 'bL6 ' |
1519 | self.assertEqual( |
1520 | - ip_address, |
1521 | - azure_helper.WALinuxAgentShim.get_ip_from_lease_value( |
1522 | - encoded_address |
1523 | - )) |
1524 | + ip_address, wa_shim.get_ip_from_lease_value(encoded_address)) |
1525 | |
1526 | def test_packed_string_with_escaped_quote(self): |
1527 | ip_address, encoded_address = '100.72.34.108', 'dH\\"l' |
1528 | self.assertEqual( |
1529 | - ip_address, |
1530 | - azure_helper.WALinuxAgentShim.get_ip_from_lease_value( |
1531 | - encoded_address |
1532 | - )) |
1533 | + ip_address, wa_shim.get_ip_from_lease_value(encoded_address)) |
1534 | |
1535 | def test_packed_string_containing_a_colon(self): |
1536 | ip_address, encoded_address = '100.72.58.108', 'dH:l' |
1537 | self.assertEqual( |
1538 | - ip_address, |
1539 | - azure_helper.WALinuxAgentShim.get_ip_from_lease_value( |
1540 | - encoded_address |
1541 | - )) |
1542 | + ip_address, wa_shim.get_ip_from_lease_value(encoded_address)) |
1543 | |
1544 | |
1545 | -class TestGoalStateParsing(TestCase): |
1546 | +class TestGoalStateParsing(CiTestCase): |
1547 | |
1548 | default_parameters = { |
1549 | 'incarnation': 1, |
1550 | @@ -195,7 +182,7 @@ class TestGoalStateParsing(TestCase): |
1551 | self.assertIsNone(certificates_xml) |
1552 | |
1553 | |
1554 | -class TestAzureEndpointHttpClient(TestCase): |
1555 | +class TestAzureEndpointHttpClient(CiTestCase): |
1556 | |
1557 | regular_headers = { |
1558 | 'x-ms-agent-name': 'WALinuxAgent', |
1559 | @@ -258,7 +245,7 @@ class TestAzureEndpointHttpClient(TestCase): |
1560 | self.read_file_or_url.call_args) |
1561 | |
1562 | |
1563 | -class TestOpenSSLManager(TestCase): |
1564 | +class TestOpenSSLManager(CiTestCase): |
1565 | |
1566 | def setUp(self): |
1567 | super(TestOpenSSLManager, self).setUp() |
1568 | @@ -300,7 +287,7 @@ class TestOpenSSLManager(TestCase): |
1569 | self.assertEqual([mock.call(manager.tmpdir)], del_dir.call_args_list) |
1570 | |
1571 | |
1572 | -class TestWALinuxAgentShim(TestCase): |
1573 | +class TestWALinuxAgentShim(CiTestCase): |
1574 | |
1575 | def setUp(self): |
1576 | super(TestWALinuxAgentShim, self).setUp() |
1577 | @@ -310,8 +297,7 @@ class TestWALinuxAgentShim(TestCase): |
1578 | self.AzureEndpointHttpClient = patches.enter_context( |
1579 | mock.patch.object(azure_helper, 'AzureEndpointHttpClient')) |
1580 | self.find_endpoint = patches.enter_context( |
1581 | - mock.patch.object( |
1582 | - azure_helper.WALinuxAgentShim, 'find_endpoint')) |
1583 | + mock.patch.object(wa_shim, 'find_endpoint')) |
1584 | self.GoalState = patches.enter_context( |
1585 | mock.patch.object(azure_helper, 'GoalState')) |
1586 | self.OpenSSLManager = patches.enter_context( |
1587 | @@ -320,7 +306,7 @@ class TestWALinuxAgentShim(TestCase): |
1588 | mock.patch.object(azure_helper.time, 'sleep', mock.MagicMock())) |
1589 | |
1590 | def test_http_client_uses_certificate(self): |
1591 | - shim = azure_helper.WALinuxAgentShim() |
1592 | + shim = wa_shim() |
1593 | shim.register_with_azure_and_fetch_data() |
1594 | self.assertEqual( |
1595 | [mock.call(self.OpenSSLManager.return_value.certificate)], |
1596 | @@ -328,7 +314,7 @@ class TestWALinuxAgentShim(TestCase): |
1597 | |
1598 | def test_correct_url_used_for_goalstate(self): |
1599 | self.find_endpoint.return_value = 'test_endpoint' |
1600 | - shim = azure_helper.WALinuxAgentShim() |
1601 | + shim = wa_shim() |
1602 | shim.register_with_azure_and_fetch_data() |
1603 | get = self.AzureEndpointHttpClient.return_value.get |
1604 | self.assertEqual( |
1605 | @@ -340,7 +326,7 @@ class TestWALinuxAgentShim(TestCase): |
1606 | self.GoalState.call_args_list) |
1607 | |
1608 | def test_certificates_used_to_determine_public_keys(self): |
1609 | - shim = azure_helper.WALinuxAgentShim() |
1610 | + shim = wa_shim() |
1611 | data = shim.register_with_azure_and_fetch_data() |
1612 | self.assertEqual( |
1613 | [mock.call(self.GoalState.return_value.certificates_xml)], |
1614 | @@ -351,13 +337,13 @@ class TestWALinuxAgentShim(TestCase): |
1615 | |
1616 | def test_absent_certificates_produces_empty_public_keys(self): |
1617 | self.GoalState.return_value.certificates_xml = None |
1618 | - shim = azure_helper.WALinuxAgentShim() |
1619 | + shim = wa_shim() |
1620 | data = shim.register_with_azure_and_fetch_data() |
1621 | self.assertEqual([], data['public-keys']) |
1622 | |
1623 | def test_correct_url_used_for_report_ready(self): |
1624 | self.find_endpoint.return_value = 'test_endpoint' |
1625 | - shim = azure_helper.WALinuxAgentShim() |
1626 | + shim = wa_shim() |
1627 | shim.register_with_azure_and_fetch_data() |
1628 | expected_url = 'http://test_endpoint/machine?comp=health' |
1629 | self.assertEqual( |
1630 | @@ -368,7 +354,7 @@ class TestWALinuxAgentShim(TestCase): |
1631 | self.GoalState.return_value.incarnation = 'TestIncarnation' |
1632 | self.GoalState.return_value.container_id = 'TestContainerId' |
1633 | self.GoalState.return_value.instance_id = 'TestInstanceId' |
1634 | - shim = azure_helper.WALinuxAgentShim() |
1635 | + shim = wa_shim() |
1636 | shim.register_with_azure_and_fetch_data() |
1637 | posted_document = ( |
1638 | self.AzureEndpointHttpClient.return_value.post.call_args[1]['data'] |
1639 | @@ -378,11 +364,11 @@ class TestWALinuxAgentShim(TestCase): |
1640 | self.assertIn('TestInstanceId', posted_document) |
1641 | |
1642 | def test_clean_up_can_be_called_at_any_time(self): |
1643 | - shim = azure_helper.WALinuxAgentShim() |
1644 | + shim = wa_shim() |
1645 | shim.clean_up() |
1646 | |
1647 | def test_clean_up_will_clean_up_openssl_manager_if_instantiated(self): |
1648 | - shim = azure_helper.WALinuxAgentShim() |
1649 | + shim = wa_shim() |
1650 | shim.register_with_azure_and_fetch_data() |
1651 | shim.clean_up() |
1652 | self.assertEqual( |
1653 | @@ -393,12 +379,12 @@ class TestWALinuxAgentShim(TestCase): |
1654 | pass |
1655 | self.AzureEndpointHttpClient.return_value.get.side_effect = ( |
1656 | SentinelException) |
1657 | - shim = azure_helper.WALinuxAgentShim() |
1658 | + shim = wa_shim() |
1659 | self.assertRaises(SentinelException, |
1660 | shim.register_with_azure_and_fetch_data) |
1661 | |
1662 | |
1663 | -class TestGetMetadataFromFabric(TestCase): |
1664 | +class TestGetMetadataFromFabric(CiTestCase): |
1665 | |
1666 | @mock.patch.object(azure_helper, 'WALinuxAgentShim') |
1667 | def test_data_from_shim_returned(self, shim): |
1668 | @@ -422,4 +408,65 @@ class TestGetMetadataFromFabric(TestCase): |
1669 | azure_helper.get_metadata_from_fabric) |
1670 | self.assertEqual(1, shim.return_value.clean_up.call_count) |
1671 | |
1672 | + |
1673 | +class TestExtractIpAddressFromNetworkd(CiTestCase): |
1674 | + |
1675 | + azure_lease = dedent("""\ |
1676 | + # This is private data. Do not parse. |
1677 | + ADDRESS=10.132.0.5 |
1678 | + NETMASK=255.255.255.255 |
1679 | + ROUTER=10.132.0.1 |
1680 | + SERVER_ADDRESS=169.254.169.254 |
1681 | + NEXT_SERVER=10.132.0.1 |
1682 | + MTU=1460 |
1683 | + T1=43200 |
1684 | + T2=75600 |
1685 | + LIFETIME=86400 |
1686 | + DNS=169.254.169.254 |
1687 | + NTP=169.254.169.254 |
1688 | + DOMAINNAME=c.ubuntu-foundations.internal |
1689 | + DOMAIN_SEARCH_LIST=c.ubuntu-foundations.internal google.internal |
1690 | + HOSTNAME=tribaal-test-171002-1349.c.ubuntu-foundations.internal |
1691 | + ROUTES=10.132.0.1/32,0.0.0.0 0.0.0.0/0,10.132.0.1 |
1692 | + CLIENTID=ff405663a200020000ab11332859494d7a8b4c |
1693 | + OPTION_245=624c3620 |
1694 | + """) |
1695 | + |
1696 | + def setUp(self): |
1697 | + super(TestExtractIpAddressFromNetworkd, self).setUp() |
1698 | + self.lease_d = self.tmp_dir() |
1699 | + |
1700 | + def test_no_valid_leases_is_none(self): |
1701 | + """No valid leases should return None.""" |
1702 | + self.assertIsNone( |
1703 | + wa_shim._networkd_get_value_from_leases(self.lease_d)) |
1704 | + |
1705 | + def test_option_245_is_found_in_single(self): |
1706 | + """A single valid lease with 245 option should return it.""" |
1707 | + populate_dir(self.lease_d, {'9': self.azure_lease}) |
1708 | + self.assertEqual( |
1709 | + '624c3620', wa_shim._networkd_get_value_from_leases(self.lease_d)) |
1710 | + |
1711 | + def test_option_245_not_found_returns_None(self): |
1712 | + """A valid lease, but no option 245 should return None.""" |
1713 | + populate_dir( |
1714 | + self.lease_d, |
1715 | + {'9': self.azure_lease.replace("OPTION_245", "OPTION_999")}) |
1716 | + self.assertIsNone( |
1717 | + wa_shim._networkd_get_value_from_leases(self.lease_d)) |
1718 | + |
1719 | + def test_multiple_returns_first(self): |
1720 | + """Somewhat arbitrarily return the first address when multiple. |
1721 | + |
1722 | + Most important at the moment is that this is consistent behavior |
1723 | + rather than changing randomly as in order of a dictionary.""" |
1724 | + myval = "624c3601" |
1725 | + populate_dir( |
1726 | + self.lease_d, |
1727 | + {'9': self.azure_lease, |
1728 | + '2': self.azure_lease.replace("624c3620", myval)}) |
1729 | + self.assertEqual( |
1730 | + myval, wa_shim._networkd_get_value_from_leases(self.lease_d)) |
1731 | + |
1732 | + |
1733 | # vi: ts=4 expandtab |
1734 | diff --git a/tests/unittests/test_datasource/test_cloudstack.py b/tests/unittests/test_datasource/test_cloudstack.py |
1735 | index 8e98e1b..96144b6 100644 |
1736 | --- a/tests/unittests/test_datasource/test_cloudstack.py |
1737 | +++ b/tests/unittests/test_datasource/test_cloudstack.py |
1738 | @@ -23,13 +23,16 @@ class TestCloudStackPasswordFetching(CiTestCase): |
1739 | default_gw = "192.201.20.0" |
1740 | get_latest_lease = mock.MagicMock(return_value=None) |
1741 | self.patches.enter_context(mock.patch( |
1742 | - 'cloudinit.sources.DataSourceCloudStack.get_latest_lease', |
1743 | - get_latest_lease)) |
1744 | + mod_name + '.get_latest_lease', get_latest_lease)) |
1745 | |
1746 | get_default_gw = mock.MagicMock(return_value=default_gw) |
1747 | self.patches.enter_context(mock.patch( |
1748 | - 'cloudinit.sources.DataSourceCloudStack.get_default_gateway', |
1749 | - get_default_gw)) |
1750 | + mod_name + '.get_default_gateway', get_default_gw)) |
1751 | + |
1752 | + get_networkd_server_address = mock.MagicMock(return_value=None) |
1753 | + self.patches.enter_context(mock.patch( |
1754 | + mod_name + '.dhcp.networkd_get_option_from_leases', |
1755 | + get_networkd_server_address)) |
1756 | |
1757 | def _set_password_server_response(self, response_string): |
1758 | subp = mock.MagicMock(return_value=(response_string, '')) |
1759 | diff --git a/tests/unittests/test_datasource/test_ovf.py b/tests/unittests/test_datasource/test_ovf.py |
1760 | index 9dbf4dd..700da86 100644 |
1761 | --- a/tests/unittests/test_datasource/test_ovf.py |
1762 | +++ b/tests/unittests/test_datasource/test_ovf.py |
1763 | @@ -5,6 +5,7 @@ |
1764 | # This file is part of cloud-init. See LICENSE file for license information. |
1765 | |
1766 | import base64 |
1767 | +from collections import OrderedDict |
1768 | |
1769 | from cloudinit.tests import helpers as test_helpers |
1770 | |
1771 | @@ -70,4 +71,167 @@ class TestReadOvfEnv(test_helpers.TestCase): |
1772 | self.assertEqual({'password': "passw0rd"}, cfg) |
1773 | self.assertIsNone(ud) |
1774 | |
1775 | + |
1776 | +class TestTransportIso9660(test_helpers.CiTestCase): |
1777 | + |
1778 | + def setUp(self): |
1779 | + super(TestTransportIso9660, self).setUp() |
1780 | + self.add_patch('cloudinit.util.find_devs_with', |
1781 | + 'm_find_devs_with') |
1782 | + self.add_patch('cloudinit.util.mounts', 'm_mounts') |
1783 | + self.add_patch('cloudinit.util.mount_cb', 'm_mount_cb') |
1784 | + self.add_patch('cloudinit.sources.DataSourceOVF.get_ovf_env', |
1785 | + 'm_get_ovf_env') |
1786 | + self.m_get_ovf_env.return_value = ('myfile', 'mycontent') |
1787 | + |
1788 | + def test_find_already_mounted(self): |
1789 | + """Check we call get_ovf_env from on matching mounted devices""" |
1790 | + mounts = { |
1791 | + '/dev/sr9': { |
1792 | + 'fstype': 'iso9660', |
1793 | + 'mountpoint': 'wark/media/sr9', |
1794 | + 'opts': 'ro', |
1795 | + } |
1796 | + } |
1797 | + self.m_mounts.return_value = mounts |
1798 | + |
1799 | + (contents, fullp, fname) = dsovf.transport_iso9660() |
1800 | + self.assertEqual("mycontent", contents) |
1801 | + self.assertEqual("/dev/sr9", fullp) |
1802 | + self.assertEqual("myfile", fname) |
1803 | + |
1804 | + def test_find_already_mounted_skips_non_iso9660(self): |
1805 | + """Check we call get_ovf_env ignoring non iso9660""" |
1806 | + mounts = { |
1807 | + '/dev/xvdb': { |
1808 | + 'fstype': 'vfat', |
1809 | + 'mountpoint': 'wark/foobar', |
1810 | + 'opts': 'defaults,noatime', |
1811 | + }, |
1812 | + '/dev/xvdc': { |
1813 | + 'fstype': 'iso9660', |
1814 | + 'mountpoint': 'wark/media/sr9', |
1815 | + 'opts': 'ro', |
1816 | + } |
1817 | + } |
1818 | + # We use an OrderedDict here to ensure we check xvdb before xvdc |
1819 | + # as we're not mocking the regex matching, however, if we place |
1820 | + # an entry in the results then we can be reasonably sure that |
1821 | + # we're skipping an entry which fails to match. |
1822 | + self.m_mounts.return_value = ( |
1823 | + OrderedDict(sorted(mounts.items(), key=lambda t: t[0]))) |
1824 | + |
1825 | + (contents, fullp, fname) = dsovf.transport_iso9660() |
1826 | + self.assertEqual("mycontent", contents) |
1827 | + self.assertEqual("/dev/xvdc", fullp) |
1828 | + self.assertEqual("myfile", fname) |
1829 | + |
1830 | + def test_find_already_mounted_matches_kname(self): |
1831 | + """Check we dont regex match on basename of the device""" |
1832 | + mounts = { |
1833 | + '/dev/foo/bar/xvdc': { |
1834 | + 'fstype': 'iso9660', |
1835 | + 'mountpoint': 'wark/media/sr9', |
1836 | + 'opts': 'ro', |
1837 | + } |
1838 | + } |
1839 | + # we're skipping an entry which fails to match. |
1840 | + self.m_mounts.return_value = mounts |
1841 | + |
1842 | + (contents, fullp, fname) = dsovf.transport_iso9660() |
1843 | + self.assertEqual(False, contents) |
1844 | + self.assertIsNone(fullp) |
1845 | + self.assertIsNone(fname) |
1846 | + |
1847 | + def test_mount_cb_called_on_blkdevs_with_iso9660(self): |
1848 | + """Check we call mount_cb on blockdevs with iso9660 only""" |
1849 | + self.m_mounts.return_value = {} |
1850 | + self.m_find_devs_with.return_value = ['/dev/sr0'] |
1851 | + self.m_mount_cb.return_value = ("myfile", "mycontent") |
1852 | + |
1853 | + (contents, fullp, fname) = dsovf.transport_iso9660() |
1854 | + |
1855 | + self.m_mount_cb.assert_called_with( |
1856 | + "/dev/sr0", dsovf.get_ovf_env, mtype="iso9660") |
1857 | + self.assertEqual("mycontent", contents) |
1858 | + self.assertEqual("/dev/sr0", fullp) |
1859 | + self.assertEqual("myfile", fname) |
1860 | + |
1861 | + def test_mount_cb_called_on_blkdevs_with_iso9660_check_regex(self): |
1862 | + """Check we call mount_cb on blockdevs with iso9660 and match regex""" |
1863 | + self.m_mounts.return_value = {} |
1864 | + self.m_find_devs_with.return_value = [ |
1865 | + '/dev/abc', '/dev/my-cdrom', '/dev/sr0'] |
1866 | + self.m_mount_cb.return_value = ("myfile", "mycontent") |
1867 | + |
1868 | + (contents, fullp, fname) = dsovf.transport_iso9660() |
1869 | + |
1870 | + self.m_mount_cb.assert_called_with( |
1871 | + "/dev/sr0", dsovf.get_ovf_env, mtype="iso9660") |
1872 | + self.assertEqual("mycontent", contents) |
1873 | + self.assertEqual("/dev/sr0", fullp) |
1874 | + self.assertEqual("myfile", fname) |
1875 | + |
1876 | + def test_mount_cb_not_called_no_matches(self): |
1877 | + """Check we don't call mount_cb if nothing matches""" |
1878 | + self.m_mounts.return_value = {} |
1879 | + self.m_find_devs_with.return_value = ['/dev/vg/myovf'] |
1880 | + |
1881 | + (contents, fullp, fname) = dsovf.transport_iso9660() |
1882 | + |
1883 | + self.assertEqual(0, self.m_mount_cb.call_count) |
1884 | + self.assertEqual(False, contents) |
1885 | + self.assertIsNone(fullp) |
1886 | + self.assertIsNone(fname) |
1887 | + |
1888 | + def test_mount_cb_called_require_iso_false(self): |
1889 | + """Check we call mount_cb on blockdevs with require_iso=False""" |
1890 | + self.m_mounts.return_value = {} |
1891 | + self.m_find_devs_with.return_value = ['/dev/xvdz'] |
1892 | + self.m_mount_cb.return_value = ("myfile", "mycontent") |
1893 | + |
1894 | + (contents, fullp, fname) = dsovf.transport_iso9660(require_iso=False) |
1895 | + |
1896 | + self.m_mount_cb.assert_called_with( |
1897 | + "/dev/xvdz", dsovf.get_ovf_env, mtype=None) |
1898 | + self.assertEqual("mycontent", contents) |
1899 | + self.assertEqual("/dev/xvdz", fullp) |
1900 | + self.assertEqual("myfile", fname) |
1901 | + |
1902 | + def test_maybe_cdrom_device_none(self): |
1903 | + """Test maybe_cdrom_device returns False for none/empty input""" |
1904 | + self.assertFalse(dsovf.maybe_cdrom_device(None)) |
1905 | + self.assertFalse(dsovf.maybe_cdrom_device('')) |
1906 | + |
1907 | + def test_maybe_cdrom_device_non_string_exception(self): |
1908 | + """Test maybe_cdrom_device raises ValueError on non-string types""" |
1909 | + with self.assertRaises(ValueError): |
1910 | + dsovf.maybe_cdrom_device({'a': 'eleven'}) |
1911 | + |
1912 | + def test_maybe_cdrom_device_false_on_multi_dir_paths(self): |
1913 | + """Test maybe_cdrom_device is false on /dev[/.*]/* paths""" |
1914 | + self.assertFalse(dsovf.maybe_cdrom_device('/dev/foo/sr0')) |
1915 | + self.assertFalse(dsovf.maybe_cdrom_device('foo/sr0')) |
1916 | + self.assertFalse(dsovf.maybe_cdrom_device('../foo/sr0')) |
1917 | + self.assertFalse(dsovf.maybe_cdrom_device('../foo/sr0')) |
1918 | + |
1919 | + def test_maybe_cdrom_device_true_on_hd_partitions(self): |
1920 | + """Test maybe_cdrom_device is false on /dev/hd[a-z][0-9]+ paths""" |
1921 | + self.assertTrue(dsovf.maybe_cdrom_device('/dev/hda1')) |
1922 | + self.assertTrue(dsovf.maybe_cdrom_device('hdz9')) |
1923 | + |
1924 | + def test_maybe_cdrom_device_true_on_valid_relative_paths(self): |
1925 | + """Test maybe_cdrom_device normalizes paths""" |
1926 | + self.assertTrue(dsovf.maybe_cdrom_device('/dev/wark/../sr9')) |
1927 | + self.assertTrue(dsovf.maybe_cdrom_device('///sr0')) |
1928 | + self.assertTrue(dsovf.maybe_cdrom_device('/sr0')) |
1929 | + self.assertTrue(dsovf.maybe_cdrom_device('//dev//hda')) |
1930 | + |
1931 | + def test_maybe_cdrom_device_true_on_xvd_partitions(self): |
1932 | + """Test maybe_cdrom_device returns true on xvd*""" |
1933 | + self.assertTrue(dsovf.maybe_cdrom_device('/dev/xvda')) |
1934 | + self.assertTrue(dsovf.maybe_cdrom_device('/dev/xvda1')) |
1935 | + self.assertTrue(dsovf.maybe_cdrom_device('xvdza1')) |
1936 | + |
1937 | +# |
1938 | # vi: ts=4 expandtab |
1939 | diff --git a/tests/unittests/test_handler/test_handler_bootcmd.py b/tests/unittests/test_handler/test_handler_bootcmd.py |
1940 | index 580017e..dbf43e0 100644 |
1941 | --- a/tests/unittests/test_handler/test_handler_bootcmd.py |
1942 | +++ b/tests/unittests/test_handler/test_handler_bootcmd.py |
1943 | @@ -29,6 +29,7 @@ class FakeExtendedTempFile(object): |
1944 | |
1945 | def __exit__(self, exc_type, exc_value, traceback): |
1946 | self.handle.close() |
1947 | + util.del_file(self.handle.name) |
1948 | |
1949 | |
1950 | class TestBootcmd(CiTestCase): |
1951 | diff --git a/tests/unittests/test_handler/test_handler_zypper_add_repo.py b/tests/unittests/test_handler/test_handler_zypper_add_repo.py |
1952 | new file mode 100644 |
1953 | index 0000000..315c2a5 |
1954 | --- /dev/null |
1955 | +++ b/tests/unittests/test_handler/test_handler_zypper_add_repo.py |
1956 | @@ -0,0 +1,237 @@ |
1957 | +# This file is part of cloud-init. See LICENSE file for license information. |
1958 | + |
1959 | +import glob |
1960 | +import os |
1961 | + |
1962 | +from cloudinit.config import cc_zypper_add_repo |
1963 | +from cloudinit import util |
1964 | + |
1965 | +from cloudinit.tests import helpers |
1966 | +from cloudinit.tests.helpers import mock |
1967 | + |
1968 | +try: |
1969 | + from configparser import ConfigParser |
1970 | +except ImportError: |
1971 | + from ConfigParser import ConfigParser |
1972 | +import logging |
1973 | +from six import StringIO |
1974 | + |
1975 | +LOG = logging.getLogger(__name__) |
1976 | + |
1977 | + |
1978 | +class TestConfig(helpers.FilesystemMockingTestCase): |
1979 | + def setUp(self): |
1980 | + super(TestConfig, self).setUp() |
1981 | + self.tmp = self.tmp_dir() |
1982 | + self.zypp_conf = 'etc/zypp/zypp.conf' |
1983 | + |
1984 | + def test_bad_repo_config(self): |
1985 | + """Config has no baseurl, no file should be written""" |
1986 | + cfg = { |
1987 | + 'repos': [ |
1988 | + { |
1989 | + 'id': 'foo', |
1990 | + 'name': 'suse-test', |
1991 | + 'enabled': '1' |
1992 | + }, |
1993 | + ] |
1994 | + } |
1995 | + self.patchUtils(self.tmp) |
1996 | + cc_zypper_add_repo._write_repos(cfg['repos'], '/etc/zypp/repos.d') |
1997 | + self.assertRaises(IOError, util.load_file, |
1998 | + "/etc/zypp/repos.d/foo.repo") |
1999 | + |
2000 | + def test_write_repos(self): |
2001 | + """Verify valid repos get written""" |
2002 | + cfg = self._get_base_config_repos() |
2003 | + root_d = self.tmp_dir() |
2004 | + cc_zypper_add_repo._write_repos(cfg['zypper']['repos'], root_d) |
2005 | + repos = glob.glob('%s/*.repo' % root_d) |
2006 | + expected_repos = ['testing-foo.repo', 'testing-bar.repo'] |
2007 | + if len(repos) != 2: |
2008 | + assert 'Number of repos written is "%d" expected 2' % len(repos) |
2009 | + for repo in repos: |
2010 | + repo_name = os.path.basename(repo) |
2011 | + if repo_name not in expected_repos: |
2012 | + assert 'Found repo with name "%s"; unexpected' % repo_name |
2013 | + # Validation that the content gets properly written is in another test |
2014 | + |
2015 | + def test_write_repo(self): |
2016 | + """Verify the content of a repo file""" |
2017 | + cfg = { |
2018 | + 'repos': [ |
2019 | + { |
2020 | + 'baseurl': 'http://foo', |
2021 | + 'name': 'test-foo', |
2022 | + 'id': 'testing-foo' |
2023 | + }, |
2024 | + ] |
2025 | + } |
2026 | + root_d = self.tmp_dir() |
2027 | + cc_zypper_add_repo._write_repos(cfg['repos'], root_d) |
2028 | + contents = util.load_file("%s/testing-foo.repo" % root_d) |
2029 | + parser = ConfigParser() |
2030 | + parser.readfp(StringIO(contents)) |
2031 | + expected = { |
2032 | + 'testing-foo': { |
2033 | + 'name': 'test-foo', |
2034 | + 'baseurl': 'http://foo', |
2035 | + 'enabled': '1', |
2036 | + 'autorefresh': '1' |
2037 | + } |
2038 | + } |
2039 | + for section in expected: |
2040 | + self.assertTrue(parser.has_section(section), |
2041 | + "Contains section {0}".format(section)) |
2042 | + for k, v in expected[section].items(): |
2043 | + self.assertEqual(parser.get(section, k), v) |
2044 | + |
2045 | + def test_config_write(self): |
2046 | + """Write valid configuration data""" |
2047 | + cfg = { |
2048 | + 'config': { |
2049 | + 'download.deltarpm': 'False', |
2050 | + 'reposdir': 'foo' |
2051 | + } |
2052 | + } |
2053 | + root_d = self.tmp_dir() |
2054 | + helpers.populate_dir(root_d, {self.zypp_conf: '# Zypp config\n'}) |
2055 | + self.reRoot(root_d) |
2056 | + cc_zypper_add_repo._write_zypp_config(cfg['config']) |
2057 | + cfg_out = os.path.join(root_d, self.zypp_conf) |
2058 | + contents = util.load_file(cfg_out) |
2059 | + expected = [ |
2060 | + '# Zypp config', |
2061 | + '# Added via cloud.cfg', |
2062 | + 'download.deltarpm=False', |
2063 | + 'reposdir=foo' |
2064 | + ] |
2065 | + for item in contents.split('\n'): |
2066 | + if item not in expected: |
2067 | + self.assertIsNone(item) |
2068 | + |
2069 | + @mock.patch('cloudinit.log.logging') |
2070 | + def test_config_write_skip_configdir(self, mock_logging): |
2071 | + """Write configuration but skip writing 'configdir' setting""" |
2072 | + cfg = { |
2073 | + 'config': { |
2074 | + 'download.deltarpm': 'False', |
2075 | + 'reposdir': 'foo', |
2076 | + 'configdir': 'bar' |
2077 | + } |
2078 | + } |
2079 | + root_d = self.tmp_dir() |
2080 | + helpers.populate_dir(root_d, {self.zypp_conf: '# Zypp config\n'}) |
2081 | + self.reRoot(root_d) |
2082 | + cc_zypper_add_repo._write_zypp_config(cfg['config']) |
2083 | + cfg_out = os.path.join(root_d, self.zypp_conf) |
2084 | + contents = util.load_file(cfg_out) |
2085 | + expected = [ |
2086 | + '# Zypp config', |
2087 | + '# Added via cloud.cfg', |
2088 | + 'download.deltarpm=False', |
2089 | + 'reposdir=foo' |
2090 | + ] |
2091 | + for item in contents.split('\n'): |
2092 | + if item not in expected: |
2093 | + self.assertIsNone(item) |
2094 | + # Not finding teh right path for mocking :( |
2095 | + # assert mock_logging.warning.called |
2096 | + |
2097 | + def test_empty_config_section_no_new_data(self): |
2098 | + """When the config section is empty no new data should be written to |
2099 | + zypp.conf""" |
2100 | + cfg = self._get_base_config_repos() |
2101 | + cfg['zypper']['config'] = None |
2102 | + root_d = self.tmp_dir() |
2103 | + helpers.populate_dir(root_d, {self.zypp_conf: '# No data'}) |
2104 | + self.reRoot(root_d) |
2105 | + cc_zypper_add_repo._write_zypp_config(cfg.get('config', {})) |
2106 | + cfg_out = os.path.join(root_d, self.zypp_conf) |
2107 | + contents = util.load_file(cfg_out) |
2108 | + self.assertEqual(contents, '# No data') |
2109 | + |
2110 | + def test_empty_config_value_no_new_data(self): |
2111 | + """When the config section is not empty but there are no values |
2112 | + no new data should be written to zypp.conf""" |
2113 | + cfg = self._get_base_config_repos() |
2114 | + cfg['zypper']['config'] = { |
2115 | + 'download.deltarpm': None |
2116 | + } |
2117 | + root_d = self.tmp_dir() |
2118 | + helpers.populate_dir(root_d, {self.zypp_conf: '# No data'}) |
2119 | + self.reRoot(root_d) |
2120 | + cc_zypper_add_repo._write_zypp_config(cfg.get('config', {})) |
2121 | + cfg_out = os.path.join(root_d, self.zypp_conf) |
2122 | + contents = util.load_file(cfg_out) |
2123 | + self.assertEqual(contents, '# No data') |
2124 | + |
2125 | + def test_handler_full_setup(self): |
2126 | + """Test that the handler ends up calling the renderers""" |
2127 | + cfg = self._get_base_config_repos() |
2128 | + cfg['zypper']['config'] = { |
2129 | + 'download.deltarpm': 'False', |
2130 | + } |
2131 | + root_d = self.tmp_dir() |
2132 | + os.makedirs('%s/etc/zypp/repos.d' % root_d) |
2133 | + helpers.populate_dir(root_d, {self.zypp_conf: '# Zypp config\n'}) |
2134 | + self.reRoot(root_d) |
2135 | + cc_zypper_add_repo.handle('zypper_add_repo', cfg, None, LOG, []) |
2136 | + cfg_out = os.path.join(root_d, self.zypp_conf) |
2137 | + contents = util.load_file(cfg_out) |
2138 | + expected = [ |
2139 | + '# Zypp config', |
2140 | + '# Added via cloud.cfg', |
2141 | + 'download.deltarpm=False', |
2142 | + ] |
2143 | + for item in contents.split('\n'): |
2144 | + if item not in expected: |
2145 | + self.assertIsNone(item) |
2146 | + repos = glob.glob('%s/etc/zypp/repos.d/*.repo' % root_d) |
2147 | + expected_repos = ['testing-foo.repo', 'testing-bar.repo'] |
2148 | + if len(repos) != 2: |
2149 | + assert 'Number of repos written is "%d" expected 2' % len(repos) |
2150 | + for repo in repos: |
2151 | + repo_name = os.path.basename(repo) |
2152 | + if repo_name not in expected_repos: |
2153 | + assert 'Found repo with name "%s"; unexpected' % repo_name |
2154 | + |
2155 | + def test_no_config_section_no_new_data(self): |
2156 | + """When there is no config section no new data should be written to |
2157 | + zypp.conf""" |
2158 | + cfg = self._get_base_config_repos() |
2159 | + root_d = self.tmp_dir() |
2160 | + helpers.populate_dir(root_d, {self.zypp_conf: '# No data'}) |
2161 | + self.reRoot(root_d) |
2162 | + cc_zypper_add_repo._write_zypp_config(cfg.get('config', {})) |
2163 | + cfg_out = os.path.join(root_d, self.zypp_conf) |
2164 | + contents = util.load_file(cfg_out) |
2165 | + self.assertEqual(contents, '# No data') |
2166 | + |
2167 | + def test_no_repo_data(self): |
2168 | + """When there is no repo data nothing should happen""" |
2169 | + root_d = self.tmp_dir() |
2170 | + self.reRoot(root_d) |
2171 | + cc_zypper_add_repo._write_repos(None, root_d) |
2172 | + content = glob.glob('%s/*' % root_d) |
2173 | + self.assertEqual(len(content), 0) |
2174 | + |
2175 | + def _get_base_config_repos(self): |
2176 | + """Basic valid repo configuration""" |
2177 | + cfg = { |
2178 | + 'zypper': { |
2179 | + 'repos': [ |
2180 | + { |
2181 | + 'baseurl': 'http://foo', |
2182 | + 'name': 'test-foo', |
2183 | + 'id': 'testing-foo' |
2184 | + }, |
2185 | + { |
2186 | + 'baseurl': 'http://bar', |
2187 | + 'name': 'test-bar', |
2188 | + 'id': 'testing-bar' |
2189 | + } |
2190 | + ] |
2191 | + } |
2192 | + } |
2193 | + return cfg |
2194 | diff --git a/tests/unittests/test_handler/test_schema.py b/tests/unittests/test_handler/test_schema.py |
2195 | index 745bb0f..b8fc893 100644 |
2196 | --- a/tests/unittests/test_handler/test_schema.py |
2197 | +++ b/tests/unittests/test_handler/test_schema.py |
2198 | @@ -27,7 +27,13 @@ class GetSchemaTest(CiTestCase): |
2199 | """Every cloudconfig module with schema is listed in allOf keyword.""" |
2200 | schema = get_schema() |
2201 | self.assertItemsEqual( |
2202 | - ['cc_bootcmd', 'cc_ntp', 'cc_resizefs', 'cc_runcmd'], |
2203 | + [ |
2204 | + 'cc_bootcmd', |
2205 | + 'cc_ntp', |
2206 | + 'cc_resizefs', |
2207 | + 'cc_runcmd', |
2208 | + 'cc_zypper_add_repo' |
2209 | + ], |
2210 | [subschema['id'] for subschema in schema['allOf']]) |
2211 | self.assertEqual('cloud-config-schema', schema['id']) |
2212 | self.assertEqual( |
2213 | diff --git a/tools/build-on-freebsd b/tools/build-on-freebsd |
2214 | index ff9153a..d23fde2 100755 |
2215 | --- a/tools/build-on-freebsd |
2216 | +++ b/tools/build-on-freebsd |
2217 | @@ -18,7 +18,6 @@ pkgs=" |
2218 | py27-jsonpatch |
2219 | py27-jsonpointer |
2220 | py27-oauthlib |
2221 | - py27-prettytable |
2222 | py27-requests |
2223 | py27-serial |
2224 | py27-six |
2225 | diff --git a/tox.ini b/tox.ini |
2226 | index 776f425..aef1f84 100644 |
2227 | --- a/tox.ini |
2228 | +++ b/tox.ini |
2229 | @@ -64,7 +64,6 @@ deps = |
2230 | # requirements |
2231 | jinja2==2.8 |
2232 | pyyaml==3.11 |
2233 | - PrettyTable==0.7.2 |
2234 | oauthlib==1.0.3 |
2235 | pyserial==3.0.1 |
2236 | configobj==5.0.6 |
2237 | @@ -89,7 +88,6 @@ deps = |
2238 | argparse==1.2.1 |
2239 | jinja2==2.2.1 |
2240 | pyyaml==3.10 |
2241 | - PrettyTable==0.7.2 |
2242 | oauthlib==0.6.0 |
2243 | configobj==4.6.0 |
2244 | requests==2.6.0 |
2245 | @@ -105,7 +103,6 @@ deps = |
2246 | argparse==1.3.0 |
2247 | jinja2==2.8 |
2248 | PyYAML==3.11 |
2249 | - PrettyTable==0.7.2 |
2250 | oauthlib==0.7.2 |
2251 | configobj==5.0.6 |
2252 | requests==2.11.1 |
looks good, thank you Ryan.