コード例 #1
0
ファイル: netboot.py プロジェクト: xhernandez/beaker
def configure_yaboot(fqdn, kernel_options, basedir, yaboot_symlink=True):
    """
    Creates bootloader files for Yaboot

    <get_tftp_root()>/etc/<pxe_basename(fqdn).lower()>
    <get_tftp_root()>/ppc/<pxe_basename(fqdn).lower()> -> ../yaboot
    """
    yaboot_conf_dir = os.path.join(basedir, 'etc')
    makedirs_ignore(yaboot_conf_dir, mode=0o755)

    basename = pxe_basename(fqdn).lower()
    # XXX I don't think multiple initrds are supported?
    config = '''init-message="Beaker scheduled job for %s"
timeout=80
delay=10
default=linux

image=/images/%s/kernel
    label=linux
    initrd=/images/%s/initrd
    append="%s netboot_method=yaboot"
''' % (fqdn, fqdn, fqdn, kernel_options)
    logger.debug('Writing yaboot config for %s as %s', fqdn, basename)
    with atomically_replaced_file(os.path.join(yaboot_conf_dir,
                                               basename)) as f:
        f.write(config)
    if yaboot_symlink:
        ppc_dir = os.path.join(basedir, 'ppc')
        makedirs_ignore(ppc_dir, mode=0o755)
        logger.debug('Creating yaboot symlink for %s as %s', fqdn, basename)
        atomic_symlink('../yaboot', os.path.join(ppc_dir, basename))
コード例 #2
0
ファイル: netboot.py プロジェクト: ustbgaofan/beaker
def configure_yaboot(fqdn, kernel_options):
    """
    Creates bootloader files for Yaboot

    <get_tftp_root()>/etc/<pxe_basename(fqdn).lower()>
    <get_tftp_root()>/ppc/<pxe_basename(fqdn).lower()> -> ../yaboot
    """
    yaboot_conf_dir = os.path.join(get_tftp_root(), 'etc')
    makedirs_ignore(yaboot_conf_dir, mode=0755)
    ppc_dir = os.path.join(get_tftp_root(), 'ppc')
    makedirs_ignore(ppc_dir, mode=0755)

    basename = pxe_basename(fqdn).lower()
    # XXX I don't think multiple initrds are supported?
    config = '''init-message="Beaker scheduled job for %s"
timeout=80
delay=10
default=linux

image=/images/%s/kernel
    label=linux
    initrd=/images/%s/initrd
    append="%s netboot_method=yaboot"
''' % (fqdn, fqdn, fqdn, kernel_options)
    logger.debug('Writing yaboot config for %s as %s', fqdn, basename)
    with atomically_replaced_file(os.path.join(yaboot_conf_dir, basename)) as f:
        f.write(config)
    logger.debug('Creating yaboot symlink for %s as %s', fqdn, basename)
    atomic_symlink('../yaboot', os.path.join(ppc_dir, basename))
コード例 #3
0
ファイル: netboot.py プロジェクト: xhernandez/beaker
def configure_efigrub(fqdn, kernel_options, basedir):
    """
    Creates bootloader file for EFI GRUB

    <get_tftp_root()>/grub/<pxe_basename(fqdn)>

    Also ensures images symlink exists:

    <get_tftp_root()>/grub/images -> <get_tftp_root()>/images
    """
    grub_dir = os.path.join(basedir, 'grub')
    makedirs_ignore(grub_dir, mode=0o755)
    atomic_symlink('../images', os.path.join(grub_dir, 'images'))

    basename = pxe_basename(fqdn)
    # Unfortunately the initrd kernel arg needs some special handling. It can be
    # supplied from the Beaker side (e.g. a system-specific driver disk) but we
    # also supply the main initrd here which we have fetched from the distro.
    initrd, kernel_options = extract_arg('initrd=', kernel_options)
    if initrd:
        initrd = ' '.join(['/images/%s/initrd' % fqdn] + initrd.split(','))
    else:
        initrd = '/images/%s/initrd' % fqdn
    config = '''default 0
timeout 10
title Beaker scheduled job for %s
    root (nd)
    kernel /images/%s/kernel %s netboot_method=efigrub
    initrd %s
''' % (fqdn, fqdn, kernel_options, initrd)
    logger.debug('Writing grub config for %s as %s', fqdn, basename)
    with atomically_replaced_file(os.path.join(grub_dir, basename)) as f:
        f.write(config)
