Merge lp:~heber013/utah/adding-setup-for-autopkgtest into lp:utah

Proposed by Heber Parrucci
Status: Merged
Approved by: Jean-Baptiste Lallement
Approved revision: 1108
Merged at revision: 1108
Proposed branch: lp:~heber013/utah/adding-setup-for-autopkgtest
Merge into: lp:utah
Diff against target: 454 lines (+288/-4)
10 files modified
examples/run_utah_tests.py (+1/-0)
setup.py (+3/-0)
templates/casper-preseed-script.jinja2 (+1/-0)
templates/utah-latecommand.jinja2 (+11/-0)
utah/config.py (+6/-0)
utah/parser.py (+2/-0)
utah/provisioning/provisioning.py (+33/-4)
utah/provisioning/qemu-scripts/qemu-setup.py (+211/-0)
utah/provisioning/qemu-scripts/qemu-setup.service (+9/-0)
utah/provisioning/vm.py (+11/-0)
To merge this branch: bzr merge lp:~heber013/utah/adding-setup-for-autopkgtest
Reviewer Review Type Date Requested Status
Jean-Baptiste Lallement Approve
Review via email: mp+326954@code.launchpad.net

Commit message

* Creating a serial console on ttyS1 for autopkgtest to use it.
* Adding option to poweroff VM after tests have run.

Description of the change

Creating a serial console on ttyS1 for autopkgtest to use it.
Adding option to poweroff VM after tests have run.

To post a comment you must log in.
Revision history for this message
Jean-Baptiste Lallement (jibel) wrote :

