Merge ~chad.smith/cloud-init:ubuntu/devel into cloud-init:ubuntu/devel

Proposed by Chad Smith
Status: Merged
Merged at revision: 04abd70b222976007b07afc5b1fa70c0a4de882e
Proposed branch: ~chad.smith/cloud-init:ubuntu/devel
Merge into: cloud-init:ubuntu/devel
Diff against target: 814 lines (+371/-90)
15 files modified
ChangeLog (+52/-0)
cloudinit/cmd/tests/test_clean.py (+2/-1)
cloudinit/cmd/tests/test_status.py (+2/-1)
cloudinit/sources/DataSourceOVF.py (+16/-5)
cloudinit/tests/helpers.py (+9/-11)
cloudinit/version.py (+1/-1)
config/cloud.cfg.tmpl (+2/-0)
debian/changelog (+16/-0)
tests/cloud_tests/collect.py (+3/-2)
tests/cloud_tests/platforms/lxd/instance.py (+106/-26)
tests/unittests/test_ds_identify.py (+40/-3)
tests/unittests/test_handler/test_schema.py (+7/-5)
tools/ds-identify (+34/-19)
tools/run-centos (+78/-13)
tox.ini (+3/-3)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Scott Moser Pending
Review via email: mp+338591@code.launchpad.net

Description of the change

