blob: a630fbc515221d5ae8c7a0b807f893b6fabd62e4 [file] [log] [blame]
lmr5d73e2f2009-10-09 20:46:36 +00001#!/usr/bin/python
2"""
3Simple script to setup unattended installs on KVM guests.
4"""
5# -*- coding: utf-8 -*-
Benson Leung517d95a2010-09-28 18:00:17 -07006import os, sys, shutil, tempfile, re
lmr5d73e2f2009-10-09 20:46:36 +00007import common
8
9
10class SetupError(Exception):
11 """
12 Simple wrapper for the builtin Exception class.
13 """
14 pass
15
16
17class UnattendedInstall(object):
18 """
19 Creates a floppy disk image that will contain a config file for unattended
20 OS install. Optionally, sets up a PXE install server using qemu built in
21 TFTP and DHCP servers to install a particular operating system. The
22 parameters to the script are retrieved from environment variables.
23 """
24 def __init__(self):
25 """
26 Gets params from environment variables and sets class attributes.
27 """
Benson Leung517d95a2010-09-28 18:00:17 -070028 script_dir = os.path.dirname(sys.modules[__name__].__file__)
29 kvm_test_dir = os.path.abspath(os.path.join(script_dir, ".."))
30 images_dir = os.path.join(kvm_test_dir, 'images')
31 self.deps_dir = os.path.join(kvm_test_dir, 'deps')
32 self.unattended_dir = os.path.join(kvm_test_dir, 'unattended')
lmr5d73e2f2009-10-09 20:46:36 +000033
Benson Leung517d95a2010-09-28 18:00:17 -070034 tftp_root = os.environ.get('KVM_TEST_tftp', '')
35 if tftp_root:
36 self.tftp_root = os.path.join(kvm_test_dir, tftp_root)
37 if not os.path.isdir(self.tftp_root):
38 os.makedirs(self.tftp_root)
39 else:
40 self.tftp_root = tftp_root
lmr5d73e2f2009-10-09 20:46:36 +000041
Benson Leung517d95a2010-09-28 18:00:17 -070042 self.kernel_args = os.environ.get('KVM_TEST_kernel_args', '')
43 self.finish_program= os.environ.get('KVM_TEST_finish_program', '')
44 cdrom_iso = os.environ.get('KVM_TEST_cdrom_cd1')
45 self.unattended_file = os.environ.get('KVM_TEST_unattended_file')
lmr5d73e2f2009-10-09 20:46:36 +000046
Benson Leung517d95a2010-09-28 18:00:17 -070047 self.qemu_img_bin = os.environ.get('KVM_TEST_qemu_img_binary')
48 if not os.path.isabs(self.qemu_img_bin):
49 self.qemu_img_bin = os.path.join(kvm_test_dir, self.qemu_img_bin)
50 self.cdrom_iso = os.path.join(kvm_test_dir, cdrom_iso)
51 self.floppy_mount = tempfile.mkdtemp(prefix='floppy_', dir='/tmp')
52 self.cdrom_mount = tempfile.mkdtemp(prefix='cdrom_', dir='/tmp')
53 self.nfs_mount = tempfile.mkdtemp(prefix='nfs_', dir='/tmp')
54 floppy_name = os.environ['KVM_TEST_floppy']
55 self.floppy_img = os.path.join(kvm_test_dir, floppy_name)
56 floppy_dir = os.path.dirname(self.floppy_img)
57 if not os.path.isdir(floppy_dir):
58 os.makedirs(floppy_dir)
lmra29a5cb2010-03-18 02:39:34 +000059
Benson Leung517d95a2010-09-28 18:00:17 -070060 self.pxe_dir = os.environ.get('KVM_TEST_pxe_dir', '')
61 self.pxe_image = os.environ.get('KVM_TEST_pxe_image', '')
62 self.pxe_initrd = os.environ.get('KVM_TEST_pxe_initrd', '')
lmr5d73e2f2009-10-09 20:46:36 +000063
Benson Leung517d95a2010-09-28 18:00:17 -070064 self.medium = os.environ.get('KVM_TEST_medium', '')
65 self.url = os.environ.get('KVM_TEST_url', '')
66 self.kernel = os.environ.get('KVM_TEST_kernel', '')
67 self.initrd = os.environ.get('KVM_TEST_initrd', '')
68 self.nfs_server = os.environ.get('KVM_TEST_nfs_server', '')
69 self.nfs_dir = os.environ.get('KVM_TEST_nfs_dir', '')
70 self.image_path = kvm_test_dir
lmree1e40f2010-06-10 15:32:27 +000071 self.kernel_path = os.path.join(self.image_path, self.kernel)
72 self.initrd_path = os.path.join(self.image_path, self.initrd)
73
lmr5d73e2f2009-10-09 20:46:36 +000074
Benson Leung517d95a2010-09-28 18:00:17 -070075 def create_boot_floppy(self):
lmr5d73e2f2009-10-09 20:46:36 +000076 """
Benson Leung517d95a2010-09-28 18:00:17 -070077 Prepares a boot floppy by creating a floppy image file, mounting it and
78 copying an answer file (kickstarts for RH based distros, answer files
79 for windows) to it. After that the image is umounted.
lmr5d73e2f2009-10-09 20:46:36 +000080 """
Benson Leung517d95a2010-09-28 18:00:17 -070081 print "Creating boot floppy"
lmr5d73e2f2009-10-09 20:46:36 +000082
Benson Leung517d95a2010-09-28 18:00:17 -070083 if os.path.exists(self.floppy_img):
84 os.remove(self.floppy_img)
lmr5d73e2f2009-10-09 20:46:36 +000085
Benson Leung517d95a2010-09-28 18:00:17 -070086 c_cmd = '%s create -f raw %s 1440k' % (self.qemu_img_bin,
87 self.floppy_img)
88 if os.system(c_cmd):
89 raise SetupError('Could not create floppy image.')
lmr0ac82102010-06-18 13:40:32 +000090
Benson Leung517d95a2010-09-28 18:00:17 -070091 f_cmd = 'mkfs.msdos -s 1 %s' % self.floppy_img
92 if os.system(f_cmd):
93 raise SetupError('Error formatting floppy image.')
lmrb010c7e2010-02-24 11:54:30 +000094
Benson Leung517d95a2010-09-28 18:00:17 -070095 try:
96 m_cmd = 'mount -o loop %s %s' % (self.floppy_img, self.floppy_mount)
97 if os.system(m_cmd):
98 raise SetupError('Could not mount floppy image.')
lmrb010c7e2010-02-24 11:54:30 +000099
Benson Leung517d95a2010-09-28 18:00:17 -0700100 if self.unattended_file.endswith('.sif'):
101 dest_fname = 'winnt.sif'
102 setup_file = 'winnt.bat'
103 setup_file_path = os.path.join(self.unattended_dir, setup_file)
104 setup_file_dest = os.path.join(self.floppy_mount, setup_file)
105 shutil.copyfile(setup_file_path, setup_file_dest)
106 elif self.unattended_file.endswith('.ks'):
107 # Red Hat kickstart install
108 dest_fname = 'ks.cfg'
109 elif self.unattended_file.endswith('.xml'):
110 if self.tftp_root is '':
111 # Windows unattended install
112 dest_fname = "autounattend.xml"
Eric Li25fc6d12010-09-28 17:22:51 -0700113 else:
Benson Leung517d95a2010-09-28 18:00:17 -0700114 # SUSE autoyast install
115 dest_fname = "autoinst.xml"
Eric Li25fc6d12010-09-28 17:22:51 -0700116
Benson Leung517d95a2010-09-28 18:00:17 -0700117 dest = os.path.join(self.floppy_mount, dest_fname)
Eric Li25fc6d12010-09-28 17:22:51 -0700118
Benson Leung517d95a2010-09-28 18:00:17 -0700119 # Replace KVM_TEST_CDKEY (in the unattended file) with the cdkey
120 # provided for this test and replace the KVM_TEST_MEDIUM with
121 # the tree url or nfs address provided for this test.
122 unattended_contents = open(self.unattended_file).read()
123 dummy_cdkey_re = r'\bKVM_TEST_CDKEY\b'
124 real_cdkey = os.environ.get('KVM_TEST_cdkey')
125 if re.search(dummy_cdkey_re, unattended_contents):
126 if real_cdkey:
127 unattended_contents = re.sub(dummy_cdkey_re, real_cdkey,
128 unattended_contents)
Eric Li25fc6d12010-09-28 17:22:51 -0700129 else:
Benson Leung517d95a2010-09-28 18:00:17 -0700130 print ("WARNING: 'cdkey' required but not specified for "
131 "this unattended installation")
Eric Li25fc6d12010-09-28 17:22:51 -0700132
Benson Leung517d95a2010-09-28 18:00:17 -0700133 dummy_re = r'\bKVM_TEST_MEDIUM\b'
134 if self.medium == "cdrom":
135 content = "cdrom"
136 elif self.medium == "url":
137 content = "url --url %s" % self.url
138 elif self.medium == "nfs":
139 content = "nfs --server=%s --dir=%s" % (self.nfs_server, self.nfs_dir)
Eric Li25fc6d12010-09-28 17:22:51 -0700140 else:
Benson Leung517d95a2010-09-28 18:00:17 -0700141 raise SetupError("Unexpected installation medium %s" % self.url)
Eric Li25fc6d12010-09-28 17:22:51 -0700142
Benson Leung517d95a2010-09-28 18:00:17 -0700143 unattended_contents = re.sub(dummy_re, content, unattended_contents)
Eric Li25fc6d12010-09-28 17:22:51 -0700144
Benson Leung517d95a2010-09-28 18:00:17 -0700145 print
146 print "Unattended install %s contents:" % dest_fname
147 print unattended_contents
148 # Write the unattended file contents to 'dest'
149 open(dest, 'w').write(unattended_contents)
150
151 if self.finish_program:
152 dest_fname = os.path.basename(self.finish_program)
153 dest = os.path.join(self.floppy_mount, dest_fname)
154 shutil.copyfile(self.finish_program, dest)
155
156 finally:
157 u_cmd = 'umount %s' % self.floppy_mount
158 if os.system(u_cmd):
159 raise SetupError('Could not unmount floppy at %s.' %
160 self.floppy_mount)
161 self.cleanup(self.floppy_mount)
162
163 os.chmod(self.floppy_img, 0755)
164
165 print "Boot floppy created successfuly"
lmr5d73e2f2009-10-09 20:46:36 +0000166
167
168 def setup_pxe_boot(self):
169 """
170 Sets up a PXE boot environment using the built in qemu TFTP server.
171 Copies the PXE Linux bootloader pxelinux.0 from the host (needs the
172 pxelinux package or equivalent for your distro), and vmlinuz and
173 initrd.img files from the CD to a directory that qemu will serve trough
174 TFTP to the VM.
175 """
Benson Leung517d95a2010-09-28 18:00:17 -0700176 print "Setting up PXE boot using TFTP root %s" % self.tftp_root
lmr5d73e2f2009-10-09 20:46:36 +0000177
178 pxe_file = None
179 pxe_paths = ['/usr/lib/syslinux/pxelinux.0',
180 '/usr/share/syslinux/pxelinux.0']
181 for path in pxe_paths:
182 if os.path.isfile(path):
183 pxe_file = path
184 break
185
186 if not pxe_file:
187 raise SetupError('Cannot find PXE boot loader pxelinux.0. Make '
188 'sure pxelinux or equivalent package for your '
189 'distro is installed.')
190
Benson Leung517d95a2010-09-28 18:00:17 -0700191 pxe_dest = os.path.join(self.tftp_root, 'pxelinux.0')
lmr5d73e2f2009-10-09 20:46:36 +0000192 shutil.copyfile(pxe_file, pxe_dest)
193
lmrb010c7e2010-02-24 11:54:30 +0000194 try:
Benson Leung517d95a2010-09-28 18:00:17 -0700195 m_cmd = 'mount -t iso9660 -v -o loop,ro %s %s' % (self.cdrom_iso,
196 self.cdrom_mount)
197 if os.system(m_cmd):
198 raise SetupError('Could not mount CD image %s.' %
199 self.cdrom_iso)
lmr5d73e2f2009-10-09 20:46:36 +0000200
Benson Leung517d95a2010-09-28 18:00:17 -0700201 pxe_dir = os.path.join(self.cdrom_mount, self.pxe_dir)
lmra29a5cb2010-03-18 02:39:34 +0000202 pxe_image = os.path.join(pxe_dir, self.pxe_image)
203 pxe_initrd = os.path.join(pxe_dir, self.pxe_initrd)
lmr5d73e2f2009-10-09 20:46:36 +0000204
lmrb010c7e2010-02-24 11:54:30 +0000205 if not os.path.isdir(pxe_dir):
206 raise SetupError('The ISO image does not have a %s dir. The '
207 'script assumes that the cd has a %s dir '
208 'where to search for the vmlinuz image.' %
lmra29a5cb2010-03-18 02:39:34 +0000209 (self.pxe_dir, self.pxe_dir))
lmr5d73e2f2009-10-09 20:46:36 +0000210
lmrb010c7e2010-02-24 11:54:30 +0000211 if not os.path.isfile(pxe_image) or not os.path.isfile(pxe_initrd):
212 raise SetupError('The location %s is lacking either a vmlinuz '
213 'or a initrd.img file. Cannot find a PXE '
lmra29a5cb2010-03-18 02:39:34 +0000214 'image to proceed.' % self.pxe_dir)
lmr5d73e2f2009-10-09 20:46:36 +0000215
Benson Leung517d95a2010-09-28 18:00:17 -0700216 tftp_image = os.path.join(self.tftp_root, 'vmlinuz')
217 tftp_initrd = os.path.join(self.tftp_root, 'initrd.img')
lmrb010c7e2010-02-24 11:54:30 +0000218 shutil.copyfile(pxe_image, tftp_image)
219 shutil.copyfile(pxe_initrd, tftp_initrd)
lmr5d73e2f2009-10-09 20:46:36 +0000220
lmrb010c7e2010-02-24 11:54:30 +0000221 finally:
Benson Leung517d95a2010-09-28 18:00:17 -0700222 u_cmd = 'umount %s' % self.cdrom_mount
223 if os.system(u_cmd):
224 raise SetupError('Could not unmount CD at %s.' %
225 self.cdrom_mount)
226 self.cleanup(self.cdrom_mount)
lmr5d73e2f2009-10-09 20:46:36 +0000227
Benson Leung517d95a2010-09-28 18:00:17 -0700228 pxe_config_dir = os.path.join(self.tftp_root, 'pxelinux.cfg')
lmr5d73e2f2009-10-09 20:46:36 +0000229 if not os.path.isdir(pxe_config_dir):
230 os.makedirs(pxe_config_dir)
231 pxe_config_path = os.path.join(pxe_config_dir, 'default')
232
233 pxe_config = open(pxe_config_path, 'w')
234 pxe_config.write('DEFAULT pxeboot\n')
235 pxe_config.write('TIMEOUT 20\n')
236 pxe_config.write('PROMPT 0\n')
237 pxe_config.write('LABEL pxeboot\n')
238 pxe_config.write(' KERNEL vmlinuz\n')
lmr5d73e2f2009-10-09 20:46:36 +0000239 pxe_config.write(' APPEND initrd=initrd.img %s\n' %
240 self.kernel_args)
241 pxe_config.close()
242
243 print "PXE boot successfuly set"
244
lmrb010c7e2010-02-24 11:54:30 +0000245
lmree1e40f2010-06-10 15:32:27 +0000246 def setup_url(self):
247 """
Benson Leung517d95a2010-09-28 18:00:17 -0700248 Download the vmlinuz and initrd.img from URL
lmree1e40f2010-06-10 15:32:27 +0000249 """
250 print "Downloading the vmlinuz and initrd.img"
251 os.chdir(self.image_path)
252
253 kernel_fetch_cmd = "wget -q %s/isolinux/%s" % (self.url, self.kernel)
254 initrd_fetch_cmd = "wget -q %s/isolinux/%s" % (self.url, self.initrd)
255
256 if os.path.exists(self.kernel):
257 os.unlink(self.kernel)
258 if os.path.exists(self.initrd):
259 os.unlink(self.initrd)
260
Benson Leung517d95a2010-09-28 18:00:17 -0700261 if os.system(kernel_fetch_cmd) != 0:
262 raise SetupError("Could not fetch vmlinuz from %s" % self.url)
263 if os.system(initrd_fetch_cmd) != 0:
264 raise SetupError("Could not fetch initrd.img from %s" % self.url)
lmree1e40f2010-06-10 15:32:27 +0000265
Benson Leung517d95a2010-09-28 18:00:17 -0700266 print "Downloading finish"
lmree1e40f2010-06-10 15:32:27 +0000267
268 def setup_nfs(self):
269 """
270 Copy the vmlinuz and initrd.img from nfs.
271 """
272 print "Copying the vmlinuz and initrd.img from nfs"
273
Benson Leung517d95a2010-09-28 18:00:17 -0700274 m_cmd = "mount %s:%s %s -o ro" % (self.nfs_server, self.nfs_dir, self.nfs_mount)
275 if os.system(m_cmd):
276 raise SetupError('Could not mount nfs server.')
277
278 kernel_fetch_cmd = "cp %s/isolinux/%s %s" % (self.nfs_mount,
279 self.kernel,
280 self.image_path)
281 initrd_fetch_cmd = "cp %s/isolinux/%s %s" % (self.nfs_mount,
282 self.initrd,
283 self.image_path)
lmree1e40f2010-06-10 15:32:27 +0000284
285 try:
Benson Leung517d95a2010-09-28 18:00:17 -0700286 if os.system(kernel_fetch_cmd):
287 raise SetupError("Could not copy the vmlinuz from %s" %
288 self.nfs_mount)
289 if os.system(initrd_fetch_cmd):
290 raise SetupError("Could not copy the initrd.img from %s" %
291 self.nfs_mount)
lmree1e40f2010-06-10 15:32:27 +0000292 finally:
Benson Leung517d95a2010-09-28 18:00:17 -0700293 u_cmd = "umount %s" % self.nfs_mount
294 if os.system(u_cmd):
295 raise SetupError("Could not unmont nfs at %s" % self.nfs_mount)
296 self.cleanup(self.nfs_mount)
297
298 def cleanup(self, mount):
299 """
300 Clean up a previously used mountpoint.
301
302 @param mount: Mountpoint to be cleaned up.
303 """
304 if os.path.isdir(mount):
305 if os.path.ismount(mount):
306 print "Path %s is still mounted, please verify" % mount
307 else:
308 print "Removing mount point %s" % mount
309 os.rmdir(mount)
lmr5d73e2f2009-10-09 20:46:36 +0000310
311
312 def setup(self):
313 print "Starting unattended install setup"
314
315 print "Variables set:"
Benson Leung517d95a2010-09-28 18:00:17 -0700316 print " medium: " + str(self.medium)
317 print " qemu_img_bin: " + str(self.qemu_img_bin)
318 print " cdrom iso: " + str(self.cdrom_iso)
319 print " unattended_file: " + str(self.unattended_file)
320 print " kernel_args: " + str(self.kernel_args)
321 print " tftp_root: " + str(self.tftp_root)
322 print " floppy_mount: " + str(self.floppy_mount)
323 print " floppy_img: " + str(self.floppy_img)
324 print " finish_program: " + str(self.finish_program)
325 print " pxe_dir: " + str(self.pxe_dir)
326 print " pxe_image: " + str(self.pxe_image)
327 print " pxe_initrd: " + str(self.pxe_initrd)
328 print " url: " + str(self.url)
329 print " kernel: " + str(self.kernel)
330 print " initrd: " + str(self.initrd)
331 print " nfs_server: " + str(self.nfs_server)
332 print " nfs_dir: " + str(self.nfs_dir)
333 print " nfs_mount: " + str(self.nfs_mount)
lmr5d73e2f2009-10-09 20:46:36 +0000334
Benson Leung517d95a2010-09-28 18:00:17 -0700335 if self.unattended_file and self.floppy_img is not None:
336 self.create_boot_floppy()
lmree1e40f2010-06-10 15:32:27 +0000337 if self.medium == "cdrom":
Benson Leung517d95a2010-09-28 18:00:17 -0700338 if self.tftp_root:
lmree1e40f2010-06-10 15:32:27 +0000339 self.setup_pxe_boot()
340 elif self.medium == "url":
341 self.setup_url()
342 elif self.medium == "nfs":
343 self.setup_nfs()
344 else:
345 raise SetupError("Unexpected installation method %s" %
Benson Leung517d95a2010-09-28 18:00:17 -0700346 self.medium)
lmr5d73e2f2009-10-09 20:46:36 +0000347 print "Unattended install setup finished successfuly"
348
349
350if __name__ == "__main__":
351 os_install = UnattendedInstall()
352 os_install.setup()