summaryrefslogtreecommitdiff
path: root/tests/cloud_tests/instances/lxd.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/cloud_tests/instances/lxd.py')
-rw-r--r--tests/cloud_tests/instances/lxd.py132
1 files changed, 76 insertions, 56 deletions
diff --git a/tests/cloud_tests/instances/lxd.py b/tests/cloud_tests/instances/lxd.py
index f0aa1214..b9c2cc6b 100644
--- a/tests/cloud_tests/instances/lxd.py
+++ b/tests/cloud_tests/instances/lxd.py
@@ -1,115 +1,135 @@
# This file is part of cloud-init. See LICENSE file for license information.
+"""Base LXD instance."""
+
from tests.cloud_tests.instances import base
+from tests.cloud_tests import util
class LXDInstance(base.Instance):
- """
- LXD container backed instance
- """
+ """LXD container backed instance."""
+
platform_name = "lxd"
- def __init__(self, name, platform, pylxd_container):
- """
- setup
+ def __init__(self, platform, name, properties, config, features,
+ pylxd_container):
+ """Set up instance.
+
+ @param platform: platform object
+ @param name: hostname of instance
+ @param properties: image properties
+ @param config: image config
+ @param features: supported feature flags
"""
- self.platform = platform
self._pylxd_container = pylxd_container
- super(LXDInstance, self).__init__(name)
+ super(LXDInstance, self).__init__(
+ platform, name, properties, config, features)
@property
def pylxd_container(self):
+ """Property function."""
self._pylxd_container.sync()
return self._pylxd_container
- def execute(self, command, stdin=None, stdout=None, stderr=None, env={}):
- """
- command: the command to execute as root inside the image
- stdin, stderr, stdout: file handles
- env: environment variables
+ def execute(self, command, stdout=None, stderr=None, env={},
+ rcs=None, description=None):
+ """Execute command in instance, recording output, error and exit code.
- Execute assumes functional networking and execution as root with the
+ Assumes functional networking and execution as root with the
target filesystem being available at /.
- return_value: tuple containing stdout data, stderr data, exit code
+ @param command: the command to execute as root inside the image
+ @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
"""
- # TODO: the pylxd api handler for container.execute needs to be
- # extended to properly pass in stdin
- # TODO: the pylxd api handler for container.execute needs to be
- # extended to get the return code, for now just use 0
+ # ensure instance is running and execute the command
self.start()
- if stdin:
- raise NotImplementedError
res = self.pylxd_container.execute(command, environment=env)
- for (f, data) in (i for i in zip((stdout, stderr), res) if i[0]):
- f.write(data)
- return res + (0,)
+
+ # 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:
+ # 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)
+
+ return (out, err, exit)
def read_data(self, remote_path, decode=False):
- """
- read data from instance filesystem
- remote_path: path in instance
- decode: return as string
- return_value: data as str or bytes
+ """Read data from instance filesystem.
+
+ @param remote_path: path in instance
+ @param decode: return as string
+ @return_value: data as str or bytes
"""
data = self.pylxd_container.files.get(remote_path)
return data.decode() if decode and isinstance(data, bytes) else data
def write_data(self, remote_path, data):
- """
- write data to instance filesystem
- remote_path: path in instance
- data: data to write, either str or bytes
+ """Write data to instance filesystem.
+
+ @param remote_path: path in instance
+ @param data: data to write, either str or bytes
"""
self.pylxd_container.files.put(remote_path, data)
def console_log(self):
- """
- return_value: bytes of this instance’s console
+ """Console log.
+
+ @return_value: bytes of this instance’s console
"""
raise NotImplementedError
def reboot(self, wait=True):
- """
- reboot instance
- """
+ """Reboot instance."""
self.shutdown(wait=wait)
self.start(wait=wait)
def shutdown(self, wait=True):
- """
- shutdown instance
- """
+ """Shutdown instance."""
if self.pylxd_container.status != 'Stopped':
self.pylxd_container.stop(wait=wait)
- def start(self, wait=True, wait_time=None):
- """
- start instance
- """
+ def start(self, wait=True, wait_for_cloud_init=False):
+ """Start instance."""
if self.pylxd_container.status != 'Running':
self.pylxd_container.start(wait=wait)
- if wait and isinstance(wait_time, int):
- self._wait_for_cloud_init(wait_time)
+ if wait:
+ self._wait_for_system(wait_for_cloud_init)
def freeze(self):
- """
- freeze instance
- """
+ """Freeze instance."""
if self.pylxd_container.status != 'Frozen':
self.pylxd_container.freeze(wait=True)
def unfreeze(self):
- """
- unfreeze instance
- """
+ """Unfreeze instance."""
if self.pylxd_container.status == 'Frozen':
self.pylxd_container.unfreeze(wait=True)
def destroy(self):
- """
- clean up instance
- """
+ """Clean up instance."""
self.unfreeze()
self.shutdown()
self.pylxd_container.delete(wait=True)