4
Script to test virtualization functionality
6
Copyright (C) 2013, 2014 Canonical Ltd.
9
Jeff Marcom <jeff.marcom@canonical.com>
10
Daniel Manrique <roadmr@ubuntu.com>
12
This program is free software: you can redistribute it and/or modify
13
it under the terms of the GNU General Public License version 3,
14
as published by the Free Software Foundation.
16
This program is distributed in the hope that it will be useful,
17
but WITHOUT ANY WARRANTY; without even the implied warranty of
18
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
GNU General Public License for more details.
21
You should have received a copy of the GNU General Public License
22
along with this program. If not, see <http://www.gnu.org/licenses/>.
26
from argparse import ArgumentParser
34
from subprocess import (
50
class XENTest(object):
54
class KVMTest(object):
56
def __init__(self, image=None, timeout=500, debug_file="virt_debug"):
58
self.timeout = timeout
59
self.debug_file = os.path.join(os.getcwd(), debug_file)
60
self.arch = check_output(['arch'], universal_newlines=True)
62
def download_image(self):
64
Downloads Cloud image for same release as host machine
67
# Check Ubuntu release info. Example {quantal, precise}
68
release = lsb_release.get_lsb_information()["CODENAME"]
71
cloud_url = "http://cloud-images.ubuntu.com"
73
if re.match("arm.*", self.arch):
74
cloud_iso = release + "-server-cloudimg-armhf.tar.gz"
76
cloud_iso = release + "-server-cloudimg-i386-disk1.img"
77
image_url = "/".join((
78
cloud_url, release, "current", cloud_iso))
80
logging.debug("Downloading {}, from {}".format(cloud_iso, cloud_url))
84
resp = urllib.request.urlretrieve(image_url, cloud_iso)
85
except (IOError, OSError, urllib.error.HTTPError) as exception:
86
logging.error("Failed download of image from %s: %s", image_url, exception)
89
# Unpack img file from tar
90
if re.match("arm.*", self.arch):
91
cloud_iso_tgz = tarfile.open(cloud_iso)
92
cloud_iso = cloud_iso.replace('tar.gz', 'img')
93
cloud_iso_tgz.extract(cloud_iso)
95
if not os.path.isfile(cloud_iso):
100
def boot_image(self, data_disk):
102
Attempts to boot the newly created qcow image using
103
the config data defined in config.iso
106
logging.debug("Attempting boot for:{}".format(data_disk))
108
# Set Arbitrary IP values
109
netrange = "10.0.0.0/8"
110
image_ip = "10.0.0.1"
111
hostfwd = "tcp::2222-:22"
114
# Should we attach the cloud config disk
115
if os.path.isfile("seed.iso"):
116
logging.debug("Attaching Cloud config disk")
117
cloud_disk = "-drive file=seed.iso,if=virtio"
119
if re.match("(arm.*|aarch64)", self.arch):
120
uname = check_output(['uname', '-r'], universal_newlines=True)
121
cloud_disk = cloud_disk.replace("virtio", "sd")
122
params = 'qemu-system-arm -machine vexpress-a15 -cpu cortex-a15 -enable-kvm -m {} -kernel /boot/vmlinuz -append "console=ttyAMA0 earlyprintk=serial root=/dev/mmcblk0 ro rootfstype=ext4" -serial stdio -dtb /lib/firmware/{}/device-tree/vexpress-v2p-ca15-tc1.dtb -initrd /boot/initrd.img -net nic -net user,net={},host={},hostfwd={} -drive file={},if=sd,cache=writeback {} -display none -nographic'.format(uname, "256", netrange, image_ip, hostfwd, data_disk, cloud_disk)
126
qemu-system-x86_64 -machine accel=kvm:tcg -m {0} -net nic
127
-net user,net={1},host={2},hostfwd={3}
128
-drive file={4},if=virtio {5} -display none -nographic
135
cloud_disk).replace("\n", "").replace(" ", "")
137
logging.debug("Using params:{}".format(params))
139
# Default file location for log file is in checkbox output directory
140
checkbox_dir = os.getenv("CHECKBOX_DATA")
142
if checkbox_dir is not None:
143
self.debug_file = os.path.join(checkbox_dir, self.debug_file)
144
logging.info("Storing VM console output in {}".format(
145
os.path.realpath(self.debug_file)))
146
# Open VM STDERR/STDOUT log file for writing
148
file = open(self.debug_file, 'w')
150
logging.error("Failed creating file:{}".format(self.debug_file))
153
# Start Virtual machine
154
self.process = Popen(
155
shlex.split(params), stdin=PIPE, stderr=file, stdout=file,
156
universal_newlines=True, shell=False)
158
def create_cloud_disk(self):
160
Generate Cloud meta data and creates an iso object
161
to be mounted as virtual device to instance during boot.
168
- [ sh, -c, echo "========= CERTIFICATION TEST =========" ]
175
final_message: CERTIFICATION BOOT COMPLETE
179
{ echo instance-id: iid-local01; echo local-hostname, certification; }
182
for file in ['user-data', 'meta-data']:
183
logging.debug("Creating cloud %s", file)
184
with open(file, "wt") as data_file:
185
os.fchmod(data_file.fileno(), 0o777)
186
data_file.write(vars()[file.replace("-", "_")])
188
# Create Data ISO hosting user & meta cloud config data
190
iso_build = check_output(
191
['genisoimage', '-output', 'seed.iso', '-volid',
192
'cidata', '-joliet', '-rock', 'user-data', 'meta-data'],
193
universal_newlines=True)
194
except CalledProcessError as exception:
195
logging.exception("Cloud data disk creation failed")
198
logging.debug('Starting KVM Test')
200
# Create temp directory:
202
date = time.strftime("%b_%d_%Y_")
203
with tempfile.TemporaryDirectory("_kvm_test", date) as temp_dir:
205
os.chmod(temp_dir, 0o744)
208
logging.debug('No image specified, downloading one now.')
209
# Download cloud image
210
self.image = self.download_image()
212
if self.image and os.path.isfile(self.image):
214
if "cloud" in self.image:
215
# Will assume we need to supply cloud meta data
216
# for instance boot to be successful
217
self.create_cloud_disk()
219
# Boot Virtual Machine
220
instance = self.boot_image(self.image)
222
time.sleep(self.timeout)
223
# Reset Console window to regain control from VM Serial I/0
225
# Check to be sure VM boot was successful
226
with open(self.debug_file, 'r') as debug_file:
227
file_contents = debug_file.read()
228
if "CERTIFICATION BOOT COMPLETE" in file_contents:
229
if "END SSH HOST KEY KEYS" in file_contents:
230
print("Booted successfully", file=sys.stderr)
232
print("Booted successfully (Previously "
233
"initalized VM)", file=sys.stderr)
236
print("E: KVM instance failed to boot", file=sys.stderr)
237
print("Console output".center(72, "="),
239
with open(self.debug_file, 'r') as console_log:
240
print(console_log.read(), file=sys.stderr)
241
print("E: KVM instance failed to boot", file=sys.stderr)
242
self.process.terminate()
244
print("Could not find downloaded image")
246
print("Could not find: {}".format(self.image), file=sys.stderr)
252
print("Executing KVM Test", file=sys.stderr)
254
DEFAULT_CFG = "/etc/checkbox.d/virtualization.cfg"
258
# Configuration data can come from three sources.
259
# Lowest priority is the config file.
260
config_file = DEFAULT_CFG
261
config = configparser.SafeConfigParser()
264
config.readfp(open(config_file))
266
logging.warn("No config file found")
269
timeout = config.getfloat("KVM", "timeout")
271
logging.warning('Invalid or Empty timeout in config file. '
272
'Falling back to default')
273
except configparser.NoSectionError as e:
277
image = config.get("KVM", "image")
278
except configparser.NoSectionError:
279
logging.exception('Invalid or Empty image in config file.')
281
# Next in priority are environment variables.
282
if 'KVM_TIMEOUT' in os.environ:
284
timeout = float(os.environ['KVM_TIMEOUT'])
285
except ValueError as err:
286
logging.warning("TIMEOUT env variable: %s" % err)
287
timeout = DEFAULT_TIMEOUT
288
if 'KVM_IMAGE' in os.environ:
289
image = os.environ['KVM_IMAGE']
291
# Finally, highest-priority are command line arguments.
293
timeout = args.timeout
295
timeout = DEFAULT_TIMEOUT
299
kvm_test = KVMTest(image, timeout)
300
result = kvm_test.start()
307
parser = ArgumentParser(description="Virtualization Test")
308
subparsers = parser.add_subparsers()
311
kvm_test_parser = subparsers.add_parser(
312
'kvm', help=("Run kvm virtualization test"))
314
#xen_test_parser = subparsers.add_parser('xen',
315
# help=("Run xen virtualization test"))
318
kvm_test_parser.add_argument(
319
'-i', '--image', type=str, default=None)
320
kvm_test_parser.add_argument(
321
'-t', '--timeout', type=int)
322
kvm_test_parser.add_argument('--debug', dest='log_level',
323
action="store_const", const=logging.DEBUG,
324
default=logging.INFO)
325
kvm_test_parser.set_defaults(func=test_kvm)
327
args = parser.parse_args()
330
logging.basicConfig(level=args.log_level)
331
except AttributeError:
332
pass # avoids exception when trying to run without specifying 'kvm'
334
# to check if not len(sys.argv) > 1
335
if len(vars(args)) == 0:
341
if __name__ == "__main__":