| 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 |