diff options
author | PMR <pmr@pmr-lander> | 2017-03-28 21:24:44 +0000 |
---|---|---|
committer | PMR <pmr@pmr-lander> | 2017-03-28 21:24:44 +0000 |
commit | 57dbfc5ed518d5a5468e758df2ef374decc32d22 (patch) | |
tree | 4ef97bbfbcca4883abbd5adbbe72d8eec85d19f6 /bin | |
parent | c52edbbf53af669b7131f74e6617f425c2eb8f8f (diff) | |
parent | f832021568e9a6205baa6234a7dd7968ebfaadf5 (diff) |
Merge #317157 from ~bladernr/plainbox-provider-checkbox:lxd-tests
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/virtualization | 227 |
1 files changed, 220 insertions, 7 deletions
diff --git a/bin/virtualization b/bin/virtualization index e2212611..438a1a2d 100755 --- a/bin/virtualization +++ b/bin/virtualization @@ -30,14 +30,18 @@ import os import re import logging import lsb_release +import platform import requests import shlex import signal from subprocess import ( Popen, PIPE, + STDOUT, + DEVNULL, CalledProcessError, check_output, + check_call, call ) import sys @@ -46,6 +50,7 @@ import tarfile import time import urllib.request from urllib.parse import urlparse +from uuid import uuid4 DEFAULT_TIMEOUT = 500 @@ -514,6 +519,206 @@ final_message: CERTIFICATION BOOT COMPLETE return status +class RunCommand(object): + """ + Runs a command and can return all needed info: + * stdout + * stderr + * return code + * original command + + Convenince class to avoid the same repetitive code to run shell commands. + """ + + def __init__(self, cmd=None): + self.stdout = None + self.stderr = None + self.returncode = None + self.cmd = cmd + self.run(self.cmd) + + def run(self, cmd): + proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE, + universal_newlines=True) + self.stdout, self.stderr = proc.communicate() + self.returncode = proc.returncode + + +class LXDTest(object): + + def __init__(self, template=None, rootfs=None): + self.rootfs_url = rootfs + self.template_url = template + self.rootfs_tarball = None + self.template_tarball = None + self.name = 'testbed' + self.image_alias = uuid4().hex + self.default_remote = "ubuntu:" + self.os_version = platform.linux_distribution()[1] + + def run_command(self, cmd): + task = RunCommand(cmd) + if task.returncode != 0: + logging.error('Command {} returnd a code of {}'.format( + task.cmd, task.returncode)) + logging.error(' STDOUT: {}'.format(task.stdout)) + logging.error(' STDERR: {}'.format(task.stderr)) + return False + else: + logging.debug('Command {}:'.format(task.cmd)) + if task.stdout != '': + logging.debug(' STDOUT: {}'.format(task.stdout)) + elif task.stderr != '': + logging.debug(' STDERR: {}'.format(task.stderr)) + else: + logging.debug(' Command returned no output') + return True + + def setup(self): + # Initialize LXD + result = True + logging.debug("Attempting to initialize LXD") + # TODO: Need a method to see if LXD is already initialized + if not self.run_command('lxd init --auto'): + logging.debug('Error encounterd while initializing LXD') + result = False + + # Retrieve and insert LXD images + if self.template_url is not None: + logging.debug("Downloading template.") + targetfile = urlparse(self.template_url).path.split('/')[-1] + filename = os.path.join('/tmp', targetfile) + if not os.path.isfile(filename): + self.template_tarball = self.download_images(self.template_url, + filename) + if not self.template_tarball: + logging.error("Unable to download {} from " + "{}".format(self.template_tarball, + self.template_url)) + logging.error("Aborting") + result = False + else: + logging.debug("Template file {} already exists. " + "Skipping Download.".format(filename)) + self.template_tarball = filename + + if self.rootfs_url is not None: + logging.debug("Downloading rootfs.") + targetfile = urlparse(self.rootfs_url).path.split('/')[-1] + filename = os.path.join('/tmp', targetfile) + if not os.path.isfile(filename): + self.rootfs_tarball = self.download_images(self.rootfs_url, + filename) + if not self.rootfs_tarball: + logging.error("Unable to download {} from{}".format( + self.rootfs_tarball, self.rootfs_url)) + logging.error("Aborting") + result = False + else: + logging.debug("Template file {} already exists. " + "Skipping Download.".format(filename)) + self.rootfs_tarball = filename + + # Insert images + if result is True: + logging.debug("Importing images into LXD") + cmd = 'lxc image import {} rootfs {} --alias {}'.format( + self.template_tarball, self.rootfs_tarball, + self.image_alias) + if not self.run_command(cmd): + logging.error('Error encountered while attempting to ' + 'import images into LXD') + result = False + else: + logging.debug("No local image available, attempting to " + "import from default remote.") + cmd = 'lxc image copy {}{} local: --alias {}'.format( + self.default_remote, self.os_version, self.image_alias) + if not self.run_command(cmd): + loggging.error('Error encountered while attempting to ' + 'import images from default remote.') + result = False + + return result + + def download_images(self, url, filename): + """ + Downloads LXD files for same release as host machine + """ + # TODO: Clean this up to use a non-internet simplestream on MAAS server + logging.debug("Attempting download of {} from {}".format(filename, + url)) + try: + resp = urllib.request.urlretrieve(url, filename) + except (IOError, + OSError, + urllib.error.HTTPError, + urllib.error.URLError) as exception: + logging.error("Failed download of image from %s: %s", + url, exception) + return False + except ValueError as verr: + logging.error("Invalid URL %s" % url) + logging.error("%s" % verr) + return False + + if not os.path.isfile(filename): + logging.warn("Can not find {}".format(filename)) + return False + + return filename + + def cleanup(self): + """ + Clean up test files an containers created + """ + logging.debug('Cleaning up images and containers created during test') + self.run_command('lxc image delete {}'.format(self.image_alias)) + self.run_command('lxc delete --force {}'.format(self.name)) + + def start(self): + """ + Creates a container and performs the test + """ + result = self.setup() + if not result: + logging.warn("One or more setup stages failed.") + + # Create container + logging.debug("Launching container") + if not self.run_command('lxc launch {} {}'.format(self.image_alias, + self.name)): + return False + + logging.debug("Container listing:") + cmd = ("lxc list") + if not self.run_command(cmd): + return False + + logging.debug("Testing container") + cmd = ("lxc exec {} dd if=/dev/urandom of=testdata.txt " + "bs=1024 count=1000".format(self.name)) + if not self.run_command(cmd): + return False + + return True + + +def test_lxd(args): + logging.debug("Executing LXD Test") + + lxd_test = LXDTest(args.template, args.rootfs) + + result = lxd_test.start() + lxd_test.cleanup() + if result: + print("PASS: Container was succssfully started and checked") + sys.exit(0) + else: + print("FAIL: Container was not started and checked") + sys.exit(1) + + def test_kvm(args): print("Executing KVM Test", file=sys.stderr) @@ -558,6 +763,11 @@ def main(): # Main cli options kvm_test_parser = subparsers.add_parser( 'kvm', help=("Run kvm virtualization test")) + lxd_test_parser = subparsers.add_parser( + 'lxd', help=("Run the LXD validation test")) + parser.add_argument('--debug', dest='log_level', + action="store_const", const=logging.DEBUG, + default=logging.INFO) # Sub test options kvm_test_parser.add_argument( @@ -567,11 +777,14 @@ def main(): kvm_test_parser.add_argument( '-l', '--log-file', default='virt_debug', help="Location for debugging output log. Defaults to %(default)s.") - kvm_test_parser.add_argument('--debug', dest='log_level', - action="store_const", const=logging.DEBUG, - default=logging.INFO) kvm_test_parser.set_defaults(func=test_kvm) + lxd_test_parser.add_argument( + '--template', type=str, default=None) + lxd_test_parser.add_argument( + '--rootfs', type=str, default=None) + lxd_test_parser.set_defaults(func=test_lxd) + args = parser.parse_args() try: @@ -582,12 +795,12 @@ def main(): # silence normal output from requests module logging.getLogger("requests").setLevel(logging.WARNING) - # to check if not len(sys.argv) > 1 - if len(vars(args)) == 0: + # Verify args + try: + args.func(args) + except AttributeError: parser.print_help() return False - args.func(args) - if __name__ == "__main__": main() |