~clint-fewbar/+junk/cobbler-packaging-enhancements

« back to all changes in this revision

Viewing changes to cobbler/pxegen.py

  • Committer: Devan Goodwin
  • Date: 2010-11-24 13:47:28 UTC
  • Revision ID: git-v1:a2da9a8ae9fbfc65650bbb500688dcad211e8288
Add support for EFI grub booting.

Patch switches location for download of loaders from mdehaan's
fedorapeople page to my own. Here I have added two new grub images to be
used for EFI booting, assuming you configure your dhcp server to use
them. All grub related files are placed under a "grub" sub-directory
beneath the tftp root.

Sync will now generate an efidefault grub menu for your tftp server.
This menu will contain options to boot all cobbler profiles at this
point.

You will also see a per-system menu generated under the grub directory
for each system interface's MAC/IP, just as with pxe with the exception
that these filenames are in uppercase.

Includes a little dead code / style cleanup in pxegen.py.

Show diffs side-by-side

added added

removed removed

Lines of Context:
55
55
    Handles building out PXE stuff
56
56
    """
57
57
 
58
 
    def __init__(self,config,logger):
 
58
    def __init__(self, config, logger):
59
59
        """
60
60
        Constructor
61
61
        """
84
84
        # copy syslinux from one of two locations
85
85
        try:
86
86
            try:
87
 
                utils.copyfile_pattern('/var/lib/cobbler/loaders/pxelinux.0', dst, api=self.api, logger=self.logger)
88
 
                utils.copyfile_pattern('/var/lib/cobbler/loaders/menu.c32', dst, api=self.api, logger=self.logger)
 
87
                utils.copyfile_pattern('/var/lib/cobbler/loaders/pxelinux.0',
 
88
                        dst, api=self.api, logger=self.logger)
 
89
                utils.copyfile_pattern('/var/lib/cobbler/loaders/menu.c32',
 
90
                        dst, api=self.api, logger=self.logger)
89
91
            except:
90
 
                utils.copyfile_pattern('/usr/share/syslinux/pxelinux.0', dst, api=self.api, logger=self.logger)
91
 
                utils.copyfile_pattern('/usr/share/syslinux/menu.c32', dst, api=self.api, logger=self.logger)
 
92
                utils.copyfile_pattern('/usr/share/syslinux/pxelinux.0',
 
93
                        dst, api=self.api, logger=self.logger)
 
94
                utils.copyfile_pattern('/usr/share/syslinux/menu.c32',
 
95
                        dst, api=self.api, logger=self.logger)
92
96
 
93
97
        except:
94
 
            utils.copyfile_pattern('/usr/lib/syslinux/pxelinux.0',   dst, api=self.api, logger=self.logger)
95
 
            utils.copyfile_pattern('/usr/lib/syslinux/menu.c32',   dst, api=self.api, logger=self.logger)
 
98
            utils.copyfile_pattern('/usr/lib/syslinux/pxelinux.0',
 
99
                    dst, api=self.api, logger=self.logger)
 
100
            utils.copyfile_pattern('/usr/lib/syslinux/menu.c32',
 
101
                    dst, api=self.api, logger=self.logger)
96
102
 
97
103
        # copy memtest only if we find it
98
 
        utils.copyfile_pattern('/boot/memtest*', dst, require_match=False, api=self.api, logger=self.logger)
 
104
        utils.copyfile_pattern('/boot/memtest*',
 
105
                dst, require_match=False, api=self.api, logger=self.logger)
99
106
  
100
107
        # copy elilo which we include for IA64 targets
101
 
        utils.copyfile_pattern('/var/lib/cobbler/loaders/elilo.efi', dst, require_match=False, api=self.api, logger=self.logger)
 
108
        utils.copyfile_pattern('/var/lib/cobbler/loaders/elilo.efi', dst,
 
109
                require_match=False, api=self.api, logger=self.logger)
102
110
 
103
111
        # copy yaboot which we include for PowerPC targets
104
 
        utils.copyfile_pattern('/var/lib/cobbler/loaders/yaboot', dst, require_match=False, api=self.api, logger=self.logger)
 
112
        utils.copyfile_pattern('/var/lib/cobbler/loaders/yaboot', dst,
 
113
                require_match=False, api=self.api, logger=self.logger)
105
114
 
106
115
        try:
107
 
            utils.copyfile_pattern('/usr/lib/syslinux/memdisk',   dst, api=self.api, logger=self.logger)
 
116
            utils.copyfile_pattern('/usr/lib/syslinux/memdisk',
 
117
                    dst, api=self.api, logger=self.logger)
108
118
        except:
109
 
            utils.copyfile_pattern('/usr/share/syslinux/memdisk', dst, require_match=False, api=self.api, logger=self.logger)
 
119
            utils.copyfile_pattern('/usr/share/syslinux/memdisk', dst,
 
120
                    require_match=False, api=self.api, logger=self.logger)
 
121
 
 
122
        # Copy grub EFI bootloaders if possible:
 
123
        grub_dst = os.path.join(dst, "grub")
 
124
        utils.copyfile_pattern('/var/lib/cobbler/loaders/grub*.efi', grub_dst,
 
125
                require_match=False, api=self.api, logger=self.logger)
 
126
 
110
127
 
111
128
    def copy_images(self):
112
129
        """