コード例 #4
0
ファイル: netboot.py プロジェクト: ShaolongHu/beaker
def configure_efigrub(fqdn, kernel_options, basedir):
    """
    Creates bootloader file for EFI GRUB

    <get_tftp_root()>/grub/<pxe_basename(fqdn)>

    Also ensures images symlink exists:

    <get_tftp_root()>/grub/images -> <get_tftp_root()>/images
    """
    grub_dir = os.path.join(basedir, 'grub')
    makedirs_ignore(grub_dir, mode=0755)
    atomic_symlink('../images', os.path.join(grub_dir, 'images'))

    basename = pxe_basename(fqdn)
    # Unfortunately the initrd kernel arg needs some special handling. It can be
    # supplied from the Beaker side (e.g. a system-specific driver disk) but we
    # also supply the main initrd here which we have fetched from the distro.
    initrd, kernel_options = extract_arg('initrd=', kernel_options)
    if initrd:
        initrd = ' '.join(['/images/%s/initrd' % fqdn] + initrd.split(','))
    else:
        initrd = '/images/%s/initrd' % fqdn
    config = '''default 0
timeout 10
title Beaker scheduled job for %s
    root (nd)
    kernel /images/%s/kernel %s netboot_method=efigrub
    initrd %s
''' % (fqdn, fqdn, kernel_options, initrd)
    logger.debug('Writing grub config for %s as %s', fqdn, basename)
    with atomically_replaced_file(os.path.join(grub_dir, basename)) as f:
        f.write(config)
コード例 #5
0
ファイル: netboot.py プロジェクト: ustbgaofan/beaker
def configure_efigrub(fqdn, kernel_options):
    """
    Creates bootloader file for EFI GRUB

    <get_tftp_root()>/grub/<pxe_basename(fqdn)>

    Also ensures images symlink exists:

    <get_tftp_root()>/grub/images -> <get_tftp_root()>/images
    """
    grub_dir = os.path.join(get_tftp_root(), 'grub')
    makedirs_ignore(grub_dir, mode=0755)
    atomic_symlink('../images', os.path.join(grub_dir, 'images'))

    basename = pxe_basename(fqdn)
    initrd, kernel_options = extract_initrd_arg(kernel_options)
    if initrd:
        initrd = ' '.join(['/images/%s/initrd' % fqdn] + initrd.split(','))
    else:
        initrd = '/images/%s/initrd' % fqdn
    config = '''default 0
timeout 10
title Beaker scheduled job for %s
    root (nd)
    kernel /images/%s/kernel %s netboot_method=efigrub
    initrd %s
''' % (fqdn, fqdn, kernel_options, initrd)
    logger.debug('Writing grub config for %s as %s', fqdn, basename)
    with atomically_replaced_file(os.path.join(grub_dir, basename)) as f:
        f.write(config)
コード例 #6
0
def configure_ppc64(fqdn, kernel_options):
    """
    Calls configure_grub2() to create the machine config files and symlink
    to the grub2 boot loader:

    <get_tftp_root()>/ppc/grub.cfg-<pxe_basename(fqdn)>
    <get_tftp_root()>/ppc/grub.cfg
    <get_tftp_root()>/ppc/<pxe_basename(fqdn).lower()-grub2> -> ../boot/grub2/powerpc-ieee1275/core.elf

    # Hacks, see the note below
    <get_tftp_root()>grub.cfg-<pxe_basename(fqdn)>
    <get_tftp_root()>boot/grub2/grub.cfg-<pxe_basename(fqdn)>


    """
    ppc_dir = os.path.join(get_tftp_root(), 'ppc')
    makedirs_ignore(ppc_dir, mode=0755)

    grub_cfg_file = os.path.join(ppc_dir, "grub.cfg-%s" % pxe_basename(fqdn))
    logger.debug('Writing grub2/ppc64 config for %s as %s', fqdn,
                 grub_cfg_file)
    configure_grub2(fqdn, ppc_dir, grub_cfg_file, kernel_options)

    # The following two hacks are to accommodate the differences in behavior
    # among various power configurations and grub2 versions
    # Remove them once they are sorted out (also see the relevant
    # code in clear_ppc64())
    # Ref: https://bugzilla.redhat.com/show_bug.cgi?id=1144106

    # hack for older grub
    grub2_conf_dir = os.path.join(get_tftp_root(), 'boot', 'grub2')
    makedirs_ignore(grub2_conf_dir, mode=0755)
    grub_cfg_file = os.path.join(grub2_conf_dir,
                                 "grub.cfg-%s" % pxe_basename(fqdn))
    logger.debug('Writing grub2/ppc64 config for %s as %s', fqdn,
                 grub_cfg_file)
    configure_grub2(fqdn, grub2_conf_dir, grub_cfg_file, kernel_options)

    # hack for power VMs
    grub_cfg_file = os.path.join(get_tftp_root(),
                                 "grub.cfg-%s" % pxe_basename(fqdn))
    logger.debug('Writing grub2/ppc64 config for %s as %s', fqdn,
                 grub_cfg_file)
    configure_grub2(fqdn, ppc_dir, grub_cfg_file, kernel_options)

    grub2_symlink = '%s-grub2' % pxe_basename(fqdn).lower()
    logger.debug('Creating grub2 symlink for %s as %s', fqdn, grub2_symlink)
    atomic_symlink('../boot/grub2/powerpc-ieee1275/core.elf',
                   os.path.join(ppc_dir, grub2_symlink))
