OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 """ | 2 """ |
3 Simple script to setup unattended installs on KVM guests. | 3 Simple script to setup unattended installs on KVM guests. |
4 """ | 4 """ |
5 # -*- coding: utf-8 -*- | 5 # -*- coding: utf-8 -*- |
6 import os, sys, shutil, tempfile, re | 6 import os, sys, shutil, tempfile, re, ConfigParser, glob, inspect |
7 import common | 7 import common |
8 | 8 |
9 | 9 |
| 10 SCRIPT_DIR = os.path.dirname(sys.modules[__name__].__file__) |
| 11 KVM_TEST_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "..")) |
| 12 |
| 13 |
10 class SetupError(Exception): | 14 class SetupError(Exception): |
11 """ | 15 """ |
12 Simple wrapper for the builtin Exception class. | 16 Simple wrapper for the builtin Exception class. |
13 """ | 17 """ |
14 pass | 18 pass |
15 | 19 |
16 | 20 |
| 21 def find_command(cmd): |
| 22 """ |
| 23 Searches for a command on common paths, error if it can't find it. |
| 24 |
| 25 @param cmd: Command to be found. |
| 26 """ |
| 27 for dir in ["/usr/local/sbin", "/usr/local/bin", |
| 28 "/usr/sbin", "/usr/bin", "/sbin", "/bin"]: |
| 29 file = os.path.join(dir, cmd) |
| 30 if os.path.exists(file): |
| 31 return file |
| 32 raise ValueError('Missing command: %s' % cmd) |
| 33 |
| 34 |
| 35 def run(cmd, info=None): |
| 36 """ |
| 37 Run a command and throw an exception if it fails. |
| 38 Optionally, you can provide additional contextual info. |
| 39 |
| 40 @param cmd: Command string. |
| 41 @param reason: Optional string that explains the context of the failure. |
| 42 |
| 43 @raise: SetupError if command fails. |
| 44 """ |
| 45 print "Running '%s'" % cmd |
| 46 cmd_name = cmd.split(' ')[0] |
| 47 find_command(cmd_name) |
| 48 if os.system(cmd): |
| 49 e_msg = 'Command failed: %s' % cmd |
| 50 if info is not None: |
| 51 e_msg += '. %s' % info |
| 52 raise SetupError(e_msg) |
| 53 |
| 54 |
| 55 def cleanup(dir): |
| 56 """ |
| 57 If dir is a mountpoint, do what is possible to unmount it. Afterwards, |
| 58 try to remove it. |
| 59 |
| 60 @param dir: Directory to be cleaned up. |
| 61 """ |
| 62 print "Cleaning up directory %s" % dir |
| 63 if os.path.ismount(dir): |
| 64 os.system('fuser -k %s' % dir) |
| 65 run('umount %s' % dir, info='Could not unmount %s' % dir) |
| 66 if os.path.isdir(dir): |
| 67 shutil.rmtree(dir) |
| 68 |
| 69 |
| 70 def clean_old_image(image): |
| 71 """ |
| 72 Clean a leftover image file from previous processes. If it contains a |
| 73 mounted file system, do the proper cleanup procedures. |
| 74 |
| 75 @param image: Path to image to be cleaned up. |
| 76 """ |
| 77 if os.path.exists(image): |
| 78 mtab = open('/etc/mtab', 'r') |
| 79 mtab_contents = mtab.read() |
| 80 mtab.close() |
| 81 if image in mtab_contents: |
| 82 os.system('fuser -k %s' % image) |
| 83 os.system('umount %s' % image) |
| 84 os.remove(image) |
| 85 |
| 86 |
| 87 class Disk(object): |
| 88 """ |
| 89 Abstract class for Disk objects, with the common methods implemented. |
| 90 """ |
| 91 def __init__(self): |
| 92 self.path = None |
| 93 |
| 94 |
| 95 def setup_answer_file(self, filename, contents): |
| 96 answer_file = open(os.path.join(self.mount, filename), 'w') |
| 97 answer_file.write(contents) |
| 98 answer_file.close() |
| 99 |
| 100 |
| 101 def copy_to(self, src): |
| 102 dst = os.path.join(self.mount, os.path.basename(src)) |
| 103 if os.path.isdir(src): |
| 104 shutil.copytree(src, dst) |
| 105 elif os.path.isfile(src): |
| 106 shutil.copyfile(src, dst) |
| 107 |
| 108 |
| 109 def close(self): |
| 110 os.chmod(self.path, 0755) |
| 111 cleanup(self.mount) |
| 112 print "Disk %s successfuly set" % self.path |
| 113 |
| 114 |
| 115 class FloppyDisk(Disk): |
| 116 """ |
| 117 Represents a 1.44 MB floppy disk. We can copy files to it, and setup it in |
| 118 convenient ways. |
| 119 """ |
| 120 def __init__(self, path): |
| 121 print "Creating floppy unattended image %s" % path |
| 122 try: |
| 123 qemu_img_binary = os.environ['KVM_TEST_qemu_img_binary'] |
| 124 except KeyError: |
| 125 qemu_img_binary = os.path.join(KVM_TEST_DIR, qemu_img_binary) |
| 126 if not os.path.exists(qemu_img_binary): |
| 127 raise SetupError('The qemu-img binary that is supposed to be used ' |
| 128 '(%s) does not exist. Please verify your ' |
| 129 'configuration' % qemu_img_binary) |
| 130 |
| 131 self.mount = tempfile.mkdtemp(prefix='floppy_', dir='/tmp') |
| 132 self.virtio_mount = None |
| 133 self.path = path |
| 134 clean_old_image(path) |
| 135 if not os.path.isdir(os.path.dirname(path)): |
| 136 os.makedirs(os.path.dirname(path)) |
| 137 |
| 138 try: |
| 139 c_cmd = '%s create -f raw %s 1440k' % (qemu_img_binary, path) |
| 140 run(c_cmd, info='Could not create floppy image') |
| 141 f_cmd = 'mkfs.msdos -s 1 %s' % path |
| 142 run(f_cmd, info='Error formatting floppy image') |
| 143 m_cmd = 'mount -o loop,rw %s %s' % (path, self.mount) |
| 144 run(m_cmd, info='Could not mount floppy image') |
| 145 except: |
| 146 cleanup(self.mount) |
| 147 |
| 148 |
| 149 def _copy_virtio_drivers(self, virtio_floppy): |
| 150 """ |
| 151 Copy the virtio drivers on the virtio floppy to the install floppy. |
| 152 |
| 153 1) Mount the floppy containing the viostor drivers |
| 154 2) Copy its contents to the root of the install floppy |
| 155 """ |
| 156 virtio_mount = tempfile.mkdtemp(prefix='virtio_floppy_', dir='/tmp') |
| 157 |
| 158 pwd = os.getcwd() |
| 159 try: |
| 160 m_cmd = 'mount -o loop %s %s' % (virtio_floppy, virtio_mount) |
| 161 run(m_cmd, info='Could not mount virtio floppy driver') |
| 162 os.chdir(virtio_mount) |
| 163 path_list = glob.glob('*') |
| 164 for path in path_list: |
| 165 self.copy_to(path) |
| 166 finally: |
| 167 os.chdir(pwd) |
| 168 cleanup(virtio_mount) |
| 169 |
| 170 |
| 171 def setup_virtio_win2003(self, virtio_floppy, virtio_oemsetup_id): |
| 172 """ |
| 173 Setup the install floppy with the virtio storage drivers, win2003 style. |
| 174 |
| 175 Win2003 and WinXP depend on the file txtsetup.oem file to install |
| 176 the virtio drivers from the floppy, which is a .ini file. |
| 177 Process: |
| 178 |
| 179 1) Copy the virtio drivers on the virtio floppy to the install floppy |
| 180 2) Parse the ini file with config parser |
| 181 3) Modify the identifier of the default session that is going to be |
| 182 executed on the config parser object |
| 183 4) Re-write the config file to the disk |
| 184 """ |
| 185 self._copy_virtio_drivers(virtio_floppy) |
| 186 txtsetup_oem = os.path.join(self.mount, 'txtsetup.oem') |
| 187 if not os.path.isfile(txtsetup_oem): |
| 188 raise SetupError('File txtsetup.oem not found on the install ' |
| 189 'floppy. Please verify if your floppy virtio ' |
| 190 'driver image has this file') |
| 191 parser = ConfigParser.ConfigParser() |
| 192 parser.read(txtsetup_oem) |
| 193 if not parser.has_section('Defaults'): |
| 194 raise SetupError('File txtsetup.oem does not have the session ' |
| 195 '"Defaults". Please check txtsetup.oem') |
| 196 default_driver = parser.get('Defaults', 'SCSI') |
| 197 if default_driver != virtio_oemsetup_id: |
| 198 parser.set('Defaults', 'SCSI', virtio_oemsetup_id) |
| 199 fp = open(txtsetup_oem, 'w') |
| 200 parser.write(fp) |
| 201 fp.close() |
| 202 |
| 203 |
| 204 def setup_virtio_win2008(self, virtio_floppy): |
| 205 """ |
| 206 Setup the install floppy with the virtio storage drivers, win2008 style. |
| 207 |
| 208 Win2008, Vista and 7 require people to point out the path to the drivers |
| 209 on the unattended file, so we just need to copy the drivers to the |
| 210 driver floppy disk. |
| 211 Process: |
| 212 |
| 213 1) Copy the virtio drivers on the virtio floppy to the install floppy |
| 214 """ |
| 215 self._copy_virtio_drivers(virtio_floppy) |
| 216 |
| 217 |
| 218 class CdromDisk(Disk): |
| 219 """ |
| 220 Represents a CDROM disk that we can master according to our needs. |
| 221 """ |
| 222 def __init__(self, path): |
| 223 print "Creating ISO unattended image %s" % path |
| 224 self.mount = tempfile.mkdtemp(prefix='cdrom_unattended_', dir='/tmp') |
| 225 self.path = path |
| 226 clean_old_image(path) |
| 227 if not os.path.isdir(os.path.dirname(path)): |
| 228 os.makedirs(os.path.dirname(path)) |
| 229 |
| 230 |
| 231 def close(self): |
| 232 g_cmd = ('mkisofs -o %s -max-iso9660-filenames ' |
| 233 '-relaxed-filenames -D --input-charset iso8859-1 ' |
| 234 '%s' % (self.path, self.mount)) |
| 235 run(g_cmd, info='Could not generate iso with answer file') |
| 236 |
| 237 os.chmod(self.path, 0755) |
| 238 cleanup(self.mount) |
| 239 print "Disk %s successfuly set" % self.path |
| 240 |
| 241 |
17 class UnattendedInstall(object): | 242 class UnattendedInstall(object): |
18 """ | 243 """ |
19 Creates a floppy disk image that will contain a config file for unattended | 244 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 | 245 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 | 246 TFTP and DHCP servers to install a particular operating system. The |
22 parameters to the script are retrieved from environment variables. | 247 parameters to the script are retrieved from environment variables. |
23 """ | 248 """ |
24 def __init__(self): | 249 def __init__(self): |
25 """ | 250 """ |
26 Gets params from environment variables and sets class attributes. | 251 Gets params from environment variables and sets class attributes. |
27 """ | 252 """ |
28 script_dir = os.path.dirname(sys.modules[__name__].__file__) | 253 images_dir = os.path.join(KVM_TEST_DIR, 'images') |
29 kvm_test_dir = os.path.abspath(os.path.join(script_dir, "..")) | 254 self.deps_dir = os.path.join(KVM_TEST_DIR, 'deps') |
30 images_dir = os.path.join(kvm_test_dir, 'images') | 255 self.unattended_dir = os.path.join(KVM_TEST_DIR, 'unattended') |
31 self.deps_dir = os.path.join(kvm_test_dir, 'deps') | |
32 self.unattended_dir = os.path.join(kvm_test_dir, 'unattended') | |
33 | 256 |
34 tftp_root = os.environ.get('KVM_TEST_tftp', '') | 257 attributes = ['kernel_args', 'finish_program', 'cdrom_cd1', |
35 if tftp_root: | 258 'unattended_file', 'medium', 'url', 'kernel', 'initrd', |
36 self.tftp_root = os.path.join(kvm_test_dir, tftp_root) | 259 'nfs_server', 'nfs_dir', 'pxe_dir', 'pxe_image', |
37 if not os.path.isdir(self.tftp_root): | 260 'pxe_initrd', 'install_virtio', 'tftp', |
38 os.makedirs(self.tftp_root) | 261 'floppy', 'cdrom_unattended'] |
39 else: | 262 for a in attributes: |
40 self.tftp_root = tftp_root | 263 self._setattr(a) |
41 | 264 |
42 self.kernel_args = os.environ.get('KVM_TEST_kernel_args', '') | 265 if self.install_virtio == 'yes': |
43 self.finish_program= os.environ.get('KVM_TEST_finish_program', '') | 266 v_attributes = ['virtio_floppy', 'virtio_storage_path', |
44 cdrom_iso = os.environ.get('KVM_TEST_cdrom_cd1') | 267 'virtio_network_path', 'virtio_oemsetup_id', |
45 self.unattended_file = os.environ.get('KVM_TEST_unattended_file') | 268 'virtio_network_installer'] |
| 269 for va in v_attributes: |
| 270 self._setattr(va) |
46 | 271 |
47 self.qemu_img_bin = os.environ.get('KVM_TEST_qemu_img_binary') | 272 # Silly attribution just to calm pylint down... |
48 if not os.path.isabs(self.qemu_img_bin): | 273 self.tftp = self.tftp |
49 self.qemu_img_bin = os.path.join(kvm_test_dir, self.qemu_img_bin) | 274 if self.tftp: |
50 self.cdrom_iso = os.path.join(kvm_test_dir, cdrom_iso) | 275 self.tftp = os.path.join(KVM_TEST_DIR, self.tftp) |
51 self.floppy_mount = tempfile.mkdtemp(prefix='floppy_', dir='/tmp') | 276 if not os.path.isdir(self.tftp): |
52 self.cdrom_mount = tempfile.mkdtemp(prefix='cdrom_', dir='/tmp') | 277 os.makedirs(self.tftp) |
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) | |
59 | 278 |
60 self.pxe_dir = os.environ.get('KVM_TEST_pxe_dir', '') | 279 self.cdrom_cd1 = os.path.join(KVM_TEST_DIR, self.cdrom_cd1) |
61 self.pxe_image = os.environ.get('KVM_TEST_pxe_image', '') | 280 self.cdrom_cd1_mount = tempfile.mkdtemp(prefix='cdrom_cd1_', dir='/tmp') |
62 self.pxe_initrd = os.environ.get('KVM_TEST_pxe_initrd', '') | 281 if self.medium == 'nfs': |
| 282 self.nfs_mount = tempfile.mkdtemp(prefix='nfs_', dir='/tmp') |
63 | 283 |
64 self.medium = os.environ.get('KVM_TEST_medium', '') | 284 self.floppy = os.path.join(KVM_TEST_DIR, self.floppy) |
65 self.url = os.environ.get('KVM_TEST_url', '') | 285 if not os.path.isdir(os.path.dirname(self.floppy)): |
66 self.kernel = os.environ.get('KVM_TEST_kernel', '') | 286 os.makedirs(os.path.dirname(self.floppy)) |
67 self.initrd = os.environ.get('KVM_TEST_initrd', '') | 287 |
68 self.nfs_server = os.environ.get('KVM_TEST_nfs_server', '') | 288 self.image_path = KVM_TEST_DIR |
69 self.nfs_dir = os.environ.get('KVM_TEST_nfs_dir', '') | |
70 self.image_path = kvm_test_dir | |
71 self.kernel_path = os.path.join(self.image_path, self.kernel) | 289 self.kernel_path = os.path.join(self.image_path, self.kernel) |
72 self.initrd_path = os.path.join(self.image_path, self.initrd) | 290 self.initrd_path = os.path.join(self.image_path, self.initrd) |
73 | 291 |
74 | 292 |
75 def create_boot_floppy(self): | 293 def _setattr(self, key): |
76 """ | 294 """ |
77 Prepares a boot floppy by creating a floppy image file, mounting it and | 295 Populate class attributes with contents of environment variables. |
78 copying an answer file (kickstarts for RH based distros, answer files | 296 |
79 for windows) to it. After that the image is umounted. | 297 Example: KVM_TEST_medium will populate self.medium. |
| 298 |
| 299 @param key: Name of the class attribute we desire to have. |
80 """ | 300 """ |
81 print "Creating boot floppy" | 301 env_name = 'KVM_TEST_%s' % key |
| 302 value = os.environ.get(env_name, '') |
| 303 setattr(self, key, value) |
82 | 304 |
83 if os.path.exists(self.floppy_img): | |
84 os.remove(self.floppy_img) | |
85 | 305 |
86 c_cmd = '%s create -f raw %s 1440k' % (self.qemu_img_bin, | 306 def render_answer_file(self): |
87 self.floppy_img) | 307 # Replace KVM_TEST_CDKEY (in the unattended file) with the cdkey |
88 if os.system(c_cmd): | 308 # provided for this test and replace the KVM_TEST_MEDIUM with |
89 raise SetupError('Could not create floppy image.') | 309 # the tree url or nfs address provided for this test. |
| 310 unattended_contents = open(self.unattended_file).read() |
| 311 dummy_cdkey_re = r'\bKVM_TEST_CDKEY\b' |
| 312 real_cdkey = os.environ.get('KVM_TEST_cdkey') |
| 313 if re.search(dummy_cdkey_re, unattended_contents): |
| 314 if real_cdkey: |
| 315 unattended_contents = re.sub(dummy_cdkey_re, real_cdkey, |
| 316 unattended_contents) |
| 317 else: |
| 318 print ("WARNING: 'cdkey' required but not specified for " |
| 319 "this unattended installation") |
90 | 320 |
91 f_cmd = 'mkfs.msdos -s 1 %s' % self.floppy_img | 321 dummy_medium_re = r'\bKVM_TEST_MEDIUM\b' |
92 if os.system(f_cmd): | 322 if self.medium == "cdrom": |
93 raise SetupError('Error formatting floppy image.') | 323 content = "cdrom" |
| 324 elif self.medium == "url": |
| 325 content = "url --url %s" % self.url |
| 326 elif self.medium == "nfs": |
| 327 content = "nfs --server=%s --dir=%s" % (self.nfs_server, |
| 328 self.nfs_dir) |
| 329 else: |
| 330 raise SetupError("Unexpected installation medium %s" % self.url) |
94 | 331 |
95 try: | 332 unattended_contents = re.sub(dummy_medium_re, content, |
96 m_cmd = 'mount -o loop %s %s' % (self.floppy_img, self.floppy_mount) | 333 unattended_contents) |
97 if os.system(m_cmd): | |
98 raise SetupError('Could not mount floppy image.') | |
99 | 334 |
100 if self.unattended_file.endswith('.sif'): | 335 def replace_virtio_key(contents, dummy_re, env): |
101 dest_fname = 'winnt.sif' | 336 """ |
102 setup_file = 'winnt.bat' | 337 Replace a virtio dummy string with contents. |
103 setup_file_path = os.path.join(self.unattended_dir, setup_file) | 338 |
104 setup_file_dest = os.path.join(self.floppy_mount, setup_file) | 339 If install_virtio is not set, replace it with a dummy string. |
105 shutil.copyfile(setup_file_path, setup_file_dest) | 340 |
106 elif self.unattended_file.endswith('.ks'): | 341 @param contents: Contents of the unattended file |
107 # Red Hat kickstart install | 342 @param dummy_re: Regular expression used to search on the. |
108 dest_fname = 'ks.cfg' | 343 unattended file contents. |
109 elif self.unattended_file.endswith('.xml'): | 344 @param env: Name of the environment variable. |
110 if self.tftp_root is '': | 345 """ |
111 # Windows unattended install | 346 dummy_path = "C:" |
112 dest_fname = "autounattend.xml" | 347 driver = os.environ.get(env, '') |
| 348 |
| 349 if re.search(dummy_re, contents): |
| 350 if self.install_virtio == "yes": |
| 351 if driver.endswith("msi"): |
| 352 driver = 'msiexec /passive /package ' + driver |
| 353 else: |
| 354 try: |
| 355 # Let's escape windows style paths properly |
| 356 drive, path = driver.split(":") |
| 357 driver = drive + ":" + re.escape(path) |
| 358 except: |
| 359 pass |
| 360 contents = re.sub(dummy_re, driver, contents) |
113 else: | 361 else: |
114 # SUSE autoyast install | 362 contents = re.sub(dummy_re, dummy_path, contents) |
115 dest_fname = "autoinst.xml" | 363 return contents |
116 | 364 |
117 dest = os.path.join(self.floppy_mount, dest_fname) | 365 vdict = {r'\bKVM_TEST_STORAGE_DRIVER_PATH\b': |
| 366 'KVM_TEST_virtio_storage_path', |
| 367 r'\bKVM_TEST_NETWORK_DRIVER_PATH\b': |
| 368 'KVM_TEST_virtio_network_path', |
| 369 r'\bKVM_TEST_VIRTIO_NETWORK_INSTALLER\b': |
| 370 'KVM_TEST_virtio_network_installer_path'} |
118 | 371 |
119 # Replace KVM_TEST_CDKEY (in the unattended file) with the cdkey | 372 for vkey in vdict: |
120 # provided for this test and replace the KVM_TEST_MEDIUM with | 373 unattended_contents = replace_virtio_key(unattended_contents, |
121 # the tree url or nfs address provided for this test. | 374 vkey, vdict[vkey]) |
122 unattended_contents = open(self.unattended_file).read() | 375 |
123 dummy_cdkey_re = r'\bKVM_TEST_CDKEY\b' | 376 print "Unattended install contents:" |
124 real_cdkey = os.environ.get('KVM_TEST_cdkey') | 377 print unattended_contents |
125 if re.search(dummy_cdkey_re, unattended_contents): | 378 return unattended_contents |
126 if real_cdkey: | 379 |
127 unattended_contents = re.sub(dummy_cdkey_re, real_cdkey, | 380 |
128 unattended_contents) | 381 def setup_boot_disk(self): |
| 382 answer_contents = self.render_answer_file() |
| 383 |
| 384 if self.unattended_file.endswith('.sif'): |
| 385 dest_fname = 'winnt.sif' |
| 386 setup_file = 'winnt.bat' |
| 387 boot_disk = FloppyDisk(self.floppy) |
| 388 boot_disk.setup_answer_file(dest_fname, answer_contents) |
| 389 setup_file_path = os.path.join(self.unattended_dir, setup_file) |
| 390 boot_disk.copy_to(setup_file_path) |
| 391 if self.install_virtio == "yes": |
| 392 boot_disk.setup_virtio_win2003(self.virtio_floppy, |
| 393 self.virtio_oemsetup_id) |
| 394 boot_disk.copy_to(self.finish_program) |
| 395 |
| 396 elif self.unattended_file.endswith('.ks'): |
| 397 # Red Hat kickstart install |
| 398 dest_fname = 'ks.cfg' |
| 399 if self.cdrom_unattended: |
| 400 boot_disk = CdromDisk(self.cdrom_unattended) |
| 401 elif self.floppy: |
| 402 boot_disk = FloppyDisk(self.floppy) |
| 403 else: |
| 404 raise SetupError("Neither cdrom_unattended nor floppy set " |
| 405 "on the config file, please verify") |
| 406 boot_disk.setup_answer_file(dest_fname, answer_contents) |
| 407 |
| 408 elif self.unattended_file.endswith('.xml'): |
| 409 if self.tftp: |
| 410 # SUSE autoyast install |
| 411 dest_fname = "autoinst.xml" |
| 412 if self.cdrom_unattended: |
| 413 boot_disk = CdromDisk(self.cdrom_unattended) |
| 414 elif self.floppy: |
| 415 boot_disk = FloppyDisk(self.floppy) |
129 else: | 416 else: |
130 print ("WARNING: 'cdkey' required but not specified for " | 417 raise SetupError("Neither cdrom_unattended nor floppy set " |
131 "this unattended installation") | 418 "on the config file, please verify") |
| 419 boot_disk.setup_answer_file(dest_fname, answer_contents) |
132 | 420 |
133 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.nf
s_dir) | |
140 else: | 421 else: |
141 raise SetupError("Unexpected installation medium %s" % self.url) | 422 # Windows unattended install |
| 423 dest_fname = "autounattend.xml" |
| 424 boot_disk = FloppyDisk(self.floppy) |
| 425 boot_disk.setup_answer_file(dest_fname, answer_contents) |
| 426 if self.install_virtio == "yes": |
| 427 boot_disk.setup_virtio_win2008(self.virtio_floppy) |
| 428 boot_disk.copy_to(self.finish_program) |
142 | 429 |
143 unattended_contents = re.sub(dummy_re, content, unattended_contents) | 430 else: |
| 431 raise SetupError('Unknown answer file %s' % |
| 432 self.unattended_file) |
144 | 433 |
145 print | 434 boot_disk.close() |
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" | |
166 | 435 |
167 | 436 |
168 def setup_pxe_boot(self): | 437 def setup_pxe_boot(self): |
169 """ | 438 """ |
170 Sets up a PXE boot environment using the built in qemu TFTP server. | 439 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 | 440 Copies the PXE Linux bootloader pxelinux.0 from the host (needs the |
172 pxelinux package or equivalent for your distro), and vmlinuz and | 441 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 | 442 initrd.img files from the CD to a directory that qemu will serve trough |
174 TFTP to the VM. | 443 TFTP to the VM. |
175 """ | 444 """ |
176 print "Setting up PXE boot using TFTP root %s" % self.tftp_root | 445 print "Setting up PXE boot using TFTP root %s" % self.tftp |
177 | 446 |
178 pxe_file = None | 447 pxe_file = None |
179 pxe_paths = ['/usr/lib/syslinux/pxelinux.0', | 448 pxe_paths = ['/usr/lib/syslinux/pxelinux.0', |
180 '/usr/share/syslinux/pxelinux.0'] | 449 '/usr/share/syslinux/pxelinux.0'] |
181 for path in pxe_paths: | 450 for path in pxe_paths: |
182 if os.path.isfile(path): | 451 if os.path.isfile(path): |
183 pxe_file = path | 452 pxe_file = path |
184 break | 453 break |
185 | 454 |
186 if not pxe_file: | 455 if not pxe_file: |
187 raise SetupError('Cannot find PXE boot loader pxelinux.0. Make ' | 456 raise SetupError('Cannot find PXE boot loader pxelinux.0. Make ' |
188 'sure pxelinux or equivalent package for your ' | 457 'sure pxelinux or equivalent package for your ' |
189 'distro is installed.') | 458 'distro is installed.') |
190 | 459 |
191 pxe_dest = os.path.join(self.tftp_root, 'pxelinux.0') | 460 pxe_dest = os.path.join(self.tftp, 'pxelinux.0') |
192 shutil.copyfile(pxe_file, pxe_dest) | 461 shutil.copyfile(pxe_file, pxe_dest) |
193 | 462 |
194 try: | 463 try: |
195 m_cmd = 'mount -t iso9660 -v -o loop,ro %s %s' % (self.cdrom_iso, | 464 m_cmd = ('mount -t iso9660 -v -o loop,ro %s %s' % |
196 self.cdrom_mount) | 465 (self.cdrom_cd1, self.cdrom_cd1_mount)) |
197 if os.system(m_cmd): | 466 run(m_cmd, info='Could not mount CD image %s.' % self.cdrom_cd1) |
198 raise SetupError('Could not mount CD image %s.' % | |
199 self.cdrom_iso) | |
200 | 467 |
201 pxe_dir = os.path.join(self.cdrom_mount, self.pxe_dir) | 468 pxe_dir = os.path.join(self.cdrom_cd1_mount, self.pxe_dir) |
202 pxe_image = os.path.join(pxe_dir, self.pxe_image) | 469 pxe_image = os.path.join(pxe_dir, self.pxe_image) |
203 pxe_initrd = os.path.join(pxe_dir, self.pxe_initrd) | 470 pxe_initrd = os.path.join(pxe_dir, self.pxe_initrd) |
204 | 471 |
205 if not os.path.isdir(pxe_dir): | 472 if not os.path.isdir(pxe_dir): |
206 raise SetupError('The ISO image does not have a %s dir. The ' | 473 raise SetupError('The ISO image does not have a %s dir. The ' |
207 'script assumes that the cd has a %s dir ' | 474 'script assumes that the cd has a %s dir ' |
208 'where to search for the vmlinuz image.' % | 475 'where to search for the vmlinuz image.' % |
209 (self.pxe_dir, self.pxe_dir)) | 476 (self.pxe_dir, self.pxe_dir)) |
210 | 477 |
211 if not os.path.isfile(pxe_image) or not os.path.isfile(pxe_initrd): | 478 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 ' | 479 raise SetupError('The location %s is lacking either a vmlinuz ' |
213 'or a initrd.img file. Cannot find a PXE ' | 480 'or a initrd.img file. Cannot find a PXE ' |
214 'image to proceed.' % self.pxe_dir) | 481 'image to proceed.' % self.pxe_dir) |
215 | 482 |
216 tftp_image = os.path.join(self.tftp_root, 'vmlinuz') | 483 tftp_image = os.path.join(self.tftp, 'vmlinuz') |
217 tftp_initrd = os.path.join(self.tftp_root, 'initrd.img') | 484 tftp_initrd = os.path.join(self.tftp, 'initrd.img') |
218 shutil.copyfile(pxe_image, tftp_image) | 485 shutil.copyfile(pxe_image, tftp_image) |
219 shutil.copyfile(pxe_initrd, tftp_initrd) | 486 shutil.copyfile(pxe_initrd, tftp_initrd) |
220 | 487 |
221 finally: | 488 finally: |
222 u_cmd = 'umount %s' % self.cdrom_mount | 489 cleanup(self.cdrom_cd1_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) | |
227 | 490 |
228 pxe_config_dir = os.path.join(self.tftp_root, 'pxelinux.cfg') | 491 pxe_config_dir = os.path.join(self.tftp, 'pxelinux.cfg') |
229 if not os.path.isdir(pxe_config_dir): | 492 if not os.path.isdir(pxe_config_dir): |
230 os.makedirs(pxe_config_dir) | 493 os.makedirs(pxe_config_dir) |
231 pxe_config_path = os.path.join(pxe_config_dir, 'default') | 494 pxe_config_path = os.path.join(pxe_config_dir, 'default') |
232 | 495 |
233 pxe_config = open(pxe_config_path, 'w') | 496 pxe_config = open(pxe_config_path, 'w') |
234 pxe_config.write('DEFAULT pxeboot\n') | 497 pxe_config.write('DEFAULT pxeboot\n') |
235 pxe_config.write('TIMEOUT 20\n') | 498 pxe_config.write('TIMEOUT 20\n') |
236 pxe_config.write('PROMPT 0\n') | 499 pxe_config.write('PROMPT 0\n') |
237 pxe_config.write('LABEL pxeboot\n') | 500 pxe_config.write('LABEL pxeboot\n') |
238 pxe_config.write(' KERNEL vmlinuz\n') | 501 pxe_config.write(' KERNEL vmlinuz\n') |
239 pxe_config.write(' APPEND initrd=initrd.img %s\n' % | 502 pxe_config.write(' APPEND initrd=initrd.img %s\n' % |
240 self.kernel_args) | 503 self.kernel_args) |
241 pxe_config.close() | 504 pxe_config.close() |
242 | 505 |
243 print "PXE boot successfuly set" | 506 print "PXE boot successfuly set" |
244 | 507 |
245 | 508 |
246 def setup_url(self): | 509 def setup_url(self): |
247 """ | 510 """ |
248 Download the vmlinuz and initrd.img from URL | 511 Download the vmlinuz and initrd.img from URL. |
249 """ | 512 """ |
250 print "Downloading the vmlinuz and initrd.img" | 513 print "Downloading the vmlinuz and initrd.img" |
251 os.chdir(self.image_path) | 514 os.chdir(self.image_path) |
252 | 515 |
253 kernel_fetch_cmd = "wget -q %s/isolinux/%s" % (self.url, self.kernel) | 516 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) | 517 initrd_fetch_cmd = "wget -q %s/isolinux/%s" % (self.url, self.initrd) |
255 | 518 |
256 if os.path.exists(self.kernel): | 519 if os.path.exists(self.kernel): |
257 os.unlink(self.kernel) | 520 os.unlink(self.kernel) |
258 if os.path.exists(self.initrd): | 521 if os.path.exists(self.initrd): |
259 os.unlink(self.initrd) | 522 os.unlink(self.initrd) |
260 | 523 |
261 if os.system(kernel_fetch_cmd) != 0: | 524 run(kernel_fetch_cmd, info="Could not fetch vmlinuz from %s" % self.url) |
262 raise SetupError("Could not fetch vmlinuz from %s" % self.url) | 525 run(initrd_fetch_cmd, info=("Could not fetch initrd.img from %s" % |
263 if os.system(initrd_fetch_cmd) != 0: | 526 self.url)) |
264 raise SetupError("Could not fetch initrd.img from %s" % self.url) | 527 print "Download of vmlinuz and initrd.img finished" |
265 | 528 |
266 print "Downloading finish" | |
267 | 529 |
268 def setup_nfs(self): | 530 def setup_nfs(self): |
269 """ | 531 """ |
270 Copy the vmlinuz and initrd.img from nfs. | 532 Copy the vmlinuz and initrd.img from nfs. |
271 """ | 533 """ |
272 print "Copying the vmlinuz and initrd.img from nfs" | 534 print "Copying the vmlinuz and initrd.img from nfs" |
273 | 535 |
274 m_cmd = "mount %s:%s %s -o ro" % (self.nfs_server, self.nfs_dir, self.nf
s_mount) | 536 m_cmd = ("mount %s:%s %s -o ro" % |
275 if os.system(m_cmd): | 537 (self.nfs_server, self.nfs_dir, self.nfs_mount)) |
276 raise SetupError('Could not mount nfs server.') | 538 run(m_cmd, info='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) | |
284 | 539 |
285 try: | 540 try: |
286 if os.system(kernel_fetch_cmd): | 541 kernel_fetch_cmd = ("cp %s/isolinux/%s %s" % |
287 raise SetupError("Could not copy the vmlinuz from %s" % | 542 (self.nfs_mount, self.kernel, self.image_path)) |
288 self.nfs_mount) | 543 run(kernel_fetch_cmd, info=("Could not copy the vmlinuz from %s" % |
289 if os.system(initrd_fetch_cmd): | 544 self.nfs_mount)) |
290 raise SetupError("Could not copy the initrd.img from %s" % | 545 initrd_fetch_cmd = ("cp %s/isolinux/%s %s" % |
291 self.nfs_mount) | 546 (self.nfs_mount, self.initrd, self.image_path)) |
| 547 run(initrd_fetch_cmd, info=("Could not copy the initrd.img from " |
| 548 "%s" % self.nfs_mount)) |
292 finally: | 549 finally: |
293 u_cmd = "umount %s" % self.nfs_mount | 550 cleanup(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) | |
310 | 551 |
311 | 552 |
312 def setup(self): | 553 def setup(self): |
| 554 """ |
| 555 Configure the environment for unattended install. |
| 556 |
| 557 Uses an appropriate strategy according to each install model. |
| 558 """ |
313 print "Starting unattended install setup" | 559 print "Starting unattended install setup" |
| 560 print |
314 | 561 |
315 print "Variables set:" | 562 print "Variables set:" |
316 print " medium: " + str(self.medium) | 563 for member in inspect.getmembers(self): |
317 print " qemu_img_bin: " + str(self.qemu_img_bin) | 564 name, value = member |
318 print " cdrom iso: " + str(self.cdrom_iso) | 565 attribute = getattr(self, name) |
319 print " unattended_file: " + str(self.unattended_file) | 566 if not (name.startswith("__") or callable(attribute) or not value): |
320 print " kernel_args: " + str(self.kernel_args) | 567 print " %s: %s" % (name, value) |
321 print " tftp_root: " + str(self.tftp_root) | 568 print |
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) | |
334 | 569 |
335 if self.unattended_file and self.floppy_img is not None: | 570 if self.unattended_file and (self.floppy or self.cdrom_unattended): |
336 self.create_boot_floppy() | 571 self.setup_boot_disk() |
337 if self.medium == "cdrom": | 572 if self.medium == "cdrom": |
338 if self.tftp_root: | 573 if self.tftp: |
339 self.setup_pxe_boot() | 574 self.setup_pxe_boot() |
340 elif self.medium == "url": | 575 elif self.medium == "url": |
341 self.setup_url() | 576 self.setup_url() |
342 elif self.medium == "nfs": | 577 elif self.medium == "nfs": |
343 self.setup_nfs() | 578 self.setup_nfs() |
344 else: | 579 else: |
345 raise SetupError("Unexpected installation method %s" % | 580 raise SetupError("Unexpected installation method %s" % |
346 self.medium) | 581 self.medium) |
347 print "Unattended install setup finished successfuly" | 582 print "Unattended install setup finished successfuly" |
348 | 583 |
349 | 584 |
350 if __name__ == "__main__": | 585 if __name__ == "__main__": |
351 os_install = UnattendedInstall() | 586 os_install = UnattendedInstall() |
352 os_install.setup() | 587 os_install.setup() |
OLD | NEW |