summaryrefslogtreecommitdiff
path: root/tests/cloud_tests/instances/lxd.py
diff options
context:
space:
mode:
authorScott Moser <smoser@brickies.net>2017-11-06 17:39:00 -0500
committerScott Moser <smoser@brickies.net>2017-11-06 17:39:00 -0500
commit8622491c29f30862a1a1d7ad2cba023981acc8ce (patch)
tree597b08c4ca66575cc9567263030876814999beeb /tests/cloud_tests/instances/lxd.py
parentbe8e3d3c5b5d3d6a3d222383a58fd5feecead7b7 (diff)
downloadvyos-cloud-init-8622491c29f30862a1a1d7ad2cba023981acc8ce.tar.gz
vyos-cloud-init-8622491c29f30862a1a1d7ad2cba023981acc8ce.zip
tests: integration test cleanup and full pass of nocloud-kvm.
Integration test harness changes: * Enable collection of console log in nocloud-kvm and lxd. * Collect the console log to results for all test runs. * change 'tmpfile' to pick name locally instead of using 'mktemp'. * drop the 'instance' attribute from nocloud-kvm Image and demote LXDImage.instance to a private attribute. This is because Images do not actually have instances. (LXDImage internally uses a booted system to modify the image). * Add 'TargetBase' as a superclass of Image and Instance providing implementations of execute, read_data, write_data, pull_file, and push_file. These all depend on an implementation of _execute. * Improve '_execute' implementations to support accepting stdin. * execute supports 'rcs=False' meaning 'do not raise exception'. * Drop support for pylxd < 2.2. older versions cannot determine exit code of 'execute', which makes them unusable. * make NoCloudKVMInstance._execute run as root via sudo. This required some changes so that 'hostname' could be reverse-looked up in order to avoid sudo taking a long time (~20 seconds). * re-use existing ssh connection in nocloud-kvm. Test changes here: * do not use /tmp, but rather /var/tmp (LP: #1707222) * make keys_to_console assertions more strict. * change user test cases to always add default (ubuntu) user so that nocloud-kvm's execute which operates over ssh can work.
Diffstat (limited to 'tests/cloud_tests/instances/lxd.py')
-rw-r--r--tests/cloud_tests/instances/lxd.py104
1 files changed, 56 insertions, 48 deletions
diff --git a/tests/cloud_tests/instances/lxd.py b/tests/cloud_tests/instances/lxd.py
index a43918c2..3b035d86 100644
--- a/tests/cloud_tests/instances/lxd.py
+++ b/tests/cloud_tests/instances/lxd.py
@@ -2,8 +2,11 @@
"""Base LXD instance."""
-from tests.cloud_tests.instances import base
-from tests.cloud_tests import util
+from . import base
+
+import os
+import shutil
+from tempfile import mkdtemp
class LXDInstance(base.Instance):
@@ -24,6 +27,8 @@ class LXDInstance(base.Instance):
self._pylxd_container = pylxd_container
super(LXDInstance, self).__init__(
platform, name, properties, config, features)
+ self.tmpd = mkdtemp(prefix="%s-%s" % (type(self).__name__, name))
+ self._setup_console_log()
@property
def pylxd_container(self):
@@ -31,74 +36,69 @@ class LXDInstance(base.Instance):
self._pylxd_container.sync()
return self._pylxd_container
- def execute(self, command, stdout=None, stderr=None, env=None,
- rcs=None, description=None):
- """Execute command in instance, recording output, error and exit code.
-
- Assumes functional networking and execution as root with the
- target filesystem being available at /.
-
- @param command: the command to execute as root inside the image
- if command is a string, then it will be executed as:
- ['sh', '-c', command]
- @param stdout: file handler to write output
- @param stderr: file handler to write error
- @param env: environment variables
- @param rcs: allowed return codes from command
- @param description: purpose of command
- @return_value: tuple containing stdout data, stderr data, exit code
- """
+ def _setup_console_log(self):
+ logf = os.path.join(self.tmpd, "console.log")
+
+ # doing this ensures we can read it. Otherwise it ends up root:root.
+ with open(logf, "w") as fp:
+ fp.write("# %s\n" % self.name)
+
+ cfg = "lxc.console.logfile=%s" % logf
+ orig = self._pylxd_container.config.get('raw.lxc', "")
+ if orig:
+ orig += "\n"
+ self._pylxd_container.config['raw.lxc'] = orig + cfg
+ self._pylxd_container.save()
+ self._console_log_file = logf
+
+ def _execute(self, command, stdin=None, env=None):
if env is None:
env = {}
- if isinstance(command, str):
- command = ['sh', '-c', command]
+ if stdin is not None:
+ # pylxd does not support input to execute.
+ # https://github.com/lxc/pylxd/issues/244
+ #
+ # The solution here is write a tmp file in the container
+ # and then execute a shell that sets it standard in to
+ # be from that file, removes it, and calls the comand.
+ tmpf = self.tmpfile()
+ self.write_data(tmpf, stdin)
+ ncmd = 'exec <"{tmpf}"; rm -f "{tmpf}"; exec "$@"'
+ command = (['sh', '-c', ncmd.format(tmpf=tmpf), 'stdinhack'] +
+ list(command))
# ensure instance is running and execute the command
self.start()
+ # execute returns a ContainerExecuteResult, named tuple
+ # (exit_code, stdout, stderr)
res = self.pylxd_container.execute(command, environment=env)
# get out, exit and err from pylxd return
- if hasattr(res, 'exit_code'):
- # pylxd 2.2 returns ContainerExecuteResult, named tuple of
- # (exit_code, out, err)
- (exit, out, err) = res
- else:
+ if not hasattr(res, 'exit_code'):
# pylxd 2.1.3 and earlier only return out and err, no exit
- # LOG.warning('using pylxd version < 2.2')
- (out, err) = res
- exit = 0
-
- # write data to file descriptors if needed
- if stdout:
- stdout.write(out)
- if stderr:
- stderr.write(err)
-
- # if the command exited with a code not allowed in rcs, then fail
- if exit not in (rcs if rcs else (0,)):
- error_desc = ('Failed command to: {}'.format(description)
- if description else None)
- raise util.InTargetExecuteError(
- out, err, exit, command, self.name, error_desc)
+ raise RuntimeError(
+ "No 'exit_code' in pylxd.container.execute return.\n"
+ "pylxd > 2.2 is required.")
- return (out, err, exit)
+ return res.stdout, res.stderr, res.exit_code
def read_data(self, remote_path, decode=False):
"""Read data from instance filesystem.
@param remote_path: path in instance
- @param decode: return as string
- @return_value: data as str or bytes
+ @param decode: decode data before returning.
+ @return_value: content of remote_path as bytes if 'decode' is False,
+ and as string if 'decode' is True.
"""
data = self.pylxd_container.files.get(remote_path)
- return data.decode() if decode and isinstance(data, bytes) else data
+ return data.decode() if decode else data
def write_data(self, remote_path, data):
"""Write data to instance filesystem.
@param remote_path: path in instance
- @param data: data to write, either str or bytes
+ @param data: data to write in bytes
"""
self.pylxd_container.files.put(remote_path, data)
@@ -107,7 +107,14 @@ class LXDInstance(base.Instance):
@return_value: bytes of this instance’s console
"""
- raise NotImplementedError
+ if not os.path.exists(self._console_log_file):
+ raise NotImplementedError(
+ "Console log '%s' does not exist. If this is a remote "
+ "lxc, then this is really NotImplementedError. If it is "
+ "A local lxc, then this is a RuntimeError."
+ "https://github.com/lxc/lxd/issues/1129")
+ with open(self._console_log_file, "rb") as fp:
+ return fp.read()
def reboot(self, wait=True):
"""Reboot instance."""
@@ -144,6 +151,7 @@ class LXDInstance(base.Instance):
if self.platform.container_exists(self.name):
raise OSError('container {} was not properly removed'
.format(self.name))
+ shutil.rmtree(self.tmpd)
super(LXDInstance, self).destroy()
# vi: ts=4 expandtab