コード例 #7
0
ファイル: netboot.py プロジェクト: ShaolongHu/beaker
def configure_netbootloader_directory(fqdn, kernel_options, netbootloader):
    tftp_root = get_tftp_root()
    if netbootloader:
        fqdn_dir = os.path.join(tftp_root, 'bootloader', fqdn)
        logger.debug('Creating custom netbootloader tree for %s in %s', fqdn, fqdn_dir)
        makedirs_ignore(fqdn_dir, mode=0755)
        grub2_cfg_file = os.path.join(fqdn_dir, 'grub.cfg-%s'%pxe_basename(fqdn))
        configure_grub2(fqdn, fqdn_dir, grub2_cfg_file, kernel_options)
        configure_pxelinux(fqdn, kernel_options, fqdn_dir)
        configure_yaboot(fqdn, kernel_options, fqdn_dir, yaboot_symlink=False)

        # create the symlink to the specified bootloader w.r.t the tftp_root
        if netbootloader.startswith('/'):
            netbootloader = netbootloader.lstrip('/')
        atomic_symlink(os.path.join('../../', netbootloader), os.path.join(fqdn_dir, 'image'))
コード例 #8
0
ファイル: netboot.py プロジェクト: joyxu/beaker
def configure_netbootloader_directory(fqdn, kernel_options, netbootloader):
    tftp_root = get_tftp_root()
    if netbootloader:
        fqdn_dir = os.path.join(tftp_root, 'bootloader', fqdn)
        logger.debug('Creating custom netbootloader tree for %s in %s', fqdn,
                     fqdn_dir)
        makedirs_ignore(fqdn_dir, mode=0755)
        grub2_cfg_file = os.path.join(fqdn_dir,
                                      'grub.cfg-%s' % pxe_basename(fqdn))
        configure_grub2(fqdn, fqdn_dir, grub2_cfg_file, kernel_options)
        configure_pxelinux(fqdn, kernel_options, fqdn_dir)
        configure_yaboot(fqdn, kernel_options, fqdn_dir, yaboot_symlink=False)

        # create the symlink to the specified bootloader w.r.t the tftp_root
        if netbootloader.startswith('/'):
            netbootloader = netbootloader.lstrip('/')
        atomic_symlink(os.path.join('../../', netbootloader),
                       os.path.join(fqdn_dir, 'image'))
