← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~smoser/cloud-init:cleanup/test-more-files-collect into cloud-init:master

 

Scott Moser has proposed merging ~smoser/cloud-init:cleanup/test-more-files-collect into cloud-init:master.

Commit message:
tests: Collect script output as binary, collect systemd journal, fix lxd.

This adds collection a gzip compressed systemd journal on systemd systems.
The file can later be reviewed with:
  zcat system.journal.gz > system.journal
  journalctl --file=system.journal [-o short-monotonic ..]

To support this:
  * modify test harness infrastructure to not assume content is utf-8.
  * fix lxd platform to support make '_execute' return bytes rather
    than a string. https://github.com/lxc/pylxd/issues/268

Requested reviews:
  cloud-init commiters (cloud-init-dev)

For more details, see:
https://code.launchpad.net/~smoser/cloud-init/+git/cloud-init/+merge/336498

see commit message
-- 
Your team cloud-init commiters is requested to review the proposed merge of ~smoser/cloud-init:cleanup/test-more-files-collect into cloud-init:master.
diff --git a/tests/cloud_tests/collect.py b/tests/cloud_tests/collect.py
index 33acbb1..5ea88e5 100644
--- a/tests/cloud_tests/collect.py
+++ b/tests/cloud_tests/collect.py
@@ -24,6 +24,13 @@ def collect_script(instance, base_dir, script, script_name):
     (out, err, exit) = instance.run_script(
         script.encode(), rcs=False,
         description='collect: {}'.format(script_name))
+    if err:
+        LOG.debug("collect script %s had stderr: %s", script_name, err)
+    if not isinstance(out, bytes):
+        raise util.PlatformError(
+            "Collection of '%s' returned type %s, expected bytes: %s" %
+            (script_name, type(out), out))
+
     c_util.write_file(os.path.join(base_dir, script_name), out)
 
 
diff --git a/tests/cloud_tests/platforms/lxd/instance.py b/tests/cloud_tests/platforms/lxd/instance.py
index 0d697c0..e9b76a6 100644
--- a/tests/cloud_tests/platforms/lxd/instance.py
+++ b/tests/cloud_tests/platforms/lxd/instance.py
@@ -6,6 +6,8 @@ import os
 import shutil
 from tempfile import mkdtemp
 
+from cloudinit.util import subp, ProcessExecutionError
+
 from ..instances import Instance
 
 
@@ -29,6 +31,7 @@ class LXDInstance(Instance):
             platform, name, properties, config, features)
         self.tmpd = mkdtemp(prefix="%s-%s" % (type(self).__name__, name))
         self._setup_console_log()
+        self.name = name
 
     @property
     def pylxd_container(self):
@@ -55,33 +58,24 @@ class LXDInstance(Instance):
         if env is None:
             env = {}
 
-        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))
+        env_args = []
+        if env:
+            env_args = ['env'] + ["%s=%s" for k, v in env.items()]
 
         # 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 not hasattr(res, 'exit_code'):
-            # pylxd 2.1.3 and earlier only return out and err, no exit
-            raise RuntimeError(
-                "No 'exit_code' in pylxd.container.execute return.\n"
-                "pylxd > 2.2 is required.")
-
-        return res.stdout, res.stderr, res.exit_code
+
+        exit_code = 0
+        try:
+            stdout, stderr = subp(
+                ['lxc', 'exec', self.name, '--'] + env_args + list(command),
+                data=stdin, decode=False)
+        except ProcessExecutionError as e:
+            exit_code = e.exit_code
+            stdout = e.stdout
+            stderr = e.stderr
+
+        return stdout, stderr, exit_code
 
     def read_data(self, remote_path, decode=False):
         """Read data from instance filesystem.
diff --git a/tests/cloud_tests/testcases.yaml b/tests/cloud_tests/testcases.yaml
index 7183e01..8e0fb62 100644
--- a/tests/cloud_tests/testcases.yaml
+++ b/tests/cloud_tests/testcases.yaml
@@ -7,22 +7,37 @@ base_test_data:
         #cloud-config
     collect_scripts:
         cloud-init.log: |
-            #!/bin/bash
+            #!/bin/sh
             cat /var/log/cloud-init.log
         cloud-init-output.log: |
-            #!/bin/bash
+            #!/bin/sh
             cat /var/log/cloud-init-output.log
         instance-id: |
-            #!/bin/bash
+            #!/bin/sh
             cat /run/cloud-init/.instance-id
         result.json: |
-            #!/bin/bash
+            #!/bin/sh
             cat /run/cloud-init/result.json
         status.json: |
-            #!/bin/bash
+            #!/bin/sh
             cat /run/cloud-init/status.json
         cloud-init-version: |
-            #!/bin/bash
+            #!/bin/sh
             dpkg-query -W -f='${Version}' cloud-init
+        system.journal.gz: |
+            #!/bin/sh
+            [ -d /run/systemd ] || { echo "not systemd."; exit 0; }
+            fail() { echo "ERROR:" "$@" 1>&2; exit 1; }
+            journal=""
+            for d in /run/log/journal /var/log/journal; do
+                for f in $d/*/system.journal; do
+                    [ -f "$f" ] || continue
+                    [ -z "$journal" ] ||
+                        fail "multiple journal found: $f $journal."
+                    journal="$f"
+                done
+            done
+            [ -f "$journal" ] || fail "no journal file found."
+            gzip --to-stdout "$journal"
 
 # vi: ts=4 expandtab
diff --git a/tests/cloud_tests/testcases/base.py b/tests/cloud_tests/testcases/base.py
index 1c5b540..2e0db73 100644
--- a/tests/cloud_tests/testcases/base.py
+++ b/tests/cloud_tests/testcases/base.py
@@ -30,12 +30,14 @@ class CloudTestCase(unittest.TestCase):
             raise AssertionError('Key "{}" not in cloud config'.format(name))
         return self.cloud_config[name]
 
-    def get_data_file(self, name):
+    def get_data_file(self, name, decode=True):
         """Get data file failing test if it is not present."""
         if name not in self.data:
             raise AssertionError('File "{}" missing from collect data'
                                  .format(name))
-        return self.data[name]
+        if not decode:
+            return self.data[name]
+        return self.data[name].decode()
 
     def get_instance_id(self):
         """Get recorded instance id."""
diff --git a/tests/cloud_tests/verify.py b/tests/cloud_tests/verify.py
index fc1efcf..2a9fd52 100644
--- a/tests/cloud_tests/verify.py
+++ b/tests/cloud_tests/verify.py
@@ -29,7 +29,7 @@ def verify_data(base_dir, tests):
         data = {}
         test_dir = os.path.join(base_dir, test_name)
         for script_name in os.listdir(test_dir):
-            with open(os.path.join(test_dir, script_name), 'r') as fp:
+            with open(os.path.join(test_dir, script_name), 'rb') as fp:
                 data[script_name] = fp.read()
 
         # get test suite and launch tests

Follow ups