~ubuntu-branches/ubuntu/trusty/plainbox-provider-checkbox/trusty

« back to all changes in this revision

Viewing changes to bin/virtualization

  • Committer: Package Import Robot
  • Author(s): Zygmunt Krynicki
  • Date: 2014-04-07 19:00:31 UTC
  • mfrom: (3.1.1 sid)
  • Revision ID: package-import@ubuntu.com-20140407190031-rf836grml6oilfyt
Tags: 0.4-1
* New upstream release. List of bugfixes:
  https://launchpad.net/plainbox-provider-checkbox/14.04/0.4
* debian/watch: look for new releases on launchpad
* debian/rules: stop using pybuild and use manage.py
  {i18n,build,install,validate} instead. This also drops dependency on
  python3-distutils-extra and replaces that with intltool
* debian/control: drop X-Python3-Version
* debian/control: make plainbox-provider-checkbox depend on python and
  python2.7 (for some scripts) rather than suggesting them.
* debian/upstream/signing-key.asc: Use armoured gpg keys to avoid having to
  keep binary files in Debian packaging. Also, replace that with my key
  since I made the 0.3 release upstream.
* debian/source/lintian-overrides: add an override for warning about no
  source for flash movie with reference to a bug report that discusses that
  issue.
* debian/source/include-binaries: drop (no longer needed)
* debian/patches: drop (no longer needed)
* debian/plainbox-provider-checkbox.lintian-overrides: drop (no longer
  needed)
* Stop being a python3 module, move to from DPMT to PAPT

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python3
 
2
 
 
3
"""
 
4
Script to test virtualization functionality
 
5
 
 
6
Copyright (C) 2013, 2014 Canonical Ltd.
 
7
 
 
8
Authors
 
9
  Jeff Marcom <jeff.marcom@canonical.com>
 
10
  Daniel Manrique <roadmr@ubuntu.com>
 
11
 
 
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.
 
15
 
 
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.
 
20
 
 
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/>.
 
23
 
 
24
"""
 
25
 
 
26
from argparse import ArgumentParser
 
27
import configparser
 
28
import os
 
29
import re
 
30
import logging
 
31
import lsb_release
 
32
import shlex
 
33
import signal
 
34
from subprocess import (
 
35
    Popen,
 
36
    PIPE,
 
37
    CalledProcessError,
 
38
    check_output,
 
39
    call
 
40
)
 
41
import sys
 
42
import tempfile
 
43
import tarfile
 
44
import time
 
45
import urllib.request
 
46
 
 
47
DEFAULT_TIMEOUT = 500
 
48
 
 
49
 
 
50
class XENTest(object):
 
51
    pass
 
52
 
 
53
 
 
54
class KVMTest(object):
 
55
 
 
56
    def __init__(self, image=None, timeout=500, debug_file="virt_debug"):
 
57
        self.image = image
 
58
        self.timeout = timeout
 
59
        self.debug_file = os.path.join(os.getcwd(), debug_file)
 
60
        self.arch = check_output(['arch'], universal_newlines=True)
 
61
 
 
62
    def download_image(self):
 
63
        """
 
64
        Downloads Cloud image for same release as host machine
 
65
        """
 
66
 
 
67
        # Check Ubuntu release info. Example {quantal, precise}
 
68
        release = lsb_release.get_lsb_information()["CODENAME"]
 
69
 
 
70
        # Construct URL
 
71
        cloud_url = "http://cloud-images.ubuntu.com"
 
72
 
 
73
        if re.match("arm.*", self.arch):
 
74
            cloud_iso = release + "-server-cloudimg-armhf.tar.gz"
 
75
        else:
 
76
            cloud_iso = release + "-server-cloudimg-i386-disk1.img"
 
77
        image_url = "/".join((
 
78
            cloud_url, release, "current", cloud_iso))
 
79
 
 
80
        logging.debug("Downloading {}, from {}".format(cloud_iso, cloud_url))
 
81
 
 
82
        # Attempt download
 
83
        try:
 
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)
 
87
            return False
 
88
 
 
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)
 
94
 
 
95
        if not os.path.isfile(cloud_iso):
 
96
            return False
 
97
 
 
98
        return cloud_iso
 
99
 
 
100
    def boot_image(self, data_disk):
 
101
        """
 
102
        Attempts to boot the newly created qcow image using
 
103
        the config data defined in config.iso
 
104
        """
 
105
 
 
106
        logging.debug("Attempting boot for:{}".format(data_disk))
 
107
 
 
108
        # Set Arbitrary IP values
 
109
        netrange = "10.0.0.0/8"
 
110
        image_ip = "10.0.0.1"
 
111
        hostfwd = "tcp::2222-:22"
 
112
        cloud_disk = ""
 
113
 
 
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"
 
118
 
 
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)
 
123
        else:
 
124
            params = \
 
125
                '''
 
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
 
129
                '''.format(
 
130
                "256",
 
131
                netrange,
 
132
                image_ip,
 
133
                hostfwd,
 
134
                data_disk,
 
135
                cloud_disk).replace("\n", "").replace("  ", "")
 
136
 
 
137
        logging.debug("Using params:{}".format(params))
 
138
 
 
139
        # Default file location for log file is in checkbox output directory
 
140
        checkbox_dir = os.getenv("CHECKBOX_DATA")
 
141
 
 
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
 
147
        try:
 
148
            file = open(self.debug_file, 'w')
 
149
        except IOError:
 
150
            logging.error("Failed creating file:{}".format(self.debug_file))
 
151
            return False
 
152
 
 
153
        # Start Virtual machine
 