コード例 #9
0
ファイル: netboot.py プロジェクト: ShaolongHu/beaker
def configure_ppc64(fqdn, kernel_options, basedir):
    """
    Calls configure_grub2() to create the machine config files and symlink
    to the grub2 boot loader:

    <get_tftp_root()>/ppc/grub.cfg-<pxe_basename(fqdn)>
    <get_tftp_root()>/ppc/grub.cfg
    <get_tftp_root()>/ppc/<pxe_basename(fqdn).lower()-grub2> -> ../boot/grub2/powerpc-ieee1275/core.elf

    # Hacks, see the note below
    <get_tftp_root()>grub.cfg-<pxe_basename(fqdn)>
    <get_tftp_root()>boot/grub2/grub.cfg-<pxe_basename(fqdn)>


    """
    ppc_dir = os.path.join(basedir, 'ppc')
    makedirs_ignore(ppc_dir, mode=0755)

    grub_cfg_file = os.path.join(ppc_dir, "grub.cfg-%s" % pxe_basename(fqdn))
    logger.debug('Writing grub2/ppc64 config for %s as %s', fqdn, grub_cfg_file)
    configure_grub2(fqdn, ppc_dir, grub_cfg_file, kernel_options)

    # The following two hacks are to accommodate the differences in behavior
    # among various power configurations and grub2 versions
    # Remove them once they are sorted out (also see the relevant
    # code in clear_ppc64())
    # Ref: https://bugzilla.redhat.com/show_bug.cgi?id=1144106

    # hack for older grub
    grub2_conf_dir = os.path.join(basedir, 'boot', 'grub2')
    makedirs_ignore(grub2_conf_dir, mode=0755)
    grub_cfg_file = os.path.join(grub2_conf_dir, "grub.cfg-%s" % pxe_basename(fqdn))
    logger.debug('Writing grub2/ppc64 config for %s as %s', fqdn, grub_cfg_file)
    configure_grub2(fqdn, grub2_conf_dir, grub_cfg_file, kernel_options)

    # hack for power VMs
    grub_cfg_file = os.path.join(basedir, "grub.cfg-%s" % pxe_basename(fqdn))
    logger.debug('Writing grub2/ppc64 config for %s as %s', fqdn, grub_cfg_file)
    configure_grub2(fqdn, ppc_dir, grub_cfg_file, kernel_options)

    grub2_symlink = '%s-grub2' % pxe_basename(fqdn).lower()
    logger.debug('Creating grub2 symlink for %s as %s', fqdn, grub2_symlink)
    atomic_symlink('../boot/grub2/powerpc-ieee1275/core.elf',
                   os.path.join(ppc_dir, grub2_symlink))
コード例 #10
0
ファイル: netboot.py プロジェクト: ustbgaofan/beaker
def configure_aarch64(fqdn, kernel_options):
    """
    Creates PXE bootloader files for aarch64 Linux

    <get_tftp_root()>/pxelinux/grub.cfg-<pxe_basename(fqdn)>

    Also ensures <fqdn>.efi is symlinked to bootaa64.efi

    Specify filename "pxelinux/<fqdn>.efi"; in your dhcpd.conf file
    We remove this when the install is done.  This allows efi
    to fall through to the next boot entry.
    """
    pxe_base = os.path.join(get_tftp_root(), 'pxelinux')
    makedirs_ignore(pxe_base, mode=0755)
    basename = "grub.cfg-%s" % pxe_basename(fqdn)
    config = '''  linux  ../images/%s/kernel %s
  initrd ../images/%s/initrd
  devicetree /pxelinux/apm-mustang.dtb
  boot
''' % (fqdn, kernel_options, fqdn)
    logger.debug('Writing aarch64 config for %s as %s', fqdn, basename)
    with atomically_replaced_file(os.path.join(pxe_base, basename)) as f:
        f.write(config)
    atomic_symlink('bootaa64.efi', os.path.join(pxe_base, "%s.efi" % fqdn))
コード例 #11
0
    for obs in obsolete_tree_ids:
        shutil.rmtree(os.path.join(tftp_root, 'distrotrees', obs), ignore_errors=True)

    # Fetch images for all the distro trees first.
    print 'Fetching images for all the distro trees'
    distro_trees = _get_all_images(tftp_root, distro_trees)

    x86_distrotrees = [distro for distro in distro_trees if distro['arch'] in ['x86_64', 'i386']]
    print 'Generating PXELINUX menus for %s distro trees' % len(x86_distrotrees)
    makedirs_ignore(os.path.join(tftp_root, 'pxelinux.cfg'), mode=0755)
    pxe_menu = atomically_replaced_file(os.path.join(tftp_root, 'pxelinux.cfg', 'beaker_menu'))
    write_menu(pxe_menu, u'pxelinux-menu', x86_distrotrees)

    print 'Generating EFI GRUB menus for %s distro trees' % len(x86_distrotrees)
    makedirs_ignore(os.path.join(tftp_root, 'grub'), mode=0755)
    atomic_symlink('../distrotrees', os.path.join(tftp_root, 'grub', 'distrotrees'))
    efi_grub_menu = atomically_replaced_file(os.path.join(tftp_root, 'grub', 'efidefault'))
    write_menu(efi_grub_menu, u'efi-grub-menu', x86_distrotrees)

    print 'Generating GRUB2 menus for x86 EFI for %s distro trees' % len(x86_distrotrees)
    makedirs_ignore(os.path.join(tftp_root, 'boot', 'grub2'), mode=0755)
    x86_grub2_menu = atomically_replaced_file(os.path.join(tftp_root, 'boot', 'grub2',
            'beaker_menu_x86.cfg'))
    write_menu(x86_grub2_menu, u'grub2-menu', x86_distrotrees)

    # XXX: would be nice if we can find a good time to move this into boot/grub2
    aarch64_distrotrees = [distro for distro in distro_trees if distro['arch'] == 'aarch64']
    if aarch64_distrotrees:
        print 'Generating GRUB2 menus for aarch64 for %s distro trees' % len(aarch64_distrotrees)
        makedirs_ignore(os.path.join(tftp_root, 'aarch64'), mode=0755)
        aarch64_menu = atomically_replaced_file(os.path.join(tftp_root, 'aarch64', 'beaker_menu.cfg'))
コード例 #12
0
def write_menus(tftp_root, tags, xml_filter):
    conf = get_conf()

    # The order of steps for cleaning images is important,
    # to avoid races and to avoid deleting stuff we shouldn't:
    # first read the directory,
    # then fetch the list of trees,
    # and then remove any which aren't in the list.
    try:
        existing_tree_ids = os.listdir(os.path.join(tftp_root, 'distrotrees'))
    except OSError as e:
        if e.errno != errno.ENOENT:
            raise
        existing_tree_ids = []

    proxy = xmlrpclib.ServerProxy('http://localhost:8000', allow_none=True)
    distro_trees = proxy.get_distro_trees({
        'arch': ['x86_64', 'i386', 'aarch64', 'ppc64', 'ppc64le'],
        'tags': tags,
        'xml': xml_filter,
    })
    current_tree_ids = set(str(dt['distro_tree_id'])
                           for dt in distro_trees)
    obsolete_tree_ids = set(existing_tree_ids).difference(current_tree_ids)
    print('Removing images for %s obsolete distro trees' % len(obsolete_tree_ids))
    for obs in obsolete_tree_ids:
        shutil.rmtree(os.path.join(tftp_root, 'distrotrees', obs), ignore_errors=True)

    # Fetch images for all the distro trees first.
    print('Fetching images for all the distro trees')
    distro_trees = _get_all_images(tftp_root, distro_trees)

    x86_distrotrees = [distro for distro in distro_trees if distro['arch'] in ['x86_64', 'i386']]
    print('Generating PXELINUX menus for %s distro trees' % len(x86_distrotrees))
    makedirs_ignore(os.path.join(tftp_root, 'pxelinux.cfg'), mode=0o755)
    pxe_menu = atomically_replaced_file(os.path.join(tftp_root, 'pxelinux.cfg', 'beaker_menu'))
    write_menu(pxe_menu, u'pxelinux-menu', x86_distrotrees)

    x86_efi_distrotrees = [distro for distro in distro_trees if distro['arch'] == 'x86_64']
    # Regardless of any filtering options selected by the admin, we always
    # filter out certain distros which are known not to have EFI support. This
    # is a space saving measure for the EFI GRUB menu, which can't be nested so
    # we try to keep it as small possible.
    x86_efi_distrotrees = [distro for distro in x86_efi_distrotrees
                           if not re.match(conf['EFI_EXCLUDED_OSMAJORS_REGEX'],
                                           distro['distro_osmajor'])]

    print('Generating EFI GRUB menus for %s distro trees' % len(x86_efi_distrotrees))
    makedirs_ignore(os.path.join(tftp_root, 'grub'), mode=0o755)
    atomic_symlink('../distrotrees', os.path.join(tftp_root, 'grub', 'distrotrees'))
    efi_grub_menu = atomically_replaced_file(os.path.join(tftp_root, 'grub', 'efidefault'))
    write_menu(efi_grub_menu, u'efi-grub-menu', x86_efi_distrotrees)

    print('Generating GRUB2 menus for x86 EFI for %s distro trees' % len(x86_efi_distrotrees))
    makedirs_ignore(os.path.join(tftp_root, 'boot', 'grub2'), mode=0o755)
    x86_grub2_menu = atomically_replaced_file(os.path.join(tftp_root, 'boot', 'grub2',
                                                           'beaker_menu_x86.cfg'))
    write_menu(x86_grub2_menu, u'grub2-menu', x86_efi_distrotrees)

    ppc64_distrotrees = [distro for distro in distro_trees if distro['arch'] == 'ppc64']
    if ppc64_distrotrees:
        print('Generating GRUB2 menus for PPC64 EFI for %s distro trees' % len(ppc64_distrotrees))
        makedirs_ignore(os.path.join(tftp_root, 'boot', 'grub2'), mode=0o755)
        ppc64_grub2_menu = atomically_replaced_file(os.path.join(tftp_root, 'boot', 'grub2',
                                                                 'beaker_menu_ppc64.cfg'))
        write_menu(ppc64_grub2_menu, u'grub2-menu', ppc64_distrotrees)

    ppc64le_distrotrees = [distro for distro in distro_trees if distro['arch'] == 'ppc64le']
    if ppc64le_distrotrees:
        print('Generating GRUB2 menus for PPC64LE EFI for %s distro trees' % len(ppc64_distrotrees))
        makedirs_ignore(os.path.join(tftp_root, 'boot', 'grub2'), mode=0o755)
        ppc64le_grub2_menu = atomically_replaced_file(os.path.join(tftp_root, 'boot', 'grub2',
                                                                   'beaker_menu_ppc64le.cfg'))
        write_menu(ppc64le_grub2_menu, u'grub2-menu', ppc64le_distrotrees)

    # XXX: would be nice if we can find a good time to move this into boot/grub2
    aarch64_distrotrees = [distro for distro in distro_trees if distro['arch'] == 'aarch64']
    if aarch64_distrotrees:
        print('Generating GRUB2 menus for aarch64 for %s distro trees' % len(aarch64_distrotrees))
        makedirs_ignore(os.path.join(tftp_root, 'aarch64'), mode=0o755)
        aarch64_menu = atomically_replaced_file(
            os.path.join(tftp_root, 'aarch64', 'beaker_menu.cfg'))
        write_menu(aarch64_menu, u'grub2-menu', aarch64_distrotrees)
