summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Moser <smoser@ubuntu.com>2018-01-26 16:00:10 -0500
committerScott Moser <smoser@ubuntu.com>2018-02-12 10:53:49 -0500
commitf576b2a24b8014e91087933d19a7a0d396787c30 (patch)
tree485f4463a251c7bf1f304f412f57ff4cde72d970
parent644048e31a9509390871a6a5ab49b92a5e6c3b87 (diff)
downloadvyos-cloud-init-f576b2a24b8014e91087933d19a7a0d396787c30.tar.gz
vyos-cloud-init-f576b2a24b8014e91087933d19a7a0d396787c30.zip
tests: add support for logs with lxd from snap and future lxd 3.
This puts in place detection for if 'show-log' will work with lxc client, and uses that if present. The 'lxc console --show-log' is not expected to work until lxd/liblxc3.0. That should come in a few months. The hope is that when that function arrives, this code will move over to using it. For other scenarios (all current lxd installs) this will now support getting logs from a snap installed lxd or a package installed lxd via the old 'lxc.console.logfile'. If installed from snap, a platform error will be raised until the user does: sudo mkdir --mode=1777 -p /var/snap/lxd/common/consoles LP: #1745663
-rw-r--r--tests/cloud_tests/collect.py5
-rw-r--r--tests/cloud_tests/platforms/lxd/instance.py132
2 files changed, 109 insertions, 28 deletions
diff --git a/tests/cloud_tests/collect.py b/tests/cloud_tests/collect.py
index 5ea88e50..d4f9135b 100644
--- a/tests/cloud_tests/collect.py
+++ b/tests/cloud_tests/collect.py
@@ -44,8 +44,9 @@ def collect_console(instance, base_dir):
LOG.debug('getting console log for %s to %s', instance, logfile)
try:
data = instance.console_log()
- except NotImplementedError:
- data = b'instance.console_log: not implemented'
+ except NotImplementedError as e:
+ # args[0] is hacky, but thats all I see to get at the message.
+ data = b'NotImplementedError:' + e.args[0].encode()
with open(logfile, "wb") as fp:
fp.write(data)
diff --git a/tests/cloud_tests/platforms/lxd/instance.py b/tests/cloud_tests/platforms/lxd/instance.py
index d2d2a1fd..0488da57 100644
--- a/tests/cloud_tests/platforms/lxd/instance.py
+++ b/tests/cloud_tests/platforms/lxd/instance.py
@@ -6,7 +6,9 @@ import os
import shutil
from tempfile import mkdtemp
-from cloudinit.util import subp, ProcessExecutionError
+from cloudinit.util import load_yaml, subp, ProcessExecutionError, which
+from tests.cloud_tests import LOG
+from tests.cloud_tests.util import PlatformError
from ..instances import Instance
@@ -15,6 +17,8 @@ class LXDInstance(Instance):
"""LXD container backed instance."""
platform_name = "lxd"
+ _console_log_method = None
+ _console_log_file = None
def __init__(self, platform, name, properties, config, features,
pylxd_container):
@@ -30,8 +34,8 @@ class LXDInstance(Instance):
super(LXDInstance, self).__init__(
platform, name, properties, config, features)
self.tmpd = mkdtemp(prefix="%s-%s" % (type(self).__name__, name))
- self._setup_console_log()
self.name = name
+ self._setup_console_log()
@property
def pylxd_container(self):
@@ -39,21 +43,6 @@ class LXDInstance(Instance):
self._pylxd_container.sync()
return self._pylxd_container
- 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 = {}
@@ -97,19 +86,80 @@ class LXDInstance(Instance):
"""
self.pylxd_container.files.put(remote_path, data)
+ @property
+ def console_log_method(self):
+ if self._console_log_method is not None:
+ return self._console_log_method
+
+ client = which('lxc')
+ if not client:
+ raise PlatformError("No 'lxc' client.")
+
+ elif _has_proper_console_support():
+ self._console_log_method = 'show-log'
+ elif client.startswith("/snap"):
+ self._console_log_method = 'logfile-snap'
+ else:
+ self._console_log_method = 'logfile-tmp'
+
+ LOG.debug("Set console log method to %s", self._console_log_method)
+ return self._console_log_method
+
+ def _setup_console_log(self):
+ method = self.console_log_method
+ if not method.startswith("logfile-"):
+ return
+
+ if method == "logfile-snap":
+ log_dir = "/var/snap/lxd/common/consoles"
+ if not os.path.exists(log_dir):
+ raise PlatformError(
+ "Unable to log with snap lxc. Please run:\n"
+ " sudo mkdir --mode=1777 -p %s" % log_dir)
+ elif method == "logfile-tmp":
+ log_dir = "/tmp"
+ else:
+ raise PlatformError(
+ "Unexpected value for console method: %s" % method)
+
+ # doing this ensures we can read it. Otherwise it ends up root:root.
+ log_file = os.path.join(log_dir, self.name)
+ with open(log_file, "w") as fp:
+ fp.write("# %s\n" % self.name)
+
+ cfg = "lxc.console.logfile=%s" % log_file
+ 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 = log_file
+
def console_log(self):
"""Console log.
- @return_value: bytes of this instance’s console
+ @return_value: bytes of this instance's console
"""
- 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()
+
+ if self._console_log_file:
+ 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()
+
+ try:
+ stdout, stderr = subp(
+ ['lxc', 'console', '--show-log', self.name], decode=False)
+ return stdout
+ except ProcessExecutionError as e:
+ raise PlatformError(
+ "console log",
+ "Console log failed [%d]: stdout=%s stderr=%s" % (
+ e.exit_code, e.stdout, e.stderr))
def reboot(self, wait=True):
"""Reboot instance."""
@@ -146,7 +196,37 @@ class LXDInstance(Instance):
if self.platform.container_exists(self.name):
raise OSError('container {} was not properly removed'
.format(self.name))
+ if self._console_log_file and os.path.exists(self._console_log_file):
+ os.unlink(self._console_log_file)
shutil.rmtree(self.tmpd)
super(LXDInstance, self).destroy()
+
+def _has_proper_console_support():
+ stdout, _ = subp(['lxc', 'info'])
+ info = load_yaml(stdout)
+ reason = None
+ if 'console' not in info.get('api_extensions', []):
+ reason = "LXD server does not support console api extension"
+ else:
+ dver = info.get('environment', {}).get('driver_version', "")
+ if dver.startswith("2.") or dver.startwith("1."):
+ reason = "LXD Driver version not 3.x+ (%s)" % dver
+ else:
+ try:
+ stdout, stderr = subp(['lxc', 'console', '--help'],
+ decode=False)
+ if not (b'console' in stdout and b'log' in stdout):
+ reason = "no '--log' in lxc console --help"
+ except ProcessExecutionError as e:
+ reason = "no 'console' command in lxc client"
+
+ if reason:
+ LOG.debug("no console-support: %s", reason)
+ return False
+ else:
+ LOG.debug("console-support looks good")
+ return True
+
+
# vi: ts=4 expandtab