Merge ~smoser/cloud-init:bug/1745663-use-lxd-console into cloud-init:master

Proposed by Scott Moser
Status: Merged
Merged at revision: f576b2a24b8014e91087933d19a7a0d396787c30
Proposed branch: ~smoser/cloud-init:bug/1745663-use-lxd-console
Merge into: cloud-init:master
Diff against target: 200 lines (+109/-28)
2 files modified
tests/cloud_tests/collect.py (+3/-2)
tests/cloud_tests/platforms/lxd/instance.py (+106/-26)
Reviewer Review Type Date Requested Status
Joshua Powers (community) Approve
Server Team CI bot continuous-integration Approve
cloud-init Commiters Pending
Review via email: mp+336722@code.launchpad.net

Commit message

tests: add support for logs in with 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

Description of the change

see commit message

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

FAILED: Continuous integration, rev:d941491687592392bdfc71d9f2c28d6222732f47
https://jenkins.ubuntu.com/server/job/cloud-init-ci/740/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:1d6cda52bc1ad7f08512a09325ada547f09e9a73
https://jenkins.ubuntu.com/server/job/cloud-init-ci/743/
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/743/rebuild

review: Approve (continuous-integration)
Revision history for this message
Joshua Powers (powersj) wrote :

LGTM

Tried this out locally and works much better :) thanks!

review: Approve
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:3aaf53c796e7a6e35b3f291dfda42a981fd67eac
https://jenkins.ubuntu.com/server/job/cloud-init-ci/747/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    FAILED: Ubuntu LTS: Integration

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

Josh,
I added some hackish support for getting log output today.
It should work in both snap lxd (tested) and package lxd (untested) if the lxd is local. Right now this only works on a local lxd. I do hope at some point we could point it at a remote, but that is work to do.

The first time you run installed from snap, it will fail and raise exception like:
ERROR - stage: set up and collect data for os: xenial encountered error: Unable to log with snap lxc. Please run:
  sudo mkdir --mode=1777 -p /var/snap/lxd/common/consoles: unexpected error in platform.

I'll be happy to remove the log to local file at some later date.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:a9b594501674590ded8a282444215cfc905ea1a9
https://jenkins.ubuntu.com/server/job/cloud-init-ci/748/
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/748/rebuild

review: Approve (continuous-integration)
Revision history for this message
Joshua Powers (powersj) wrote :

+1 - I'm good with this as well. It does seem a little hacky, but to get us to 3.0 that is fine and we still get console logs :)

First run (abbreviated output):
https://paste.ubuntu.com/26490439/

Second run:
https://paste.ubuntu.com/26490453/

