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
diff --git a/ChangeLog b/ChangeLog
index 31c2dcb..be4c357 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,55 @@
118.1:
2 - OVF: Fix VMware support for 64-bit platforms. [Sankar Tanguturi]
3 - ds-identify: Fix searching for iso9660 OVF cdroms. (LP: #1749980)
4 - SUSE: Fix groups used for ownership of cloud-init.log [Robert Schweikert]
5 - ds-identify: check /writable/system-data/ for nocloud seed.
6 (LP: #1747070)
7 - tests: run nosetests in cloudinit/ directory, fix py26 fallout.
8 - tools: run-centos: git clone rather than tar.
9 - tests: add support for logs with lxd from snap and future lxd 3.
10 (LP: #1745663)
11 - EC2: Fix get_instance_id called against cached datasource pickle.
12 (LP: #1748354)
13 - cli: fix cloud-init status to report running when before result.json
14 (LP: #1747965)
15 - net: accept network-config in netplan format for renaming interfaces
16 (LP: #1709715)
17 - Fix ssh keys validation in ssh_util [Tatiana Kholkina]
18 - docs: Update RTD content for cloud-init subcommands.
19 - OVF: Extend well-known labels to include OVFENV. (LP: #1698669)
20 - Fix potential cases of uninitialized variables. (LP: #1744796)
21 - tests: Collect script output as binary, collect systemd journal, fix lxd.
22 - HACKING.rst: mention setting user name and email via git config.
23 - Azure VM Preprovisioning support. [Douglas Jordan] (LP: #1734991)
24 - tools/read-version: Fix read-version when in a git worktree.
25 - docs: Fix typos in docs and one debug message. [Florian Grignon]
26 - btrfs: support resizing if root is mounted ro.
27 [Robert Schweikert] (LP: #1734787)
28 - OpenNebula: Improve network configuration support.
29 [Akihiko Ota] (LP: #1719157, #1716397, #1736750)
30 - tests: Fix EC2 Platform to return console output as bytes.
31 - tests: Fix attempted use of /run in a test case.
32 - GCE: Improvements and changes to ssh key behavior for default user.
33 [Max Illfelder] (LP: #1670456, #1707033, #1707037, #1707039)
34 - subp: make ProcessExecutionError have expected types in stderr, stdout.
35 - tests: when querying ntp server, do not do dns resolution.
36 - Recognize uppercase vfat disk labels [James Penick] (LP: #1598783)
37 - tests: remove zesty as supported OS to test [Joshua Powers]
38 - Do not log warning on config files that represent None. (LP: #1742479)
39 - tests: Use git hash pip dependency format for pylxd.
40 - tests: add integration requirements text file [Joshua Powers]
41 - MAAS: add check_instance_id based off oauth tokens. (LP: #1712680)
42 - tests: update apt sources list test [Joshua Powers]
43 - tests: clean up image properties [Joshua Powers]
44 - tests: rename test ssh keys to avoid appearance of leaking private keys.
45 [Joshua Powers]
46 - tests: Enable AWS EC2 Integration Testing [Joshua Powers]
47 - cli: cloud-init clean handles symlinks (LP: #1741093)
48 - SUSE: Add a basic test of network config rendering. [Robert Schweikert]
49 - Azure: Only bounce network when necessary. (LP: #1722668)
50 - lint: Fix lints seen by pylint version 1.8.1.
51 - cli: Fix error in cloud-init modules --mode=init. (LP: #1736600)
52
117.2:5317.2:
2 - ds-identify: failure in NoCloud due to unset variable usage.54 - ds-identify: failure in NoCloud due to unset variable usage.
3 (LP: #1737704)55 (LP: #1737704)
diff --git a/cloudinit/cmd/tests/test_clean.py b/cloudinit/cmd/tests/test_clean.py
index 6713af4..5a3ec3b 100644
--- a/cloudinit/cmd/tests/test_clean.py
+++ b/cloudinit/cmd/tests/test_clean.py
@@ -165,10 +165,11 @@ class TestClean(CiTestCase):
165 wrap_and_call(165 wrap_and_call(
166 'cloudinit.cmd.clean',166 'cloudinit.cmd.clean',
167 {'Init': {'side_effect': self.init_class},167 {'Init': {'side_effect': self.init_class},
168 'sys.exit': {'side_effect': self.sys_exit},
168 'sys.argv': {'new': ['clean', '--logs']}},169 'sys.argv': {'new': ['clean', '--logs']}},
169 clean.main)170 clean.main)
170171
171 self.assertRaisesCodeEqual(0, context_manager.exception.code)172 self.assertEqual(0, context_manager.exception.code)
172 self.assertFalse(173 self.assertFalse(
173 os.path.exists(self.log1), 'Unexpected log {0}'.format(self.log1))174 os.path.exists(self.log1), 'Unexpected log {0}'.format(self.log1))
174175
diff --git a/cloudinit/cmd/tests/test_status.py b/cloudinit/cmd/tests/test_status.py
index 4a5a8c0..37a8993 100644
--- a/cloudinit/cmd/tests/test_status.py
+++ b/cloudinit/cmd/tests/test_status.py
@@ -380,10 +380,11 @@ class TestStatus(CiTestCase):
380 wrap_and_call(380 wrap_and_call(
381 'cloudinit.cmd.status',381 'cloudinit.cmd.status',
382 {'sys.argv': {'new': ['status']},382 {'sys.argv': {'new': ['status']},
383 'sys.exit': {'side_effect': self.sys_exit},
383 '_is_cloudinit_disabled': (False, ''),384 '_is_cloudinit_disabled': (False, ''),
384 'Init': {'side_effect': self.init_class}},385 'Init': {'side_effect': self.init_class}},
385 status.main)386 status.main)
386 self.assertRaisesCodeEqual(0, context_manager.exception.code)387 self.assertEqual(0, context_manager.exception.code)
387 self.assertEqual('status: running\n', m_stdout.getvalue())388 self.assertEqual('status: running\n', m_stdout.getvalue())
388389
389# vi: ts=4 expandtab syntax=python390# vi: ts=4 expandtab syntax=python
diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py
index 6e62f98..dc914a7 100644
--- a/cloudinit/sources/DataSourceOVF.py
+++ b/cloudinit/sources/DataSourceOVF.py
@@ -95,11 +95,20 @@ class DataSourceOVF(sources.DataSource):
95 "VMware Customization support")95 "VMware Customization support")
96 elif not util.get_cfg_option_bool(96 elif not util.get_cfg_option_bool(
97 self.sys_cfg, "disable_vmware_customization", True):97 self.sys_cfg, "disable_vmware_customization", True):
98 deployPkgPluginPath = search_file("/usr/lib/vmware-tools",98
99 "libdeployPkgPlugin.so")99 search_paths = (
100 if not deployPkgPluginPath:100 "/usr/lib/vmware-tools", "/usr/lib64/vmware-tools",
101 deployPkgPluginPath = search_file("/usr/lib/open-vm-tools",101 "/usr/lib/open-vm-tools", "/usr/lib64/open-vm-tools")
102 "libdeployPkgPlugin.so")102
103 plugin = "libdeployPkgPlugin.so"
104 deployPkgPluginPath = None
105 for path in search_paths:
106 deployPkgPluginPath = search_file(path, plugin)
107 if deployPkgPluginPath:
108 LOG.debug("Found the customization plugin at %s",
109 deployPkgPluginPath)
110 break
111
103 if deployPkgPluginPath:112 if deployPkgPluginPath:
104 # When the VM is powered on, the "VMware Tools" daemon113 # When the VM is powered on, the "VMware Tools" daemon
105 # copies the customization specification file to114 # copies the customization specification file to
@@ -111,6 +120,8 @@ class DataSourceOVF(sources.DataSource):
111 msg="waiting for configuration file",120 msg="waiting for configuration file",
112 func=wait_for_imc_cfg_file,121 func=wait_for_imc_cfg_file,
113 args=("cust.cfg", max_wait))122 args=("cust.cfg", max_wait))
123 else:
124 LOG.debug("Did not find the customization plugin.")
114125
115 if vmwareImcConfigFilePath:126 if vmwareImcConfigFilePath:
116 LOG.debug("Found VMware Customization Config File at %s",127 LOG.debug("Found VMware Customization Config File at %s",
diff --git a/cloudinit/tests/helpers.py b/cloudinit/tests/helpers.py
index 0080c72..41d9a8e 100644
--- a/cloudinit/tests/helpers.py
+++ b/cloudinit/tests/helpers.py
@@ -173,17 +173,15 @@ class CiTestCase(TestCase):
173 dir = self.tmp_dir()173 dir = self.tmp_dir()
174 return os.path.normpath(os.path.abspath(os.path.join(dir, path)))174 return os.path.normpath(os.path.abspath(os.path.join(dir, path)))
175175
176 def assertRaisesCodeEqual(self, expected, found):176 def sys_exit(self, code):
177 """Handle centos6 having different context manager for assertRaises.177 """Provide a wrapper around sys.exit for python 2.6
178 with assertRaises(Exception) as e:178
179 raise Exception("BOO")179 In 2.6, this code would produce 'cm.exception' with value int(2)
180180 rather than the SystemExit that was raised by sys.exit(2).
181 centos6 will have e.exception as an integer.181 with assertRaises(SystemExit) as cm:
182 anything nwere will have it as something with a '.code'"""182 sys.exit(2)
183 if isinstance(found, int):183 """
184 self.assertEqual(expected, found)184 raise SystemExit(code)
185 else:
186 self.assertEqual(expected, found.code)
187185
188186
189class ResourceUsingTestCase(CiTestCase):187class ResourceUsingTestCase(CiTestCase):
diff --git a/cloudinit/version.py b/cloudinit/version.py
index be6262d..4a682ad 100644
--- a/cloudinit/version.py
+++ b/cloudinit/version.py
@@ -4,7 +4,7 @@
4#4#
5# This file is part of cloud-init. See LICENSE file for license information.5# This file is part of cloud-init. See LICENSE file for license information.
66
7__VERSION__ = "17.2"7__VERSION__ = "18.1"
88
9FEATURES = [9FEATURES = [
10 # supports network config version 110 # supports network config version 1
diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl
index 32de9c9..fad1184 100644
--- a/config/cloud.cfg.tmpl
+++ b/config/cloud.cfg.tmpl
@@ -4,6 +4,8 @@
44
5{% if variant in ["freebsd"] %}5{% if variant in ["freebsd"] %}
6syslog_fix_perms: root:wheel6syslog_fix_perms: root:wheel
7{% elif variant in ["suse"] %}
8syslog_fix_perms: root:root
7{% endif %}9{% endif %}
8# A set of users which may be applied and/or used by various modules10# A set of users which may be applied and/or used by various modules
9# when a 'default' entry is found it will reference the 'default_user'11# when a 'default' entry is found it will reference the 'default_user'
diff --git a/debian/changelog b/debian/changelog
index abbd5a9..2552bb6 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,19 @@
1cloud-init (18.1-0ubuntu1) bionic; urgency=medium
2
3 * New upstream snapshot.
4 - release 18.1 (LP: #1751145)
5 - OVF: Fix VMware support for 64-bit platforms. [Sankar Tanguturi]
6 - ds-identify: Fix searching for iso9660 OVF cdroms. (LP: #1749980)
7 - SUSE: Fix groups used for ownership of cloud-init.log [Robert Schweikert]
8 - ds-identify: check /writable/system-data/ for nocloud seed.
9 (LP: #1747070)
10 - tests: run nosetests in cloudinit/ directory, fix py26 fallout.
11 - tools: run-centos: git clone rather than tar.
12 - tests: add support for logs with lxd from snap and future lxd 3.
13 (LP: #1745663)
14
15 -- Chad Smith <chad.smith@canonical.com> Thu, 22 Feb 2018 15:42:11 -0700
16
1cloud-init (17.2-34-g644048e3-0ubuntu1) bionic; urgency=medium17cloud-init (17.2-34-g644048e3-0ubuntu1) bionic; urgency=medium
218
3 * New upstream snapshot.19 * New upstream snapshot.
diff --git a/tests/cloud_tests/collect.py b/tests/cloud_tests/collect.py
index 5ea88e5..d4f9135 100644
--- a/tests/cloud_tests/collect.py
+++ b/tests/cloud_tests/collect.py
@@ -44,8 +44,9 @@ def collect_console(instance, base_dir):
44 LOG.debug('getting console log for %s to %s', instance, logfile)44 LOG.debug('getting console log for %s to %s', instance, logfile)
45 try:45 try:
46 data = instance.console_log()46 data = instance.console_log()
47 except NotImplementedError:47 except NotImplementedError as e:
48 data = b'instance.console_log: not implemented'48 # args[0] is hacky, but thats all I see to get at the message.
49 data = b'NotImplementedError:' + e.args[0].encode()
49 with open(logfile, "wb") as fp:50 with open(logfile, "wb") as fp:
50 fp.write(data)51 fp.write(data)
5152
diff --git a/tests/cloud_tests/platforms/lxd/instance.py b/tests/cloud_tests/platforms/lxd/instance.py
index d2d2a1f..0488da5 100644
--- a/tests/cloud_tests/platforms/lxd/instance.py
+++ b/tests/cloud_tests/platforms/lxd/instance.py
@@ -6,7 +6,9 @@ import os
6import shutil6import shutil
7from tempfile import mkdtemp7from tempfile import mkdtemp
88
9from cloudinit.util import subp, ProcessExecutionError9from cloudinit.util import load_yaml, subp, ProcessExecutionError, which
10from tests.cloud_tests import LOG
11from tests.cloud_tests.util import PlatformError
1012
11from ..instances import Instance13from ..instances import Instance
1214
@@ -15,6 +17,8 @@ class LXDInstance(Instance):
15 """LXD container backed instance."""17 """LXD container backed instance."""
1618
17 platform_name = "lxd"19 platform_name = "lxd"
20 _console_log_method = None
21 _console_log_file = None
1822
19 def __init__(self, platform, name, properties, config, features,23 def __init__(self, platform, name, properties, config, features,
20 pylxd_container):24 pylxd_container):
@@ -30,8 +34,8 @@ class LXDInstance(Instance):
30 super(LXDInstance, self).__init__(34 super(LXDInstance, self).__init__(
31 platform, name, properties, config, features)35 platform, name, properties, config, features)
32 self.tmpd = mkdtemp(prefix="%s-%s" % (type(self).__name__, name))36 self.tmpd = mkdtemp(prefix="%s-%s" % (type(self).__name__, name))
33 self._setup_console_log()
34 self.name = name37 self.name = name
38 self._setup_console_log()
3539
36 @property40 @property
37 def pylxd_container(self):41 def pylxd_container(self):
@@ -39,21 +43,6 @@ class LXDInstance(Instance):
39 self._pylxd_container.sync()43 self._pylxd_container.sync()
40 return self._pylxd_container44 return self._pylxd_container
4145
42 def _setup_console_log(self):
43 logf = os.path.join(self.tmpd, "console.log")
44
45 # doing this ensures we can read it. Otherwise it ends up root:root.
46 with open(logf, "w") as fp:
47 fp.write("# %s\n" % self.name)
48
49 cfg = "lxc.console.logfile=%s" % logf
50 orig = self._pylxd_container.config.get('raw.lxc', "")
51 if orig:
52 orig += "\n"
53 self._pylxd_container.config['raw.lxc'] = orig + cfg
54 self._pylxd_container.save()
55 self._console_log_file = logf
56
57 def _execute(self, command, stdin=None, env=None):46 def _execute(self, command, stdin=None, env=None):
58 if env is None:47 if env is None:
59 env = {}48 env = {}
@@ -97,19 +86,80 @@ class LXDInstance(Instance):
97 """86 """
98 self.pylxd_container.files.put(remote_path, data)87 self.pylxd_container.files.put(remote_path, data)
9988
89 @property
90 def console_log_method(self):
91 if self._console_log_method is not None:
92 return self._console_log_method
93
94 client = which('lxc')
95 if not client:
96 raise PlatformError("No 'lxc' client.")
97
98 elif _has_proper_console_support():
99 self._console_log_method = 'show-log'
100 elif client.startswith("/snap"):
101 self._console_log_method = 'logfile-snap'
102 else:
103 self._console_log_method = 'logfile-tmp'
104
105 LOG.debug("Set console log method to %s", self._console_log_method)
106 return self._console_log_method
107
108 def _setup_console_log(self):
109 method = self.console_log_method
110 if not method.startswith("logfile-"):
111 return
112
113 if method == "logfile-snap":
114 log_dir = "/var/snap/lxd/common/consoles"
115 if not os.path.exists(log_dir):
116 raise PlatformError(
117 "Unable to log with snap lxc. Please run:\n"
118 " sudo mkdir --mode=1777 -p %s" % log_dir)
119 elif method == "logfile-tmp":
120 log_dir = "/tmp"
121 else:
122 raise PlatformError(
123 "Unexpected value for console method: %s" % method)
124
125 # doing this ensures we can read it. Otherwise it ends up root:root.
126 log_file = os.path.join(log_dir, self.name)
127 with open(log_file, "w") as fp:
128 fp.write("# %s\n" % self.name)
129
130 cfg = "lxc.console.logfile=%s" % log_file
131 orig = self._pylxd_container.config.get('raw.lxc', "")
132 if orig:
133 orig += "\n"
134 self._pylxd_container.config['raw.lxc'] = orig + cfg
135 self._pylxd_container.save()
136 self._console_log_file = log_file
137
100 def console_log(self):138 def console_log(self):
101 """Console log.139 """Console log.
102140
103 @return_value: bytes of this instance’s console141 @return_value: bytes of this instance's console
104 """142 """
105 if not os.path.exists(self._console_log_file):143
106 raise NotImplementedError(144 if self._console_log_file:
107 "Console log '%s' does not exist. If this is a remote "145 if not os.path.exists(self._console_log_file):
108 "lxc, then this is really NotImplementedError. If it is "146 raise NotImplementedError(
109 "A local lxc, then this is a RuntimeError."147 "Console log '%s' does not exist. If this is a remote "
110 "https://github.com/lxc/lxd/issues/1129")148 "lxc, then this is really NotImplementedError. If it is "
111 with open(self._console_log_file, "rb") as fp:149 "A local lxc, then this is a RuntimeError."
112 return fp.read()150 "https://github.com/lxc/lxd/issues/1129")
151 with open(self._console_log_file, "rb") as fp:
152 return fp.read()
153
154 try:
155 stdout, stderr = subp(
156 ['lxc', 'console', '--show-log', self.name], decode=False)
157 return stdout
158 except ProcessExecutionError as e:
159 raise PlatformError(
160 "console log",
161 "Console log failed [%d]: stdout=%s stderr=%s" % (
162 e.exit_code, e.stdout, e.stderr))
113163
114 def reboot(self, wait=True):164 def reboot(self, wait=True):
115 """Reboot instance."""165 """Reboot instance."""
@@ -146,7 +196,37 @@ class LXDInstance(Instance):
146 if self.platform.container_exists(self.name):196 if self.platform.container_exists(self.name):
147 raise OSError('container {} was not properly removed'197 raise OSError('container {} was not properly removed'
148 .format(self.name))198 .format(self.name))
199 if self._console_log_file and os.path.exists(self._console_log_file):
200 os.unlink(self._console_log_file)
149 shutil.rmtree(self.tmpd)201 shutil.rmtree(self.tmpd)
150 super(LXDInstance, self).destroy()202 super(LXDInstance, self).destroy()
151203
204
205def _has_proper_console_support():
206 stdout, _ = subp(['lxc', 'info'])
207 info = load_yaml(stdout)
208 reason = None
209 if 'console' not in info.get('api_extensions', []):
210 reason = "LXD server does not support console api extension"
211 else:
212 dver = info.get('environment', {}).get('driver_version', "")
213 if dver.startswith("2.") or dver.startwith("1."):
214 reason = "LXD Driver version not 3.x+ (%s)" % dver
215 else:
216 try:
217 stdout, stderr = subp(['lxc', 'console', '--help'],
218 decode=False)
219 if not (b'console' in stdout and b'log' in stdout):
220 reason = "no '--log' in lxc console --help"
221 except ProcessExecutionError as e:
222 reason = "no 'console' command in lxc client"
223
224 if reason:
225 LOG.debug("no console-support: %s", reason)
226 return False
227 else:
228 LOG.debug("console-support looks good")
229 return True
230
231
152# vi: ts=4 expandtab232# vi: ts=4 expandtab
diff --git a/tests/unittests/test_ds_identify.py b/tests/unittests/test_ds_identify.py
index 31cc622..9be3f96 100644
--- a/tests/unittests/test_ds_identify.py
+++ b/tests/unittests/test_ds_identify.py
@@ -337,6 +337,16 @@ class TestDsIdentify(CiTestCase):
337 """OVF is identified when vmware customization is enabled."""337 """OVF is identified when vmware customization is enabled."""
338 self._test_ds_found('OVF-vmware-customization')338 self._test_ds_found('OVF-vmware-customization')
339339
340 def test_ovf_on_vmware_iso_found_open_vm_tools_64(self):
341 """OVF is identified when open-vm-tools installed in /usr/lib64."""
342 cust64 = copy.deepcopy(VALID_CFG['OVF-vmware-customization'])
343 p32 = 'usr/lib/vmware-tools/plugins/vmsvc/libdeployPkgPlugin.so'
344 open64 = 'usr/lib64/open-vm-tools/plugins/vmsvc/libdeployPkgPlugin.so'
345 cust64['files'][open64] = cust64['files'][p32]
346 del cust64['files'][p32]
347 return self._check_via_dict(
348 cust64, RC_FOUND, dslist=[cust64.get('ds'), DS_NONE])
349
340 def test_ovf_on_vmware_iso_found_by_cdrom_with_matching_fs_label(self):350 def test_ovf_on_vmware_iso_found_by_cdrom_with_matching_fs_label(self):
341 """OVF is identified by well-known iso9660 labels."""351 """OVF is identified by well-known iso9660 labels."""
342 ovf_cdrom_by_label = copy.deepcopy(VALID_CFG['OVF'])352 ovf_cdrom_by_label = copy.deepcopy(VALID_CFG['OVF'])
@@ -350,8 +360,10 @@ class TestDsIdentify(CiTestCase):
350 "OVFENV", "ovfenv"]360 "OVFENV", "ovfenv"]
351 for valid_ovf_label in valid_ovf_labels:361 for valid_ovf_label in valid_ovf_labels:
352 ovf_cdrom_by_label['mocks'][0]['out'] = blkid_out([362 ovf_cdrom_by_label['mocks'][0]['out'] = blkid_out([
363 {'DEVNAME': 'sda1', 'TYPE': 'ext4', 'LABEL': 'rootfs'},
353 {'DEVNAME': 'sr0', 'TYPE': 'iso9660',364 {'DEVNAME': 'sr0', 'TYPE': 'iso9660',
354 'LABEL': valid_ovf_label}])365 'LABEL': valid_ovf_label},
366 {'DEVNAME': 'vda1', 'TYPE': 'ntfs', 'LABEL': 'data'}])
355 self._check_via_dict(367 self._check_via_dict(
356 ovf_cdrom_by_label, rc=RC_FOUND, dslist=['OVF', DS_NONE])368 ovf_cdrom_by_label, rc=RC_FOUND, dslist=['OVF', DS_NONE])
357369
@@ -359,6 +371,14 @@ class TestDsIdentify(CiTestCase):
359 """NoCloud is found with iso9660 filesystem on non-cdrom disk."""371 """NoCloud is found with iso9660 filesystem on non-cdrom disk."""
360 self._test_ds_found('NoCloud')372 self._test_ds_found('NoCloud')
361373
374 def test_nocloud_seed(self):
375 """Nocloud seed directory."""
376 self._test_ds_found('NoCloud-seed')
377
378 def test_nocloud_seed_ubuntu_core_writable(self):
379 """Nocloud seed directory ubuntu core writable"""
380 self._test_ds_found('NoCloud-seed-ubuntu-core')
381
362382
363def blkid_out(disks=None):383def blkid_out(disks=None):
364 """Convert a list of disk dictionaries into blkid content."""384 """Convert a list of disk dictionaries into blkid content."""
@@ -454,6 +474,22 @@ VALID_CFG = {
454 'dev/vdb': 'pretend iso content for cidata\n',474 'dev/vdb': 'pretend iso content for cidata\n',
455 }475 }
456 },476 },
477 'NoCloud-seed': {
478 'ds': 'NoCloud',
479 'files': {
480 os.path.join(P_SEED_DIR, 'nocloud', 'user-data'): 'ud\n',
481 os.path.join(P_SEED_DIR, 'nocloud', 'meta-data'): 'md\n',
482 }
483 },
484 'NoCloud-seed-ubuntu-core': {
485 'ds': 'NoCloud',
486 'files': {
487 os.path.join('writable/system-data', P_SEED_DIR,
488 'nocloud-net', 'user-data'): 'ud\n',
489 os.path.join('writable/system-data', P_SEED_DIR,
490 'nocloud-net', 'meta-data'): 'md\n',
491 }
492 },
457 'OpenStack': {493 'OpenStack': {
458 'ds': 'OpenStack',494 'ds': 'OpenStack',
459 'files': {P_PRODUCT_NAME: 'OpenStack Nova\n'},495 'files': {P_PRODUCT_NAME: 'OpenStack Nova\n'},
@@ -489,8 +525,9 @@ VALID_CFG = {
489 'mocks': [525 'mocks': [
490 {'name': 'blkid', 'ret': 0,526 {'name': 'blkid', 'ret': 0,
491 'out': blkid_out(527 'out': blkid_out(
492 [{'DEVNAME': 'vda1', 'TYPE': 'vfat', 'PARTUUID': uuid4()},528 [{'DEVNAME': 'sr0', 'TYPE': 'iso9660', 'LABEL': ''},
493 {'DEVNAME': 'sr0', 'TYPE': 'iso9660', 'LABEL': ''}])529 {'DEVNAME': 'sr1', 'TYPE': 'iso9660', 'LABEL': 'ignoreme'},
530 {'DEVNAME': 'vda1', 'TYPE': 'vfat', 'PARTUUID': uuid4()}]),
494 },531 },
495 MOCK_VIRT_IS_VMWARE,532 MOCK_VIRT_IS_VMWARE,
496 ],533 ],
diff --git a/tests/unittests/test_handler/test_schema.py b/tests/unittests/test_handler/test_schema.py
index 648573f..df67a0e 100644
--- a/tests/unittests/test_handler/test_schema.py
+++ b/tests/unittests/test_handler/test_schema.py
@@ -336,11 +336,13 @@ class MainTest(CiTestCase):
336336
337 def test_main_missing_args(self):337 def test_main_missing_args(self):
338 """Main exits non-zero and reports an error on missing parameters."""338 """Main exits non-zero and reports an error on missing parameters."""
339 with mock.patch('sys.argv', ['mycmd']):339 with mock.patch('sys.exit', side_effect=self.sys_exit):
340 with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:340 with mock.patch('sys.argv', ['mycmd']):
341 with self.assertRaises(SystemExit) as context_manager:341 with mock.patch('sys.stderr', new_callable=StringIO) as \
342 main()342 m_stderr:
343 self.assertEqual('1', str(context_manager.exception))343 with self.assertRaises(SystemExit) as context_manager:
344 main()
345 self.assertEqual(1, context_manager.exception.code)
344 self.assertEqual(346 self.assertEqual(
345 'Expected either --config-file argument or --doc\n',347 'Expected either --config-file argument or --doc\n',
346 m_stderr.getvalue())348 m_stderr.getvalue())
diff --git a/tools/ds-identify b/tools/ds-identify
index cd26824..ec368d5 100755
--- a/tools/ds-identify
+++ b/tools/ds-identify
@@ -186,7 +186,8 @@ block_dev_with_label() {
186read_fs_info() {186read_fs_info() {
187 cached "${DI_BLKID_OUTPUT}" && return 0187 cached "${DI_BLKID_OUTPUT}" && return 0
188 # do not rely on links in /dev/disk which might not be present yet.188 # do not rely on links in /dev/disk which might not be present yet.
189 # note that older blkid versions do not report DEVNAME in 'export' output.189 # Note that blkid < 2.22 (centos6, trusty) do not output DEVNAME.
190 # that means that DI_ISO9660_DEVS will not be set.
190 if is_container; then191 if is_container; then
191 # blkid will in a container, or at least currently in lxd192 # blkid will in a container, or at least currently in lxd
192 # not provide useful information.193 # not provide useful information.
@@ -203,21 +204,26 @@ read_fs_info() {
203 DI_ISO9660_DEVS="$UNAVAILABLE:error"204 DI_ISO9660_DEVS="$UNAVAILABLE:error"
204 return $ret205 return $ret
205 }206 }
206 IFS="$CR"207 # 'set --' will collapse multiple consecutive entries in IFS for
207 set -- $out208 # whitespace characters (\n, tab, " ") so we cannot rely on getting
208 IFS="$oifs"209 # empty lines in "$@" below.
209 for line in "$@" ""; do210 IFS="$CR"; set -- $out; IFS="$oifs"
211
212 for line in "$@"; do
210 case "${line}" in213 case "${line}" in
211 DEVNAME=*) dev=${line#DEVNAME=};;214 DEVNAME=*)
215 [ -n "$dev" -a "$ftype" = "iso9660" ] &&
216 isodevs="${isodevs} ${dev}=$label"
217 ftype=""; dev=""; label="";
218 dev=${line#DEVNAME=};;
212 LABEL=*) label="${line#LABEL=}";219 LABEL=*) label="${line#LABEL=}";
213 labels="${labels}${line#LABEL=}${delim}";;220 labels="${labels}${line#LABEL=}${delim}";;
214 TYPE=*) ftype=${line#TYPE=};;221 TYPE=*) ftype=${line#TYPE=};;
215 "") if [ "$ftype" = "iso9660" ]; then
216 isodevs="${isodevs} ${dev}=$label"
217 fi
218 ftype=""; devname=""; label="";
219 esac222 esac
220 done223 done
224 [ -n "$dev" -a "$ftype" = "iso9660" ] &&
225 isodevs="${isodevs} ${dev}=$label"
226
221 DI_FS_LABELS="${labels%${delim}}"227 DI_FS_LABELS="${labels%${delim}}"
222 DI_ISO9660_DEVS="${isodevs# }"228 DI_ISO9660_DEVS="${isodevs# }"
223}229}
@@ -470,6 +476,16 @@ check_seed_dir() {
470 return 0476 return 0
471}477}
472478
479check_writable_seed_dir() {
480 # ubuntu core bind-mounts /writable/system-data/var/lib/cloud
481 # over the top of /var/lib/cloud, but the mount might not be done yet.
482 local wdir="/writable/system-data"
483 [ -d "${PATH_ROOT}$wdir" ] || return 1
484 local sdir="${PATH_ROOT}$wdir${PATH_VAR_LIB_CLOUD#${PATH_ROOT}}"
485 local PATH_VAR_LIB_CLOUD="$sdir"
486 check_seed_dir "$@"
487}
488
473probe_floppy() {489probe_floppy() {
474 cached "${STATE_FLOPPY_PROBED}" && return "${STATE_FLOPPY_PROBED}"490 cached "${STATE_FLOPPY_PROBED}" && return "${STATE_FLOPPY_PROBED}"
475 local fpath=/dev/floppy491 local fpath=/dev/floppy
@@ -569,6 +585,7 @@ dscheck_NoCloud() {
569 esac585 esac
570 for d in nocloud nocloud-net; do586 for d in nocloud nocloud-net; do
571 check_seed_dir "$d" meta-data user-data && return ${DS_FOUND}587 check_seed_dir "$d" meta-data user-data && return ${DS_FOUND}
588 check_writable_seed_dir "$d" meta-data user-data && return ${DS_FOUND}
572 done589 done
573 if has_fs_with_label "${fslabel}"; then590 if has_fs_with_label "${fslabel}"; then
574 return ${DS_FOUND}591 return ${DS_FOUND}
@@ -633,8 +650,9 @@ ovf_vmware_guest_customization() {
633650
634 # we have to have the plugin to do vmware customization651 # we have to have the plugin to do vmware customization
635 local found="" pkg="" pre="${PATH_ROOT}/usr/lib"652 local found="" pkg="" pre="${PATH_ROOT}/usr/lib"
653 local ppath="plugins/vmsvc/libdeployPkgPlugin.so"
636 for pkg in vmware-tools open-vm-tools; do654 for pkg in vmware-tools open-vm-tools; do
637 if [ -f "$pre/$pkg/plugins/vmsvc/libdeployPkgPlugin.so" ]; then655 if [ -f "$pre/$pkg/$ppath" -o -f "${pre}64/$pkg/$ppath" ]; then
638 found="$pkg"; break;656 found="$pkg"; break;
639 fi657 fi
640 done658 done
@@ -685,15 +703,12 @@ dscheck_OVF() {
685 # Azure provides ovf. Skip false positive by dis-allowing.703 # Azure provides ovf. Skip false positive by dis-allowing.
686 is_azure_chassis && return $DS_NOT_FOUND704 is_azure_chassis && return $DS_NOT_FOUND
687705
688 local isodevs="${DI_ISO9660_DEVS}"
689 case "$isodevs" in
690 ""|$UNAVAILABLE:*) return ${DS_NOT_FOUND};;
691 esac
692
693 # DI_ISO9660_DEVS is <device>=label, like /dev/sr0=OVF-TRANSPORT706 # DI_ISO9660_DEVS is <device>=label, like /dev/sr0=OVF-TRANSPORT
694 for tok in $isodevs; do707 if [ "${DI_ISO9660_DEVS#${UNAVAILABLE}:}" = "${DI_ISO9660_DEVS}" ]; then
695 is_cdrom_ovf "${tok%%=*}" "${tok#*=}" && return $DS_FOUND708 for tok in ${DI_ISO9660_DEVS}; do
696 done709 is_cdrom_ovf "${tok%%=*}" "${tok#*=}" && return $DS_FOUND
710 done
711 fi
697712
698 if ovf_vmware_guest_customization; then713 if ovf_vmware_guest_customization; then
699 return ${DS_FOUND}714 return ${DS_FOUND}
diff --git a/tools/run-centos b/tools/run-centos
index d58ef3e..cb241ee 100755
--- a/tools/run-centos
+++ b/tools/run-centos
@@ -23,6 +23,9 @@ Usage: ${0##*/} [ options ] version
2323
24 options:24 options:
25 -a | --artifact keep .rpm artifacts25 -a | --artifact keep .rpm artifacts
26 --dirty apply local changes before running tests.
27 If not provided, a clean checkout of branch is tested.
28 Inside container, changes are in local-changes.diff.
26 -k | --keep keep container after tests29 -k | --keep keep container after tests
27 -r | --rpm build .rpm30 -r | --rpm build .rpm
28 -s | --srpm build .src.rpm31 -s | --srpm build .src.rpm
@@ -80,25 +83,84 @@ inside() {
80inject_cloud_init(){83inject_cloud_init(){
81 # take current cloud-init git dir and put it inside $name at84 # take current cloud-init git dir and put it inside $name at
82 # ~$user/cloud-init.85 # ~$user/cloud-init.
83 local name="$1" user="$2" top_d="" dname="" pstat=""86 local name="$1" user="$2" dirty="$3"
84 top_d=$(git rev-parse --show-toplevel) || {87 local changes="" top_d="" dname="cloud-init" pstat=""
85 errorrc "Failed to get git top level in $PWD";88 local gitdir="" commitish=""
89 gitdir=$(git rev-parse --git-dir) || {
90 errorrc "Failed to get git dir in $PWD";
86 return91 return
87 }92 }
88 dname=$(basename "${top_d}") || return93 local t=${gitdir%/*}
89 debug 1 "collecting ${top_d} ($dname) into user $user in $name."94 case "$t" in
90 tar -C "${top_d}/.." -cpf - "$dname" |95 */worktrees)
96 if [ -f "${t%worktrees}/config" ]; then
97 gitdir="${t%worktrees}"
98 fi
99 esac
100
101 # attempt to get branch name.
102 commitish=$(git rev-parse --abbrev-ref HEAD) || {
103 errorrc "Failed git rev-parse --abbrev-ref HEAD"
104 return
105 }
106 if [ "$commitish" = "HEAD" ]; then
107 # detached head
108 commitish=$(git rev-parse HEAD) || {
109 errorrc "failed git rev-parse HEAD"
110 return
111 }
112 fi
113
114 local local_changes=false
115 if ! git diff --quiet "$commitish"; then
116 # there are local changes not committed.
117 local_changes=true
118 if [ "$dirty" = "false" ]; then
119 error "WARNING: You had uncommitted changes. Those changes will "
120 error "be put into 'local-changes.diff' inside the container. "
121 error "To test these changes you must pass --dirty."
122 fi
123 fi
124
125 debug 1 "collecting ${gitdir} ($dname) into user $user in $name."
126 tar -C "${gitdir}" -cpf - . |
91 inside_as "$name" "$user" sh -ec '127 inside_as "$name" "$user" sh -ec '
92 dname=$1128 dname=$1
129 commitish=$2
93 rm -Rf "$dname"130 rm -Rf "$dname"
131 mkdir -p $dname/.git
132 cd $dname/.git
94 tar -xpf -133 tar -xpf -
95 [ "$dname" = "cloud-init" ] || mv "$dname" cloud-init' \134 cd ..
96 extract "$dname"135 git config core.bare false
136 out=$(git checkout $commitish 2>&1) ||
137 { echo "failed git checkout $commitish: $out" 1>&2; exit 1; }
138 out=$(git checkout . 2>&1) ||
139 { echo "failed git checkout .: $out" 1>&2; exit 1; }
140 ' extract "$dname" "$commitish"
97 [ "${PIPESTATUS[*]}" = "0 0" ] || {141 [ "${PIPESTATUS[*]}" = "0 0" ] || {
98 error "Failed to push tarball of '$top_d' into $name" \142 error "Failed to push tarball of '$gitdir' into $name" \
99 " for user $user (dname=$dname)"143 " for user $user (dname=$dname)"
100 return 1144 return 1
101 }145 }
146
147 echo "local_changes=$local_changes dirty=$dirty"
148 if [ "$local_changes" = "true" ]; then
149 git diff "$commitish" |
150 inside_as "$name" "$user" sh -exc '
151 cd "$1"
152 if [ "$2" = "true" ]; then
153 git apply
154 else
155 cat > local-changes.diff
156 fi
157 ' insert_changes "$dname" "$dirty"
158 [ "${PIPESTATUS[*]}" = "0 0" ] || {
159 error "Failed to apply local changes."
160 return 1
161 }
162 fi
163
102 return 0164 return 0
103}165}
104166
@@ -179,7 +241,7 @@ delete_container() {
179241
180main() {242main() {
181 local short_opts="ahkrsuv"243 local short_opts="ahkrsuv"
182 local long_opts="artifact,help,keep,rpm,srpm,unittest,verbose"244 local long_opts="artifact,dirty,help,keep,rpm,srpm,unittest,verbose"
183 local getopt_out=""245 local getopt_out=""
184 getopt_out=$(getopt --name "${0##*/}" \246 getopt_out=$(getopt --name "${0##*/}" \
185 --options "${short_opts}" --long "${long_opts}" -- "$@") &&247 --options "${short_opts}" --long "${long_opts}" -- "$@") &&
@@ -188,11 +250,13 @@ main() {
188250
189 local cur="" next=""251 local cur="" next=""
190 local artifact="" keep="" rpm="" srpm="" unittest="" version=""252 local artifact="" keep="" rpm="" srpm="" unittest="" version=""
253 local dirty=false
191254
192 while [ $# -ne 0 ]; do255 while [ $# -ne 0 ]; do
193 cur="${1:-}"; next="${2:-}";256 cur="${1:-}"; next="${2:-}";
194 case "$cur" in257 case "$cur" in
195 -a|--artifact) artifact=1;;258 -a|--artifact) artifact=1;;
259 --dirty) dirty=true;;
196 -h|--help) Usage ; exit 0;;260 -h|--help) Usage ; exit 0;;
197 -k|--keep) KEEP=true;;261 -k|--keep) KEEP=true;;
198 -r|--rpm) rpm=1;;262 -r|--rpm) rpm=1;;
@@ -231,7 +295,7 @@ main() {
231 inside "$name" useradd "$user"295 inside "$name" useradd "$user"
232296
233 debug 1 "inserting cloud-init"297 debug 1 "inserting cloud-init"
234 inject_cloud_init "$name" "$user" || {298 inject_cloud_init "$name" "$user" "$dirty" || {
235 errorrc "FAIL: injecting cloud-init into $name failed."299 errorrc "FAIL: injecting cloud-init into $name failed."
236 return300 return
237 }301 }
@@ -244,12 +308,13 @@ main() {
244308
245 local errors=0309 local errors=0
246 inside_as_cd "$name" "$user" "$cdir" \310 inside_as_cd "$name" "$user" "$cdir" \
247 sh -ec "git checkout .; git status" ||311 sh -ec "git status" ||
248 { errorrc "git checkout failed."; errors=$(($errors+1)); }312 { errorrc "git checkout failed."; errors=$(($errors+1)); }
249313
250 if [ -n "$unittest" ]; then314 if [ -n "$unittest" ]; then
251 debug 1 "running unit tests."315 debug 1 "running unit tests."
252 inside_as_cd "$name" "$user" "$cdir" nosetests tests/unittests ||316 inside_as_cd "$name" "$user" "$cdir" \
317 nosetests tests/unittests cloudinit ||
253 { errorrc "nosetests failed."; errors=$(($errors+1)); }318 { errorrc "nosetests failed."; errors=$(($errors+1)); }
254 fi319 fi
255320
diff --git a/tox.ini b/tox.ini
index bb74853..1f990af 100644
--- a/tox.ini
+++ b/tox.ini
@@ -45,7 +45,7 @@ deps = -r{toxinidir}/test-requirements.txt
4545
46[testenv:py26]46[testenv:py26]
47deps = -r{toxinidir}/test-requirements.txt47deps = -r{toxinidir}/test-requirements.txt
48commands = nosetests {posargs:tests/unittests}48commands = nosetests {posargs:tests/unittests cloudinit}
49setenv =49setenv =
50 LC_ALL = C50 LC_ALL = C
5151
@@ -83,7 +83,7 @@ deps =
8383
84[testenv:centos6]84[testenv:centos6]
85basepython = python2.685basepython = python2.6
86commands = nosetests {posargs:tests/unittests}86commands = nosetests {posargs:tests/unittests cloudinit}
87deps =87deps =
88 # requirements88 # requirements
89 argparse==1.2.189 argparse==1.2.1
@@ -98,7 +98,7 @@ deps =
9898
99[testenv:opensusel42]99[testenv:opensusel42]
100basepython = python2.7100basepython = python2.7
101commands = nosetests {posargs:tests/unittests}101commands = nosetests {posargs:tests/unittests cloudinit}
102deps =102deps =
103 # requirements103 # requirements
104 argparse==1.3.0104 argparse==1.3.0

Subscribers

People subscribed via source and target branches