164
181
        utils.linkfile(filename, newfile, api=self.api, logger=self.logger)
165
182
        return True
166
183
 
167
 
    def write_all_system_files(self,system):
 
184
    def write_all_system_files(self, system):
168
185
 
169
186
        profile = system.get_conceptual_parent()
170
187
        if profile is None:
171
188
            raise CX("system %(system)s references a missing profile %(profile)s" % { "system" : system.name, "profile" : system.profile})
 
189
 
172
190
        distro = profile.get_conceptual_parent()
173
191
        image_based = False
174
192
        image = None
202
220
 
203
221
            # Write system specific zPXE file
204
222
            if system.is_management_supported():
205
 
                self.write_pxe_file(f2,system,profile,distro,distro.arch)
 
223
                self.write_pxe_file(f2, system, profile, distro, distro.arch)
206
224
            else:
207
225
                # ensure the file doesn't exist
208
226
                utils.rmfile(f2)
214
232
 
215
233
            ip = interface["ip_address"]
216
234
 
217
 
            f1 = utils.get_config_filename(system,interface=name)
 
235
            f1 = utils.get_config_filename(system, interface=name)
218
236
            if f1 is None:
219
237
                self.logger.warning("invalid interface recorded for system (%s,%s)" % (system.name,name))
220
238
                continue;
231
249
            if working_arch in [ "i386", "x86", "x86_64", "standard"]:
232
250
                # pxelinux wants a file named $name under pxelinux.cfg
233
251
                f2 = os.path.join(self.bootloc, "pxelinux.cfg", f1)
 
252
 
 
253
                # Only generating grub menus for these arch's:
 
254
                grub_path = os.path.join(self.bootloc, "grub", f1.upper())
 
255
 
234
256
            elif working_arch == "ia64":
235
257
                # elilo expects files to be named "$name.conf" in the root
236
258
                # and can not do files based on the MAC address
239
261
 
240
262
                filename = "%s.conf" % utils.get_config_filename(system,interface=name)
241
263
                f2 = os.path.join(self.bootloc, filename)
 
264
 
242
265
            elif working_arch.startswith("ppc"):
243
266
                # Determine filename for system-specific yaboot.conf
244
267
                filename = "%s" % utils.get_config_filename(system, interface=name).lower()
254
277
 
255
278
            if system.is_management_supported():
256
279
                if not image_based:
257
 
                    self.write_pxe_file(f2,system,profile,distro,working_arch)
 
280
                    self.write_pxe_file(f2, system, profile, distro, working_arch)
 