Approved. Thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'examples/run_utah_tests.py'
--- examples/run_utah_tests.py 2013-06-20 00:06:02 +0000
+++ examples/run_utah_tests.py 2017-07-06 18:08:08 +0000
@@ -54,6 +54,7 @@
54 if image is None:54 if image is None:
55 image = ISO(arch=args.arch, installtype=args.type, series=args.series)55 image = ISO(arch=args.arch, installtype=args.type, series=args.series)
56 kw = {'clean': (not args.no_destroy),56 kw = {'clean': (not args.no_destroy),
57 'poweroff': args.poweroff,
57 'boot': args.boot,58 'boot': args.boot,
58 'debug': args.debug,59 'debug': args.debug,
59 'image': image,60 'image': image,
6061
=== modified file 'setup.py'
--- setup.py 2013-06-19 20:30:45 +0000
+++ setup.py 2017-07-06 18:08:08 +0000
@@ -52,6 +52,9 @@
52 'utah.client.probe',52 'utah.client.probe',
53 'utah.provisioning',53 'utah.provisioning',
54 'utah.provisioning.baremetal'],54 'utah.provisioning.baremetal'],
55 package_data={
56 'utah': ['provisioning/qemu-scripts/*']
57 },
55 maintainer=maintainer,58 maintainer=maintainer,
56 maintainer_email=maintainer_email,59 maintainer_email=maintainer_email,
57)60)
5861
=== modified file 'templates/casper-preseed-script.jinja2'
--- templates/casper-preseed-script.jinja2 2013-05-22 16:56:32 +0000
+++ templates/casper-preseed-script.jinja2 2017-07-06 18:08:08 +0000
@@ -6,3 +6,4 @@
6cp utah-copy-files.sh /root/6cp utah-copy-files.sh /root/
7cp -r utah-copy-files /root/7cp -r utah-copy-files /root/
8cp -r utah-autorun /root/8cp -r utah-autorun /root/
9cp qemu* /root
910
=== modified file 'templates/utah-latecommand.jinja2'
--- templates/utah-latecommand.jinja2 2016-06-10 17:40:15 +0000
+++ templates/utah-latecommand.jinja2 2017-07-06 18:08:08 +0000
@@ -98,9 +98,20 @@
98 fi98 fi
99}99}
100100
101qemu_setup() {
102 if [ -f qemu-setup.py ] ; then
103 cp qemu-setup.py /target/usr/local/bin/qemu-setup.py
104 chmod 755 /target/usr/local/bin/qemu-setup.py
105 cp qemu-setup.service /target/lib/systemd/system/qemu-setup.service
106 chmod 644 /target/lib/systemd/system/qemu-setup.service
107 chroot /target /bin/systemctl enable qemu-setup.service
108 fi
109}
110
101copy_ssh_keys111copy_ssh_keys
102write_utah_data112write_utah_data
103autologin113autologin
104autorun114autorun
105copy_provisioned_files115copy_provisioned_files
106copy_rsyslog_cfg # do this last to avoid getting each syslog msg 2 times116copy_rsyslog_cfg # do this last to avoid getting each syslog msg 2 times
117qemu_setup
107118
=== modified file 'utah/config.py'
--- utah/config.py 2017-06-06 20:18:58 +0000
+++ utah/config.py 2017-07-06 18:08:08 +0000
@@ -81,6 +81,12 @@
81 'type': 'boolean',81 'type': 'boolean',
82 'default': True,82 'default': True,
83 },83 },
84 'poweroff': {
85 'description': (
86 'Setting for whether to power off VM after a test run'),
87 'type': 'boolean',
88 'default': False,
89 },
84 'client_install_timeout': {90 'client_install_timeout': {
85 'description': (91 'description': (
86 'Maximum amount of time to wait '92 'Maximum amount of time to wait '
8793
=== modified file 'utah/parser.py'
--- utah/parser.py 2013-05-28 11:58:47 +0000
+++ utah/parser.py 2017-07-06 18:08:08 +0000
@@ -110,6 +110,8 @@
110 '(%(choices)s) (Default is %(default)s)')110 '(%(choices)s) (Default is %(default)s)')
111 parser.add_argument('-n', '--no-destroy', action='store_true',111 parser.add_argument('-n', '--no-destroy', action='store_true',
112 help='Preserve VM after tests have run')112 help='Preserve VM after tests have run')
113 parser.add_argument('--poweroff', action='store_true',
114 help='Power off VM after tests have run')
113 parser.add_argument('-d', '--debug', action='store_true',115 parser.add_argument('-d', '--debug', action='store_true',
114 help='Enable debug logging')116 help='Enable debug logging')
115 parser.add_argument('-j', '--json', action='store_true',117 parser.add_argument('-j', '--json', action='store_true',
116118
=== modified file 'utah/provisioning/provisioning.py'
--- utah/provisioning/provisioning.py 2017-03-03 14:35:49 +0000
+++ utah/provisioning/provisioning.py 2017-07-06 18:08:08 +0000
@@ -24,6 +24,7 @@
24import pipes24import pipes
25import re25import re
26import shutil26import shutil
27import stat
27import sys28import sys
28import urllib29import urllib
29import uuid30import uuid
@@ -63,8 +64,8 @@
6364
64 """65 """
6566
66 def __init__(self, boot=config.boot, clean=True, debug=False,67 def __init__(self, boot=config.boot, clean=True, poweroff=False,
67 image=None, initrd=config.initrd, inventory=None,68 debug=False, image=None, initrd=config.initrd, inventory=None,
68 kernel=config.kernel, machineid=config.machineid,69 kernel=config.kernel, machineid=config.machineid,
69 machineuuid=config.machineuuid, name=config.name, new=False,70 machineuuid=config.machineuuid, name=config.name, new=False,
70 preseed=config.preseed, rewrite=config.rewrite,71 preseed=config.preseed, rewrite=config.rewrite,
@@ -110,6 +111,7 @@
110 # TODO: Consider a global temp file creator, maybe as part of install.111 # TODO: Consider a global temp file creator, maybe as part of install.
111 self.boot = boot112 self.boot = boot
112 self.clean = clean113 self.clean = clean
114 self.poweroff = poweroff
113 self.debug = debug115 self.debug = debug
114 self.image = image116 self.image = image
115 self.inventory = inventory117 self.inventory = inventory
@@ -522,11 +524,13 @@
522 if self.clean:524 if self.clean:
523 cleanup.add_path(path)525 cleanup.add_path(path)
524526
525 def cleanfunction(self, function, *args, **kw):527 def cleanfunction(self, function, force=False, *args, **kw):
526 """Register a function to be run on cleanup.528 """Register a function to be run on cleanup.
527529
528 :param function: A callable that will do some cleanup.530 :param function: A callable that will do some cleanup.
529 :type function: callable531 :type function: callable
532 :param force: whether to force adding the cleanup function.
533 :type force: boolean
530 :param args: Positional arguments to the function.534 :param args: Positional arguments to the function.
531 :type args: Tuple535 :type args: Tuple
532 :param kw: Keyword arguments to the function.536 :param kw: Keyword arguments to the function.
@@ -536,7 +540,7 @@
536540
537 """541 """
538 # TODO: consider doing this directly instead of through Machine542 # TODO: consider doing this directly instead of through Machine
539 if self.clean:543 if self.clean or force:
540 cleanup.add_function(60, function, *args, **kw)544 cleanup.add_function(60, function, *args, **kw)
541545
542 def cleancommand(self, cmd):546 def cleancommand(self, cmd):
@@ -703,6 +707,13 @@
703 filename,707 filename,
704 log_file='/var/log/utah-install')708 log_file='/var/log/utah-install')
705709
710 def _copy_qemu_scripts(self, tmpdir=None):
711 initrd_work_dir = os.path.join(tmpdir, 'initrd')
712 if not os.path.exists(initrd_work_dir):
713 os.makedirs(initrd_work_dir)
714 # copy the qemu scripts into initrd
715 self.copy_qemu_scripts_to_path(initrd_work_dir)
716
706 def _setuppreseed(self, tmpdir=None):717 def _setuppreseed(self, tmpdir=None):
707 """Rewrite the preseed to automate installation and access."""718 """Rewrite the preseed to automate installation and access."""
708 # TODO: document this better719 # TODO: document this better
@@ -978,3 +989,21 @@
978 """989 """
979 iface = netifaces.ifaddresses(ifname)990 iface = netifaces.ifaddresses(ifname)
980 return iface[netifaces.AF_INET][0]['addr']991 return iface[netifaces.AF_INET][0]['addr']
992
993 def get_qemu_scripts_dir(self):
994 """Return path of the source directory containing qemu setup scripts."""
995 src_dir = os.path.dirname(os.path.realpath(__file__))
996 return os.path.join(src_dir, 'qemu-scripts')
997
998 def copy_qemu_scripts_to_path(self, path):
999 """Copy all qemu scripts to required path and ensure correct permissions.
1000 :param path: Target path to copy scripts to.
1001 """
1002 src_dir = self.get_qemu_scripts_dir()
1003 mask = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH |
1004 stat.S_IXOTH)
1005 for src_name in os.listdir(src_dir):
1006 src = os.path.join(src_dir, src_name)
1007 dst = os.path.join(path, src_name)
1008 shutil.copy2(src, dst)
1009 os.chmod(dst, mask)
9811010
=== added directory 'utah/provisioning/qemu-scripts'
=== added file 'utah/provisioning/qemu-scripts/qemu-setup.py'
--- utah/provisioning/qemu-scripts/qemu-setup.py 1970-01-01 00:00:00 +0000
+++ utah/provisioning/qemu-scripts/qemu-setup.py 2017-07-06 18:08:08 +0000
@@ -0,0 +1,211 @@
1# Ubuntu Testing Automation Harness
2# Copyright 2017 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU General Public License version 3, as published
6# by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU General Public License for more details.
12
13# You should have received a copy of the GNU General Public License along
14# with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import os
17import subprocess
18import time
19
20AUTOPKGTEST_NAME = 'autopkgtest.service'
21AUTOPKGTEST_SERVICE = os.path.join('/lib/systemd/system', AUTOPKGTEST_NAME)
22AUTOPKGTEST_CFG = '/etc/default/grub.d/90-autopkgtest.cfg'
23
24""" This script is run on first boot of the qemu after installation, by the
25 qemu-setup.service. It will prepare the system by:
26 - Creating a serial console on ttyS1 that will be used in future by
27 autopkgtest. The service will be started automatically on all subsequent
28 boots by autopkgtest.service.
29 - Disable qemu-setup.service to stop this script from running on subsequent
30 boots.
31 - Power down the system to complete the setup process.
32"""
33
34
35def wait_for_network():
36 """Wait until network connection is active."""
37 max_count = 60
38 for count in range(max_count):
39 status = subprocess.call(['wget', '-q', '--spider', 'launchpad.net'])
40 if status == 0:
41 break
42 elif count >= max_count - 1:
43 raise Exception('No network connection available')
44 else:
45 print('wget attempt {} failed, trying again...'.format(count + 1))
46 time.sleep(1)
47
48
49def wait_for_locks():
50 """Wait for file locks to be released for doing install operations."""
51 max_count = 60
52 lock_files = [
53 '/var/lib/dpkg/lock',
54 '/var/cache/apt/archives/lock',
55 '/var/lib/apt/lists/lock',
56 ]
57 for count in range(max_count):
58 status = subprocess.call(['fuser'] + lock_files)
59 if status == 1:
60 break
61 elif count >= max_count - 1:
62 raise RuntimeError('File locks not released')
63 else:
64 print('file locks active, trying again...'.format(count + 1))
65 time.sleep(1)
66
67
68def get_architecture():
69 """Return architecture of running system."""
70 return subprocess.check_output(
71 ['dpkg', '--print-architecture']).decode().strip()
72
73
74def install_packages(packages):
75 """Install specfied list of packages."""
76 wait_for_locks()
77 subprocess.check_call(['dpkg', '--configure', '-a'])
78 subprocess.check_call(['apt-get', 'install', '-y'] + packages)
79
80
81def create_file(path, content):
82 """Create a file with specified path and content."""
83 with open(path, 'w') as f:
84 f.write(content)
85
86
87def setup_root_shell_service():
88 """Setup root shell service on ttyS1 for use by autopkgtest."""
89 create_file(
90 AUTOPKGTEST_SERVICE,
91 '[Unit]\n'
92 'Description=autopkgtest root shell on ttyS1\n'
93 'ConditionPathExists=/dev/ttyS1\n\n'
94 '[Service]\n'
95 'ExecStart=/bin/sh\n'
96 'StandardInput=tty-fail\n'
97 'StandardOutput=tty\n'
98 'StandardError=tty\n'
99 'TTYPath=/dev/ttyS1\n'
100 'SendSIGHUP=yes\n'
101 'SuccessExitStatus=0 208 SIGHUP SIGINT SIGTERM SIGPIPE\n\n'
102 '[Install]\n'
103 'WantedBy=multi-user.target\n'
104 )
105 os.chmod(AUTOPKGTEST_SERVICE, 0o644)
106 subprocess.check_call(['systemctl', 'enable', AUTOPKGTEST_NAME])
107
108
109def _grubd_autopkgtest_setup(architecture):
110 """Add autopkgtest config to grub.d directory.
111 :param architecture: Required architecture.
112 :return: True if changes made, False otherwise.
113 """
114 if architecture == 'i386':
115 create_file(
116 AUTOPKGTEST_CFG,
117 'GRUB_CMDLINE_LINUX_DEFAULT="console=ttyS0 vmalloc=512M"'
118 )
119 return True
120 elif architecture == 'amd64':
121 create_file(
122 AUTOPKGTEST_CFG,
123 'GRUB_CMDLINE_LINUX_DEFAULT="console=ttyS0"'
124 )
125 return True
126 return False
127
128
129def _grub_autopkgtest_setup(architecture):
130 """Add autopkgtest config to grub directory.
131 :param architecture: Required architecture.
132 :return: True if changes made, False otherwise.
133 """
134 changed = False
135 if architecture == 'i386':
136 subprocess.check_call(
137 ['sed', '-i',
138 '/CMDLINE_LINUX_DEFAULT/ s/"$/ console=ttyS0 '
139 'vmalloc=512M"/', '/etc/default/grub']
140 )
141 changed = True
142 elif architecture == 'amd64':
143 subprocess.check_call(
144 ['sed', '-i',
145 '/CMDLINE_LINUX_DEFAULT/ s/"$/ console=ttyS0"/',
146 '/etc/default/grub']
147 )
148 changed = True
149
150 zero_timeout = subprocess.check_output(
151 ['grep', '-q', 'GRUB_HIDDEN_TIMEOUT=0', '/etc/default/grub']
152 ) == 0
153
154 if zero_timeout:
155 subprocess.check_output(
156 ['sed', '-i', '/^GRUB_TIMEOUT=/ s/=.*$/=1/',
157 '/etc/default/grub']
158 )
159 changed = True
160
161 return changed
162
163
164def setup_serial_console():
165 """Setup a serial console for autopkgtest to use."""
166 # This method is derived from:
167 # /usr/share/autopkgtest/setup-commands/setup-testbed
168 setup_root_shell_service()
169 grub = subprocess.call(['which', 'update-grub']) == 0
170 if not os.path.isfile(AUTOPKGTEST_CFG) and grub:
171 arch = get_architecture()
172 if os.path.isdir('/etc/default/grub.d'):
173 changed = _grubd_autopkgtest_setup(arch)
174 else:
175 changed = _grub_autopkgtest_setup(arch)
176 if changed:
177 subprocess.check_call(['update-grub'])
178
179
180def disable_qemu_setup_on_boot():
181 """Disable this setup script from running again on future boot up."""
182 subprocess.call(['systemctl', 'disable', 'qemu-setup.service'])
183
184
185def system_shutdown():
186 """Shutdown the system to complete first boot setup."""
187 subprocess.call(['shutdown', '--poweroff', '0'])
188
189
190def do_setup():
191 """Complete first boot setup actions."""
192 wait_for_network()
193 setup_serial_console()
194 install_packages(['openssh-server'])
195
196
197def do_teardown():
198 """Cleanup actions to disable this from running after first boot."""
199 disable_qemu_setup_on_boot()
200 system_shutdown()
201
202
203def main():
204 try:
205 do_setup()
206 finally:
207 do_teardown()
208
209
210if __name__ == '__main__':
211 main()
0212
=== added file 'utah/provisioning/qemu-scripts/qemu-setup.service'
--- utah/provisioning/qemu-scripts/qemu-setup.service 1970-01-01 00:00:00 +0000
+++ utah/provisioning/qemu-scripts/qemu-setup.service 2017-07-06 18:08:08 +0000
@@ -0,0 +1,9 @@
1[Unit]
2Description=Ubuntu System Tests QEMU setup autostart script
3
4[Service]
5Type=oneshot
6ExecStart=/bin/sh -c "/usr/bin/python3 /usr/local/bin/qemu-setup.py > /dev/ttyS0 2>&1"
7
8[Install]
9WantedBy=multi-user.target
010
=== modified file 'utah/provisioning/vm.py'
--- utah/provisioning/vm.py 2016-12-23 15:01:36 +0000
+++ utah/provisioning/vm.py 2017-07-06 18:08:08 +0000
@@ -27,6 +27,7 @@
2727
28from xml.etree import ElementTree28from xml.etree import ElementTree
2929
30from utah.cleanup import cleanup
30from utah.config import config31from utah.config import config
31from utah.process import ProcessChecker, ProcessRunner32from utah.process import ProcessChecker, ProcessRunner
32from utah.provisioning.exceptions import UTAHProvisioningException33from utah.provisioning.exceptions import UTAHProvisioningException
@@ -518,6 +519,8 @@
518 initrd_dir = os.path.join(tmpdir, 'initrd.d')519 initrd_dir = os.path.join(tmpdir, 'initrd.d')
519 provision_data.update_initrd(initrd_dir)520 provision_data.update_initrd(initrd_dir)
520521
522 self._copy_qemu_scripts(tmpdir=tmpdir)
523
521 self._setuplatecommand(tmpdir=tmpdir)524 self._setuplatecommand(tmpdir=tmpdir)
522525
523 self._setuppreseed(tmpdir=tmpdir)526 self._setuppreseed(tmpdir=tmpdir)
@@ -546,6 +549,8 @@
546 self.vm = self.lv.defineXML(ElementTree.tostring(xml.getroot()))549 self.vm = self.lv.defineXML(ElementTree.tostring(xml.getroot()))
547 self.cleanfunction(self.vm.destroy)550 self.cleanfunction(self.vm.destroy)
548 self.cleanfunction(self.vm.undefine)551 self.cleanfunction(self.vm.undefine)
552 if self.poweroff:
553 self.cleanfunction(self.vm.shutdown, force=True)
549554
550 def _start(self):555 def _start(self):
551 """Start the VM."""556 """Start the VM."""
@@ -620,6 +625,12 @@
620 "UPDATE machines SET state='destroyed' ""WHERE machineid=?",625 "UPDATE machines SET state='destroyed' ""WHERE machineid=?",
621 [machineid])626 [machineid])
622627
628 def shut_off(self, machineid):
629 """Update the database to indicate a machine is shut off."""
630 self.execute(
631 "UPDATE machines SET state='shut off' ""WHERE machineid=?",
632 [machineid])
633
623634
624def get_vm(**kw):635def get_vm(**kw):
625 """Return a Machine object for a VM with the passed in arguments.636 """Return a Machine object for a VM with the passed in arguments.

Subscribers

People subscribed via source and target branches