summaryrefslogtreecommitdiff
diff options
-rwxr-xr-xbin/virtualization227
-rw-r--r--jobs/virtualization.txt.in17
2 files changed, 236 insertions, 8 deletions
diff --git a/bin/virtualization b/bin/virtualization
index e221261..438a1a2 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()
diff --git a/jobs/virtualization.txt.in b/jobs/virtualization.txt.in
index 919691b..2a466af 100644
--- a/jobs/virtualization.txt.in
+++ b/jobs/virtualization.txt.in
@@ -8,9 +8,24 @@ requires:
package.name == 'qemu-system'
package.name == 'qemu-utils'
virtualization.kvm == 'supported'
-command: virtualization kvm --debug --log-file=$PLAINBOX_SESSION_SHARE/virt_debug
+command: virtualization --debug kvm --log-file=$PLAINBOX_SESSION_SHARE/virt_debug
_description:
Verifies that a KVM guest can be created and booted using an Ubuntu Server
cloud image.
_summary:
Verify KVM guest boots
+
+plugin: shell
+category_id: 2013.com.canonical.plainbox::virtualization
+id: virtualization/verify_lxd
+user: root
+environ: LXD_TEMPLATE LXD_ROOTFS
+estimated_duration: 30.0
+requires:
+ package.name == 'lxd-client'
+ package.name == 'lxd'
+command: virtualization lxd --template $LXD_TEMPLATE --rootfs $LXD_ROOTFS
+_description:
+ Verifies that an LXD container can be created and launched
+_summary:
+ Verify LXD container launches