281
                    if grub_path:
 
282
                        self.write_pxe_file(grub_path, system, profile, distro, 
 
283
                                working_arch, format="grub")
258
284
                else:
259
 
                    self.write_pxe_file(f2,system,None,None,working_arch,image=profile)
 
285
                    self.write_pxe_file(f2, system, None, None, working_arch, image=profile)
260
286
            else:
261
287
                # ensure the file doesn't exist
262
288
                utils.rmfile(f2)
 
289
                if grub_path:
 
290
                    utils.rmfile(grub_path)
263
291
 
264
292
    def make_pxe_menu(self):
265
293
        self.make_s390_pseudo_pxe_menu()
304
332
        listfile.close()
305
333
 
306
334
    def make_actual_pxe_menu(self):
 
335
        """
 
336
        Generates both pxe and grub boot menus.
 
337
        """
307
338
        # only do this if there is NOT a system named default.
308
339
        default = self.systems.find(name="default")
309
340
 
322
353
        image_list = [image for image in self.images]
323
354
        image_list.sort(sort_name)
324
355
 
325
 
        # build out the menu entries
 
356
        # Build out menu items and append each to this master list, used for
 
357
        # the default menus:
326
358
        pxe_menu_items = ""
 
359
        grub_menu_items = ""
 
360
 
 
361
        # For now, profiles are the only items we want grub EFI boot menu entries for:
327
362
        for profile in profile_list:
328
363
            if not profile.enable_menu:
329
364
               # This profile has been excluded from the menu
333
368
            if distro.name.find("-xen") != -1 or distro.arch not in ["i386", "x86_64"]:
334
369
                # can't PXE Xen
335
370
                continue
336
 
            contents = self.write_pxe_file(None,None,profile,distro,distro.arch,include_header=False)
 
371
            contents = self.write_pxe_file(filename=None, system=None,
 
372
                    profile=profile, distro=distro,
 
373
                    arch=distro.arch, include_header=False)
337
374
            if contents is not None:
338
375
                pxe_menu_items = pxe_menu_items + contents + "\n"
339
376
 
 
377
            grub_contents = self.write_pxe_file(filename=None, system=None,
 
378
                    profile=profile, distro=distro,
 
379
                    arch=distro.arch, include_header=False, format="grub")
 
380
            if grub_contents is not None:
 
381
                grub_menu_items = grub_menu_items + grub_contents + "\n"
 
382
 
 
383
 
340
384
        # image names towards the bottom
341
385
        for image in image_list:
342
386
            if os.path.exists(image.file):
343
 
                contents = self.write_pxe_file(None,None,None,None,image.arch,image=image)
 
387
                contents = self.write_pxe_file(filename=None, system=None,
 
388
                        profile=None, distro=None, arch=image.arch, image=image)
344
389
                if contents is not None:
345
390
                    pxe_menu_items = pxe_menu_items + contents + "\n"
346
391
 
354
399
                contents = self.write_memtest_pxe("/%s" % base)
355
400
                pxe_menu_items = pxe_menu_items + contents + "\n"
356
401
              
357
 
        # save the template.
 
402
        # Write the PXE menu:
358
403
        metadata = { "pxe_menu_items" : pxe_menu_items, "pxe_timeout_profile" : timeout_action}
359
404
        outfile = os.path.join(self.bootloc, "pxelinux.cfg", "default")
360
 
 
361
 
        # read the default template file
362
405
        template_src = open(os.path.join(self.settings.pxe_template_dir,"pxedefault.template"))
363
406
        template_data = template_src.read()
 
407
        self.templar.render(template_data, metadata, outfile, None)
 
408
        template_src.close()
364
409
 
365
 
        # write the template
 
410
        # Write the grub menu:
 
411
        metadata = { "grub_menu_items" : grub_menu_items }
 
412
        outfile = os.path.join(self.bootloc, "grub", "efidefault")
 
413
        template_src = open(os.path.join(self.settings.pxe_template_dir, "efidefault.template"))
 
