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, ConfigParser, glob, inspect | 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__) | 10 SCRIPT_DIR = os.path.dirname(sys.modules[__name__].__file__) |
(...skipping 224 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
235 run(g_cmd, info='Could not generate iso with answer file') | 235 run(g_cmd, info='Could not generate iso with answer file') |
236 | 236 |
237 os.chmod(self.path, 0755) | 237 os.chmod(self.path, 0755) |
238 cleanup(self.mount) | 238 cleanup(self.mount) |
239 print "Disk %s successfuly set" % self.path | 239 print "Disk %s successfuly set" % self.path |
240 | 240 |
241 | 241 |
242 class UnattendedInstall(object): | 242 class UnattendedInstall(object): |
243 """ | 243 """ |
244 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 |
245 OS install. The parameters to the script are retrieved from environment | 245 OS install. Optionally, sets up a PXE install server using qemu built in |
246 variables. | 246 TFTP and DHCP servers to install a particular operating system. The |
| 247 parameters to the script are retrieved from environment variables. |
247 """ | 248 """ |
248 def __init__(self): | 249 def __init__(self): |
249 """ | 250 """ |
250 Gets params from environment variables and sets class attributes. | 251 Gets params from environment variables and sets class attributes. |
251 """ | 252 """ |
252 images_dir = os.path.join(KVM_TEST_DIR, 'images') | 253 images_dir = os.path.join(KVM_TEST_DIR, 'images') |
253 self.deps_dir = os.path.join(KVM_TEST_DIR, 'deps') | 254 self.deps_dir = os.path.join(KVM_TEST_DIR, 'deps') |
254 self.unattended_dir = os.path.join(KVM_TEST_DIR, 'unattended') | 255 self.unattended_dir = os.path.join(KVM_TEST_DIR, 'unattended') |
255 | 256 |
256 attributes = ['kernel_args', 'finish_program', 'cdrom_cd1', | 257 attributes = ['kernel_args', 'finish_program', 'cdrom_cd1', |
257 'unattended_file', 'medium', 'url', 'kernel', 'initrd', | 258 'unattended_file', 'medium', 'url', 'kernel', 'initrd', |
258 'nfs_server', 'nfs_dir', 'install_virtio', 'floppy', | 259 'nfs_server', 'nfs_dir', 'pxe_dir', 'pxe_image', |
259 'cdrom_unattended', 'boot_path', 'extra_params'] | 260 'pxe_initrd', 'install_virtio', 'tftp', |
260 | 261 'floppy', 'cdrom_unattended'] |
261 for a in attributes: | 262 for a in attributes: |
262 self._setattr(a) | 263 self._setattr(a) |
263 | 264 |
264 if self.install_virtio == 'yes': | 265 if self.install_virtio == 'yes': |
265 v_attributes = ['virtio_floppy', 'virtio_storage_path', | 266 v_attributes = ['virtio_floppy', 'virtio_storage_path', |
266 'virtio_network_path', 'virtio_oemsetup_id', | 267 'virtio_network_path', 'virtio_oemsetup_id', |
267 'virtio_network_installer'] | 268 'virtio_network_installer'] |
268 for va in v_attributes: | 269 for va in v_attributes: |
269 self._setattr(va) | 270 self._setattr(va) |
270 | 271 |
| 272 # Silly attribution just to calm pylint down... |
| 273 self.tftp = self.tftp |
| 274 if self.tftp: |
| 275 self.tftp = os.path.join(KVM_TEST_DIR, self.tftp) |
| 276 if not os.path.isdir(self.tftp): |
| 277 os.makedirs(self.tftp) |
| 278 |
271 if self.cdrom_cd1: | 279 if self.cdrom_cd1: |
272 self.cdrom_cd1 = os.path.join(KVM_TEST_DIR, self.cdrom_cd1) | 280 self.cdrom_cd1 = os.path.join(KVM_TEST_DIR, self.cdrom_cd1) |
273 self.cdrom_cd1_mount = tempfile.mkdtemp(prefix='cdrom_cd1_', dir='/tmp') | 281 self.cdrom_cd1_mount = tempfile.mkdtemp(prefix='cdrom_cd1_', dir='/tmp') |
274 if self.medium == 'nfs': | 282 if self.medium == 'nfs': |
275 self.nfs_mount = tempfile.mkdtemp(prefix='nfs_', dir='/tmp') | 283 self.nfs_mount = tempfile.mkdtemp(prefix='nfs_', dir='/tmp') |
276 | 284 |
277 if self.floppy: | 285 if self.floppy: |
278 self.floppy = os.path.join(KVM_TEST_DIR, self.floppy) | 286 self.floppy = os.path.join(KVM_TEST_DIR, self.floppy) |
279 if not os.path.isdir(os.path.dirname(self.floppy)): | 287 if not os.path.isdir(os.path.dirname(self.floppy)): |
280 os.makedirs(os.path.dirname(self.floppy)) | 288 os.makedirs(os.path.dirname(self.floppy)) |
281 | 289 |
282 self.image_path = os.path.dirname(self.kernel) | 290 self.image_path = KVM_TEST_DIR |
| 291 self.kernel_path = os.path.join(self.image_path, self.kernel) |
| 292 self.initrd_path = os.path.join(self.image_path, self.initrd) |
283 | 293 |
284 | 294 |
285 def _setattr(self, key): | 295 def _setattr(self, key): |
286 """ | 296 """ |
287 Populate class attributes with contents of environment variables. | 297 Populate class attributes with contents of environment variables. |
288 | 298 |
289 Example: KVM_TEST_medium will populate self.medium. | 299 Example: KVM_TEST_medium will populate self.medium. |
290 | 300 |
291 @param key: Name of the class attribute we desire to have. | 301 @param key: Name of the class attribute we desire to have. |
292 """ | 302 """ |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
391 if self.cdrom_unattended: | 401 if self.cdrom_unattended: |
392 boot_disk = CdromDisk(self.cdrom_unattended) | 402 boot_disk = CdromDisk(self.cdrom_unattended) |
393 elif self.floppy: | 403 elif self.floppy: |
394 boot_disk = FloppyDisk(self.floppy) | 404 boot_disk = FloppyDisk(self.floppy) |
395 else: | 405 else: |
396 raise SetupError("Neither cdrom_unattended nor floppy set " | 406 raise SetupError("Neither cdrom_unattended nor floppy set " |
397 "on the config file, please verify") | 407 "on the config file, please verify") |
398 boot_disk.setup_answer_file(dest_fname, answer_contents) | 408 boot_disk.setup_answer_file(dest_fname, answer_contents) |
399 | 409 |
400 elif self.unattended_file.endswith('.xml'): | 410 elif self.unattended_file.endswith('.xml'): |
401 if "autoyast" in self.extra_params: | 411 if self.tftp: |
402 # SUSE autoyast install | 412 # SUSE autoyast install |
403 dest_fname = "autoinst.xml" | 413 dest_fname = "autoinst.xml" |
404 if self.cdrom_unattended: | 414 if self.cdrom_unattended: |
405 boot_disk = CdromDisk(self.cdrom_unattended) | 415 boot_disk = CdromDisk(self.cdrom_unattended) |
406 elif self.floppy: | 416 elif self.floppy: |
407 boot_disk = FloppyDisk(self.floppy) | 417 boot_disk = FloppyDisk(self.floppy) |
408 else: | 418 else: |
409 raise SetupError("Neither cdrom_unattended nor floppy set " | 419 raise SetupError("Neither cdrom_unattended nor floppy set " |
410 "on the config file, please verify") | 420 "on the config file, please verify") |
411 boot_disk.setup_answer_file(dest_fname, answer_contents) | 421 boot_disk.setup_answer_file(dest_fname, answer_contents) |
412 | 422 |
413 else: | 423 else: |
414 # Windows unattended install | 424 # Windows unattended install |
415 dest_fname = "autounattend.xml" | 425 dest_fname = "autounattend.xml" |
416 boot_disk = FloppyDisk(self.floppy) | 426 boot_disk = FloppyDisk(self.floppy) |
417 boot_disk.setup_answer_file(dest_fname, answer_contents) | 427 boot_disk.setup_answer_file(dest_fname, answer_contents) |
418 if self.install_virtio == "yes": | 428 if self.install_virtio == "yes": |
419 boot_disk.setup_virtio_win2008(self.virtio_floppy) | 429 boot_disk.setup_virtio_win2008(self.virtio_floppy) |
420 boot_disk.copy_to(self.finish_program) | 430 boot_disk.copy_to(self.finish_program) |
421 | 431 |
422 else: | 432 else: |
423 raise SetupError('Unknown answer file %s' % | 433 raise SetupError('Unknown answer file %s' % |
424 self.unattended_file) | 434 self.unattended_file) |
425 | 435 |
426 boot_disk.close() | 436 boot_disk.close() |
427 | 437 |
428 | 438 |
429 def setup_cdrom(self): | 439 def setup_pxe_boot(self): |
430 """ | 440 """ |
431 Mount cdrom and copy vmlinuz and initrd.img. | 441 Sets up a PXE boot environment using the built in qemu TFTP server. |
| 442 Copies the PXE Linux bootloader pxelinux.0 from the host (needs the |
| 443 pxelinux package or equivalent for your distro), and vmlinuz and |
| 444 initrd.img files from the CD to a directory that qemu will serve trough |
| 445 TFTP to the VM. |
432 """ | 446 """ |
433 print "Copying vmlinuz and initrd.img from cdrom" | 447 print "Setting up PXE boot using TFTP root %s" % self.tftp |
434 m_cmd = ('mount -t iso9660 -v -o loop,ro %s %s' % | 448 |
435 (self.cdrom_cd1, self.cdrom_cd1_mount)) | 449 pxe_file = None |
436 run(m_cmd, info='Could not mount CD image %s.' % self.cdrom_cd1) | 450 pxe_paths = ['/usr/lib/syslinux/pxelinux.0', |
| 451 '/usr/share/syslinux/pxelinux.0'] |
| 452 for path in pxe_paths: |
| 453 if os.path.isfile(path): |
| 454 pxe_file = path |
| 455 break |
| 456 |
| 457 if not pxe_file: |
| 458 raise SetupError('Cannot find PXE boot loader pxelinux.0. Make ' |
| 459 'sure pxelinux or equivalent package for your ' |
| 460 'distro is installed.') |
| 461 |
| 462 pxe_dest = os.path.join(self.tftp, 'pxelinux.0') |
| 463 shutil.copyfile(pxe_file, pxe_dest) |
437 | 464 |
438 try: | 465 try: |
439 img_path_cmd = ("mkdir -p %s" % self.image_path) | 466 m_cmd = ('mount -t iso9660 -v -o loop,ro %s %s' % |
440 run(img_path_cmd, info=("Could not create image path dir %s" % | 467 (self.cdrom_cd1, self.cdrom_cd1_mount)) |
441 self.image_path)) | 468 run(m_cmd, info='Could not mount CD image %s.' % self.cdrom_cd1) |
442 kernel_fetch_cmd = ("cp %s/%s/%s %s" % | 469 |
443 (self.cdrom_cd1_mount, self.boot_path, | 470 pxe_dir = os.path.join(self.cdrom_cd1_mount, self.pxe_dir) |
444 os.path.basename(self.kernel), self.kernel)) | 471 pxe_image = os.path.join(pxe_dir, self.pxe_image) |
445 run(kernel_fetch_cmd, info=("Could not copy the vmlinuz from %s" % | 472 pxe_initrd = os.path.join(pxe_dir, self.pxe_initrd) |
446 self.cdrom_cd1_mount)) | 473 |
447 initrd_fetch_cmd = ("cp %s/%s/%s %s" % | 474 if not os.path.isdir(pxe_dir): |
448 (self.cdrom_cd1_mount, self.boot_path, | 475 raise SetupError('The ISO image does not have a %s dir. The ' |
449 os.path.basename(self.initrd), self.initrd)) | 476 'script assumes that the cd has a %s dir ' |
450 run(initrd_fetch_cmd, info=("Could not copy the initrd.img from " | 477 'where to search for the vmlinuz image.' % |
451 "%s" % self.cdrom_cd1_mount)) | 478 (self.pxe_dir, self.pxe_dir)) |
| 479 |
| 480 if not os.path.isfile(pxe_image) or not os.path.isfile(pxe_initrd): |
| 481 raise SetupError('The location %s is lacking either a vmlinuz ' |
| 482 'or a initrd.img file. Cannot find a PXE ' |
| 483 'image to proceed.' % self.pxe_dir) |
| 484 |
| 485 tftp_image = os.path.join(self.tftp, 'vmlinuz') |
| 486 tftp_initrd = os.path.join(self.tftp, 'initrd.img') |
| 487 shutil.copyfile(pxe_image, tftp_image) |
| 488 shutil.copyfile(pxe_initrd, tftp_initrd) |
| 489 |
452 finally: | 490 finally: |
453 cleanup(self.cdrom_cd1_mount) | 491 cleanup(self.cdrom_cd1_mount) |
454 | 492 |
| 493 pxe_config_dir = os.path.join(self.tftp, 'pxelinux.cfg') |
| 494 if not os.path.isdir(pxe_config_dir): |
| 495 os.makedirs(pxe_config_dir) |
| 496 pxe_config_path = os.path.join(pxe_config_dir, 'default') |
| 497 |
| 498 pxe_config = open(pxe_config_path, 'w') |
| 499 pxe_config.write('DEFAULT pxeboot\n') |
| 500 pxe_config.write('TIMEOUT 20\n') |
| 501 pxe_config.write('PROMPT 0\n') |
| 502 pxe_config.write('LABEL pxeboot\n') |
| 503 pxe_config.write(' KERNEL vmlinuz\n') |
| 504 pxe_config.write(' APPEND initrd=initrd.img %s\n' % |
| 505 self.kernel_args) |
| 506 pxe_config.close() |
| 507 |
| 508 print "PXE boot successfuly set" |
| 509 |
455 | 510 |
456 def setup_url(self): | 511 def setup_url(self): |
457 """ | 512 """ |
458 Download the vmlinuz and initrd.img from URL. | 513 Download the vmlinuz and initrd.img from URL. |
459 """ | 514 """ |
460 print "Downloading vmlinuz and initrd.img from URL" | 515 print "Downloading the vmlinuz and initrd.img" |
461 os.chdir(self.image_path) | 516 os.chdir(self.image_path) |
462 | 517 |
463 kernel_fetch_cmd = "wget -q %s/%s/%s" % (self.url, self.boot_path, | 518 kernel_fetch_cmd = "wget -q %s/isolinux/%s" % (self.url, self.kernel) |
464 os.path.basename(self.kernel)) | 519 initrd_fetch_cmd = "wget -q %s/isolinux/%s" % (self.url, self.initrd) |
465 initrd_fetch_cmd = "wget -q %s/%s/%s" % (self.url, self.boot_path, | |
466 os.path.basename(self.initrd)) | |
467 | 520 |
468 if os.path.exists(self.kernel): | 521 if os.path.exists(self.kernel): |
469 os.unlink(self.kernel) | 522 os.unlink(self.kernel) |
470 if os.path.exists(self.initrd): | 523 if os.path.exists(self.initrd): |
471 os.unlink(self.initrd) | 524 os.unlink(self.initrd) |
472 | 525 |
473 run(kernel_fetch_cmd, info="Could not fetch vmlinuz from %s" % self.url) | 526 run(kernel_fetch_cmd, info="Could not fetch vmlinuz from %s" % self.url) |
474 run(initrd_fetch_cmd, info=("Could not fetch initrd.img from %s" % | 527 run(initrd_fetch_cmd, info=("Could not fetch initrd.img from %s" % |
475 self.url)) | 528 self.url)) |
| 529 print "Download of vmlinuz and initrd.img finished" |
476 | 530 |
477 | 531 |
478 def setup_nfs(self): | 532 def setup_nfs(self): |
479 """ | 533 """ |
480 Copy the vmlinuz and initrd.img from nfs. | 534 Copy the vmlinuz and initrd.img from nfs. |
481 """ | 535 """ |
482 print "Copying the vmlinuz and initrd.img from nfs" | 536 print "Copying the vmlinuz and initrd.img from nfs" |
483 | 537 |
484 m_cmd = ("mount %s:%s %s -o ro" % | 538 m_cmd = ("mount %s:%s %s -o ro" % |
485 (self.nfs_server, self.nfs_dir, self.nfs_mount)) | 539 (self.nfs_server, self.nfs_dir, self.nfs_mount)) |
486 run(m_cmd, info='Could not mount nfs server') | 540 run(m_cmd, info='Could not mount nfs server') |
487 | 541 |
488 try: | 542 try: |
489 kernel_fetch_cmd = ("cp %s/%s/%s %s" % | 543 kernel_fetch_cmd = ("cp %s/isolinux/%s %s" % |
490 (self.nfs_mount, self.boot_path, | 544 (self.nfs_mount, self.kernel, self.image_path)) |
491 os.path.basename(self.kernel), self.image_path)) | |
492 run(kernel_fetch_cmd, info=("Could not copy the vmlinuz from %s" % | 545 run(kernel_fetch_cmd, info=("Could not copy the vmlinuz from %s" % |
493 self.nfs_mount)) | 546 self.nfs_mount)) |
494 initrd_fetch_cmd = ("cp %s/%s/%s %s" % | 547 initrd_fetch_cmd = ("cp %s/isolinux/%s %s" % |
495 (self.nfs_mount, self.boot_path, | 548 (self.nfs_mount, self.initrd, self.image_path)) |
496 os.path.basename(self.initrd), self.image_path)) | |
497 run(initrd_fetch_cmd, info=("Could not copy the initrd.img from " | 549 run(initrd_fetch_cmd, info=("Could not copy the initrd.img from " |
498 "%s" % self.nfs_mount)) | 550 "%s" % self.nfs_mount)) |
499 finally: | 551 finally: |
500 cleanup(self.nfs_mount) | 552 cleanup(self.nfs_mount) |
501 | 553 |
502 | 554 |
503 def setup(self): | 555 def setup(self): |
504 """ | 556 """ |
505 Configure the environment for unattended install. | 557 Configure the environment for unattended install. |
506 | 558 |
507 Uses an appropriate strategy according to each install model. | 559 Uses an appropriate strategy according to each install model. |
508 """ | 560 """ |
509 print "Starting unattended install setup" | 561 print "Starting unattended install setup" |
510 print | 562 print |
511 | 563 |
512 print "Variables set:" | 564 print "Variables set:" |
513 for member in inspect.getmembers(self): | 565 for member in inspect.getmembers(self): |
514 name, value = member | 566 name, value = member |
515 attribute = getattr(self, name) | 567 attribute = getattr(self, name) |
516 if not (name.startswith("__") or callable(attribute) or not value): | 568 if not (name.startswith("__") or callable(attribute) or not value): |
517 print " %s: %s" % (name, value) | 569 print " %s: %s" % (name, value) |
518 print | 570 print |
519 | 571 |
520 if self.unattended_file and (self.floppy or self.cdrom_unattended): | 572 if self.unattended_file and (self.floppy or self.cdrom_unattended): |
521 self.setup_boot_disk() | 573 self.setup_boot_disk() |
522 if self.medium == "cdrom": | 574 if self.medium == "cdrom": |
523 if self.kernel and self.initrd: | 575 if self.tftp: |
524 self.setup_cdrom() | 576 self.setup_pxe_boot() |
525 elif self.medium == "url": | 577 elif self.medium == "url": |
526 self.setup_url() | 578 self.setup_url() |
527 elif self.medium == "nfs": | 579 elif self.medium == "nfs": |
528 self.setup_nfs() | 580 self.setup_nfs() |
529 else: | 581 else: |
530 raise SetupError("Unexpected installation method %s" % | 582 raise SetupError("Unexpected installation method %s" % |
531 self.medium) | 583 self.medium) |
532 print "Unattended install setup finished successfuly" | 584 print "Unattended install setup finished successfuly" |
533 | 585 |
534 | 586 |
535 if __name__ == "__main__": | 587 if __name__ == "__main__": |
536 os_install = UnattendedInstall() | 588 os_install = UnattendedInstall() |
537 os_install.setup() | 589 os_install.setup() |
OLD | NEW |