コード例 #13
0
ファイル: pxemenu.py プロジェクト: walbon/beaker
    print 'Generating PXELINUX menus for %s distro trees' % len(x86_distrotrees)
    makedirs_ignore(os.path.join(tftp_root, 'pxelinux.cfg'), mode=0755)
    pxe_menu = atomically_replaced_file(os.path.join(tftp_root, 'pxelinux.cfg', 'beaker_menu'))
    write_menu(pxe_menu, u'pxelinux-menu', x86_distrotrees)

    x86_efi_distrotrees = [distro for distro in distro_trees if distro['arch'] == 'x86_64']
    # Regardless of any filtering options selected by the admin, we always 
    # filter out certain distros which are known not to have EFI support. This 
    # is a space saving measure for the EFI GRUB menu, which can't be nested so 
    # we try to keep it as small possible.
    x86_efi_distrotrees = [distro for distro in x86_efi_distrotrees
            if not re.match(conf['EFI_EXCLUDED_OSMAJORS_REGEX'], distro['distro_osmajor'])]

    print 'Generating EFI GRUB menus for %s distro trees' % len(x86_efi_distrotrees)
    makedirs_ignore(os.path.join(tftp_root, 'grub'), mode=0755)
    atomic_symlink('../distrotrees', os.path.join(tftp_root, 'grub', 'distrotrees'))
    efi_grub_menu = atomically_replaced_file(os.path.join(tftp_root, 'grub', 'efidefault'))
    write_menu(efi_grub_menu, u'efi-grub-menu', x86_efi_distrotrees)

    print 'Generating GRUB2 menus for x86 EFI for %s distro trees' % len(x86_efi_distrotrees)
    makedirs_ignore(os.path.join(tftp_root, 'boot', 'grub2'), mode=0755)
    x86_grub2_menu = atomically_replaced_file(os.path.join(tftp_root, 'boot', 'grub2',
            'beaker_menu_x86.cfg'))
    write_menu(x86_grub2_menu, u'grub2-menu', x86_efi_distrotrees)

    # XXX: would be nice if we can find a good time to move this into boot/grub2
    aarch64_distrotrees = [distro for distro in distro_trees if distro['arch'] == 'aarch64']
    if aarch64_distrotrees:
        print 'Generating GRUB2 menus for aarch64 for %s distro trees' % len(aarch64_distrotrees)
        makedirs_ignore(os.path.join(tftp_root, 'aarch64'), mode=0755)
        aarch64_menu = atomically_replaced_file(os.path.join(tftp_root, 'aarch64', 'beaker_menu.cfg'))