414
        template_data = template_src.read()
366
415
        self.templar.render(template_data, metadata, outfile, None)
367
416
        template_src.close()
368
417
 
398
447
        return buffer
399
448
 
400
449
 
401
 
    def write_pxe_file(self,filename,system,profile,distro,arch,image=None,include_header=True,metadata=None):
 
450
    def write_pxe_file(self, filename, system, profile, distro, arch,
 
451
            image=None, include_header=True, metadata=None, format="pxe"):
402
452
        """
403
453
        Write a configuration file for the boot loader(s).
404
454
        More system-specific configuration may come in later, if so
410
460
        short-circuited and simpler.  All of it goes through the
411
461
        templating engine, see the templates in /etc/cobbler for
412
462
        more details
 
463
 
 
464
        Can be used for different formats, "pxe" (default) and "grub".
413
465
        """
414
466
 
415
467
        if arch is None:
463
515
        # ---
464
516
        # choose a template
465
517
        if system:
466
 
            if system.netboot_enabled:
467
 
                template = os.path.join(self.settings.pxe_template_dir,"pxesystem.template")
468
 
    
469
 
                if arch.startswith("s390"):
470
 
                    template = os.path.join(self.settings.pxe_template_dir,"pxesystem_s390x.template")
471
 
                elif arch == "ia64":
472
 
                    template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ia64.template")
473
 
                elif arch.startswith("ppc"):
474
 
                    template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ppc.template")
475
 
            else:
476
 
                # local booting on ppc requires removing the system-specific dhcpd.conf filename
477
 
                if arch is not None and arch.startswith("ppc"):
478
 
                    # Disable yaboot network booting for all interfaces on the system
479
 
                    for (name,interface) in system.interfaces.iteritems():
480
 
 
481
 
                        filename = "%s" % utils.get_config_filename(system, interface=name).lower()
482
 
 
483
 
                        # Remove symlink to the yaboot binary
484
 
                        f3 = os.path.join(self.bootloc, "ppc", filename)
485
 
                        if os.path.lexists(f3):
486
 
                            utils.rmfile(f3)
487
 
 
488
 
                        # Remove the interface-specific config file
489
 
                        f3 = os.path.join(self.bootloc, "etc", filename)
490
 
                        if os.path.lexists(f3):
491
 
                            utils.rmfile(f3)
492
 
 
493
 
                    # Yaboot/OF doesn't support booting locally once you've
494
 
                    # booted off the network, so nothing left to do
495
 
                    return None
496
 
                elif arch is not None and arch.startswith("s390"):
497
 
                    template = os.path.join(self.settings.pxe_template_dir,"pxelocal_s390x.template")
498
 
                elif arch is not None and arch.startswith("ia64"):
499
 
                    template = os.path.join(self.settings.pxe_template_dir,"pxelocal_ia64.template")
 
518
            if format == "grub":
 
519
                template = os.path.join(self.settings.pxe_template_dir, "grubsystem.template")
 
520
            else: # pxe
 
521
                if system.netboot_enabled:
 
522
                    template = os.path.join(self.settings.pxe_template_dir,"pxesystem.template")
 
523
        
 
524
                    if arch.startswith("s390"):
 
525
                        template = os.path.join(self.settings.pxe_template_dir,"pxesystem_s390x.template")
 
526
                    elif arch == "ia64":
 
527
                        template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ia64.template")
 
528
                    elif arch.startswith("ppc"):
 
529
                        template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ppc.template")
500
530
                else:
501
 
                    template = os.path.join(self.settings.pxe_template_dir,"pxelocal.template")
 
531
                    # local booting on ppc requires removing the system-specific dhcpd.conf filename
 
532
                    if arch is not None and arch.startswith("ppc"):
 
533
                        # Disable yaboot network booting for all interfaces on the system
 
534
                        for (name,interface) in system.interfaces.iteritems():
 
535
 
 
536
                            filename = "%s" % utils.get_config_filename(system, interface=name).lower()
 
537
 
 
538
                            # Remove symlink to the yaboot binary
 
539
                            f3 = os.path.join(self.bootloc, "ppc", filename)
 
540
                            if os.path.lexists(f3):
 
541
                                utils.rmfile(f3)
 
542
 
 
543
                            # Remove the interface-specific config file
 
544
                            f3 = os.path.join(self.bootloc, "etc", filename)
 
545
                            if os.path.lexists(f3):
 
546
                                utils.rmfile(f3)
 
547
 
 
548
                        # Yaboot/OF doesn't support booting locally once you've
 
549
                        # booted off the network, so nothing left to do
 
550
                        return None
 
551
                    elif arch is not None and arch.startswith("s390"):
 
552
                        template = os.path.join(self.settings.pxe_template_dir,"pxelocal_s390x.template")
 
553
                    elif arch is not None and arch.startswith("ia64"):
 
554
                        template = os.path.join(self.settings.pxe_template_dir,"pxelocal_ia64.template")
 
555
                    else:
 
556
                        template = os.path.join(self.settings.pxe_template_dir,"pxelocal.template")
502
557
        else:
503
558
            # not a system record, so this is a profile record
504
559
 
505
560
            if arch.startswith("s390"):
506
561
                template = os.path.join(self.settings.pxe_template_dir,"pxeprofile_s390x.template")
 
562
            elif format == "grub":
 
563
                template = os.path.join(self.settings.pxe_template_dir,"grubprofile.template")
507
564
            else:
508
565
                template = os.path.join(self.settings.pxe_template_dir,"pxeprofile.template")
509
566
 
510
 
        # now build the kernel command line
 
567
        # generate the kernel options and append line:
 
568
        kernel_options = self.build_kernel_options(system, profile, distro,
 
569
                image, arch, kickstart_path)
 
570
        metadata["kernel_options"] = kernel_options
 
571
 
 
572
        if metadata.has_key("initrd_path") and (not arch or arch not in ["ia64", "ppc", "ppc64"]):
 
573
            append_line = "append initrd=%s" % (metadata["initrd_path"])
 
574
        else:
 
575
            append_line = "append "
 
576
        append_line = "%s %s" % (append_line, kernel_options)
 
577
        if arch.startswith("ppc") or arch.startswith("s390"):
 
578
            # remove the prefix "append"
 
579
            # TODO: this looks like it's removing more than append, really
 
580
            # not sure what's up here...
 
581
            append_line = append_line[7:]
 
582
        metadata["append_line"] = append_line
 
583
 
 
584
        # store variables for templating
 
585
        metadata["menu_label"] = ""
 
586
        if profile:
 
587
            if not arch in [ "ia64", "ppc", "ppc64", "s390", "s390x" ]:
 
588
                metadata["menu_label"] = "MENU LABEL %s" % profile.name
 
589
                metadata["profile_name"] = profile.name
 
590
        elif image:
 
591
            metadata["menu_label"] = "MENU LABEL %s" % image.name
 
592
            metadata["profile_name"] = image.name
 
593
 
 
594
        if system:
 
595
            metadata["system_name"] = system.name
 
596
 
 
597
 
 
598
        # get the template
 
599
        if kernel_path is not None:
 
600
            template_fh = open(template)
 
601
            template_data = template_fh.read()
 
602
            template_fh.close()
 
603
        else:
 
604
            # this is something we can't PXE boot
 
605
            template_data = "\n"
 
606
 
 
607
        # save file and/or return results, depending on how called.
 
608
        buffer = self.templar.render(template_data, metadata, None)
 
609
        if filename is not None:
 
610
            self.logger.info("generating: %s" % filename)
 
611
            fd = open(filename, "w")
 
612
            fd.write(buffer)
 
613
            fd.close()
 
614
        return buffer
 
615
 
 
616
    def build_kernel_options(self, system, profile, distro, image, arch,
 
617
            kickstart_path):
 
