~clint-fewbar/ubuntu/oneiric/cobbler/misc-fixes

« back to all changes in this revision

Viewing changes to .pc/56_ubuntu_arm_generate_pxe_files.patch/cobbler/pxegen.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez
  • Date: 2011-08-17 12:22:53 UTC
  • Revision ID: package-import@ubuntu.com-20110817122253-lofpjlfqv4f2fi1r
Tags: 2.1.0+git20110602-0ubuntu20
* debian/patches:
  - 49_ubuntu_add_arm_arch_support.patch: Updated to allow import of ARM
    arch. (LP: #827674)
  - 56_ubuntu_arm_generate_pxe_files.patch: Added. Add templates and
    generate files for PXE Menu creation for profile/systems. (LP: #827681)
    Additionally make sure hostname is set correctly.
* Add preseed for ARM architectures.
  - debian/ubuntu-server-arm.seed: Add
  - debian/cobbler.install: Install preseed.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
Builds out filesystem trees/data based on the object tree.
 
3
This is the code behind 'cobbler sync'.
 
4
 
 
5
Copyright 2006-2009, Red Hat, Inc
 
6
Michael DeHaan <mdehaan@redhat.com>
 
7
 
 
8
This program is free software; you can redistribute it and/or modify
 
9
it under the terms of the GNU General Public License as published by
 
10
the Free Software Foundation; either version 2 of the License, or
 
11
(at your option) any later version.
 
12
 
 
13
This program is distributed in the hope that it will be useful,
 
14
but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
GNU General Public License for more details.
 
17
 
 
18
You should have received a copy of the GNU General Public License
 
19
along with this program; if not, write to the Free Software
 
20
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
21
02110-1301  USA
 
22
"""
 
23
 
 
24
import os
 
25
import os.path
 
26
import shutil
 
27
import shlex
 
28
import time
 
29
import sys
 
30
import glob
 
31
import traceback
 
32
import errno
 
33
try:
 
34
    import subprocess as sub_process
 
35
except:
 
36
    import sub_process
 
37
import string
 
38
import socket
 
39
 
 
40
import utils
 
41
from cexceptions import *
 
42
import templar 
 
43
 
 
44
import item_distro
 
45
import item_profile
 
46
import item_repo
 
47
import item_system
 
48
import item_image
 
49
 
 
50
from utils import _
 
51
 
 
52
 
 
53
class PXEGen:
 
54
    """
 
55
    Handles building out PXE stuff
 
56
    """
 
57
 
 
58
    def __init__(self, config, logger):
 
59
        """
 
60
        Constructor
 
61
        """
 
62
        self.config      = config
 
63
        self.logger      = logger
 
64
        self.api         = config.api
 
65
        self.distros     = config.distros()
 
66
        self.profiles    = config.profiles()
 
67
        self.systems     = config.systems()
 
68
        self.settings    = config.settings()
 
69
        self.repos       = config.repos()
 
70
        self.images      = config.images()
 
71
        self.templar     = templar.Templar(config)
 
72
        self.bootloc     = utils.tftpboot_location()
 
73
        # FIXME: not used anymore, can remove?
 
74
        self.verbose     = False
 
75
 
 
76
    def copy_bootloaders(self):
 
77
        """
 
78
        Copy bootloaders to the configured tftpboot directory
 
79
        NOTE: we support different arch's if defined in
 
80
        /etc/cobbler/settings.
 
81
        """
 
82
        dst = self.bootloc
 
83
        grub_dst = os.path.join(dst, "grub")
 
84
        image_dst = os.path.join(dst, "images")
 
85
 
 
86
        # copy syslinux from one of two locations
 
87
        try:
 
88
            try:
 
89
                utils.copyfile_pattern('/var/lib/cobbler/loaders/pxelinux.0',
 
90
                        dst, api=self.api, cache=False, logger=self.logger)
 
91
                utils.copyfile_pattern('/var/lib/cobbler/loaders/menu.c32',
 
92
                        dst, api=self.api, cache=False, logger=self.logger)
 
93
 
 
94
                if self.settings.use_gpxe:
 
95
                    utils.copyfile_pattern('/var/lib/cobbler/loaders/gpxelinux.0',
 
96
                        dst, api=self.api, cache=False, logger=self.logger)
 
97
            except:
 
98
                utils.copyfile_pattern('/usr/share/syslinux/pxelinux.0',
 
99
                        dst, api=self.api, cache=False, logger=self.logger)
 
100
                utils.copyfile_pattern('/usr/share/syslinux/menu.c32',
 
101
                        dst, api=self.api, cache=False, logger=self.logger)
 
102
 
 
103
                if self.settings.use_gpxe:
 
104
                    utils.copyfile_pattern('/usr/share/syslinux/pxelinux.0',
 
105
                        dst, api=self.api, cache=False, logger=self.logger)
 
106
 
 
107
        except:
 
108
            utils.copyfile_pattern('/usr/lib/syslinux/pxelinux.0',
 
109
                    dst, api=self.api, cache=False, logger=self.logger)
 
110
            utils.copyfile_pattern('/usr/lib/syslinux/menu.c32',
 
111
                    dst, api=self.api, cache=False, logger=self.logger)
 
112
 
 
113
            if self.settings.use_gpxe:
 
114
                utils.copyfile_pattern('/usr/lib/syslinux/gpxelinux.0',
 
115
                    dst, api=self.api, cache=False, logger=self.logger)
 
116
 
 
117
        # copy memtest only if we find it
 
118
        utils.copyfile_pattern('/boot/memtest*', image_dst,
 
119
                require_match=False, api=self.api, cache=False, logger=self.logger)
 
120
 
 
121
        # copy elilo which we include for IA64 targets
 
122
        utils.copyfile_pattern('/var/lib/cobbler/loaders/elilo.efi', dst,
 
123
                require_match=False, api=self.api, cache=False, logger=self.logger)
 
124
 
 
125
        # copy yaboot which we include for PowerPC targets
 
126
        utils.copyfile_pattern('/var/lib/cobbler/loaders/yaboot', dst,
 
127
                require_match=False, api=self.api, cache=False, logger=self.logger)
 
128
 
 
129
        try:
 
130
            utils.copyfile_pattern('/usr/lib/syslinux/memdisk',
 
131
                    dst, api=self.api, cache=False, logger=self.logger)
 
132
        except:
 
133
            utils.copyfile_pattern('/usr/share/syslinux/memdisk', dst,
 
134
                    require_match=False, api=self.api, cache=False, logger=self.logger)
 
135
 
 
136
        # Copy grub EFI bootloaders if possible:
 
137
        utils.copyfile_pattern('/var/lib/cobbler/loaders/grub*.efi', grub_dst,
 
138
                require_match=False, api=self.api, cache=False, logger=self.logger)
 
139
 
 
140
 
 
141
    def copy_images(self):
 
142
        """
 
143
        Like copy_distros except for images.
 
144
        """
 
145
        errors = list()
 
146
        for i in self.images:
 
147
            try:
 
148
                self.copy_single_image_files(i)
 
149
            except CX, e:
 
150
                errors.append(e)
 
151
                self.logger.error(e.value)
 
152
 
 
153
        # FIXME: using logging module so this ends up in cobbler.log?
 
154
 
 
155
    def copy_single_distro_files(self, d, dirtree, symlink_ok):
 
156
        distros = os.path.join(dirtree, "images")
 
157
        distro_dir = os.path.join(distros,d.name)
 
158
        utils.mkdir(distro_dir)
 
159
        kernel = utils.find_kernel(d.kernel) # full path
 
160
        initrd = utils.find_initrd(d.initrd) # full path
 
161
 
 
162
        if kernel is None:
 
163
            raise CX("kernel not found: %(file)s, distro: %(distro)s" % 
 
164
                    { "file" : d.kernel, "distro" : d.name })
 
165
 
 
166
        if initrd is None:
 
167
            raise CX("initrd not found: %(file)s, distro: %(distro)s" % 
 
168
                    { "file" : d.initrd, "distro" : d.name })
 
169
 
 
170
        # Kernels referenced by remote URL are passed through to koan directly,
 
171
        # no need for copying the kernel locally:
 
172
        if not utils.file_is_remote(kernel):
 
173
            b_kernel = os.path.basename(kernel)
 
174
            dst1 = os.path.join(distro_dir, b_kernel)
 
175
            utils.linkfile(kernel, dst1, symlink_ok=symlink_ok, 
 
176
                    api=self.api, logger=self.logger)
 
177
 
 
178
        if not utils.file_is_remote(initrd):
 
179
            b_initrd = os.path.basename(initrd)
 
180
            dst2 = os.path.join(distro_dir, b_initrd)
 
181
            utils.linkfile(initrd, dst2, symlink_ok=symlink_ok, 
 
182
                    api=self.api, logger=self.logger)
 
183
 
 
184
    def copy_single_image_files(self, img):
 
185
        images_dir = os.path.join(self.bootloc, "images2")
 
186
        filename = img.file 
 
187
        if not os.path.exists(filename):
 
188
            # likely for virtual usage, cannot use
 
189
            return
 
190
        if not os.path.exists(images_dir):
 
191
            os.makedirs(images_dir)
 
192
        basename = os.path.basename(img.file)
 
193
        newfile = os.path.join(images_dir, img.name)
 
194
        utils.linkfile(filename, newfile, api=self.api, logger=self.logger)
 
195
        return True
 
196
 
 
197
    def write_all_system_files(self, system):
 
198
 
 
199
        profile = system.get_conceptual_parent()
 
200
        if profile is None:
 
201
            raise CX("system %(system)s references a missing profile %(profile)s" % { "system" : system.name, "profile" : system.profile})
 
202
 
 
203
        distro = profile.get_conceptual_parent()
 
204
        image_based = False
 
205
        image = None
 
206
        if distro is None:
 
207
            if profile.COLLECTION_TYPE == "profile":
 
208
               raise CX("profile %(profile)s references a missing distro %(distro)s" % { "profile" : system.profile, "distro" : profile.distro})
 
209
            else:
 
210
               image_based = True
 
211
               image = profile
 
212
 
 
213
        # hack: s390 generates files per system not per interface
 
214
        if not image_based and distro.arch.startswith("s390"):
 
215
            # Always write a system specific _conf and _parm file
 
216
            f2 = os.path.join(self.bootloc, "s390x", "s_%s" % system.name)
 
217
            cf = "%s_conf" % f2
 
218
            pf = "%s_parm" % f2
 
219
            template_cf = open("/etc/cobbler/pxe/s390x_conf.template")
 
220
            template_pf = open("/etc/cobbler/pxe/s390x_parm.template")
 
221
            blended = utils.blender(self.api, True, system)
 
222
            self.templar.render(template_cf, blended, cf)
 
223
            # FIXME: profiles also need this data!
 
224
            # FIXME: the _conf and _parm files are limited to 80 characters in length 
 
225
            ipaddress = socket.gethostbyname_ex(blended["http_server"])[2][0]
 
226
            kickstart_path = "http://%s/cblr/svc/op/ks/system/%s" % (ipaddress, system.name)
 
227
            # gather default kernel_options and default kernel_options_s390x
 
228
            kopts = blended.get("kernel_options","")
 
229
            hkopts = shlex.split(utils.hash_to_string(kopts))
 
230
            blended["kickstart_expanded"] = "ks=%s" % kickstart_path
 
231
            blended["kernel_options"] = hkopts
 
232
            self.templar.render(template_pf, blended, pf)
 
233
 
 
234
            # Write system specific zPXE file
 
235
            if system.is_management_supported():
 
236
                self.write_pxe_file(f2, system, profile, distro, distro.arch)
 
237
            else:
 
238
                # ensure the file doesn't exist
 
239
                utils.rmfile(f2)
 
240
            return
 
241
 
 
242
        # generate one record for each described NIC ..
 
243
 
 
244
        for (name,interface) in system.interfaces.iteritems():
 
245
 
 
246
            ip = interface["ip_address"]
 
247
 
 
248
            f1 = utils.get_config_filename(system, interface=name)
 
249
            if f1 is None:
 
250
                self.logger.warning("invalid interface recorded for system (%s,%s)" % (system.name,name))
 
251
                continue;
 
252
 
 
253
            if image_based:
 
254
                working_arch = image.arch
 
255
            else:
 
256
                working_arch = distro.arch
 
257
 
 
258
            if working_arch is None:
 
259
                raise "internal error, invalid arch supplied"
 
260
 
 
261
            # for tftp only ...
 
262
            grub_path = None
 
263
            if working_arch in [ "i386", "x86", "x86_64", "standard"]:
 
264
                if self.settings.use_gpxe:
 
265
                    # pxelinux wants a file named $name under pxelinux.cfg
 
266
                    f2 = os.path.join(self.bootloc, "gpxe", f1)
 
267
                else:
 
268
                    # pxelinux wants a file named $name under pxelinux.cfg
 
269
                    f2 = os.path.join(self.bootloc, "pxelinux.cfg", f1)
 
270
 
 
271
                # Only generating grub menus for these arch's:
 
272
                grub_path = os.path.join(self.bootloc, "grub", f1.upper())
 
273
 
 
274
            elif working_arch == "ia64":
 
275
                # elilo expects files to be named "$name.conf" in the root
 
276
                # and can not do files based on the MAC address
 
277
                if ip is not None and ip != "":
 
278
                    self.logger.warning("Warning: Itanium system object (%s) needs an IP address to PXE" % system.name)
 
279
 
 
280
                filename = "%s.conf" % utils.get_config_filename(system,interface=name)
 
281
                f2 = os.path.join(self.bootloc, filename)
 
282
 
 
283
            elif working_arch.startswith("ppc"):
 
284
                # Determine filename for system-specific yaboot.conf
 
285
                filename = "%s" % utils.get_config_filename(system, interface=name).lower()
 
286
                f2 = os.path.join(self.bootloc, "etc", filename)
 
287
 
 
288
                # Link to the yaboot binary
 
289
                f3 = os.path.join(self.bootloc, "ppc", filename)
 
290
                if os.path.lexists(f3):
 
291
                    utils.rmfile(f3)
 
292
                os.symlink("../yaboot", f3)
 
293
            else:
 
294
                continue 
 
295
 
 
296
            if system.is_management_supported():
 
297
                if not image_based:
 
298
                    self.write_pxe_file(f2, system, profile, distro, working_arch)
 
299
                    if grub_path:
 
300
                        self.write_pxe_file(grub_path, system, profile, distro, 
 
301
                                working_arch, format="grub")
 
302
                else:
 
303
                    self.write_pxe_file(f2, system, None, None, working_arch, image=profile)
 
304
            else:
 
305
                # ensure the file doesn't exist
 
306
                utils.rmfile(f2)
 
307
                if grub_path:
 
308
                    utils.rmfile(grub_path)
 
309
 
 
310
    def make_pxe_menu(self):
 
311
        self.make_s390_pseudo_pxe_menu()
 
312
        self.make_actual_pxe_menu() 
 
313
 
 
314
    def make_s390_pseudo_pxe_menu(self):
 
315
        s390path = os.path.join(self.bootloc, "s390x")
 
316
        if not os.path.exists(s390path):
 
317
            utils.mkdir(s390path)
 
318
        profile_list = [profile for profile in self.profiles]
 
319
        image_list = [image for image in self.images]
 
320
        def sort_name(a,b):
 
321
           return cmp(a.name,b.name)
 
322
        profile_list.sort(sort_name)
 
323
        image_list.sort(sort_name)
 
324
        listfile = open(os.path.join(s390path, "profile_list"),"w+")
 
325
        for profile in profile_list:
 
326
            distro = profile.get_conceptual_parent()
 
327
            if distro is None:
 
328
                raise CX("profile is missing distribution: %s, %s" % (profile.name, profile.distro))
 
329
            if distro.arch.startswith("s390"):
 
330
                listfile.write("%s\n" % profile.name)
 
331
                f2 = os.path.join(self.bootloc, "s390x", "p_%s" % profile.name)
 
332
                self.write_pxe_file(f2,None,profile,distro,distro.arch)
 
333
                cf = "%s_conf" % f2
 
334
                pf = "%s_parm" % f2
 
335
                template_cf = open("/etc/cobbler/pxe/s390x_conf.template")
 
336
                template_pf = open("/etc/cobbler/pxe/s390x_parm.template")
 
337
                blended = utils.blender(self.api, True, profile)
 
338
                self.templar.render(template_cf, blended, cf)
 
339
                # FIXME: profiles also need this data!
 
340
                # FIXME: the _conf and _parm files are limited to 80 characters in length 
 
341
                ipaddress = socket.gethostbyname_ex(blended["http_server"])[2][0]
 
342
                kickstart_path = "http://%s/cblr/svc/op/ks/profile/%s" % (ipaddress, profile.name)
 
343
                # gather default kernel_options and default kernel_options_s390x
 
344
                kopts = blended.get("kernel_options","")
 
345
                hkopts = shlex.split(utils.hash_to_string(kopts))
 
346
                blended["kickstart_expanded"] = "ks=%s" % kickstart_path
 
347
                blended["kernel_options"] = hkopts
 
348
                self.templar.render(template_pf, blended, pf)
 
349
 
 
350
        listfile.close()
 
351
 
 
352
    def make_actual_pxe_menu(self):
 
353
        """
 
354
        Generates both pxe and grub boot menus.
 
355
        """
 
356
        # only do this if there is NOT a system named default.
 
357
        default = self.systems.find(name="default")
 
358
 
 
359
        if default is None:
 
360
            timeout_action = "local"
 
361
        else:
 
362
            timeout_action = default.profile
 
363
 
 
364
        # sort the profiles
 
365
        profile_list = [profile for profile in self.profiles]
 
366
        def sort_name(a,b):
 
367
           return cmp(a.name,b.name)
 
368
        profile_list.sort(sort_name)
 
369
 
 
370
        # sort the images
 
371
        image_list = [image for image in self.images]
 
372
        image_list.sort(sort_name)
 
373
 
 
374
        # Build out menu items and append each to this master list, used for
 
375
        # the default menus:
 
376
        pxe_menu_items = ""
 
377
        grub_menu_items = ""
 
378
 
 
379
        # For now, profiles are the only items we want grub EFI boot menu entries for:
 
380
        for profile in profile_list:
 
381
            if not profile.enable_menu:
 
382
               # This profile has been excluded from the menu
 
383
               continue
 
384
            distro = profile.get_conceptual_parent()
 
385
            # xen distros can be ruled out as they won't boot
 
386
            if distro.name.find("-xen") != -1 or distro.arch not in ["i386", "x86_64"]:
 
387
                # can't PXE Xen
 
388
                continue
 
389
            contents = self.write_pxe_file(filename=None, system=None,
 
390
                    profile=profile, distro=distro,
 
391
                    arch=distro.arch, include_header=False)
 
392
            if contents is not None:
 
393
                pxe_menu_items = pxe_menu_items + contents + "\n"
 
394
 
 
395
            grub_contents = self.write_pxe_file(filename=None, system=None,
 
396
                    profile=profile, distro=distro,
 
397
                    arch=distro.arch, include_header=False, format="grub")
 
398
            if grub_contents is not None:
 
399
                grub_menu_items = grub_menu_items + grub_contents + "\n"
 
400
 
 
401
 
 
402
        # image names towards the bottom
 
403
        for image in image_list:
 
404
            if os.path.exists(image.file):
 
405
                contents = self.write_pxe_file(filename=None, system=None,
 
406
                        profile=None, distro=None, arch=image.arch, image=image)
 
407
                if contents is not None:
 
408
                    pxe_menu_items = pxe_menu_items + contents + "\n"
 
409
 
 
410
        # if we have any memtest files in images, make entries for them
 
411
        # after we list the profiles
 
412
        memtests = glob.glob(self.bootloc + "/memtest*")
 
413
        if len(memtests) > 0:
 
414
            pxe_menu_items = pxe_menu_items + "\n\n"
 
415
            for memtest in glob.glob(self.bootloc + '/memtest*'):
 
416
                base = os.path.basename(memtest)
 
417
                contents = self.write_memtest_pxe("/%s" % base)
 
418
                pxe_menu_items = pxe_menu_items + contents + "\n"
 
419
              
 
420
        # Write the PXE menu:
 
421
        metadata = { "pxe_menu_items" : pxe_menu_items, "pxe_timeout_profile" : timeout_action}
 
422
        if self.settings.use_gpxe:
 
423
            outfile = os.path.join(self.bootloc, "gpxe", "default")
 
424
            gpxe_menu = os.path.join(self.bootloc, "gpxe", "menu.gpxe")
 
425
            gpxe_template = os.path.join(self.settings.pxe_template_dir,"gpxemenu.template")
 
426
            utils.copyfile_pattern(gpxe_template, gpxe_menu, api=self.api, 
 
427
                                cache=False, logger=self.logger)
 
428
        else:
 
429
            outfile = os.path.join(self.bootloc, "pxelinux.cfg", "default")
 
430
        template_src = open(os.path.join(self.settings.pxe_template_dir,"pxedefault.template"))
 
431
        template_data = template_src.read()
 
432
        self.templar.render(template_data, metadata, outfile, None)
 
433
        template_src.close()
 
434
 
 
435
        # Write the grub menu:
 
436
        metadata = { "grub_menu_items" : grub_menu_items }
 
437
        outfile = os.path.join(self.bootloc, "grub", "efidefault")
 
438
        template_src = open(os.path.join(self.settings.pxe_template_dir, "efidefault.template"))
 
439
        template_data = template_src.read()
 
440
        self.templar.render(template_data, metadata, outfile, None)
 
441
        template_src.close()
 
442
 
 
443
    def write_memtest_pxe(self,filename):
 
444
        """
 
445
        Write a configuration file for memtest
 
446
        """
 
447
 
 
448
        # FIXME: this should be handled via "cobbler image add" now that it is available,
 
449
        # though it would be nice if there was a less-manual way to add those as images.
 
450
 
 
451
        # just some random variables
 
452
        template = None
 
453
        metadata = {}
 
454
        buffer = ""
 
455
 
 
456
        template = os.path.join(self.settings.pxe_template_dir,"pxeprofile.template")
 
457
 
 
458
        # store variables for templating
 
459
        metadata["menu_label"] = "MENU LABEL %s" % os.path.basename(filename)
 
460
        metadata["profile_name"] = os.path.basename(filename)
 
461
        metadata["kernel_path"] = "/%s" % os.path.basename(filename)
 
462
        metadata["initrd_path"] = ""
 
463
        metadata["append_line"] = ""
 
464
 
 
465
        # get the template
 
466
        template_fh = open(template)
 
467
        template_data = template_fh.read()
 
468
        template_fh.close()
 
469
 
 
470
        # return results
 
471
        buffer = self.templar.render(template_data, metadata, None)
 
472
        return buffer
 
473
 
 
474
 
 
475
    def write_pxe_file(self, filename, system, profile, distro, arch,
 
476
            image=None, include_header=True, metadata=None, format="pxe"):
 
477
        """
 
478
        Write a configuration file for the boot loader(s).
 
479
        More system-specific configuration may come in later, if so
 
480
        that would appear inside the system object in api.py
 
481
 
 
482
        NOTE: relevant to tftp and pseudo-PXE (s390) only
 
483
 
 
484
        ia64 is mostly the same as syslinux stuff, s390 is a bit
 
485
        short-circuited and simpler.  All of it goes through the
 
486
        templating engine, see the templates in /etc/cobbler for
 
487
        more details
 
488
 
 
489
        Can be used for different formats, "pxe" (default) and "grub".
 
490
        """
 
491
 
 
492
        if arch is None:
 
493
            raise "missing arch"
 
494
 
 
495
        if image and not os.path.exists(image.file):
 
496
            return None  # nfs:// URLs or something, can't use for TFTP
 
497
 
 
498
        if metadata is None:
 
499
            metadata = {}
 
500
        # ---
 
501
        # just some random variables
 
502
        template = None
 
503
        buffer = ""
 
504
 
 
505
        # ---
 
506
        kickstart_path = None
 
507
        kernel_path = None
 
508
        initrd_path = None
 
509
        img_path = None
 
510
 
 
511
        if image is None: 
 
512
            if self.settings.use_gpxe:
 
513
                # not image based, it's something normalish
 
514
                img_path = os.path.join("/images",distro.name)
 
515
 
 
516
                blended = utils.blender(self.api, True, system)
 
517
                ipaddress = socket.gethostbyname_ex(bleneded["http_server"])[2][0]
 
518
                kernel_path = "http://%s/cobbler/images%s" % (ipaddress, distro.kernel)
 
519
                initrd_path = "http://%s/cobbler/images%s" % (ipaddress, distro.initrd)
 
520
            else:
 
521
                # not image based, it's something normalish
 
522
                img_path = os.path.join("/images",distro.name)
 
523
                kernel_path = os.path.join("/images",distro.name,os.path.basename(distro.kernel))
 
524
                initrd_path = os.path.join("/images",distro.name,os.path.basename(distro.initrd))
 
525
        
 
526
            # Find the kickstart if we inherit from another profile
 
527
            if system:
 
528
                blended = utils.blender(self.api, True, system)
 
529
            else:
 
530
                blended = utils.blender(self.api, True, profile)
 
531
            kickstart_path = blended.get("kickstart","")
 
532
            
 
533
        else:
 
534
            # this is an image we are making available, not kernel+initrd
 
535
            if image.image_type == "direct":
 
536
                kernel_path = os.path.join("/images2",image.name)
 
537
            elif image.image_type == "memdisk":
 
538
                kernel_path = "/memdisk"
 
539
                initrd_path = os.path.join("/images2",image.name)
 
540
            else:
 
541
                # CD-ROM ISO or virt-clone image? We can't PXE boot it.
 
542
                kernel_path = None
 
543
                initrd_path = None
 
544
 
 
545
        if img_path is not None and not metadata.has_key("img_path"):
 
546
            metadata["img_path"] = img_path
 
547
        if kernel_path is not None and not metadata.has_key("kernel_path"):
 
548
            metadata["kernel_path"] = kernel_path
 
549
        if initrd_path is not None and not metadata.has_key("initrd_path"):
 
550
            metadata["initrd_path"] = initrd_path
 
551
 
 
552
        # ---
 
553
        # choose a template
 
554
        if system:
 
555
            if format == "grub":
 
556
                template = os.path.join(self.settings.pxe_template_dir, "grubsystem.template")
 
557
            else: # pxe
 
558
                if system.netboot_enabled:
 
559
                    template = os.path.join(self.settings.pxe_template_dir,"pxesystem.template")
 
560
 
 
561
                    if arch.startswith("s390"):
 
562
                        template = os.path.join(self.settings.pxe_template_dir,"pxesystem_s390x.template")
 
563
                    elif arch == "ia64":
 
564
                        template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ia64.template")
 
565
                    elif arch.startswith("ppc"):
 
566
                        template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ppc.template")
 
567
                    elif distro.os_version.startswith("esxi"):
 
568
                        # ESXi uses a very different pxe method, using more files than
 
569
                        # a standard kickstart and different options - so giving it a dedicated
 
570
                        # PXE template makes more sense than shoe-horning it into the existing
 
571
                        # templates
 
572
                        template = os.path.join(self.settings.pxe_template_dir,"pxesystem_esxi.template")
 
573
                else:
 
574
                    # local booting on ppc requires removing the system-specific dhcpd.conf filename
 
575
                    if arch is not None and arch.startswith("ppc"):
 
576
                        # Disable yaboot network booting for all interfaces on the system
 
577
                        for (name,interface) in system.interfaces.iteritems():
 
578
 
 
579
                            filename = "%s" % utils.get_config_filename(system, interface=name).lower()
 
580
 
 
581
                            # Remove symlink to the yaboot binary
 
582
                            f3 = os.path.join(self.bootloc, "ppc", filename)
 
583
                            if os.path.lexists(f3):
 
584
                                utils.rmfile(f3)
 
585
 
 
586
                            # Remove the interface-specific config file
 
587
                            f3 = os.path.join(self.bootloc, "etc", filename)
 
588
                            if os.path.lexists(f3):
 
589
                                utils.rmfile(f3)
 
590
 
 
591
                        # Yaboot/OF doesn't support booting locally once you've
 
592
                        # booted off the network, so nothing left to do
 
593
                        return None
 
594
                    elif arch is not None and arch.startswith("s390"):
 
595
                        template = os.path.join(self.settings.pxe_template_dir,"pxelocal_s390x.template")
 
596
                    elif arch is not None and arch.startswith("ia64"):
 
597
                        template = os.path.join(self.settings.pxe_template_dir,"pxelocal_ia64.template")
 
598
                    else:
 
599
                        template = os.path.join(self.settings.pxe_template_dir,"pxelocal.template")
 
600
        else:
 
601
            # not a system record, so this is a profile record
 
602
 
 
603
            if arch.startswith("s390"):
 
604
                template = os.path.join(self.settings.pxe_template_dir,"pxeprofile_s390x.template")
 
605
            elif format == "grub":
 
606
                template = os.path.join(self.settings.pxe_template_dir,"grubprofile.template")
 
607
            elif distro.os_version.startswith("esxi"):
 
608
                # ESXi uses a very different pxe method, see comment above in the system section
 
609
                template = os.path.join(self.settings.pxe_template_dir,"pxeprofile_esxi.template")
 
610
            else:
 
611
                template = os.path.join(self.settings.pxe_template_dir,"pxeprofile.template")
 
612
 
 
613
 
 
614
        if kernel_path is not None:
 
615
            metadata["kernel_path"] = kernel_path
 
616
        if initrd_path is not None:
 
617
            metadata["initrd_path"] = initrd_path
 
618
 
 
619
        # generate the kernel options and append line:
 
620
        kernel_options = self.build_kernel_options(system, profile, distro,
 
621
                image, arch, kickstart_path)
 
622
        metadata["kernel_options"] = kernel_options
 
623
 
 
624
        if distro.os_version.startswith("esxi") and filename is not None:
 
625
            append_line = "BOOTIF=%s" % (os.path.basename(filename))
 
626
        elif metadata.has_key("initrd_path") and (not arch or arch not in ["ia64", "ppc", "ppc64"]):
 
627
            append_line = "append initrd=%s" % (metadata["initrd_path"])
 
628
        else:
 
629
            append_line = "append "
 
630
        append_line = "%s%s" % (append_line, kernel_options)
 
631
        if arch.startswith("ppc") or arch.startswith("s390"):
 
632
            # remove the prefix "append"
 
633
            # TODO: this looks like it's removing more than append, really
 
634
            # not sure what's up here...
 
635
            append_line = append_line[7:]
 
636
        metadata["append_line"] = append_line
 
637
 
 
638
        # store variables for templating
 
639
        metadata["menu_label"] = ""
 
640
        if profile:
 
641
            if not arch in [ "ia64", "ppc", "ppc64", "s390", "s390x" ]:
 
642
                metadata["menu_label"] = "MENU LABEL %s" % profile.name
 
643
                metadata["profile_name"] = profile.name
 
644
        elif image:
 
645
            metadata["menu_label"] = "MENU LABEL %s" % image.name
 
646
            metadata["profile_name"] = image.name
 
647
 
 
648
        if system:
 
649
            metadata["system_name"] = system.name
 
650
 
 
651
 
 
652
        # get the template
 
653
        if kernel_path is not None:
 
654
            template_fh = open(template)
 
655
            template_data = template_fh.read()
 
656
            template_fh.close()
 
657
        else:
 
658
            # this is something we can't PXE boot
 
659
            template_data = "\n"
 
660
 
 
661
        # save file and/or return results, depending on how called.
 
662
        buffer = self.templar.render(template_data, metadata, None)
 
663
        if filename is not None:
 
664
            self.logger.info("generating: %s" % filename)
 
665
            fd = open(filename, "w")
 
666
            fd.write(buffer)
 
667
            fd.close()
 
668
        return buffer
 
669
 
 
670
    def build_kernel_options(self, system, profile, distro, image, arch,
 
671
            kickstart_path):
 
672
        """
 
673
        Builds the full kernel options line.
 
674
        """
 
675
 
 
676
        if system is not None:
 
677
            blended = utils.blender(self.api, False, system)
 
678
        elif profile is not None:
 
679
            blended = utils.blender(self.api, False, profile)
 
680
        else:
 
681
            blended = utils.blender(self.api, False, image)
 
682
 
 
683
        append_line = ""
 
684
        kopts = blended.get("kernel_options", dict())
 
685
        # support additional initrd= entries in kernel options.
 
686
        if "initrd" in kopts:
 
687
            append_line = ",%s" % kopts.pop("initrd")
 
688
        hkopts = utils.hash_to_string(kopts)
 
689
        append_line = "%s %s" % (append_line, hkopts)
 
690
        # FIXME - the append_line length limit is architecture specific
 
691
        # TODO: why is this checked here, before we finish adding everything?
 
692
        if len(append_line) >= 255:
 
693
            self.logger.warning("warning: kernel option length exceeds 255")
 
694
 
 
695
        # kickstart path rewriting (get URLs for local files)
 
696
        if kickstart_path is not None and kickstart_path != "":
 
697
 
 
698
            # FIXME: need to make shorter rewrite rules for these URLs
 
699
 
 
700
            ipaddress = socket.gethostbyname_ex(blended["http_server"])[2][0]
 
701
            if system is not None and kickstart_path.startswith("/"):
 
702
                kickstart_path = "http://%s/cblr/svc/op/ks/system/%s" % (ipaddress, system.name)
 
703
            elif kickstart_path.startswith("/"):
 
704
                kickstart_path = "http://%s/cblr/svc/op/ks/profile/%s" % (ipaddress, profile.name)
 
705
 
 
706
            if distro.breed is None or distro.breed == "redhat":
 
707
                append_line = "%s ks=%s" % (append_line, kickstart_path)
 
708
            elif distro.breed == "suse":
 
709
                append_line = "%s autoyast=%s" % (append_line, kickstart_path)
 
710
            elif distro.breed == "debian" or distro.breed == "ubuntu":
 
711
                append_line = "%s auto url=%s" % (append_line, kickstart_path)
 
712
 
 
713
                # rework kernel options for debian distros
 
714
                translations = { 'ksdevice':"interface" , 'lang':"locale" }
 
715
                for k,v in translations.iteritems():
 
716
                    append_line = append_line.replace("%s="%k,"%s="%v)
 
717
 
 
718
                # interface=bootif causes a failure
 
719
                append_line = append_line.replace("interface=bootif","")
 
720
            elif distro.breed == "vmware":
 
721
                if distro.os_version.find("esxi") != -1:
 
722
                    # ESXi is very picky, it's easier just to redo the
 
723
                    # entire append line here since 
 
724
                    append_line = " ks=%s %s" % (kickstart_path, hkopts)
 
725
                    # ESXi likes even fewer options, so we remove them too
 
726
                    append_line = append_line.replace("kssendmac","")
 
727
                else:
 
728
                    append_line = "%s vmkopts=debugLogToSerial:1 mem=512M ks=%s" % \
 
729
                        (append_line, kickstart_path)
 
730
                # interface=bootif causes a failure
 
731
                append_line = append_line.replace("ksdevice=bootif","")
 
732
 
 
733
        if distro is not None and (distro.breed in [ "debian", "ubuntu" ]):
 
734
            # Hostname is required as a parameter, the one in the preseed is
 
735
            # not respected, so calculate if we have one here.
 
736
            # We're trying: first part of FQDN in hostname field, then system
 
737
            # name, then profile name.
 
738
            # In Ubuntu, this is at least used for the volume group name when
 
739
            # using LVM.
 
740
            domain = "local.lan"
 
741
            if system is not None:
 
742
                if system.hostname is not None:
 
743
                    # If this is a FQDN, grab the first bit
 
744
                    hostname = system.hostname.split(".")[0]
 
745
                    _domain = system.hostname.split(".")[1:]
 
746
                    if _domain:
 
747
                        domain = ".".join( _domain )
 
748
                else:
 
749
                    hostname = system.name
 
750
            else:
 
751
                hostname = profile.name
 
752
 
 
753
            # At least for debian deployments configured for DHCP networking
 
754
            # this values are not used, but specifying here avoids questions
 
755
            append_line = "%s hostname=%s" % (append_line, hostname)
 
756
            append_line = "%s domain=%s" % (append_line, domain)
 
757
 
 
758
            # A similar issue exists with suite name, as installer requires
 
759
            # the existence of "stable" in the dists directory
 
760
            append_line = "%s suite=%s" % (append_line, distro.os_version)
 
761
 
 
762
        return append_line
 
763
 
 
764
    def write_templates(self,obj,write_file=False,path=None):
 
765
        """
 
766
        A semi-generic function that will take an object
 
767
        with a template_files hash {source:destiation}, and 
 
768
        generate a rendered file.  The write_file option 
 
769
        allows for generating of the rendered output without
 
770
        actually creating any files.
 
771
 
 
772
        The return value is a hash of the destination file
 
773
        names (after variable substitution is done) and the
 
774
        data in the file.
 
775
        """
 
776
 
 
777
        results = {}
 
778
 
 
779
        try:
 
780
           templates = obj.template_files
 
781
        except:
 
782
           return results
 
783
 
 
784
        blended = utils.blender(self.api, False, obj)
 
785
 
 
786
        ksmeta = blended.get("ks_meta",{})
 
787
        try:
 
788
            del blended["ks_meta"]
 
789
        except:
 
790
            pass
 
791
        blended.update(ksmeta) # make available at top level
 
792
 
 
793
        templates = blended.get("template_files",{})
 
794
        try:
 
795
            del blended["template_files"]
 
796
        except:
 
797
            pass
 
798
        blended.update(templates) # make available at top level
 
799
 
 
800
        (success, templates) = utils.input_string_or_hash(templates)
 
801
 
 
802
        if not success:
 
803
            return results
 
804
 
 
805
 
 
806
        for template in templates.keys():
 
807
            dest = templates[template]
 
808
            if dest is None:
 
809
               continue
 
810
 
 
811
            # Run the source and destination files through 
 
812
            # templar first to allow for variables in the path 
 
813
            template = self.templar.render(template, blended, None).strip()
 
814
            dest     = self.templar.render(dest, blended, None).strip()
 
815
            # Get the path for the destination output
 
816
            dest_dir = os.path.dirname(dest)
 
817
 
 
818
            # If we're looking for a single template, skip if this ones
 
819
            # destination is not it.
 
820
            if not path is None and path != dest:
 
821
               continue
 
822
 
 
823
            # If we are writing output to a file, force all templated 
 
824
            # configs into the rendered directory to ensure that a user 
 
825
            # granted cobbler privileges via sudo can't overwrite 
 
826
            # arbitrary system files (This also makes cleanup easier).
 
827
            if os.path.isabs(dest_dir):
 
828
               if write_file:
 
829
                   raise CX(" warning: template destination (%s) is an absolute path, skipping." % dest_dir)
 
830
                   continue
 
831
            else:
 
832
                dest_dir = os.path.join(self.settings.webdir, "rendered", dest_dir)
 
833
                dest = os.path.join(dest_dir, os.path.basename(dest))
 
834
                if not os.path.exists(dest_dir):
 
835
                    utils.mkdir(dest_dir)
 
836
 
 
837
            # Check for problems
 
838
            if not os.path.exists(template):
 
839
               raise CX("template source %s does not exist" % template)
 
840
               continue
 
841
            elif write_file and not os.path.isdir(dest_dir):
 
842
               raise CX("template destination (%s) is invalid" % dest_dir)
 
843
               continue
 
844
            elif write_file and os.path.exists(dest): 
 
845
               raise CX("template destination (%s) already exists" % dest)
 
846
               continue
 
847
            elif write_file and os.path.isdir(dest):
 
848
               raise CX("template destination (%s) is a directory" % dest)
 
849
               continue
 
850
            elif template == "" or dest == "": 
 
851
               raise CX("either the template source or destination was blank (unknown variable used?)" % dest)
 
852
               continue
 
853
            
 
854
            template_fh = open(template)
 
855
            template_data = template_fh.read()
 
856
            template_fh.close()
 
857
 
 
858
            buffer = self.templar.render(template_data, blended, None)
 
859
            results[dest] = buffer
 
860
 
 
861
            if write_file:
 
862
                self.logger.info("generating: %s" % dest)
 
863
                fd = open(dest, "w")
 
864
                fd.write(buffer)
 
865
                fd.close()
 
866
 
 
867
        return results
 
868