Console on 2nd run:
http://paste.ubuntu.com/26490455/

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/tests/cloud_tests/collect.py b/tests/cloud_tests/collect.py
2index 5ea88e5..d4f9135 100644
3--- a/tests/cloud_tests/collect.py
4+++ b/tests/cloud_tests/collect.py
5@@ -44,8 +44,9 @@ def collect_console(instance, base_dir):
6 LOG.debug('getting console log for %s to %s', instance, logfile)
7 try:
8 data = instance.console_log()
9- except NotImplementedError:
10- data = b'instance.console_log: not implemented'
11+ except NotImplementedError as e:
12+ # args[0] is hacky, but thats all I see to get at the message.
13+ data = b'NotImplementedError:' + e.args[0].encode()
14 with open(logfile, "wb") as fp:
15 fp.write(data)
16
17diff --git a/tests/cloud_tests/platforms/lxd/instance.py b/tests/cloud_tests/platforms/lxd/instance.py
18index d2d2a1f..0488da5 100644
19--- a/tests/cloud_tests/platforms/lxd/instance.py
20+++ b/tests/cloud_tests/platforms/lxd/instance.py
21@@ -6,7 +6,9 @@ import os
22 import shutil
23 from tempfile import mkdtemp
24
25-from cloudinit.util import subp, ProcessExecutionError
26+from cloudinit.util import load_yaml, subp, ProcessExecutionError, which
27+from tests.cloud_tests import LOG
28+from tests.cloud_tests.util import PlatformError
29
30 from ..instances import Instance
31
32@@ -15,6 +17,8 @@ class LXDInstance(Instance):
33 """LXD container backed instance."""
34
35 platform_name = "lxd"
36+ _console_log_method = None
37+ _console_log_file = None
38
39 def __init__(self, platform, name, properties, config, features,
40 pylxd_container):
41@@ -30,8 +34,8 @@ class LXDInstance(Instance):
42 super(LXDInstance, self).__init__(
43 platform, name, properties, config, features)
44 self.tmpd = mkdtemp(prefix="%s-%s" % (type(self).__name__, name))
45- self._setup_console_log()
46 self.name = name
47+ self._setup_console_log()
48
49 @property
50 def pylxd_container(self):
51@@ -39,21 +43,6 @@ class LXDInstance(Instance):
52 self._pylxd_container.sync()
53 return self._pylxd_container
54
55- def _setup_console_log(self):
56- logf = os.path.join(self.tmpd, "console.log")
57-
58- # doing this ensures we can read it. Otherwise it ends up root:root.
59- with open(logf, "w") as fp:
60- fp.write("# %s\n" % self.name)
61-
62- cfg = "lxc.console.logfile=%s" % logf
63- orig = self._pylxd_container.config.get('raw.lxc', "")
64- if orig:
65- orig += "\n"
66- self._pylxd_container.config['raw.lxc'] = orig + cfg
67- self._pylxd_container.save()
68- self._console_log_file = logf
69-
70 def _execute(self, command, stdin=None, env=None):
71 if env is None:
72 env = {}
73@@ -97,19 +86,80 @@ class LXDInstance(Instance):
74 """
75 self.pylxd_container.files.put(remote_path, data)
76
77+ @property
78+ def console_log_method(self):
79+ if self._console_log_method is not None:
80+ return self._console_log_method
81+
82+ client = which('lxc')
83+ if not client:
84+ raise PlatformError("No 'lxc' client.")
85+
86+ elif _has_proper_console_support():
87+ self._console_log_method = 'show-log'
88+ elif client.startswith("/snap"):
89+ self._console_log_method = 'logfile-snap'
90+ else:
91+ self._console_log_method = 'logfile-tmp'
92+
93+ LOG.debug("Set console log method to %s", self._console_log_method)
94+ return self._console_log_method
95+
96+ def _setup_console_log(self):
97+ method = self.console_log_method
98+ if not method.startswith("logfile-"):
99+ return
100+
101+ if method == "logfile-snap":
102+ log_dir = "/var/snap/lxd/common/consoles"
103+ if not os.path.exists(log_dir):
104+ raise PlatformError(
105+ "Unable to log with snap lxc. Please run:\n"
106+ " sudo mkdir --mode=1777 -p %s" % log_dir)
107+ elif method == "logfile-tmp":
108+ log_dir = "/tmp"
109+ else:
110+ raise PlatformError(
111+ "Unexpected value for console method: %s" % method)
112+
113+ # doing this ensures we can read it. Otherwise it ends up root:root.
114+ log_file = os.path.join(log_dir, self.name)
115+ with open(log_file, "w") as fp:
116+ fp.write("# %s\n" % self.name)
117+
118+ cfg = "lxc.console.logfile=%s" % log_file
119+ orig = self._pylxd_container.config.get('raw.lxc', "")
120+ if orig:
121+ orig += "\n"
122+ self._pylxd_container.config['raw.lxc'] = orig + cfg
123+ self._pylxd_container.save()
124+ self._console_log_file = log_file
125+
126 def console_log(self):
127 """Console log.
128
129- @return_value: bytes of this instance’s console
130+ @return_value: bytes of this instance's console
131 """
132- if not os.path.exists(self._console_log_file):
133- raise NotImplementedError(
134- "Console log '%s' does not exist. If this is a remote "
135- "lxc, then this is really NotImplementedError. If it is "
136- "A local lxc, then this is a RuntimeError."
137- "https://github.com/lxc/lxd/issues/1129")
138- with open(self._console_log_file, "rb") as fp:
139- return fp.read()
140+
141+ if self._console_log_file:
142+ if not os.path.exists(self._console_log_file):
143+ raise NotImplementedError(
144+ "Console log '%s' does not exist. If this is a remote "
145+ "lxc, then this is really NotImplementedError. If it is "
146+ "A local lxc, then this is a RuntimeError."
147+ "https://github.com/lxc/lxd/issues/1129")
148+ with open(self._console_log_file, "rb") as fp:
149+ return fp.read()
150+
151+ try:
152+ stdout, stderr = subp(
153+ ['lxc', 'console', '--show-log', self.name], decode=False)
154+ return stdout
155+ except ProcessExecutionError as e:
156+ raise PlatformError(
157+ "console log",
158+ "Console log failed [%d]: stdout=%s stderr=%s" % (
159+ e.exit_code, e.stdout, e.stderr))
160
161 def reboot(self, wait=True):
162 """Reboot instance."""
163@@ -146,7 +196,37 @@ class LXDInstance(Instance):
164 if self.platform.container_exists(self.name):
165 raise OSError('container {} was not properly removed'
166 .format(self.name))
167+ if self._console_log_file and os.path.exists(self._console_log_file):
168+ os.unlink(self._console_log_file)
169 shutil.rmtree(self.tmpd)
170 super(LXDInstance, self).destroy()
171
172+
173+def _has_proper_console_support():
174+ stdout, _ = subp(['lxc', 'info'])
175+ info = load_yaml(stdout)
176+ reason = None
177+ if 'console' not in info.get('api_extensions', []):
178+ reason = "LXD server does not support console api extension"
179+ else:
180+ dver = info.get('environment', {}).get('driver_version', "")
181+ if dver.startswith("2.") or dver.startwith("1."):
182+ reason = "LXD Driver version not 3.x+ (%s)" % dver
183+ else:
184+ try:
185+ stdout, stderr = subp(['lxc', 'console', '--help'],
186+ decode=False)
187+ if not (b'console' in stdout and b'log' in stdout):
188+ reason = "no '--log' in lxc console --help"
189+ except ProcessExecutionError as e:
190+ reason = "no 'console' command in lxc client"
191+
192+ if reason:
193+ LOG.debug("no console-support: %s", reason)
194+ return False
195+ else:
196+ LOG.debug("console-support looks good")
197+ return True
198+
199+
200 # vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches