summaryrefslogtreecommitdiff
path: root/tests/cloud_tests/instances
diff options
context:
space:
mode:
authorWesley Wiedenmeier <wesley.wiedenmeier@gmail.com>2016-12-22 17:27:37 -0500
committerScott Moser <smoser@brickies.net>2016-12-22 17:41:39 -0500
commitf53fc46aa732e3b29991b3e5e39da31a722945ee (patch)
treea301733aa9991b58b218f61b187240d275e44968 /tests/cloud_tests/instances
parentb2a9f33616c806ae6e052520a8589113308f567c (diff)
downloadvyos-cloud-init-f53fc46aa732e3b29991b3e5e39da31a722945ee.tar.gz
vyos-cloud-init-f53fc46aa732e3b29991b3e5e39da31a722945ee.zip
integration test: initial commit of integration test framework
The adds in end-to-end testing of cloud-init. The framework utilizes LXD and cloud images as a backend to test user-data passed in. Arbitrary data is then captured from predefined commands specified by the user. After collection, data verification is completed by running a series of Python unit tests against the collected data. Currently only the Ubuntu Trusty, Xenial, Yakkety, and Zesty releases are supported. Test cases for 50% of the modules is complete and available. Additionally a Read the Docs file was created to guide test writing and execution.
Diffstat (limited to 'tests/cloud_tests/instances')
-rw-r--r--tests/cloud_tests/instances/__init__.py10
-rw-r--r--tests/cloud_tests/instances/base.py120
-rw-r--r--tests/cloud_tests/instances/lxd.py121
3 files changed, 251 insertions, 0 deletions
diff --git a/tests/cloud_tests/instances/__init__.py b/tests/cloud_tests/instances/__init__.py
new file mode 100644
index 00000000..85bea99f
--- /dev/null
+++ b/tests/cloud_tests/instances/__init__.py
@@ -0,0 +1,10 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+
+def get_instance(snapshot, *args, **kwargs):
+ """
+ get instance from snapshot
+ """
+ return snapshot.launch(*args, **kwargs)
+
+# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/instances/base.py b/tests/cloud_tests/instances/base.py
new file mode 100644
index 00000000..9559d286
--- /dev/null
+++ b/tests/cloud_tests/instances/base.py
@@ -0,0 +1,120 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+import os
+import uuid
+
+
+class Instance(object):
+ """
+ Base instance object
+ """
+ platform_name = None
+
+ def __init__(self, name):
+ """
+ setup
+ """
+ self.name = name
+
+ 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
+
+ Execute assumes functional networking and execution as root with the
+ target filesystem being available at /.
+
+ return_value: tuple containing stdout data, stderr data, exit code
+ """
+ raise NotImplementedError
+
+ def read_data(self, remote_path, encode=False):
+ """
+ read_data from instance filesystem
+ remote_path: path in instance
+ decode: return as string
+ return_value: data as str or bytes
+ """
+ raise NotImplementedError
+
+ 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
+ """
+ raise NotImplementedError
+
+ def pull_file(self, remote_path, local_path):
+ """
+ copy file at 'remote_path', from instance to 'local_path'
+ """
+ with open(local_path, 'wb') as fp:
+ fp.write(self.read_data(remote_path), encode=True)
+
+ def push_file(self, local_path, remote_path):
+ """
+ copy file at 'local_path' to instance at 'remote_path'
+ """
+ with open(local_path, 'rb') as fp:
+ self.write_data(remote_path, fp.read())
+
+ def run_script(self, script):
+ """
+ run script in target and return stdout
+ """
+ script_path = os.path.join('/tmp', str(uuid.uuid1()))
+ self.write_data(script_path, script)
+ (out, err, exit_code) = self.execute(['/bin/bash', script_path])
+ return out
+
+ def console_log(self):
+ """
+ return_value: bytes of this instance’s console
+ """
+ raise NotImplementedError
+
+ def reboot(self, wait=True):
+ """
+ reboot instance
+ """
+ raise NotImplementedError
+
+ def shutdown(self, wait=True):
+ """
+ shutdown instance
+ """
+ raise NotImplementedError
+
+ def start(self, wait=True):
+ """
+ start instance
+ """
+ raise NotImplementedError
+
+ def destroy(self):
+ """
+ clean up instance
+ """
+ pass
+
+ def _wait_for_cloud_init(self, wait_time):
+ """
+ wait until system has fully booted and cloud-init has finished
+ """
+ if not wait_time:
+ return
+
+ found_msg = 'found'
+ cmd = ('for ((i=0;i<{wait};i++)); do [ -f "{file}" ] && '
+ '{{ echo "{msg}";break; }} || sleep 1; done').format(
+ file='/run/cloud-init/result.json',
+ wait=wait_time, msg=found_msg)
+
+ (out, err, exit) = self.execute(['/bin/bash', '-c', cmd])
+ if out.strip() != found_msg:
+ raise OSError('timeout: after {}s, cloud-init has not started'
+ .format(wait_time))
+
+# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/instances/lxd.py b/tests/cloud_tests/instances/lxd.py
new file mode 100644
index 00000000..f0aa1214
--- /dev/null
+++ b/tests/cloud_tests/instances/lxd.py
@@ -0,0 +1,121 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from tests.cloud_tests.instances import base
+
+
+class LXDInstance(base.Instance):
+ """
+ LXD container backed instance
+ """
+ platform_name = "lxd"
+
+ def __init__(self, name, platform, pylxd_container):
+ """
+ setup
+ """
+ self.platform = platform
+ self._pylxd_container = pylxd_container
+ super(LXDInstance, self).__init__(name)
+
+ @property
+ def pylxd_container(self):
+ 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
+
+ Execute assumes functional networking and execution as root with the
+ target filesystem being available at /.
+
+ 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
+ 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,)
+
+ 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
+ """
+ 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
+ """
+ self.pylxd_container.files.put(remote_path, data)
+
+ def console_log(self):
+ """
+ return_value: bytes of this instance’s console
+ """
+ raise NotImplementedError
+
+ def reboot(self, wait=True):
+ """
+ reboot instance
+ """
+ self.shutdown(wait=wait)
+ self.start(wait=wait)
+
+ def shutdown(self, wait=True):
+ """
+ shutdown instance
+ """
+ if self.pylxd_container.status != 'Stopped':
+ self.pylxd_container.stop(wait=wait)
+
+ def start(self, wait=True, wait_time=None):
+ """
+ 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)
+
+ def freeze(self):
+ """
+ freeze instance
+ """
+ if self.pylxd_container.status != 'Frozen':
+ self.pylxd_container.freeze(wait=True)
+
+ def unfreeze(self):
+ """
+ unfreeze instance
+ """
+ if self.pylxd_container.status == 'Frozen':
+ self.pylxd_container.unfreeze(wait=True)
+
+ def destroy(self):
+ """
+ clean up instance
+ """
+ self.unfreeze()
+ self.shutdown()
+ self.pylxd_container.delete(wait=True)
+ if self.platform.container_exists(self.name):
+ raise OSError('container {} was not properly removed'
+ .format(self.name))
+ super(LXDInstance, self).destroy()
+
+# vi: ts=4 expandtab