Merge ~bladernr/plainbox-provider-checkbox:lxd-tests into plainbox-provider-checkbox:master

Proposed by Jeff Lane 
Status: Merged
Approved by: Jeff Lane 
Approved revision: f832021568e9a6205baa6234a7dd7968ebfaadf5
Merged at revision: 57dbfc5ed518d5a5468e758df2ef374decc32d22
Proposed branch: ~bladernr/plainbox-provider-checkbox:lxd-tests
Merge into: plainbox-provider-checkbox:master
Diff against target: 315 lines (+236/-8)
2 files modified
bin/virtualization (+220/-7)
jobs/virtualization.txt.in (+16/-1)
Reviewer Review Type Date Requested Status
Jeff Lane  Approve
Review via email: mp+317157@code.launchpad.net

Description of the change

Introduces a LXD test to the virtualization script. Additionally fixes some bugs I introduced and changes the ordering of the --debug option to make it script wide rather than test specific.

To post a comment you must log in.
Revision history for this message
Jeff Lane  (bladernr) wrote :

Updated with some further refinements

Revision history for this message
Jeff Lane  (bladernr) wrote :

Been waiting and asking since February for a review, no one has reviewed. I"ve tested this a lot, so I'm going to self-approve it.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/bin/virtualization b/bin/virtualization
2index e221261..438a1a2 100755
3--- a/bin/virtualization
4+++ b/bin/virtualization
5@@ -30,14 +30,18 @@ import os
6 import re
7 import logging
8 import lsb_release
9+import platform
10 import requests
11 import shlex
12 import signal
13 from subprocess import (
14 Popen,
15 PIPE,
16+ STDOUT,
17+ DEVNULL,
18 CalledProcessError,
19 check_output,
20+ check_call,
21 call
22 )
23 import sys
24@@ -46,6 +50,7 @@ import tarfile
25 import time
26 import urllib.request
27 from urllib.parse import urlparse
28+from uuid import uuid4
29
30 DEFAULT_TIMEOUT = 500
31
32@@ -514,6 +519,206 @@ final_message: CERTIFICATION BOOT COMPLETE
33 return status
34
35
36+class RunCommand(object):
37+ """
38+ Runs a command and can return all needed info:
39+ * stdout
40+ * stderr
41+ * return code
42+ * original command
43+
44+ Convenince class to avoid the same repetitive code to run shell commands.
45+ """
46+
47+ def __init__(self, cmd=None):
48+ self.stdout = None
49+ self.stderr = None
50+ self.returncode = None
51+ self.cmd = cmd
52+ self.run(self.cmd)
53+
54+ def run(self, cmd):
55+ proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE,
56+ universal_newlines=True)
57+ self.stdout, self.stderr = proc.communicate()
58+ self.returncode = proc.returncode
59+
60+
61+class LXDTest(object):
62+
63+ def __init__(self, template=None, rootfs=None):
64+ self.rootfs_url = rootfs
65+ self.template_url = template
66+ self.rootfs_tarball = None
67+ self.template_tarball = None
68+ self.name = 'testbed'
69+ self.image_alias = uuid4().hex
70+ self.default_remote = "ubuntu:"
71+ self.os_version = platform.linux_distribution()[1]
72+
73+ def run_command(self, cmd):
74+ task = RunCommand(cmd)
75+ if task.returncode != 0:
76+ logging.error('Command {} returnd a code of {}'.format(
77+ task.cmd, task.returncode))
78+ logging.error(' STDOUT: {}'.format(task.stdout))
79+ logging.error(' STDERR: {}'.format(task.stderr))
80+ return False
81+ else:
82+ logging.debug('Command {}:'.format(task.cmd))
83+ if task.stdout != '':
84+ logging.debug(' STDOUT: {}'.format(task.stdout))
85+ elif task.stderr != '':
86+ logging.debug(' STDERR: {}'.format(task.stderr))
87+ else:
88+ logging.debug(' Command returned no output')
89+ return True
90+
91+ def setup(self):
92+ # Initialize LXD
93+ result = True
94+ logging.debug("Attempting to initialize LXD")
95+ # TODO: Need a method to see if LXD is already initialized
96+ if not self.run_command('lxd init --auto'):
97+ logging.debug('Error encounterd while initializing LXD')
98+ result = False
99+
100+ # Retrieve and insert LXD images
101+ if self.template_url is not None:
102+ logging.debug("Downloading template.")
103+ targetfile = urlparse(self.template_url).path.split('/')[-1]
104+ filename = os.path.join('/tmp', targetfile)
105+ if not os.path.isfile(filename):
106+ self.template_tarball = self.download_images(self.template_url,
107+ filename)
108+ if not self.template_tarball:
109+ logging.error("Unable to download {} from "
110+ "{}".format(self.template_tarball,
111+ self.template_url))
112+ logging.error("Aborting")
113+ result = False
114+ else:
115+ logging.debug("Template file {} already exists. "
116+ "Skipping Download.".format(filename))
117+ self.template_tarball = filename
118+
119+ if self.rootfs_url is not None:
120+ logging.debug("Downloading rootfs.")
121+ targetfile = urlparse(self.rootfs_url).path.split('/')[-1]
122+ filename = os.path.join('/tmp', targetfile)
123+ if not os.path.isfile(filename):
124+ self.rootfs_tarball = self.download_images(self.rootfs_url,
125+ filename)
126+ if not self.rootfs_tarball:
127+ logging.error("Unable to download {} from{}".format(
128+ self.rootfs_tarball, self.rootfs_url))
129+ logging.error("Aborting")
130+ result = False
131+ else:
132+ logging.debug("Template file {} already exists. "
133+ "Skipping Download.".format(filename))
134+ self.rootfs_tarball = filename
135+
136+ # Insert images
137+ if result is True:
138+ logging.debug("Importing images into LXD")
139+ cmd = 'lxc image import {} rootfs {} --alias {}'.format(
140+ self.template_tarball, self.rootfs_tarball,
141+ self.image_alias)
142+ if not self.run_command(cmd):
143+ logging.error('Error encountered while attempting to '
144+ 'import images into LXD')
145+ result = False
146+ else:
147+ logging.debug("No local image available, attempting to "
148+ "import from default remote.")
149+ cmd = 'lxc image copy {}{} local: --alias {}'.format(
150+ self.default_remote, self.os_version, self.image_alias)
151+ if not self.run_command(cmd):
152+ loggging.error('Error encountered while attempting to '
153+ 'import images from default remote.')
154+ result = False
155+
156+ return result
157+
158+ def download_images(self, url, filename):
159+ """
160+ Downloads LXD files for same release as host machine
161+ """
162+ # TODO: Clean this up to use a non-internet simplestream on MAAS server
163+ logging.debug("Attempting download of {} from {}".format(filename,
164+ url))
165+ try:
166+ resp = urllib.request.urlretrieve(url, filename)
167+ except (IOError,
168+ OSError,
169+ urllib.error.HTTPError,
170+ urllib.error.URLError) as exception:
171+ logging.error("Failed download of image from %s: %s",
172+ url, exception)
173+ return False
174+ except ValueError as verr:
175+ logging.error("Invalid URL %s" % url)
176+ logging.error("%s" % verr)
177+ return False
178+
179+ if not os.path.isfile(filename):
180+ logging.warn("Can not find {}".format(filename))
181+ return False
182+
183+ return filename
184+
185+ def cleanup(self):
186+ """
187+ Clean up test files an containers created
188+ """
189+ logging.debug('Cleaning up images and containers created during test')
190+ self.run_command('lxc image delete {}'.format(self.image_alias))
191+ self.run_command('lxc delete --force {}'.format(self.name))
192+
193+ def start(self):
194+ """
195+ Creates a container and performs the test
196+ """
197+ result = self.setup()
198+ if not result:
199+ logging.warn("One or more setup stages failed.")
200+
201+ # Create container
202+ logging.debug("Launching container")
203+ if not self.run_command('lxc launch {} {}'.format(self.image_alias,
204+ self.name)):
205+ return False
206+
207+ logging.debug("Container listing:")
208+ cmd = ("lxc list")
209+ if not self.run_command(cmd):
210+ return False
211+
212+ logging.debug("Testing container")
213+ cmd = ("lxc exec {} dd if=/dev/urandom of=testdata.txt "
214+ "bs=1024 count=1000".format(self.name))
215+ if not self.run_command(cmd):
216+ return False
217+
218+ return True
219+
220+
221+def test_lxd(args):
222+ logging.debug("Executing LXD Test")
223+
224+ lxd_test = LXDTest(args.template, args.rootfs)
225+
226+ result = lxd_test.start()
227+ lxd_test.cleanup()
228+ if result:
229+ print("PASS: Container was succssfully started and checked")
230+ sys.exit(0)
231+ else:
232+ print("FAIL: Container was not started and checked")
233+ sys.exit(1)
234+
235+
236 def test_kvm(args):
237 print("Executing KVM Test", file=sys.stderr)
238
239@@ -558,6 +763,11 @@ def main():
240 # Main cli options
241 kvm_test_parser = subparsers.add_parser(
242 'kvm', help=("Run kvm virtualization test"))
243+ lxd_test_parser = subparsers.add_parser(
244+ 'lxd', help=("Run the LXD validation test"))
245+ parser.add_argument('--debug', dest='log_level',
246+ action="store_const", const=logging.DEBUG,
247+ default=logging.INFO)
248
249 # Sub test options
250 kvm_test_parser.add_argument(
251@@ -567,11 +777,14 @@ def main():
252 kvm_test_parser.add_argument(
253 '-l', '--log-file', default='virt_debug',
254 help="Location for debugging output log. Defaults to %(default)s.")
255- kvm_test_parser.add_argument('--debug', dest='log_level',
256- action="store_const", const=logging.DEBUG,
257- default=logging.INFO)
258 kvm_test_parser.set_defaults(func=test_kvm)
259
260+ lxd_test_parser.add_argument(
261+ '--template', type=str, default=None)
262+ lxd_test_parser.add_argument(
263+ '--rootfs', type=str, default=None)
264+ lxd_test_parser.set_defaults(func=test_lxd)
265+
266 args = parser.parse_args()
267
268 try:
269@@ -582,12 +795,12 @@ def main():
270 # silence normal output from requests module
271 logging.getLogger("requests").setLevel(logging.WARNING)
272
273- # to check if not len(sys.argv) > 1
274- if len(vars(args)) == 0:
275+ # Verify args
276+ try:
277+ args.func(args)
278+ except AttributeError:
279 parser.print_help()
280 return False
281
282- args.func(args)
283-
284 if __name__ == "__main__":
285 main()
286diff --git a/jobs/virtualization.txt.in b/jobs/virtualization.txt.in
287index 919691b..2a466af 100644
288--- a/jobs/virtualization.txt.in
289+++ b/jobs/virtualization.txt.in
290@@ -8,9 +8,24 @@ requires:
291 package.name == 'qemu-system'
292 package.name == 'qemu-utils'
293 virtualization.kvm == 'supported'
294-command: virtualization kvm --debug --log-file=$PLAINBOX_SESSION_SHARE/virt_debug
295+command: virtualization --debug kvm --log-file=$PLAINBOX_SESSION_SHARE/virt_debug
296 _description:
297 Verifies that a KVM guest can be created and booted using an Ubuntu Server
298 cloud image.
299 _summary:
300 Verify KVM guest boots
301+
302+plugin: shell
303+category_id: 2013.com.canonical.plainbox::virtualization
304+id: virtualization/verify_lxd
305+user: root
306+environ: LXD_TEMPLATE LXD_ROOTFS
307+estimated_duration: 30.0
308+requires:
309+ package.name == 'lxd-client'
310+ package.name == 'lxd'
311+command: virtualization lxd --template $LXD_TEMPLATE --rootfs $LXD_ROOTFS
312+_description:
313+ Verifies that an LXD container can be created and launched
314+_summary:
315+ Verify LXD container launches

Subscribers

People subscribed via source and target branches