154
        self.process = Popen(
 
155
            shlex.split(params), stdin=PIPE, stderr=file, stdout=file,
 
156
            universal_newlines=True, shell=False)
 
157
 
 
158
    def create_cloud_disk(self):
 
159
        """
 
160
        Generate Cloud meta data and creates an iso object
 
161
        to be mounted as virtual device to instance during boot.
 
162
        """
 
163
 
 
164
        user_data = """\
 
165
#cloud-config
 
166
 
 
167
runcmd:
 
168
 - [ sh, -c, echo "========= CERTIFICATION TEST =========" ]
 
169
 
 
170
power_state:
 
171
    mode: halt
 
172
    message: Bye
 
173
    timeout: 480
 
174
 
 
175
final_message: CERTIFICATION BOOT COMPLETE
 
176
"""
 
177
 
 
178
        meta_data = """\
 
179
{ echo instance-id: iid-local01; echo local-hostname, certification; }
 
180
"""
 
181
 
 
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("-", "_")])
 
187
 
 
188
        # Create Data ISO hosting user & meta cloud config data
 
189
        try:
 
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")
 
196
 
 
197
    def start(self):
 
198
        logging.debug('Starting KVM Test')
 
199
        status = 1
 
200
        # Create temp directory:
 
201
 
 
202
        date = time.strftime("%b_%d_%Y_")
 
203
        with tempfile.TemporaryDirectory("_kvm_test", date) as temp_dir:
 
204
 
 
205
            os.chmod(temp_dir, 0o744)
 
206
            os.chdir(temp_dir)
 
207
            if not self.image:
 
208
                logging.debug('No image specified, downloading one now.')
 
209
                # Download cloud image
 
210
                self.image = self.download_image()
 
211
 
 
212
            if self.image and os.path.isfile(self.image):
 
213
 
 
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()
 
218
 
 
219
                # Boot Virtual Machine
 
220
                instance = self.boot_image(self.image)
 
221
 
 
222
                time.sleep(self.timeout)
 
223
                # Reset Console window to regain control from VM Serial I/0
 
224
                call('reset')
 
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)
 
231
                        else:
 
232
                            print("Booted successfully (Previously "
 
233
                                  "initalized VM)", file=sys.stderr)
 
234
                        status = 0
 
235
                    else:
 
236
                        print("E: KVM instance failed to boot", file=sys.stderr)
 
237
                        print("Console output".center(72, "="),
 
238
                              file=sys.stderr)
 
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()
 
243
            elif not self.image:
 
244
                print("Could not find downloaded image")
 
245
            else:
 
246
                print("Could not find: {}".format(self.image), file=sys.stderr)
 
247
 
 
248
        return status
 
249
 
 
250
 
 
251
def test_kvm(args):
 
252
    print("Executing KVM Test", file=sys.stderr)
 
253
 
 
254
    DEFAULT_CFG = "/etc/checkbox.d/virtualization.cfg"
 
255
    image = ""
 
256
    timeout = ""
 
257
 
 
258
    # Configuration data can come from three sources.
 
259
    # Lowest priority is the config file.
 
260
    config_file = DEFAULT_CFG
 
261
    config = configparser.SafeConfigParser()
 
262
 
 
263
    try:
 
264
        config.readfp(open(config_file))
 
265
    except IOError:
 
266
        logging.warn("No config file found")
 
267
    else:
 
268
        try:
 
269
            timeout = config.getfloat("KVM", "timeout")
 
270
        except ValueError:
 
271
            logging.warning('Invalid or Empty timeout in config file. '
 
272
                            'Falling back to default')
 
273
        except configparser.NoSectionError as e:
 
274
            logging.exception(e)
 
275
 
 
276
        try:
 
277
            image = config.get("KVM", "image")
 
278
        except configparser.NoSectionError:
 
279
            logging.exception('Invalid or Empty image in config file.')
 
280
 
 
281
    # Next in priority are environment variables.
 
282
    if 'KVM_TIMEOUT' in os.environ:
 
283
        try:
 
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']
 
290
 
 
291
    # Finally, highest-priority are command line arguments.
 
292
    if args.timeout:
 
293
        timeout = args.timeout
 
294
    elif not timeout:
 
295
        timeout = DEFAULT_TIMEOUT
 
296
    if args.image:
 
297
        image = args.image
 
298
 
 
299
    kvm_test = KVMTest(image, timeout)
 
300
    result = kvm_test.start()
 
301
 
 
302
    sys.exit(result)
 
303
 
 
304
 
 
305
def main():
 
306
 
 
307
    parser = ArgumentParser(description="Virtualization Test")
 
308
    subparsers = parser.add_subparsers()
 
309
 
 
310
    # Main cli options
 
311
    kvm_test_parser = subparsers.add_parser(
 
312
        'kvm', help=("Run kvm virtualization test"))
 
313
 
 
314
    #xen_test_parser = subparsers.add_parser('xen',
 
315
    #    help=("Run xen virtualization test"))
 
316
 
 
317
    # Sub test options
 
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)
 
326
 
 
327
    args = parser.parse_args()
 
328
 
 
329
    try:
 
330
        logging.basicConfig(level=args.log_level)
 
331
    except AttributeError:
 
332
        pass  # avoids exception when trying to run without specifying 'kvm'
 
333
 
 
334
    # to check if not len(sys.argv) > 1
 
335
    if len(vars(args)) == 0:
 
336
        parser.print_help()
 
337
        return False
 
338
 
 
339
    args.func(args)
 
340
 
 
341
if __name__ == "__main__":
 
342
    main()