Upstream snapshot for cloud-init master to sync 18.1 release to bionic

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:04abd70b222976007b07afc5b1fa70c0a4de882e
https://jenkins.ubuntu.com/server/job/cloud-init-ci/785/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/785/rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/ChangeLog b/ChangeLog
2index 31c2dcb..be4c357 100644
3--- a/ChangeLog
4+++ b/ChangeLog
5@@ -1,3 +1,55 @@
6+18.1:
7+ - OVF: Fix VMware support for 64-bit platforms. [Sankar Tanguturi]
8+ - ds-identify: Fix searching for iso9660 OVF cdroms. (LP: #1749980)
9+ - SUSE: Fix groups used for ownership of cloud-init.log [Robert Schweikert]
10+ - ds-identify: check /writable/system-data/ for nocloud seed.
11+ (LP: #1747070)
12+ - tests: run nosetests in cloudinit/ directory, fix py26 fallout.
13+ - tools: run-centos: git clone rather than tar.
14+ - tests: add support for logs with lxd from snap and future lxd 3.
15+ (LP: #1745663)
16+ - EC2: Fix get_instance_id called against cached datasource pickle.
17+ (LP: #1748354)
18+ - cli: fix cloud-init status to report running when before result.json
19+ (LP: #1747965)
20+ - net: accept network-config in netplan format for renaming interfaces
21+ (LP: #1709715)
22+ - Fix ssh keys validation in ssh_util [Tatiana Kholkina]
23+ - docs: Update RTD content for cloud-init subcommands.
24+ - OVF: Extend well-known labels to include OVFENV. (LP: #1698669)
25+ - Fix potential cases of uninitialized variables. (LP: #1744796)
26+ - tests: Collect script output as binary, collect systemd journal, fix lxd.
27+ - HACKING.rst: mention setting user name and email via git config.
28+ - Azure VM Preprovisioning support. [Douglas Jordan] (LP: #1734991)
29+ - tools/read-version: Fix read-version when in a git worktree.
30+ - docs: Fix typos in docs and one debug message. [Florian Grignon]
31+ - btrfs: support resizing if root is mounted ro.
32+ [Robert Schweikert] (LP: #1734787)
33+ - OpenNebula: Improve network configuration support.
34+ [Akihiko Ota] (LP: #1719157, #1716397, #1736750)
35+ - tests: Fix EC2 Platform to return console output as bytes.
36+ - tests: Fix attempted use of /run in a test case.
37+ - GCE: Improvements and changes to ssh key behavior for default user.
38+ [Max Illfelder] (LP: #1670456, #1707033, #1707037, #1707039)
39+ - subp: make ProcessExecutionError have expected types in stderr, stdout.
40+ - tests: when querying ntp server, do not do dns resolution.
41+ - Recognize uppercase vfat disk labels [James Penick] (LP: #1598783)
42+ - tests: remove zesty as supported OS to test [Joshua Powers]
43+ - Do not log warning on config files that represent None. (LP: #1742479)
44+ - tests: Use git hash pip dependency format for pylxd.
45+ - tests: add integration requirements text file [Joshua Powers]
46+ - MAAS: add check_instance_id based off oauth tokens. (LP: #1712680)
47+ - tests: update apt sources list test [Joshua Powers]
48+ - tests: clean up image properties [Joshua Powers]
49+ - tests: rename test ssh keys to avoid appearance of leaking private keys.
50+ [Joshua Powers]
51+ - tests: Enable AWS EC2 Integration Testing [Joshua Powers]
52+ - cli: cloud-init clean handles symlinks (LP: #1741093)
53+ - SUSE: Add a basic test of network config rendering. [Robert Schweikert]
54+ - Azure: Only bounce network when necessary. (LP: #1722668)
55+ - lint: Fix lints seen by pylint version 1.8.1.
56+ - cli: Fix error in cloud-init modules --mode=init. (LP: #1736600)
57+
58 17.2:
59 - ds-identify: failure in NoCloud due to unset variable usage.
60 (LP: #1737704)
61diff --git a/cloudinit/cmd/tests/test_clean.py b/cloudinit/cmd/tests/test_clean.py
62index 6713af4..5a3ec3b 100644
63--- a/cloudinit/cmd/tests/test_clean.py
64+++ b/cloudinit/cmd/tests/test_clean.py
65@@ -165,10 +165,11 @@ class TestClean(CiTestCase):
66 wrap_and_call(
67 'cloudinit.cmd.clean',
68 {'Init': {'side_effect': self.init_class},
69+ 'sys.exit': {'side_effect': self.sys_exit},
70 'sys.argv': {'new': ['clean', '--logs']}},
71 clean.main)
72
73- self.assertRaisesCodeEqual(0, context_manager.exception.code)
74+ self.assertEqual(0, context_manager.exception.code)
75 self.assertFalse(
76 os.path.exists(self.log1), 'Unexpected log {0}'.format(self.log1))
77
78diff --git a/cloudinit/cmd/tests/test_status.py b/cloudinit/cmd/tests/test_status.py
79index 4a5a8c0..37a8993 100644
80--- a/cloudinit/cmd/tests/test_status.py
81+++ b/cloudinit/cmd/tests/test_status.py
82@@ -380,10 +380,11 @@ class TestStatus(CiTestCase):
83 wrap_and_call(
84 'cloudinit.cmd.status',
85 {'sys.argv': {'new': ['status']},
86+ 'sys.exit': {'side_effect': self.sys_exit},
87 '_is_cloudinit_disabled': (False, ''),
88 'Init': {'side_effect': self.init_class}},
89 status.main)
90- self.assertRaisesCodeEqual(0, context_manager.exception.code)
91+ self.assertEqual(0, context_manager.exception.code)
92 self.assertEqual('status: running\n', m_stdout.getvalue())
93
94 # vi: ts=4 expandtab syntax=python
95diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py
96index 6e62f98..dc914a7 100644
97--- a/cloudinit/sources/DataSourceOVF.py
98+++ b/cloudinit/sources/DataSourceOVF.py
99@@ -95,11 +95,20 @@ class DataSourceOVF(sources.DataSource):
100 "VMware Customization support")
101 elif not util.get_cfg_option_bool(
102 self.sys_cfg, "disable_vmware_customization", True):
103- deployPkgPluginPath = search_file("/usr/lib/vmware-tools",
104- "libdeployPkgPlugin.so")
105- if not deployPkgPluginPath:
106- deployPkgPluginPath = search_file("/usr/lib/open-vm-tools",
107- "libdeployPkgPlugin.so")
108+
109+ search_paths = (
110+ "/usr/lib/vmware-tools", "/usr/lib64/vmware-tools",
111+ "/usr/lib/open-vm-tools", "/usr/lib64/open-vm-tools")
112+
113+ plugin = "libdeployPkgPlugin.so"
114+ deployPkgPluginPath = None
115+ for path in search_paths:
116+ deployPkgPluginPath = search_file(path, plugin)
117+ if deployPkgPluginPath:
118+ LOG.debug("Found the customization plugin at %s",
119+ deployPkgPluginPath)
120+ break
121+
122 if deployPkgPluginPath:
123 # When the VM is powered on, the "VMware Tools" daemon
124 # copies the customization specification file to
125@@ -111,6 +120,8 @@ class DataSourceOVF(sources.DataSource):
126 msg="waiting for configuration file",
127 func=wait_for_imc_cfg_file,
128 args=("cust.cfg", max_wait))
129+ else:
130+ LOG.debug("Did not find the customization plugin.")
131
132 if vmwareImcConfigFilePath:
133 LOG.debug("Found VMware Customization Config File at %s",
134diff --git a/cloudinit/tests/helpers.py b/cloudinit/tests/helpers.py
135index 0080c72..41d9a8e 100644
136--- a/cloudinit/tests/helpers.py
137+++ b/cloudinit/tests/helpers.py
138@@ -173,17 +173,15 @@ class CiTestCase(TestCase):
139 dir = self.tmp_dir()
140 return os.path.normpath(os.path.abspath(os.path.join(dir, path)))
141
142- def assertRaisesCodeEqual(self, expected, found):
143- """Handle centos6 having different context manager for assertRaises.
144- with assertRaises(Exception) as e:
145- raise Exception("BOO")
146-
147- centos6 will have e.exception as an integer.
148- anything nwere will have it as something with a '.code'"""
149- if isinstance(found, int):
150- self.assertEqual(expected, found)
151- else:
152- self.assertEqual(expected, found.code)
153+ def sys_exit(self, code):
154+ """Provide a wrapper around sys.exit for python 2.6
155+
156+ In 2.6, this code would produce 'cm.exception' with value int(2)
157+ rather than the SystemExit that was raised by sys.exit(2).
158+ with assertRaises(SystemExit) as cm:
159+ sys.exit(2)
160+ """
161+ raise SystemExit(code)
162
163
164 class ResourceUsingTestCase(CiTestCase):
165diff --git a/cloudinit/version.py b/cloudinit/version.py
166index be6262d..4a682ad 100644
167--- a/cloudinit/version.py
168+++ b/cloudinit/version.py
169@@ -4,7 +4,7 @@
170 #
171 # This file is part of cloud-init. See LICENSE file for license information.
172
173-__VERSION__ = "17.2"
174+__VERSION__ = "18.1"
175
176 FEATURES = [
177 # supports network config version 1
178diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl
179index 32de9c9..fad1184 100644
180--- a/config/cloud.cfg.tmpl
181+++ b/config/cloud.cfg.tmpl
182@@ -4,6 +4,8 @@
183
184 {% if variant in ["freebsd"] %}
185 syslog_fix_perms: root:wheel
186+{% elif variant in ["suse"] %}
187+syslog_fix_perms: root:root
188 {% endif %}
189 # A set of users which may be applied and/or used by various modules
190 # when a 'default' entry is found it will reference the 'default_user'
191diff --git a/debian/changelog b/debian/changelog
192index abbd5a9..2552bb6 100644
193--- a/debian/changelog
194+++ b/debian/changelog
195@@ -1,3 +1,19 @@
196+cloud-init (18.1-0ubuntu1) bionic; urgency=medium
197+
198+ * New upstream snapshot.
199+ - release 18.1 (LP: #1751145)
200+ - OVF: Fix VMware support for 64-bit platforms. [Sankar Tanguturi]
201+ - ds-identify: Fix searching for iso9660 OVF cdroms. (LP: #1749980)
202+ - SUSE: Fix groups used for ownership of cloud-init.log [Robert Schweikert]
203+ - ds-identify: check /writable/system-data/ for nocloud seed.
204+ (LP: #1747070)
205+ - tests: run nosetests in cloudinit/ directory, fix py26 fallout.
206+ - tools: run-centos: git clone rather than tar.
207+ - tests: add support for logs with lxd from snap and future lxd 3.
208+ (LP: #1745663)
209+
210+ -- Chad Smith <chad.smith@canonical.com> Thu, 22 Feb 2018 15:42:11 -0700
211+
212 cloud-init (17.2-34-g644048e3-0ubuntu1) bionic; urgency=medium
213
214 * New upstream snapshot.
215diff --git a/tests/cloud_tests/collect.py b/tests/cloud_tests/collect.py
216index 5ea88e5..d4f9135 100644
217--- a/tests/cloud_tests/collect.py
218+++ b/tests/cloud_tests/collect.py
219@@ -44,8 +44,9 @@ def collect_console(instance, base_dir):
220 LOG.debug('getting console log for %s to %s', instance, logfile)
221 try:
222 data = instance.console_log()
223- except NotImplementedError:
224- data = b'instance.console_log: not implemented'
225+ except NotImplementedError as e:
226+ # args[0] is hacky, but thats all I see to get at the message.
227+ data = b'NotImplementedError:' + e.args[0].encode()
228 with open(logfile, "wb") as fp:
229 fp.write(data)
230
231diff --git a/tests/cloud_tests/platforms/lxd/instance.py b/tests/cloud_tests/platforms/lxd/instance.py
232index d2d2a1f..0488da5 100644
233--- a/tests/cloud_tests/platforms/lxd/instance.py
234+++ b/tests/cloud_tests/platforms/lxd/instance.py
235@@ -6,7 +6,9 @@ import os
236 import shutil
237 from tempfile import mkdtemp
238
239-from cloudinit.util import subp, ProcessExecutionError
240+from cloudinit.util import load_yaml, subp, ProcessExecutionError, which
241+from tests.cloud_tests import LOG
242+from tests.cloud_tests.util import PlatformError
243
244 from ..instances import Instance
245
246@@ -15,6 +17,8 @@ class LXDInstance(Instance):
247 """LXD container backed instance."""
248
249 platform_name = "lxd"
250+ _console_log_method = None
251+ _console_log_file = None
252
253 def __init__(self, platform, name, properties, config, features,
254 pylxd_container):
255@@ -30,8 +34,8 @@ class LXDInstance(Instance):
256 super(LXDInstance, self).__init__(
257 platform, name, properties, config, features)
258 self.tmpd = mkdtemp(prefix="%s-%s" % (type(self).__name__, name))
259- self._setup_console_log()
260 self.name = name
261+ self._setup_console_log()
262
263 @property
264 def pylxd_container(self):
265@@ -39,21 +43,6 @@ class LXDInstance(Instance):
266 self._pylxd_container.sync()
267 return self._pylxd_container
268
269- def _setup_console_log(self):
270- logf = os.path.join(self.tmpd, "console.log")
271-
272- # doing this ensures we can read it. Otherwise it ends up root:root.
273- with open(logf, "w") as fp:
274- fp.write("# %s\n" % self.name)
275-
276- cfg = "lxc.console.logfile=%s" % logf
277- orig = self._pylxd_container.config.get('raw.lxc', "")
278- if orig:
279- orig += "\n"
280- self._pylxd_container.config['raw.lxc'] = orig + cfg
281- self._pylxd_container.save()
282- self._console_log_file = logf
283-
284 def _execute(self, command, stdin=None, env=None):
285 if env is None:
286 env = {}
287@@ -97,19 +86,80 @@ class LXDInstance(Instance):
288 """
289 self.pylxd_container.files.put(remote_path, data)
290
291+ @property
292+ def console_log_method(self):
293+ if self._console_log_method is not None:
294+ return self._console_log_method
295+
296+ client = which('lxc')
297+ if not client:
298+ raise PlatformError("No 'lxc' client.")
299+
300+ elif _has_proper_console_support():
301+ self._console_log_method = 'show-log'
302+ elif client.startswith("/snap"):
303+ self._console_log_method = 'logfile-snap'
304+ else:
305+ self._console_log_method = 'logfile-tmp'
306+
307+ LOG.debug("Set console log method to %s", self._console_log_method)
308+ return self._console_log_method
309+
310+ def _setup_console_log(self):
311+ method = self.console_log_method
312+ if not method.startswith("logfile-"):
313+ return
314+
315+ if method == "logfile-snap":
316+ log_dir = "/var/snap/lxd/common/consoles"
317+ if not os.path.exists(log_dir):
318+ raise PlatformError(
319+ "Unable to log with snap lxc. Please run:\n"
320+ " sudo mkdir --mode=1777 -p %s" % log_dir)
321+ elif method == "logfile-tmp":
322+ log_dir = "/tmp"
323+ else:
324+ raise PlatformError(
325+ "Unexpected value for console method: %s" % method)
326+
327+ # doing this ensures we can read it. Otherwise it ends up root:root.
328+ log_file = os.path.join(log_dir, self.name)
329+ with open(log_file, "w") as fp:
330+ fp.write("# %s\n" % self.name)
331+
332+ cfg = "lxc.console.logfile=%s" % log_file
333+ orig = self._pylxd_container.config.get('raw.lxc', "")
334+ if orig:
335+ orig += "\n"
336+ self._pylxd_container.config['raw.lxc'] = orig + cfg
337+ self._pylxd_container.save()
338+ self._console_log_file = log_file
339+
340 def console_log(self):
341 """Console log.
342
343- @return_value: bytes of this instance’s console
344+ @return_value: bytes of this instance's console
345 """
346- if not os.path.exists(self._console_log_file):
347- raise NotImplementedError(
348- "Console log '%s' does not exist. If this is a remote "
349- "lxc, then this is really NotImplementedError. If it is "
350- "A local lxc, then this is a RuntimeError."
351- "https://github.com/lxc/lxd/issues/1129")
352- with open(self._console_log_file, "rb") as fp:
353- return fp.read()
354+
355+ if self._console_log_file:
356+ if not os.path.exists(self._console_log_file):
357+ raise NotImplementedError(
358+ "Console log '%s' does not exist. If this is a remote "
359+ "lxc, then this is really NotImplementedError. If it is "
360+ "A local lxc, then this is a RuntimeError."
361+ "https://github.com/lxc/lxd/issues/1129")
362+ with open(self._console_log_file, "rb") as fp:
363+ return fp.read()
364+
365+ try:
366+ stdout, stderr = subp(
367+ ['lxc', 'console', '--show-log', self.name], decode=False)
368+ return stdout
369+ except ProcessExecutionError as e:
370+ raise PlatformError(
371+ "console log",
372+ "Console log failed [%d]: stdout=%s stderr=%s" % (
373+ e.exit_code, e.stdout, e.stderr))
374
375 def reboot(self, wait=True):
376 """Reboot instance."""
377@@ -146,7 +196,37 @@ class LXDInstance(Instance):
378 if self.platform.container_exists(self.name):
379 raise OSError('container {} was not properly removed'
380 .format(self.name))
381+ if self._console_log_file and os.path.exists(self._console_log_file):
382+ os.unlink(self._console_log_file)
383 shutil.rmtree(self.tmpd)
384 super(LXDInstance, self).destroy()
385
386+
387+def _has_proper_console_support():
388+ stdout, _ = subp(['lxc', 'info'])
389+ info = load_yaml(stdout)
390+ reason = None
391+ if 'console' not in info.get('api_extensions', []):
392+ reason = "LXD server does not support console api extension"
393+ else:
394+ dver = info.get('environment', {}).get('driver_version', "")
395+ if dver.startswith("2.") or dver.startwith("1."):
396+ reason = "LXD Driver version not 3.x+ (%s)" % dver
397+ else:
398+ try:
399+ stdout, stderr = subp(['lxc', 'console', '--help'],
400+ decode=False)
401+ if not (b'console' in stdout and b'log' in stdout):
402+ reason = "no '--log' in lxc console --help"
403+ except ProcessExecutionError as e:
404+ reason = "no 'console' command in lxc client"
405+
406+ if reason:
407+ LOG.debug("no console-support: %s", reason)
408+ return False
409+ else:
410+ LOG.debug("console-support looks good")
411+ return True
412+
413+
414 # vi: ts=4 expandtab
415diff --git a/tests/unittests/test_ds_identify.py b/tests/unittests/test_ds_identify.py
416index 31cc622..9be3f96 100644
417--- a/tests/unittests/test_ds_identify.py
418+++ b/tests/unittests/test_ds_identify.py
419@@ -337,6 +337,16 @@ class TestDsIdentify(CiTestCase):
420 """OVF is identified when vmware customization is enabled."""
421 self._test_ds_found('OVF-vmware-customization')
422
423+ def test_ovf_on_vmware_iso_found_open_vm_tools_64(self):
424+ """OVF is identified when open-vm-tools installed in /usr/lib64."""
425+ cust64 = copy.deepcopy(VALID_CFG['OVF-vmware-customization'])
426+ p32 = 'usr/lib/vmware-tools/plugins/vmsvc/libdeployPkgPlugin.so'
427+ open64 = 'usr/lib64/open-vm-tools/plugins/vmsvc/libdeployPkgPlugin.so'
428+ cust64['files'][open64] = cust64['files'][p32]
429+ del cust64['files'][p32]
430+ return self._check_via_dict(
431+ cust64, RC_FOUND, dslist=[cust64.get('ds'), DS_NONE])
432+
433 def test_ovf_on_vmware_iso_found_by_cdrom_with_matching_fs_label(self):
434 """OVF is identified by well-known iso9660 labels."""
435 ovf_cdrom_by_label = copy.deepcopy(VALID_CFG['OVF'])
436@@ -350,8 +360,10 @@ class TestDsIdentify(CiTestCase):
437 "OVFENV", "ovfenv"]
438 for valid_ovf_label in valid_ovf_labels:
439 ovf_cdrom_by_label['mocks'][0]['out'] = blkid_out([
440+ {'DEVNAME': 'sda1', 'TYPE': 'ext4', 'LABEL': 'rootfs'},
441 {'DEVNAME': 'sr0', 'TYPE': 'iso9660',
442- 'LABEL': valid_ovf_label}])
443+ 'LABEL': valid_ovf_label},
444+ {'DEVNAME': 'vda1', 'TYPE': 'ntfs', 'LABEL': 'data'}])
445 self._check_via_dict(
446 ovf_cdrom_by_label, rc=RC_FOUND, dslist=['OVF', DS_NONE])
447
448@@ -359,6 +371,14 @@ class TestDsIdentify(CiTestCase):
449 """NoCloud is found with iso9660 filesystem on non-cdrom disk."""
450 self._test_ds_found('NoCloud')
451
452+ def test_nocloud_seed(self):
453+ """Nocloud seed directory."""
454+ self._test_ds_found('NoCloud-seed')
455+
456+ def test_nocloud_seed_ubuntu_core_writable(self):
457+ """Nocloud seed directory ubuntu core writable"""
458+ self._test_ds_found('NoCloud-seed-ubuntu-core')
459+
460
461 def blkid_out(disks=None):
462 """Convert a list of disk dictionaries into blkid content."""
463@@ -454,6 +474,22 @@ VALID_CFG = {
464 'dev/vdb': 'pretend iso content for cidata\n',
465 }
466 },
467+ 'NoCloud-seed': {
468+ 'ds': 'NoCloud',
469+ 'files': {
470+ os.path.join(P_SEED_DIR, 'nocloud', 'user-data'): 'ud\n',
471+ os.path.join(P_SEED_DIR, 'nocloud', 'meta-data'): 'md\n',
472+ }
473+ },
474+ 'NoCloud-seed-ubuntu-core': {
475+ 'ds': 'NoCloud',
476+ 'files': {
477+ os.path.join('writable/system-data', P_SEED_DIR,
478+ 'nocloud-net', 'user-data'): 'ud\n',
479+ os.path.join('writable/system-data', P_SEED_DIR,
480+ 'nocloud-net', 'meta-data'): 'md\n',
481+ }
482+ },
483 'OpenStack': {
484 'ds': 'OpenStack',
485 'files': {P_PRODUCT_NAME: 'OpenStack Nova\n'},
486@@ -489,8 +525,9 @@ VALID_CFG = {
487 'mocks': [
488 {'name': 'blkid', 'ret': 0,
489 'out': blkid_out(
490- [{'DEVNAME': 'vda1', 'TYPE': 'vfat', 'PARTUUID': uuid4()},
491- {'DEVNAME': 'sr0', 'TYPE': 'iso9660', 'LABEL': ''}])
492+ [{'DEVNAME': 'sr0', 'TYPE': 'iso9660', 'LABEL': ''},
493+ {'DEVNAME': 'sr1', 'TYPE': 'iso9660', 'LABEL': 'ignoreme'},
494+ {'DEVNAME': 'vda1', 'TYPE': 'vfat', 'PARTUUID': uuid4()}]),
495 },
496 MOCK_VIRT_IS_VMWARE,
497 ],
498diff --git a/tests/unittests/test_handler/test_schema.py b/tests/unittests/test_handler/test_schema.py
499index 648573f..df67a0e 100644
500--- a/tests/unittests/test_handler/test_schema.py
501+++ b/tests/unittests/test_handler/test_schema.py
502@@ -336,11 +336,13 @@ class MainTest(CiTestCase):
503
504 def test_main_missing_args(self):
505 """Main exits non-zero and reports an error on missing parameters."""
506- with mock.patch('sys.argv', ['mycmd']):
507- with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:
508- with self.assertRaises(SystemExit) as context_manager:
509- main()
510- self.assertEqual('1', str(context_manager.exception))
511+ with mock.patch('sys.exit', side_effect=self.sys_exit):
512+ with mock.patch('sys.argv', ['mycmd']):
513+ with mock.patch('sys.stderr', new_callable=StringIO) as \
514+ m_stderr:
515+ with self.assertRaises(SystemExit) as context_manager:
516+ main()
517+ self.assertEqual(1, context_manager.exception.code)
518 self.assertEqual(
519 'Expected either --config-file argument or --doc\n',
520 m_stderr.getvalue())
521diff --git a/tools/ds-identify b/tools/ds-identify
522index cd26824..ec368d5 100755
523--- a/tools/ds-identify
524+++ b/tools/ds-identify
525@@ -186,7 +186,8 @@ block_dev_with_label() {
526 read_fs_info() {
527 cached "${DI_BLKID_OUTPUT}" && return 0
528 # do not rely on links in /dev/disk which might not be present yet.
529- # note that older blkid versions do not report DEVNAME in 'export' output.
530+ # Note that blkid < 2.22 (centos6, trusty) do not output DEVNAME.
531+ # that means that DI_ISO9660_DEVS will not be set.
532 if is_container; then
533 # blkid will in a container, or at least currently in lxd
534 # not provide useful information.
535@@ -203,21 +204,26 @@ read_fs_info() {
536 DI_ISO9660_DEVS="$UNAVAILABLE:error"
537 return $ret
538 }
539- IFS="$CR"
540- set -- $out
541- IFS="$oifs"
542- for line in "$@" ""; do
543+ # 'set --' will collapse multiple consecutive entries in IFS for
544+ # whitespace characters (\n, tab, " ") so we cannot rely on getting
545+ # empty lines in "$@" below.
546+ IFS="$CR"; set -- $out; IFS="$oifs"
547+
548+ for line in "$@"; do
549 case "${line}" in
550- DEVNAME=*) dev=${line#DEVNAME=};;
551+ DEVNAME=*)
552+ [ -n "$dev" -a "$ftype" = "iso9660" ] &&
553+ isodevs="${isodevs} ${dev}=$label"
554+ ftype=""; dev=""; label="";
555+ dev=${line#DEVNAME=};;
556 LABEL=*) label="${line#LABEL=}";
557 labels="${labels}${line#LABEL=}${delim}";;
558 TYPE=*) ftype=${line#TYPE=};;
559- "") if [ "$ftype" = "iso9660" ]; then
560- isodevs="${isodevs} ${dev}=$label"
561- fi
562- ftype=""; devname=""; label="";
563 esac
564 done
565+ [ -n "$dev" -a "$ftype" = "iso9660" ] &&
566+ isodevs="${isodevs} ${dev}=$label"
567+
568 DI_FS_LABELS="${labels%${delim}}"
569 DI_ISO9660_DEVS="${isodevs# }"
570 }
571@@ -470,6 +476,16 @@ check_seed_dir() {
572 return 0
573 }
574
575+check_writable_seed_dir() {
576+ # ubuntu core bind-mounts /writable/system-data/var/lib/cloud
577+ # over the top of /var/lib/cloud, but the mount might not be done yet.
578+ local wdir="/writable/system-data"
579+ [ -d "${PATH_ROOT}$wdir" ] || return 1
580+ local sdir="${PATH_ROOT}$wdir${PATH_VAR_LIB_CLOUD#${PATH_ROOT}}"
581+ local PATH_VAR_LIB_CLOUD="$sdir"
582+ check_seed_dir "$@"
583+}
584+
585 probe_floppy() {
586 cached "${STATE_FLOPPY_PROBED}" && return "${STATE_FLOPPY_PROBED}"
587 local fpath=/dev/floppy
588@@ -569,6 +585,7 @@ dscheck_NoCloud() {
589 esac
590 for d in nocloud nocloud-net; do
591 check_seed_dir "$d" meta-data user-data && return ${DS_FOUND}
592+ check_writable_seed_dir "$d" meta-data user-data && return ${DS_FOUND}
593 done
594 if has_fs_with_label "${fslabel}"; then
595 return ${DS_FOUND}
596@@ -633,8 +650,9 @@ ovf_vmware_guest_customization() {
597
598 # we have to have the plugin to do vmware customization
599 local found="" pkg="" pre="${PATH_ROOT}/usr/lib"
600+ local ppath="plugins/vmsvc/libdeployPkgPlugin.so"
601 for pkg in vmware-tools open-vm-tools; do
602- if [ -f "$pre/$pkg/plugins/vmsvc/libdeployPkgPlugin.so" ]; then
603+ if [ -f "$pre/$pkg/$ppath" -o -f "${pre}64/$pkg/$ppath" ]; then
604 found="$pkg"; break;
605 fi
606 done
607@@ -685,15 +703,12 @@ dscheck_OVF() {
608 # Azure provides ovf. Skip false positive by dis-allowing.
609 is_azure_chassis && return $DS_NOT_FOUND
610
611- local isodevs="${DI_ISO9660_DEVS}"
612- case "$isodevs" in
613- ""|$UNAVAILABLE:*) return ${DS_NOT_FOUND};;
614- esac
615-
616 # DI_ISO9660_DEVS is <device>=label, like /dev/sr0=OVF-TRANSPORT
617- for tok in $isodevs; do
618- is_cdrom_ovf "${tok%%=*}" "${tok#*=}" && return $DS_FOUND
619- done
620+ if [ "${DI_ISO9660_DEVS#${UNAVAILABLE}:}" = "${DI_ISO9660_DEVS}" ]; then
621+ for tok in ${DI_ISO9660_DEVS}; do
622+ is_cdrom_ovf "${tok%%=*}" "${tok#*=}" && return $DS_FOUND
623+ done
624+ fi
625
626 if ovf_vmware_guest_customization; then
627 return ${DS_FOUND}
628diff --git a/tools/run-centos b/tools/run-centos
629index d58ef3e..cb241ee 100755
630--- a/tools/run-centos
631+++ b/tools/run-centos
632@@ -23,6 +23,9 @@ Usage: ${0##*/} [ options ] version
633
634 options:
635 -a | --artifact keep .rpm artifacts
636+ --dirty apply local changes before running tests.
637+ If not provided, a clean checkout of branch is tested.
638+ Inside container, changes are in local-changes.diff.
639 -k | --keep keep container after tests
640 -r | --rpm build .rpm
641 -s | --srpm build .src.rpm
642@@ -80,25 +83,84 @@ inside() {
643 inject_cloud_init(){
644 # take current cloud-init git dir and put it inside $name at
645 # ~$user/cloud-init.
646- local name="$1" user="$2" top_d="" dname="" pstat=""
647- top_d=$(git rev-parse --show-toplevel) || {
648- errorrc "Failed to get git top level in $PWD";
649+ local name="$1" user="$2" dirty="$3"
650+ local changes="" top_d="" dname="cloud-init" pstat=""
651+ local gitdir="" commitish=""
652+ gitdir=$(git rev-parse --git-dir) || {
653+ errorrc "Failed to get git dir in $PWD";
654 return
655 }
656- dname=$(basename "${top_d}") || return
657- debug 1 "collecting ${top_d} ($dname) into user $user in $name."
658- tar -C "${top_d}/.." -cpf - "$dname" |
659+ local t=${gitdir%/*}
660+ case "$t" in
661+ */worktrees)
662+ if [ -f "${t%worktrees}/config" ]; then
663+ gitdir="${t%worktrees}"
664+ fi
665+ esac
666+
667+ # attempt to get branch name.
668+ commitish=$(git rev-parse --abbrev-ref HEAD) || {
669+ errorrc "Failed git rev-parse --abbrev-ref HEAD"
670+ return
671+ }
672+ if [ "$commitish" = "HEAD" ]; then
673+ # detached head
674+ commitish=$(git rev-parse HEAD) || {
675+ errorrc "failed git rev-parse HEAD"
676+ return
677+ }
678+ fi
679+
680+ local local_changes=false
681+ if ! git diff --quiet "$commitish"; then
682+ # there are local changes not committed.
683+ local_changes=true
684+ if [ "$dirty" = "false" ]; then
685+ error "WARNING: You had uncommitted changes. Those changes will "
686+ error "be put into 'local-changes.diff' inside the container. "
687+ error "To test these changes you must pass --dirty."
688+ fi
689+ fi
690+
691+ debug 1 "collecting ${gitdir} ($dname) into user $user in $name."
692+ tar -C "${gitdir}" -cpf - . |
693 inside_as "$name" "$user" sh -ec '
694 dname=$1
695+ commitish=$2
696 rm -Rf "$dname"
697+ mkdir -p $dname/.git
698+ cd $dname/.git
699 tar -xpf -
700- [ "$dname" = "cloud-init" ] || mv "$dname" cloud-init' \
701- extract "$dname"
702+ cd ..
703+ git config core.bare false
704+ out=$(git checkout $commitish 2>&1) ||
705+ { echo "failed git checkout $commitish: $out" 1>&2; exit 1; }
706+ out=$(git checkout . 2>&1) ||
707+ { echo "failed git checkout .: $out" 1>&2; exit 1; }
708+ ' extract "$dname" "$commitish"
709 [ "${PIPESTATUS[*]}" = "0 0" ] || {
710- error "Failed to push tarball of '$top_d' into $name" \
711+ error "Failed to push tarball of '$gitdir' into $name" \
712 " for user $user (dname=$dname)"
713 return 1
714 }
715+
716+ echo "local_changes=$local_changes dirty=$dirty"
717+ if [ "$local_changes" = "true" ]; then
718+ git diff "$commitish" |
719+ inside_as "$name" "$user" sh -exc '
720+ cd "$1"
721+ if [ "$2" = "true" ]; then
722+ git apply
723+ else
724+ cat > local-changes.diff
725+ fi
726+ ' insert_changes "$dname" "$dirty"
727+ [ "${PIPESTATUS[*]}" = "0 0" ] || {
728+ error "Failed to apply local changes."
729+ return 1
730+ }
731+ fi
732+
733 return 0
734 }
735
736@@ -179,7 +241,7 @@ delete_container() {
737
738 main() {
739 local short_opts="ahkrsuv"
740- local long_opts="artifact,help,keep,rpm,srpm,unittest,verbose"
741+ local long_opts="artifact,dirty,help,keep,rpm,srpm,unittest,verbose"
742 local getopt_out=""
743 getopt_out=$(getopt --name "${0##*/}" \
744 --options "${short_opts}" --long "${long_opts}" -- "$@") &&
745@@ -188,11 +250,13 @@ main() {
746
747 local cur="" next=""
748 local artifact="" keep="" rpm="" srpm="" unittest="" version=""
749+ local dirty=false
750
751 while [ $# -ne 0 ]; do
752 cur="${1:-}"; next="${2:-}";
753 case "$cur" in
754 -a|--artifact) artifact=1;;
755+ --dirty) dirty=true;;
756 -h|--help) Usage ; exit 0;;
757 -k|--keep) KEEP=true;;
758 -r|--rpm) rpm=1;;
759@@ -231,7 +295,7 @@ main() {
760 inside "$name" useradd "$user"
761
762 debug 1 "inserting cloud-init"
763- inject_cloud_init "$name" "$user" || {
764+ inject_cloud_init "$name" "$user" "$dirty" || {
765 errorrc "FAIL: injecting cloud-init into $name failed."
766 return
767 }
768@@ -244,12 +308,13 @@ main() {
769
770 local errors=0
771 inside_as_cd "$name" "$user" "$cdir" \
772- sh -ec "git checkout .; git status" ||
773+ sh -ec "git status" ||
774 { errorrc "git checkout failed."; errors=$(($errors+1)); }
775
776 if [ -n "$unittest" ]; then
777 debug 1 "running unit tests."
778- inside_as_cd "$name" "$user" "$cdir" nosetests tests/unittests ||
779+ inside_as_cd "$name" "$user" "$cdir" \
780+ nosetests tests/unittests cloudinit ||
781 { errorrc "nosetests failed."; errors=$(($errors+1)); }
782 fi
783
784diff --git a/tox.ini b/tox.ini
785index bb74853..1f990af 100644
786--- a/tox.ini
787+++ b/tox.ini
788@@ -45,7 +45,7 @@ deps = -r{toxinidir}/test-requirements.txt
789
790 [testenv:py26]
791 deps = -r{toxinidir}/test-requirements.txt
792-commands = nosetests {posargs:tests/unittests}
793+commands = nosetests {posargs:tests/unittests cloudinit}
794 setenv =
795 LC_ALL = C
796
797@@ -83,7 +83,7 @@ deps =
798
799 [testenv:centos6]
800 basepython = python2.6
801-commands = nosetests {posargs:tests/unittests}
802+commands = nosetests {posargs:tests/unittests cloudinit}
803 deps =
804 # requirements
805 argparse==1.2.1
806@@ -98,7 +98,7 @@ deps =
807
808 [testenv:opensusel42]
809 basepython = python2.7
810-commands = nosetests {posargs:tests/unittests}
811+commands = nosetests {posargs:tests/unittests cloudinit}
812 deps =
813 # requirements
814 argparse==1.3.0

Subscribers

People subscribed via source and target branches