618
        """
 
619
        Builds the full kernel options line.
 
620
        """
 
621
 
511
622
        if system is not None:
512
623
            blended = utils.blender(self.api, True, system)
513
624
        elif profile is not None:
514
625
            blended = utils.blender(self.api, True, profile)
515
626
        else:
516
627
            blended = utils.blender(self.api, True, image)
517
 
        kopts = blended.get("kernel_options","")
518
628
 
519
 
        # generate the append line
 
629
        kopts = blended.get("kernel_options", "")
520
630
        hkopts = utils.hash_to_string(kopts)
521
 
        if metadata.has_key("initrd_path") and (not arch or arch not in ["ia64", "ppc", "ppc64"]):
522
 
            append_line = "append initrd=%s %s" % (metadata["initrd_path"], hkopts)
523
 
        else:
524
 
            append_line = "append %s" % hkopts
525
 
 
 
631
        append_line = "%s" % hkopts
526
632
        # FIXME - the append_line length limit is architecture specific
527
 
        if len(append_line) >= 255 + len("append "):
 
633
        # TODO: why is this checked here, before we finish adding everything?
 
634
        if len(append_line) >= 255:
528
635
            self.logger.warning("warning: kernel option length exceeds 255")
529
636
 
530
637
        # kickstart path rewriting (get URLs for local files)
553
660
                # interface=bootif causes a failure
554
661
                append_line = append_line.replace("interface=bootif","")
555
662
            elif distro.breed == "vmware":
556
 
                append_line = "%s vmkopts=debugLogToSerial:1 mem=512M ks=%s" % (append_line, kickstart_path)
 
663
                append_line = "%s vmkopts=debugLogToSerial:1 mem=512M ks=%s" % \
 
664
                    (append_line, kickstart_path)
557
665
                # interface=bootif causes a failure
558
666
                append_line = append_line.replace("ksdevice=bootif","")
559
667
 
560
 
 
561
668
        if distro is not None and (distro.breed in [ "debian", "ubuntu" ]):
562
669
            # Hostname is required as a parameter, the one in the preseed is
563
670
            # not respected, so calculate if we have one here.
587
694
            # the existence of "stable" in the dists directory
588
695
            append_line = "%s suite=%s" % (append_line, distro.os_version)
589
696
 
590
 
        if arch.startswith("ppc") or arch.startswith("s390"):
591
 
            # remove the prefix "append"
592
 
            append_line = append_line[7:]
593
 
 
594
 
        # store variables for templating
595
 
        metadata["menu_label"] = ""
596
 
        if profile:
597
 
            if not arch in [ "ia64", "ppc", "ppc64", "s390", "s390x" ]:
598
 
                metadata["menu_label"] = "MENU LABEL %s" % profile.name
599
 
                metadata["profile_name"] = profile.name
600
 
        elif image:
601
 
            metadata["menu_label"] = "MENU LABEL %s" % image.name
602
 
            metadata["profile_name"] = image.name
603
 
 
604
 
        if system:
605
 
            metadata["system_name"] = system.name
606
 
 
607
 
        metadata["append_line"] = append_line
608
 
 
609
 
        # get the template
610
 
        if kernel_path is not None:
611
 
            template_fh = open(template)
612
 
            template_data = template_fh.read()
613
 
            template_fh.close()
614
 
        else:
615
 
            # this is something we can't PXE boot
616
 
            template_data = "\n"        
617
 
 
618
 
        # save file and/or return results, depending on how called.
619
 
        buffer = self.templar.render(template_data, metadata, None)
620
 
        if filename is not None:
621
 
            self.logger.info("generating: %s" % filename)
622
 
            fd = open(filename, "w")
623
 
            fd.write(buffer)
624
 
            fd.close()
625
 
        return buffer
 
697
        return append_line
626
698
 
627
699
    def write_templates(self,obj,write_file=False,path=None):
628
700
        """