PSARC 2012/074 UEFI/GRUB2/Large Disk Boot (UGLDB) Install Phase I:
7155522 Install needs to support UEFI & GPT (phase I)
7154007 Distro Constructor support for UEFI/GRUB2/Large Disk Boot Project
7047968 installadm should utilize pybootmgmt
7126748 installadm create-client generates unwanted error message if DHCP
service is not configured
--- a/usr/src/cmd/distro_const/checkpoints/create_iso.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/distro_const/checkpoints/create_iso.py Fri Mar 23 04:40:57 2012 -0700
@@ -21,7 +21,7 @@
#
#
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
#
""" create_iso.py - Generates an ISO media based on the prepared package image
@@ -59,6 +59,7 @@
self.media_dir = None
self.bios_eltorito = None
+ self.uefi_eltorito = None
self.add_timestamp = None
self.distro_name = None
self.dist_iso = None
@@ -111,7 +112,9 @@
if dc_pers_dict:
self.dc_pers_dict = dc_pers_dict[0].data_dict
if self.arch == 'i386':
+ # We must have at least a bios eltorito. UEFI may be provided.
self.bios_eltorito = self.dc_pers_dict["bios-eltorito-img"]
+ self.uefi_eltorito = self.dc_pers_dict.get("uefi-eltorito-img")
except KeyError, msg:
raise RuntimeError("Error retrieving a value from the DOC: " + \
str(msg))
@@ -142,14 +145,26 @@
# set the mkisofs_cmd
if self.arch == "i386":
- self.mkisofs_cmd = [cli.MKISOFS, "-quiet", "-o", self.dist_iso,
- "-b", self.bios_eltorito, "-c",
- ".catalog", "-no-emul-boot",
- "-boot-load-size", "4", "-boot-info-table",
- "-N", "-l", "-R", "-U", "-allow-multidot",
- "-no-iso-translate", "-cache-inodes", "-d",
- "-D", "-volset", self.volsetid, "-V",
- self.partial_distro_name, self.pkg_img_path]
+ # The mkisofs args are split up like this to allow optional
+ # inclusion/exclusion of the uefi_args. The order of the boot
+ # arguments for making a hybrid BIOS/UEFI bootabable is very
+ # rigid and any reordering will almost certainly result in an
+ # unbootable image.
+ base_args = [cli.MKISOFS, "-quiet", "-o", self.dist_iso]
+ ctlg_args = ["-c", ".catalog"]
+ bios_args = ["-b", self.bios_eltorito, "-no-emul-boot",
+ "-boot-load-size", "4", "-boot-info-table"]
+ uefi_args = ["-eltorito-platform", "efi",
+ "-eltorito-alt-boot",
+ "-b", self.uefi_eltorito, "-no-emul-boot"]
+ misc_args = ["-N", "-l", "-R", "-U", "-allow-multidot",
+ "-no-iso-translate", "-cache-inodes", "-d",
+ "-D", "-volset", self.volsetid, "-V",
+ self.partial_distro_name, self.pkg_img_path]
+ self.mkisofs_cmd = base_args + ctlg_args + bios_args
+ if self.uefi_eltorito is not None:
+ self.mkisofs_cmd.extend(uefi_args)
+ self.mkisofs_cmd.extend(misc_args)
else:
self.mkisofs_cmd = [cli.MKISOFS, "-quiet", "-o", self.dist_iso,
"-G", bootblock, "-B", "...", "-N", "-l",
--- a/usr/src/cmd/distro_const/checkpoints/pre_pkg_img_mod.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/distro_const/checkpoints/pre_pkg_img_mod.py Fri Mar 23 04:40:57 2012 -0700
@@ -21,7 +21,7 @@
#
#
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
#
""" pre_pkg_img_mod.py - Customizations to the package image area before
@@ -170,11 +170,14 @@
"etc/inet/hosts"))
def save_menu_lst(self):
- """ class method to save the original /boot/grub/menu.lst file
+ """ class method to save the original /boot/grub/menu.lst file if it
+ exists. It will not exist on GRUB2 based images so it is silently
+ ignored if not present.
"""
-
+
save_list = ["boot/grub/menu.lst"]
- self.save_files_directories(save_list)
+ if os.path.exists(os.path.join(self.pkg_img_path, save_list[0])):
+ self.save_files_directories(save_list)
def save_files_directories(self, save_list=None):
""" class method for saving key files and directories for restoration
--- a/usr/src/cmd/install-tools/usbcopy Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/install-tools/usbcopy Fri Mar 23 04:40:57 2012 -0700
@@ -19,8 +19,7 @@
#
# CDDL HEADER END
#
-# Copyright 2010 Sun Microsystems, Inc. All rights reserved.
-# Use is subject to license terms.
+# Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
#
# Make sure we get the right versions of the commands
@@ -30,6 +29,94 @@
# formats on us
export LC_ALL=C
+function cleanup {
+
+ {
+
+ # unmounting, and uninstalling the lofi'ed devices and
+ # cleanup temporary files.
+
+ for devs in $(mount -p | grep $usbdev | awk '{print $1}'); do
+ umount -f $devs
+ done
+
+ lofiadm "${img}" && \
+ lofiadm -d "${img}"
+
+ if [[ -d "${usbmntpt}" ]]; then
+ rmdir "${usbmntpt}"
+ fi
+
+ rm -f $fdi
+ } > /dev/null 2>&1
+
+}
+
+function error_handler {
+
+ print -u2 "\nError:\n"
+ print -u2 -r -- "${progname}: $*"
+ cleanup
+
+ exit 1
+
+}
+
+function setup_grub2 {
+
+ # There are pre-built MBR stage 1 and UEFI system images on the USB image.
+ # These need to be used instead of trying to construct it ourselves in order
+ # to avoid grub2 version mismatches.
+ mbrimg="$usbmntpt/boot/mbr.img"
+ uefiimg="$usbmntpt/boot/uefi.img"
+
+ # cat the (MBR) usb.img and (ESP) uefi.img to the USB device.
+ cat $mbrimg $uefiimg > $dev || \
+ error_handler "Failed to write ${mbrimg} and ${uefiimg} to ${dev}"
+
+ # Read the geometry of the physical device
+ while read pcyl ncyl acyl bcyl nhead nsect secsiz; do
+ devblksize=$secsiz
+ devblocks=$(($ncyl * $nhead * $nsect))
+ done < <(fdisk -G $dev | grep -v "*")
+
+ # Size the embedded MBR image
+ mbrnbytes=$(ls -lL $mbrimg | awk '{print $5}')
+ mbrnblocks=$(($mbrnbytes / $devblksize))
+
+ # Size the uefi.img
+ uefinbytes=$(ls -lL $uefiimg | awk '{print $5}')
+ uefinblocks=$(($uefinbytes / $devblksize))
+
+ # fdisk table should end up looking like this.
+ # The first line/partition for the ESP is already embedded in the mbr image
+ # that we just cat'd to the disk. So we need to add the solaris partition.
+ # id active bhead bsect bcyl ehead esect ecyl rsect numsect
+ # 239 0 0 0 0 0 0 0 mbrnblocks(a) uefinblocks(b)
+ # 191 0 0 0 0 0 0 0 (a)+(b):(c) diskblocks-(c)
+ solstart=$(($mbrnblocks + $uefinblocks))
+ solnblocks=$(($devblocks - $solstart))
+ addsolpart="191:0:0:0:0:0:0:0:$solstart:$solnblocks"
+ fdisk -A $addsolpart $dev
+
+ # fdisk is stupid and overwrites the MBR boot code region with mboot so we
+ # have to rewrite the first 440 bytes of the MBR image to restore what it
+ # so helpfully overwrote.
+ dd if=$mbrimg bs=1 count=440 oseek=0 of=$bdev 2>/dev/null|| \
+ error_handler "Failed to restore MBR stage1 image from ${usbimg}"
+
+}
+
+
+function setup_legacy_grub {
+
+ # Install grub stages to usb. The USB image is already lofi mounted so no need
+ # to mount the USB device.
+ echo Installing grub to USB device $s0cdev
+ installgrub -mf $usbmntpt/boot/grub/stage1 $usbmntpt/boot/grub/stage2 $s0cdev > /dev/null
+
+}
+
if [ `/usr/bin/id -u` != "0" ] ; then
echo "Must be root to run."
exit 1
@@ -46,7 +133,7 @@
exit 1
fi
# Image size (in MB)
-ibytes=$(ls -l $img | awk '{print $5}')
+ibytes=$(ls -lL $img | awk '{print $5}')
isz=$((ibytes >> 20))
#nawk script to output the details of plugged in USB drives
@@ -99,6 +186,7 @@
done
dev=${log[$choice]}
+bdev=${dev/rdsk/dsk}
s0cdev=${dev/p0/s0}
s0bdev=${s0cdev/rdsk/dsk}
mountdev=${s0bdev/s0}
@@ -159,24 +247,41 @@
# Drop in config file to ensure HAL ignores the disk, otherwise it will attempt
# to mount it after the partition table is rewritten
fdi=/etc/hal/fdi/preprobe/10osvendor/99-usbcopy.fdi
-trap "rm -f $fdi" SIGTERM EXIT
+trap cleanup SIGTERM EXIT
cat >$fdi << EOF
<?xml version="1.0" encoding="UTF-8"?>
<deviceinfo version="0.2">
<device>
- <match key="block.device" string="$s0bdev">
+ <match key="block.device" string="$bdev">
<merge key="info.ignore" type="bool">true</merge>
</match>
</device>
</deviceinfo>
EOF
-# Install fdisk table with Solaris using entire disk, default VTOC
-fdisk -B $dev
+# lofi mount the USB image file into a temp dir
+usbmntpt="$(mktemp -d)"
+usbdev="$(lofiadm -a "${img}")" || \
+ error_handler "Failed to lofiadm ${img}"
+mount -F ufs -o ro $usbdev $usbmntpt || \
+ error_handler "Failed to mount ${usbdev} on ${usbmntpt}"
+
+haslegacygrub=0
+if [ -f $usbmntpt/boot/grub/grub.cfg ] ; then
+ # GRUB2
+ setup_grub2
+elif [ -f $usbmntpt/boot/grub/menu.lst ] ; then
+ # Legacy GRUB
+ # Install fdisk table with Solaris using entire disk, default VTOC
+ haslegacygrub=1
+ fdisk -B $dev
+else
+ error_handler "Failed to find boot loader configuration"
+fi
# Now create root partition. We want to find number of cylinders in backup
-# partition from label created by fdisk -B and then generate root partition
+# partition from label created by fdisk and then generate root partition
# using whole disk minus cylinder 1
acyls=$(prtvtoc $dev | awk '/accessible/{print $2}')
cyls=$((acyls - 1))
@@ -245,16 +350,12 @@
echo "Finished $isz MB in $SECONDS seconds ($speed)"
echo "$retries block(s) re-written due to verification failure"
-# Mount image
-mnt=/tmp/usb.$$
-trap "{ umount -f $mnt; rmdir $mnt; rm -f $fdi; }" SIGTERM EXIT
+if [ $haslegacygrub -eq 1 ] ; then
+ # Legacy GRUB. Install boot loader to USB physical device
+ setup_legacy_grub
+fi
-mkdir $mnt
-mount -o ro $s0bdev $mnt
-
-# Install grub stages to usb
-echo Installing grub to USB device $s0cdev
-installgrub -mf $mnt/boot/grub/stage1 $mnt/boot/grub/stage2 $s0cdev > /dev/null
+cleanup
echo "Completed copy to USB"
exit 0
--- a/usr/src/cmd/install-tools/usbgen Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/install-tools/usbgen Fri Mar 23 04:40:57 2012 -0700
@@ -20,8 +20,7 @@
#
# CDDL HEADER END
#
-#
-# Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
#
#
@@ -337,18 +336,21 @@
set +o errexit
#
-# Remove "Hard Disk" GRUB entries from the USB image as they apply only to ISO.
+# If the ISO is a legacy GRUB based image remove "Hard Disk" GRUB entries from
+# from the USB image as they apply only to ISO. Not applicable to GRUB2 menu.cfg
#
-nawk '
- BEGIN { inhard=0 }
- /^title.*Hard Disk$/ { inhard=1 }
- /^title/ { if (index($0,"Hard Disk") == 0) inhard=0 }
- inhard == 0 {print}
-' ${usb_path}/boot/grub/menu.lst > ${usb_path}/boot/grub/menu2.lst
-if [[ $? == 0 ]] ; then
- mv ${usb_path}/boot/grub/menu2.lst ${usb_path}/boot/grub/menu.lst
-else
- print -u2 "Warning: Could not remove \"Hard Disk\" entry from boot menu"
+if [[ -f ${usb_path}/boot/grub/menu.lst ]] ; then
+ nawk '
+ BEGIN { inhard=0 }
+ /^title.*Hard Disk$/ { inhard=1 }
+ /^title/ { if (index($0,"Hard Disk") == 0) inhard=0 }
+ inhard == 0 {print}
+ ' ${usb_path}/boot/grub/menu.lst > ${usb_path}/boot/grub/menu2.lst
+ if [[ $? == 0 ]] ; then
+ mv ${usb_path}/boot/grub/menu2.lst ${usb_path}/boot/grub/menu.lst
+ else
+ print -u2 "Warning: Could not remove \"Hard Disk\" entry from boot menu"
+ fi
fi
#
--- a/usr/src/cmd/installadm/client_control.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/installadm/client_control.py Fri Mar 23 04:40:57 2012 -0700
@@ -19,7 +19,7 @@
#
# CDDL HEADER END
#
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
#
'''
AI create-client / delete-client
@@ -27,7 +27,6 @@
import logging
import os
import re
-import shutil
import sys
import osol_install.auto_install.AI_database as AIdb
@@ -36,6 +35,7 @@
import osol_install.auto_install.grub as grub
import osol_install.auto_install.service_config as config
+from osol_install.auto_install.grub import AIGrubCfg as grubcfg
from osol_install.auto_install.installadm_common import _, cli_wrap as cw
from osol_install.auto_install.service import AIService
from solaris_install import force_delete
@@ -54,6 +54,9 @@
_pxegrub_path(client_id)[1], # x86 pxegrub symlink
os.path.join(com.BOOT_DIR, client_id)] # SPARC symlink
+ for boot_type in grub.NBP_TYPE.values():
+ cleanup.append(os.path.join(com.BOOT_DIR, client_id + '.' + boot_type))
+
# Now add in any files passed by the caller
cleanup.extend(more_files)
@@ -81,57 +84,87 @@
return client_id, os.path.join(com.BOOT_DIR, client_id)
+def _bootfile_path(client_id, archtype):
+ return os.path.join(com.BOOT_DIR, client_id + '.' + archtype)
+
+
_PXE_CLIENT_DHCP_CONFIG = """
No local DHCP configuration found. If not already configured, the
following should be added to the DHCP configuration:
Boot server IP : %s
- Boot file : %s
+ Boot file(s) : %s
"""
-def setup_x86_client(service, mac_address, bootargs=''):
+def setup_x86_client(service, mac_address, bootargs='',
+ suppress_dhcp_msgs=False):
''' Set up an x86 client
- Creates a relative symlink from the <svcname>'s bootfile to
- /etc/netboot/<client_id>
- Creates /etc/netboot/menu.lst.<client_id> boot configuration file
+ Creates relative symlink(s) in /etc/netboot::
+ <client_id>.<archtype> -> ./<svcname>/<bootfile_path>
+ e.g., 01223344223344.bios -> ./mysvc/boot/grub/pxegrub
+ Creates /etc/netboot/<cfg_file>.<client_id> boot configuration file
Adds client info to AI_SERVICE_DIR_PATH/<svcname>/.config file
Arguments:
- image_path - directory path to AI image
+ service - the AIService to associate with client
mac_address - client MAC address (as formed by
MACAddress class, i.e., 'ABABABABABAB')
bootargs = bootargs of client (x86)
+ suppress_dhcp_msgs - if True, suppresses output of DHCP
+ configuration messages
Returns: Nothing
'''
# create a client-identifier (01 + MAC ADDRESS)
client_id = "01" + mac_address
-
- menulst = os.path.join(service.config_dir, grub.MENULST)
- client_menulst = _menulst_path(client_id)
+ clientinfo = dict()
- # copy service's menu.lst file to menu.lst.<client_id>
- try:
- shutil.copy(menulst, client_menulst)
- except IOError as err:
- print >> sys.stderr, cw(_("Unable to copy grub menu.lst file: %s") %
- err.strerror)
- return
+ svcgrub = grubcfg(service.name, path=service.image.path,
+ config_dir=service.config_dir)
- # create a symlink from the boot directory to the sevice's bootfile.
- # note this must be relative from the boot directory.
- bootfile, pxegrub_path = _pxegrub_path(client_id)
- os.symlink("./" + service.dhcp_bootfile, pxegrub_path)
+ # Call setup_client - it will return netconfig_files, config_files,
+ # and tuples, e.g.:
+ # netconfig_files: ['/etc/netboot/menu.lst.01234234234234']
+ # config_files: ['/etc/netboot/menu.conf.01234234234234']
+ # boot_tuples: [('00:00', 'bios', 'mysvc/boot/grub/pxegrub2'),
+ # ('00:07', 'uefi', 'mysvc/boot/grub/grub2netx64.efi')]
+ # If the client specifies bootargs, use them. Otherwise, inherit
+ # the bootargs specified in the service.
+ netconfig_files, config_files, boot_tuples = \
+ svcgrub.setup_client(mac_address, bootargs=bootargs,
+ service_bootargs=service.bootargs)
- clientinfo = {config.FILES: [client_menulst, pxegrub_path]}
-
- # if the client specifies bootargs, use them. Otherwise, inherit
- # the bootargs specified in the service (do nothing)
+ # update the bootargs in the service .config file
if bootargs:
- grub.update_bootargs(client_menulst, service.bootargs, bootargs)
clientinfo[config.BOOTARGS] = bootargs
+ # keep track of client files to delete when client is removed
+ client_files = list(netconfig_files)
+ client_files.extend(config_files)
+
+ # create symlink(s) from the boot directory to the sevice's bootfile(s).
+ # note these must be relative from the boot directory.
+ for arch, archtype, relpath in boot_tuples:
+ # name of symlink is /etc/netboot/<clientid>.<archtype>
+ bootfile_symlink = _bootfile_path(client_id, archtype)
+ dotpath = './' + relpath
+ logging.debug('creating symlink %s->%s', bootfile_symlink, dotpath)
+ os.symlink(dotpath, bootfile_symlink)
+ client_files.append(bootfile_symlink)
+
+ # if this is archtype bios, create <clientid> symlink to
+ # <clientid>.bios for backward compatibility with existing dhcp
+ # servers.
+ if archtype == 'bios':
+ clientid_path = os.path.join(com.BOOT_DIR, client_id)
+ dot_client_arch = './' + client_id + '.' + archtype
+ logging.debug('creating symlink %s->%s', clientid_path,
+ dot_client_arch)
+ os.symlink(dot_client_arch, clientid_path)
+
+ logging.debug('adding client_files to .config: %s', client_files)
+ clientinfo[config.FILES] = client_files
config.add_client_info(service.name, client_id, clientinfo)
# Configure DHCP for this client if the configuration is local, otherwise
@@ -143,9 +176,11 @@
# rather than the non-delimited string that 'mac_address' is.
full_mac = AIdb.formatValue('mac', mac_address)
try:
- print cw(_("Adding host entry for %s to local DHCP configuration.")
- % full_mac)
- server.add_host(full_mac, bootfile)
+ if not suppress_dhcp_msgs:
+ print cw(_("Adding host entry for %s to local DHCP "
+ "configuration.") % full_mac)
+ server.add_option_arch()
+ server.add_host(full_mac, boot_tuples)
except dhcp.DHCPServerError as err:
print cw(_("Unable to add host (%s) to DHCP configuration: %s") %
(full_mac, err))
@@ -158,11 +193,11 @@
print >> sys.stderr, cw(_("\nUnable to restart the DHCP SMF "
"service: %s\n" % err))
return
- else:
+ elif not suppress_dhcp_msgs:
print cw(_("\nLocal DHCP configuration complete, but the DHCP "
"server SMF service is offline. To enable the "
"changes made, enable: %s.\nPlease see svcadm(1M) "
- "for further information.\n") %
+ "for further information.\n") %
dhcp.DHCP_SERVER_IPV4_SVC)
else:
# No local DHCP, tell the user all about their boot configuration
@@ -170,12 +205,19 @@
if valid_nets:
server_ip = valid_nets[0]
- print _(_PXE_CLIENT_DHCP_CONFIG % (server_ip, bootfile))
+ if not suppress_dhcp_msgs:
+ boofile_text = '\n'
+ for archval, archtype, relpath in boot_tuples:
+ bootfilename = client_id + '.' + archtype
+ boofile_text = (boofile_text +
+ '\t' + archtype + ' clients (arch ' +
+ archval + '): ' + bootfilename + '\n')
+ print _(_PXE_CLIENT_DHCP_CONFIG % (server_ip, boofile_text))
- if len(valid_nets) > 1:
- print cw(_("\nNote: determined more than one IP address "
- "configured for use with AI. Please ensure the above "
- "'Boot server IP' is correct.\n"))
+ if len(valid_nets) > 1:
+ print cw(_("\nNote: determined more than one IP address "
+ "configured for use with AI. Please ensure the "
+ "above 'Boot server IP' is correct.\n"))
def setup_sparc_client(service, mac_address):
@@ -201,34 +243,39 @@
config.add_client_info(service.name, client_id, clientinfo)
-def remove_client(client_id):
+def remove_client(client_id, suppress_dhcp_msgs=False):
''' Remove client configuration
If client configuration incomplete (e.g., dangling symlink),
- cleanup anyway.
+ cleanup anyway. Optionally suppress dhcp informational messages.
'''
- logging.debug("Removing client config for %s", client_id)
+ logging.debug("Removing client config for %s, suppress_dhcp_msgs=%s",
+ client_id, suppress_dhcp_msgs)
(service, datadict) = config.find_client(client_id)
- more_files = list()
+ if datadict:
+ more_files = datadict.get(config.FILES, list())
+ else:
+ more_files = list()
if service:
# remove client info from .config file
config.remove_client_from_config(service, client_id)
if AIService(service).arch == 'i386':
# suggest dhcp unconfiguration
- remove_client_dhcp_config(client_id)
+ remove_client_dhcp_config(client_id, suppress_dhcp_msgs)
# remove client specific symlinks/files
+ logging.debug("Cleaning up files %s", more_files)
_cleanup_files(client_id, more_files)
-def remove_client_dhcp_config(client_id):
+def remove_client_dhcp_config(client_id, suppress_dhcp_msgs=False):
'''
If a local DHCP server is running, remove any client configuration for
this client from its configuration. If not, inform end-user that the
client-service binding should no longer be referenced in the DHCP
- configuration.
+ configuration. Suppress dhcp informational messages if indicated.
'''
server = dhcp.DHCPServer()
if server.is_configured():
@@ -237,8 +284,9 @@
mac_address = client_id[2:]
mac_address = AIdb.formatValue('mac', mac_address)
if server.host_is_configured(mac_address):
- print cw(_("Removing host entry '%s' from local DHCP "
- "configuration.") % mac_address)
+ if not suppress_dhcp_msgs:
+ print cw(_("Removing host entry '%s' from local DHCP "
+ "configuration.") % mac_address)
server.remove_host(mac_address)
if server.is_online():
@@ -251,6 +299,7 @@
else:
# No local DHCP configuration, inform user that it needs to be
# unconfigured elsewhere.
- print cw(_("No local DHCP configuration found. Unless it will be "
- "reused, the bootfile '%s' may be removed from the DHCP "
- "configuration\n" % client_id))
+ if not suppress_dhcp_msgs:
+ print cw(_("No local DHCP configuration found. Unless it will be "
+ "reused, the bootfile(s) associated with '%s' may be "
+ "removed from the DHCP configuration.\n" % client_id))
--- a/usr/src/cmd/installadm/create_client.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/installadm/create_client.py Fri Mar 23 04:40:57 2012 -0700
@@ -36,6 +36,7 @@
from optparse import OptionParser, OptionValueError
+from bootmgmt import BootmgmtError
from osol_install.auto_install.installadm_common import _, cli_wrap as cw
from solaris_install import Popen
@@ -80,7 +81,7 @@
help=_("Service to associate client with"), nargs=1)
(options, args) = parser.parse_args(cmd_options)
- if args:
+ if args:
parser.error(_("Unexpected argument(s): %s" % args))
# check that we got a service name and mac address
@@ -91,7 +92,7 @@
parser.error(_("MAC address is required (-e|--macaddr <macaddr>)."))
# Verify that the server settings are not obviously broken.
- # These checks cannot be complete, but check for things which
+ # These checks cannot be complete, but check for things which
# will definitely cause failure.
logging.debug("Calling %s", com.CHECK_SETUP_SCRIPT)
ret = Popen([com.CHECK_SETUP_SCRIPT]).wait()
@@ -130,7 +131,8 @@
return options
-def create_new_client(arch, service, mac_address, bootargs=None):
+def create_new_client(arch, service, mac_address, bootargs=None,
+ suppress_dhcp_msgs=False):
'''Create a new client of a service and ensure the Automated
Install SMF service is enabled.
@@ -138,15 +140,18 @@
service - The AIService to attach to
mac_address - mac address of client
bootargs - boot arguments to insert in client menu.lst file (x86)
+ suppress_dhcp_msgs - if True, suppresses informational messages
+ about DHCP configuration
Returns: Nothing
'''
logging.debug("creating new client for service %s, mac %s, "
- "arch %s, bootargs %s",
- service.name, mac_address, arch, bootargs)
+ "arch %s, bootargs %s, suppress_dhcp_msgs=%s",
+ service.name, mac_address, arch, bootargs,
+ suppress_dhcp_msgs)
if arch == 'i386':
- clientctrl.setup_x86_client(service, mac_address,
- bootargs=bootargs)
+ clientctrl.setup_x86_client(service, mac_address, bootargs=bootargs,
+ suppress_dhcp_msgs=suppress_dhcp_msgs)
else:
clientctrl.setup_sparc_client(service, mac_address)
@@ -175,7 +180,8 @@
bootargs = ",".join(options.boot_args).lstrip().rstrip() + ","
logging.debug('bootargs=%s', bootargs)
- clientctrl.remove_client("01" + options.mac_address)
+ clientctrl.remove_client("01" + options.mac_address,
+ suppress_dhcp_msgs=True)
# wrap the whole program's execution to catch exceptions as we should not
# throw them anywhere
@@ -183,11 +189,10 @@
try:
create_new_client(options.arch, service,
options.mac_address, bootargs)
- except OSError as err:
- raise SystemExit(err)
- except (aismf.ServicesError, config.ServiceCfgError,
- svc.MountError) as err:
- raise SystemExit(err)
+ except (OSError, BootmgmtError, aismf.ServicesError,
+ config.ServiceCfgError, svc.MountError) as err:
+ raise SystemExit(_('\nError: Unable to create client, %s:\n%s') %
+ (options.mac_address, err))
if __name__ == "__main__":
--- a/usr/src/cmd/installadm/create_service.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/installadm/create_service.py Fri Mar 23 04:40:57 2012 -0700
@@ -42,6 +42,9 @@
import pkg.client.history
from optparse import OptionParser, OptionValueError
+
+from bootmgmt import BootmgmtError
+from osol_install.auto_install.grub import GrubError
from osol_install.auto_install.installadm_common import _, cli_wrap as cw
from osol_install.auto_install.service import AIService, AIServiceError, \
DEFAULT_ARCH, MountError, UnsupportedAliasError
@@ -388,6 +391,10 @@
service = AIService.create(options.svcname, image,
alias=options.aliasof,
bootargs=options.bootargs)
+ except (GrubError, BootmgmtError) as err:
+ AIService(options.svcname).delete()
+ raise SystemExit(_('\nError: Unable to create alias, %s:\n%s') %
+ (options.svcname, err))
except AIServiceError as err:
raise SystemExit(err)
@@ -587,6 +594,10 @@
else:
service = AIService.create(options.svcname, image,
bootargs=options.bootargs)
+ except (GrubError, BootmgmtError) as err:
+ AIService(options.svcname).delete()
+ raise SystemExit(_('\nError: Unable to create service, %s:\n%s') %
+ (options.svcname, err))
except AIServiceError as err:
raise SystemExit(err)
@@ -612,6 +623,10 @@
defaultarchsvc = AIService.create(defaultarch, image,
bootargs=options.bootargs,
alias=options.svcname)
+ except (GrubError, BootmgmtError) as err:
+ AIService(defaultarch).delete()
+ raise SystemExit(_('\nError: Unable to create alias, %s:\n%s') %
+ (defaultarch, err))
except AIServiceError as err:
raise SystemExit(err)
except UnsupportedAliasError as err:
--- a/usr/src/cmd/installadm/delete_service.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/installadm/delete_service.py Fri Mar 23 04:40:57 2012 -0700
@@ -35,14 +35,14 @@
from optparse import OptionParser
-from osol_install.auto_install.installadm_common import _, cli_wrap as cw
+from osol_install.auto_install.installadm_common import _, cli_wrap as cw
from osol_install.auto_install.service import AIService, DEFAULT_ARCH
-
+
def get_usage():
''' get usage for delete-service'''
return(_('delete-service [-r|--autoremove] [-y|--noprompt] <svcname>]'))
-
+
def parse_options(cmd_options=None):
'''
@@ -50,7 +50,7 @@
Args: None
Returns: A tuple of a dictionary of service properties of service
to delete and an options object
-
+
'''
usage = '\n' + get_usage()
parser = OptionParser(usage=usage)
@@ -65,15 +65,15 @@
'service deletion'))
(options, args) = parser.parse_args(cmd_options)
-
+
# Confirm install service's name was passed in
if not args:
parser.error(_("Missing required argument, <svcname>"))
elif len(args) > 1:
parser.error(_("Too many arguments: %s") % args)
-
+
service_name = args[0]
-
+
# validate service name
try:
com.validate_service_name(service_name)
@@ -82,11 +82,11 @@
if not config.is_service(service_name):
raise SystemExit(_("\nError: The specified service does "
"not exist: %s\n") % service_name)
-
+
# add service_name to the options
options.service_name = service_name
logging.debug("options = %s", options)
-
+
return options
@@ -98,9 +98,15 @@
inform the end-user that the DHCP configuration should not reference this
bootfile any longer.
'''
+ logging.debug("in remove_dhcp_configuration, service=%s", service.name)
+
+ # Skip SPARC services, since they share a global bootfile
+ if service.arch == 'sparc':
+ return
+
server = dhcp.DHCPServer()
if server.is_configured():
- # Server is configured. Regardless of it's current state, check for
+ # Server is configured. Regardless of its current state, check for
# this bootfile in the service's architecture class. If it is set as
# the default for this architecture, unset it.
try:
@@ -110,16 +116,29 @@
"%s\n" % err))
return
- # Skip SPARC services, since they share a global bootfile
- if (service.arch != 'sparc' and arch_class is not None and
- arch_class.bootfile == service.dhcp_bootfile):
+ if arch_class is None or arch_class.bootfile is None:
+ # nothing to do
+ return
+
+ logging.debug("arch_class.bootfile is %s", arch_class.bootfile)
+ if isinstance(arch_class.bootfile, list):
+ # The list consists of tuples: (archval, relpath to bootfile)
+ # e.g., [('00:00', '<svcname>/boot/grub/pxegrub2'),..]
+ # Using the first tuple, get the service name.
+ relpath = arch_class.bootfile[0][1]
+ else:
+ relpath = arch_class.bootfile
+ parts = relpath.partition('/')
+ arch_svcname = parts[0]
+
+ if arch_svcname == service.name:
try:
- print cw(_("Removing this service's bootfile from local DHCP "
- "configuration\n"))
+ print cw(_("Removing this service's bootfile(s) from local "
+ "DHCP configuration\n"))
arch_class.unset_bootfile()
except dhcp.DHCPServerError as err:
print >> sys.stderr, cw(_("\nUnable to unset this service's "
- "bootfile in the DHCP "
+ "bootfile(s) in the DHCP "
"configuration: %s\n" % err))
return
@@ -142,10 +161,8 @@
logging.debug("delete_specified_service %s %s %s", service_name,
auto_remove, noprompt)
- # get service properties
- svcprops = config.get_service_props(service_name)
service = AIService(service_name)
-
+
# If the '-r' option has not been specified, look for all
# dependent aliases and clients
all_aliases = config.get_aliased_services(service_name, recurse=True)
@@ -194,12 +211,12 @@
logging.debug("recursively calling delete_specified_service for %s",
dependent)
delete_specified_service(dependent, True, True)
-
+
clients = config.get_clients(service_name).keys()
for dependent in clients:
logging.debug("calling remove_client for %s", dependent)
clientctrl.remove_client(dependent)
-
+
logging.debug("now deleting service %s", service_name)
# remove DHCP bootfile configuration for this service, if set
@@ -227,7 +244,7 @@
if os.geteuid() != 0:
raise SystemExit(_("Error: Root privileges are required for this "
"command.\n"))
-
+
# parse server options
options = parse_options(cmd_options)
delete_specified_service(options.service_name, options.autoremove,
--- a/usr/src/cmd/installadm/dhcp.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/installadm/dhcp.py Fri Mar 23 04:40:57 2012 -0700
@@ -20,7 +20,7 @@
#
# CDDL HEADER END
#
-# Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
#
'''
Classes, methods and related routines used in the configuration and management
@@ -36,7 +36,8 @@
import osol_install.auto_install.installadm_common as com
-from osol_install.auto_install.installadm_common import _, cli_wrap as cw
+from osol_install.auto_install.installadm_common import _, cli_wrap as cw, \
+ MACAddress
from osol_install.libaimdns import getifaddrs
from solaris_install import Popen, CalledProcessError
@@ -53,6 +54,8 @@
DHCP_SERVER_IPV4_SVC = "svc:/network/dhcp/server:ipv4"
+OPTIONARCH = "option arch code 93 = unsigned integer 16;"
+
# Mappings for name services (DNS, NIS) and related properties
_ns_map = {'dns': {'SVC_NAME': "svc:/network/dns/client",
'DOMAIN_PROP': "config/domain",
@@ -98,6 +101,9 @@
# network, the authoritative directive should be uncommented.
authoritative;
+# arch option for PXEClient
+%(optionarch)s
+
# Set logging facility (accompanies setting in syslog.conf)
log-facility local7;
"""
@@ -155,11 +161,11 @@
# The following defines a block quote for an x86 class stanza. Class stanzas
# set details for an entire architecture.
# Optionally passed in the format list:
-# bootfile - The bootfile for this architecture class
+# bootfile_clause - The bootfile if/else clause for this architecture class
CFGFILE_PXE_CLASS_STANZA = """
class "PXEBoot" {
match if (substring(option vendor-class-identifier, 0, 9) = "PXEClient");
- filename "%(bootfile)s";
+ %(bootfile_clause)s
}
"""
@@ -176,7 +182,10 @@
"""
# This string can be used when adding a new bootfile to an existing class.
-CFGFILE_CLASS_BOOTFILE_STRING = """ filename "%(bootfile)s";
+CFGFILE_CLASS_BOOTFILE_STRING_SPARC = """ filename "%(bootfile)s";
+"""
+
+CFGFILE_CLASS_BOOTFILE_STRING_X86 = """ %(bootfile)s
"""
# The following defines a block quote for an host-specific stanza. These
@@ -192,6 +201,16 @@
}
"""
+CFGFILE_HOST_STANZA_IFCLAUSE = """
+host %(hostname)s {
+ hardware ethernet %(macaddr)s;
+ %(bootfile_clause)s
+}
+"""
+
+# This string is used to add the arch option to the config file if needed.
+CFGFILE_OPTIONARCH_STRING = "%s\n\n" % OPTIONARCH
+
class DHCPServerError(StandardError):
'''
@@ -226,6 +245,7 @@
'''
def __init__(self):
self.block = CFGFILE_BASE
+ self.optionarch = OPTIONARCH
class _DHCPConfigSubnet(_DHCPConfigStanza):
@@ -261,6 +281,9 @@
def __init__(self, bootfile):
self.block = CFGFILE_PXE_CLASS_STANZA
self.bootfile = bootfile
+ self.bootfile_clause = ''.join(_create_bootfile_clause(bootfile))
+ logging.debug('_DHCPConfigPXEClass bootfile clause is\n%s ',
+ self.bootfile_clause)
class _DHCPConfigSPARCClass(_DHCPConfigStanza):
@@ -282,12 +305,23 @@
hostname - Label for this stanza
macaddr - Ethernet (mac) address of the client
bootfile - Bootfile for this client
+ bootfile is either a string OR
+ a list of tuples from NetBootConfig in the form
+ [(arch_string, type_string, rel_path_to_bootfile), ...]
'''
def __init__(self, hostname, macaddr, bootfile):
- self.block = CFGFILE_HOST_STANZA
self.hostname = hostname
self.macaddr = macaddr
self.bootfile = bootfile
+ if isinstance(bootfile, list):
+ clientid = '01' + str(MACAddress(macaddr))
+ self.bootfile_clause = \
+ ''.join(_create_bootfile_clause(bootfile, clientid=clientid))
+ logging.debug('_DHCPConfigHost bootfile clause is\n%s ',
+ self.bootfile_clause)
+ self.block = CFGFILE_HOST_STANZA_IFCLAUSE
+ else:
+ self.block = CFGFILE_HOST_STANZA
class DHCPData(object):
@@ -342,7 +376,13 @@
arch - The architecture class being queried
Attributes:
arch - The architecture of this architecture class entry
- bootfile - The bootfile for this architecture class
+ bootfile - The bootfile for this architecture class:
+ a string for sparc:
+ e.g.: http://192.168.56.1:5555/cgi-bin/wanboot-cgi
+ or a list of tuples for x86)
+ [('00:00', 'default-i386/boot/grub/pxegrub2'),
+ ('00:07', 'default-i386/boot/grub/grub2netx64.efi')]
+
'''
def __init__(self, server, arch, bootfile=None):
self.server = server
@@ -373,19 +413,28 @@
# we find that it's for the architecture in question, then we can break
# out of parsing once we're done reading it all in. If not, move on to
# the next class, if there are any.
+ if_opt_arch_pattern = 'if\s+option\s+arch\s*=\s*(\d{1,2}.\d{1,2})\s*{'
found = False
bootfile = None
+ in_ifclause = False
classlines = list()
for line in server._current_config():
if line.startswith("class"):
# New class stanza, init a new class list
classlines = list()
continue
- if line.endswith("}") and found:
- # We're at the end of the class we wanted. The classlines list
- # now contains each line of the class stanza for this
- # architecture.
- break
+ if line.endswith("}"):
+ if in_ifclause:
+ # We're at the end of the if clause, clear the flag.
+ in_ifclause = False
+ elif found:
+ # We're at the end of the class we wanted. The classlines
+ # list now contains each line of the class stanza for this
+ # architecture.
+ break
+ if line.startswith("if") and line.endswith("{"):
+ # We're entering an if clause inside the stanza
+ in_ifclause = True
m = regexp.search(line)
if m is not None:
# We've found the class stanza for this architecture.
@@ -398,11 +447,21 @@
matches = [m.group(1)
for m in filter(bool, map(regexp.match, classlines))]
if matches:
- bootfile = matches[0]
+ if arch == 'i386' and len(matches) > 1:
+ regexp = re.compile(if_opt_arch_pattern)
+ arches = [m.group(1)
+ for m in filter(bool, map(regexp.search, classlines))]
+ if arches:
+ bootfile = zip(arches, matches)
+ else:
+ bootfile = matches[0]
+ else:
+ bootfile = matches[0]
# Finally, if we found the class, instantiate and return a
# DHCPArchClass object.
if found:
+ logging.debug('dhcp:get_arch, found class, bootfile=%s', bootfile)
return cls(server, arch, bootfile)
else:
return None
@@ -456,6 +515,9 @@
Note: If action is 'update' and bootfile is "None", the bootfile will
be unset entirely.
'''
+ logging.debug('\nin _edit_class_bootfile, action=%s, bootfile=%s',
+ action, bootfile)
+
if action not in ['set', 'update']:
raise ValueError(_("invalid action: %s") % action)
@@ -472,19 +534,26 @@
# use "not x86", since we only support the two architectures, and
# there is no simple way to determine a SPARC client explicitly.
if self.arch == 'i386':
+ cfgfile_class_bf_string = CFGFILE_CLASS_BOOTFILE_STRING_X86
vci_re = re.compile(X86_VCI_PATTERN)
+ if bootfile:
+ # Create a dictionary for use in the format string below.
+ # For x86, bf will be a string consisting of an if/else
+ # clause to be inserted into the dhcp conf file.
+ bf = {'bootfile': ''.join(_create_bootfile_clause(bootfile))}
+ logging.debug('_edit_class_bootfile: setting bf to %s' % bf)
elif self.arch == 'sparc':
vci_re = re.compile(SPARC_VCI_PATTERN)
+ cfgfile_class_bf_string = CFGFILE_CLASS_BOOTFILE_STRING_SPARC
bootfile = fixup_sparc_bootfile(bootfile, True)
+ # Create a dictionary for use in the format string below
+ bf = {'bootfile': bootfile}
else:
raise DHCPServerError(_("unsupported architecture: %s") % \
self.arch)
bootfile_re = re.compile('filename\s+\S+;')
- # Create a dictionary for use in the format string below
- bf = {'bootfile': bootfile}
-
current_cfgfile = self.server._properties['config_file']
tmp_cfgfile = "%s~" % current_cfgfile
@@ -502,6 +571,9 @@
in_class = False
seek_bootfile = False
edit_complete = False
+ in_ifclause = False
+ removing_ifclause = False
+ ifclause = list()
# dhcpd server runs under dhcpserv user account and needs to be able
# to read its config file
orig_umask = os.umask(0022)
@@ -521,19 +593,38 @@
# class stanza and can perform accordingly.
in_class = True
if line.strip().endswith("}"):
- # We are out of this particular class, so unset our
- # flag and move on. Note we could assert that
- # seek_bootfile shouldn't be set here (see below), but
- # it could be possible to have more than one class
- # stanza for this architecture. So, just unset that
- # flag as well.
- in_class = False
- seek_bootfile = False
+ if in_ifclause:
+ if removing_ifclause:
+ if bootfile is not None:
+ # we've been removing the old bootfile
+ # ifclause and now want to insert the
+ # new bootfile lines
+ tmp_cfg.write(cfgfile_class_bf_string % bf)
+ # We're removing this if clause
+ edit_complete = True
+ continue
+
+ # We are out of this if clause. Append saved
+ # if clause lines to file
+ in_ifclause = False
+ for ifline in ifclause:
+ tmp_cfg.write(ifline)
+ else:
+ # We are out of this particular class, so unset our
+ # flag and move on. Note we could assert that
+ # seek_bootfile shouldn't be set here (see below),
+ # but it could be possible to have more than one
+ # class stanza for this architecture. So, just
+ # unset that flag as well.
+ in_class = False
+ seek_bootfile = False
if seek_bootfile:
if bootfile_re.search(line) is not None:
# We're at the bootfile line in the class for this
# architecture, and we want to update it (i.e. the
- # action is 'update'). If the bootfile argument is
+ # action is 'update'). If we're in an if clause,
+ # then keep processing until we're out of the
+ # clause. Otherwise, if the bootfile argument is
# set, then we're updating to a new bootfile
# setting. Create a new entry and drop it in,
# leaving the old one behind. If a new bootfile
@@ -541,11 +632,23 @@
# altogether - just leave this line behind. Either
# way, set our 'done' flag. We'll then just copy
# the rest of the file and return.
- if bootfile is not None:
- tmp_cfg.write(CFGFILE_CLASS_BOOTFILE_STRING % \
- bf)
+ if in_ifclause:
+ # we are unsetting or replacing the bootfile.
+ # If in if clause, remove the entire clause.
+ ifclause.append(line)
+ removing_ifclause = True
+ continue
+ elif bootfile is not None:
+ tmp_cfg.write(cfgfile_class_bf_string % bf)
edit_complete = True
continue
+ if (line.strip().startswith('if') and
+ line.strip().endswith("{")):
+ in_ifclause = True
+ ifclause = list()
+ if in_ifclause:
+ ifclause.append(line)
+ continue
if in_class:
if vci_re.search(line) is not None:
# We've found the class for this architecture
@@ -557,7 +660,7 @@
# 'done' flag. We'll then just copy the rest of
# the file and return.
tmp_cfg.write(line)
- tmp_cfg.write(CFGFILE_CLASS_BOOTFILE_STRING % \
+ tmp_cfg.write(cfgfile_class_bf_string % \
bf)
edit_complete = True
continue
@@ -849,6 +952,42 @@
broadcast, router, nextserver)
self._add_stanza_to_config_file(new_stanza)
+ def add_option_arch(self):
+ '''
+ Add the arch option to the config file if not already there
+ '''
+ current_cfgfile = self._properties['config_file']
+ tmp_cfgfile = "%s~" % current_cfgfile
+
+ # Get the full configuration file, not just this server's current
+ # config, as we'll be making a copy of the current file for editing.
+ current_lines = list()
+ if os.path.exists(current_cfgfile):
+ with open(current_cfgfile, "r") as current:
+ current_lines = current.readlines()
+
+ # if arch is already defined, nothing to do
+ for line in current_lines:
+ if line.strip().startswith(OPTIONARCH):
+ return
+
+ # Need to add option arch. Make the copy and add it.
+ written = False
+ with open(tmp_cfgfile, "w") as tmp_cfg:
+ for line in current_lines:
+ if line.strip().startswith('class') and not written:
+ tmp_cfg.write(CFGFILE_OPTIONARCH_STRING)
+ written = True
+ tmp_cfg.write(line)
+
+ if not written:
+ # there were no class stanzas in the file. Append to the end.
+ with open(tmp_cfgfile, 'a') as cfg:
+ cfg.write(CFGFILE_OPTIONARCH_STRING)
+
+ # Rename the temporary new file to the configfile
+ os.rename(tmp_cfgfile, current_cfgfile)
+
def _add_range_to_subnet(self, subnet, loaddr, hiaddr):
'''
Add a new IP range to an already established subnet stanza. This can
@@ -921,7 +1060,9 @@
Add a host stanza to the DHCP configuration.
Arguments:
macaddr - Hardware ethernet address of the client
- bootfile - Bootfile to set for this client
+ bootfile - Bootfile to set for this client OR
+ list of tuples from grub:setup_client in the form
+ [(arch_string, type_string, rel_path_to_bootfile), ...]
hostname - Label for this stanza (optional)
'''
logging.debug("dhcp.add_host: adding host [%s] bootfile '%s'",
@@ -956,6 +1097,7 @@
regexp = re.compile(macaddr)
found = False
+ in_ifclause = False
in_host = False
remove_finished = False
new_cfg = list()
@@ -985,25 +1127,33 @@
hostlines = list()
hostlines.append(line)
continue
+ if in_host and line.strip().startswith("if ") and \
+ line.strip().endswith("{"):
+ # We're entering an if clause inside the host stanza
+ in_ifclause = True
if in_host and line.strip().endswith("}"):
- # We're at the end of a host stanza, clear our flag.
- in_host = False
-
- # Add this final line to the hostlines list.
- hostlines.append(line)
+ if in_ifclause:
+ # We're at the end of the if clause, clear the flag.
+ in_ifclause = False
+ else:
+ # We're at the end of a host stanza, clear our flag.
+ in_host = False
- # If we've not found our host yet, then this host stanza
- # we've just hit the end of needs to be written out to the
- # new file; add these lines to 'new_cfg'.
- if not found:
- new_cfg.extend(hostlines)
+ # Add this final line to the hostlines list.
+ hostlines.append(line)
+
+ # If we've not found our host yet, then this host
+ # stanza we've just hit the end of needs to be written
+ # out to the new file; add these lines to 'new_cfg'.
+ if not found:
+ new_cfg.extend(hostlines)
+ continue
+
+ # If we have found the host we're removing, set our
+ # finished flag and just move on, leaving this host
+ # stanza behind.
+ remove_finished = True
continue
-
- # If we have found the host we're removing, set our
- # finished flag and just move on, leaving this host
- # stanza behind.
- remove_finished = True
- continue
if in_host and regexp.search(line):
# We've found the right host stanza
found = True
@@ -1046,7 +1196,7 @@
def add_arch_class(self, arch, bootfile):
'''
- Add an x86 architecture class to the DHCP configuration.
+ Add an x86 or SPARC architecture class to the DHCP configuration.
Arguments:
arch - The architecture to set this bootfile for.
bootfile - The bootfile
@@ -1059,6 +1209,9 @@
"set in the DHCP configuration") % arch)
if arch == 'i386':
+ self.add_option_arch()
+ logging.debug('creating _DHCPConfigPXEClass, bootfile=%s',
+ bootfile)
new_stanza = _DHCPConfigPXEClass(bootfile)
elif arch == 'sparc':
bootfile = fixup_sparc_bootfile(bootfile, True)
@@ -1372,12 +1525,12 @@
logger='', stderr_loglevel=logging.DEBUG)
regexp = re.compile('^default\s+(%s)\s+' % IP_PATTERN)
- for route in [m.group(1)
- for m in filter(bool, map(regexp.match, p.stdout.splitlines()))]:
- if _ip_is_in_network(route, subnet_ip, _get_mask(route)):
- logging.debug("dhcp._get_default_route_for_subnet: found %s",
- route)
- return route
+ for route in [m.group(1) for m in
+ filter(bool, map(regexp.match, p.stdout.splitlines()))]:
+ if _ip_is_in_network(route, subnet_ip, _get_mask(route)):
+ logging.debug("dhcp._get_default_route_for_subnet: found %s",
+ route)
+ return route
print >> sys.stderr, cw(_("\nUnable to determine a route for network %s. "
"Setting the route temporarily to %s; this "
@@ -1428,3 +1581,31 @@
logging.debug("dhcp.fixup_sparc_bootfile: setting IP address %s", ipaddr)
return re.sub('\$serverIP', ipaddr, bootfile)
+
+
+def _create_bootfile_clause(boot_tuples, clientid=None):
+ '''
+ Create the bootfile if/else clause for the host or PXEClient stanzas.
+ Input: boot_tuples: List of tuples returned by NetBootConfig
+ Example:
+ [('00:00', 'bios', 'default-i386/boot/grub/pxegrub2'),
+ ('00:07', 'uefi', 'default-i386/boot/grub/grub2netx64.efi')]
+ clientid: clientid if creating clause for host section in dhcp
+ configuration file
+ Returns: list of lines to be used to create if/else bootfile clause
+ '''
+ bf_clause = list()
+ for arch, archtype, relpath in boot_tuples:
+ line = 'if option arch = %s {\n' % arch
+ if bf_clause:
+ line = ' } else ' + line
+ bf_clause.append(line)
+ if clientid:
+ bootfile = clientid + '.' + archtype
+ else:
+ bootfile = relpath
+ bootline = ' filename "%s";\n' % bootfile
+ bf_clause.append(bootline)
+ if bf_clause:
+ bf_clause.append(' }')
+ return bf_clause
--- a/usr/src/cmd/installadm/grub.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/installadm/grub.py Fri Mar 23 04:40:57 2012 -0700
@@ -24,170 +24,598 @@
# Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
#
'''
-functions supporting creating and modifying menu.lst
+Classes and functions supporting creating and modifying bootmgmt
+configuration files
'''
-import fileinput
import logging
import os
-import sys
+from grp import getgrnam
+from pwd import getpwnam
+
+import osol_install.auto_install.installadm_common as com
+from bootmgmt import BootmgmtUnsupportedPropertyError
+from bootmgmt.bootconfig import BootConfig, NetBootConfig, \
+ SolarisNetBootInstance
+from bootmgmt.bootloader import BootLoader
from osol_install.auto_install.installadm_common import XDEBUG
MENULST = 'menu.lst'
+# NBP_TYPE values come from RFC 4578
+NBP_TYPE = {'0000': 'bios', '0007': 'uefi'}
-def update_bootargs(menulstpath, oldbootargs, bootargs):
- '''Update bootargs in menu.lst
+
+class GrubError(StandardError):
+ '''Base class for errors unique to grub module'''
+ pass
+
+
+def update_boot_instance_svcname(boot_instance, oldname, newname):
+ '''Update the svcname/mountdir in a boot_instance
Input:
- menulstpath - path to menu.lst file to update
- oldbootargs - bootargs to replace
- bootargs - replacement bootargs
+ boot_instance - boot_instance to update
+ oldname - current svcname
+ newname - replacement svcname
+ Returns: updated boot_instance
'''
- logging.log(XDEBUG, 'in update_bootargs menu.lst=%s oldbootargs=%s '
- 'bootargs=%s', menulstpath, oldbootargs, bootargs)
+ logging.log(XDEBUG, 'in update_boot_instance_svcname %s %s',
+ oldname, newname)
+ kernel = boot_instance.kernel
+ boot_instance.kernel = update_kernel_ba_svcname(kernel, oldname, newname)
- bootargs = bootargs.strip()
- for line in fileinput.input(menulstpath, inplace=1):
- newline = line
- parts = line.partition(' -B')
- if parts[1]:
- ending = parts[2].lstrip()
- # Need to check for oldbootargs because '' is
- # a valid value
- if oldbootargs and oldbootargs in ending:
- ending = ending.replace(oldbootargs, bootargs)
- else:
- ending = bootargs + ending
- newline = parts[0] + ' -B ' + ending
- sys.stdout.write(newline)
+ boot_archive = boot_instance.boot_archive
+ boot_instance.boot_archive = update_kernel_ba_svcname(boot_archive,
+ oldname, newname)
+
+ kargs = boot_instance.kargs
+ boot_instance.kargs = update_kargs_install_service(kargs, oldname, newname)
+ return boot_instance
-def update_svcname(menulstpath, newsvcname, mountdir):
- ''' Update the svcname/mountdir in menu.lst
+def update_kargs_install_service(current_kargs, oldsvcname, newsvcname):
+ '''Update install_service in kargs string
Input:
- menulstpath - path to menu.lst file to update
+ current_kargs - current value of kargs string to update
+ oldsvcname - current svcname
newsvcname - replacement svcname
- mountdir - mountdir to replace with new svcname
+ Returns: updated kargs string
+
+ '''
+ logging.log(XDEBUG, 'in update_kargs_install_service current_kargs=%s '
+ 'oldsvcname=%s newsvcname=%s', current_kargs,
+ oldsvcname, newsvcname)
+
+ new_kargs = current_kargs
+ install_svc = 'install_service='
+ parts = current_kargs.partition(install_svc)
+ if parts[1]:
+ ending = parts[2].partition(',')[2]
+ new_kargs = parts[0] + install_svc + newsvcname + ',' + ending
+ return(new_kargs)
+
+
+def update_kernel_ba_svcname(current_string, oldsvcname, newsvcname):
+ '''Update the svcname/mountdir in kernel or boot_archive string
+
+ Input:
+ current_string - current value of string to update
+ oldsvcname - current svcname
+ newsvcname - replacement svcname
+ Returns: updated string
'''
- logging.log(XDEBUG, 'in update_menulst %s %s %s',
- menulstpath, newsvcname, mountdir)
- install_svc = 'install_service='
- kernel_str = '\tkernel$ /'
- module_str = '\tmodule$ /'
- for line in fileinput.input(menulstpath, inplace=1):
- newline = line
- parts = newline.partition(kernel_str)
- if parts[1]:
- ending = parts[2].partition('/')[2]
- newline = kernel_str + mountdir + '/' + ending
- parts = newline.partition(install_svc)
- if parts[1]:
- ending = parts[2].partition(',')[2]
- newline = parts[0] + install_svc + newsvcname + ',' + \
- ending
- else:
- parts = newline.partition(module_str)
- if parts[1]:
- ending = parts[2].partition('/')[2]
- newline = module_str + mountdir + '/' + ending
- sys.stdout.write(newline)
+ logging.log(XDEBUG, 'in update_kernel_ba_svcname %s %s %s',
+ current_string, oldsvcname, newsvcname)
+ new_string = current_string
+ parts = current_string.partition(oldsvcname)
+ if parts[1]:
+ new_string = parts[0] + newsvcname + parts[2]
+ return new_string
-def update_imagepath(menulstpath, oldpath, newpath):
- ''' Update the imagepath in menu.lst
+def update_kargs_bootargs(current_kargs, oldbootargs, newbootargs):
+ '''Update bootargs in kargs string
Input:
- menulstpath - path to menu.lst file to update
+ current_kargs - current value of kargs string to update
+ oldbootargs - bootargs to replace
+ newbootargs - replacement bootargs
+ Returns: updated kargs string
+
+ '''
+ logging.log(XDEBUG, 'in update_kargs_bootargs current_kargs=%s '
+ 'oldbootargs=%s newbootargs=%s', current_kargs,
+ oldbootargs, newbootargs)
+
+ newbootargs = newbootargs.strip()
+ parts = current_kargs.partition('-B')
+ if not parts[1]:
+ return current_kargs
+ ending = parts[2].lstrip()
+ # Need to check for oldbootargs because '' is
+ # a valid value
+ if oldbootargs and oldbootargs in ending:
+ ending = ending.replace(oldbootargs, newbootargs)
+ else:
+ ending = newbootargs + ending
+ return(parts[0] + '-B ' + ending)
+
+
+def update_kargs_imagepath(current_kargs, oldpath, newpath):
+ '''Update imagepath in kargs string
+
+ Input:
+ current_kargs - current value of kargs string to update
oldpath - imagepath to replace
newpath - new imagepath
+ Returns: updated kargs string
'''
- logging.log(XDEBUG, 'in update_imagepath %s %s %s',
- menulstpath, oldpath, newpath)
+ logging.log(XDEBUG, 'in update_kargs_imagepath current_kargs=%s '
+ 'oldpath=%s newpath=%s', current_kargs, oldpath, newpath)
+
install_media = 'install_media='
- for line in fileinput.input(menulstpath, inplace=1):
- newline = line
- parts = newline.partition(install_media)
- # Example line (spaces added for readability):
- # kernel$ /mysvc/platform/i86pc/kernel/$ISADIR/unix -B
- # install_media=http://$serverIP:5555//export/auto_install/myimg,
- # install_service=mysvc,install_svc_address=$serverIP:5555
- if parts[1]:
- # The line contains 'install_media='.
- # parts[2] is:
- # http://$serverIP:5555//export/auto_install/myimg,
- # install_service=mysvc,install_svc_address=$serverIP:5555
- ending_parts = parts[2].partition(',')
- media_str = ending_parts[0]
- # http://$serverIP:5555//export/auto_install/myimg
+ parts = current_kargs.partition(install_media)
+
+ # Example line (spaces added for readability):
+ # -B install_media=http://$serverIP:5555//export/auto_install/myimg,
+ # install_service=mysvc,install_svc_address=$serverIP:5555
+
+ if not parts[1]:
+ return current_kargs
+
+ # The line contains 'install_media='.
+ # parts[2] is:
+ # http://$serverIP:5555//export/auto_install/myimg,
+ # install_service=mysvc,install_svc_address=$serverIP:5555
+ ending_parts = parts[2].partition(',')
+ media_str = ending_parts[0]
+ # http://$serverIP:5555//export/auto_install/myimg
- rest_of_string = ending_parts[1] + ending_parts[2]
- # ,install_service=mysvc,install_svc_address=$serverIP:5555
+ rest_of_string = ending_parts[1] + ending_parts[2]
+ # ,install_service=mysvc,install_svc_address=$serverIP:5555
- new_media_str = media_str.replace(oldpath, newpath, 1)
- # http://$serverIP:5555//foo/newpath
+ new_media_str = media_str.replace(oldpath, newpath, 1)
+ # http://$serverIP:5555//foo/newpath
+
+ new_kargs = parts[0] + install_media + new_media_str + rest_of_string
+ # kernel$ /mysvc/platform/i86pc/kernel/$ISADIR/unix -B
+ # install_media=http://$serverIP:5555//foo/newpath,
+ # install_service=mysvc,install_svc_address=$serverIP:5555
- newline = parts[0] + install_media + new_media_str + rest_of_string
- # kernel$ /mysvc/platform/i86pc/kernel/$ISADIR/unix -B
- # install_media=http://$serverIP:5555//foo/newpath,
- # install_service=mysvc,install_svc_address=$serverIP:5555
- sys.stdout.write(newline)
+ return new_kargs
+
+
+def set_perms(filename, uid, gid, mode):
+ '''Update permissions of a file'''
+ logging.debug("Setting ownership of %s to: %s:%s", filename, uid, gid)
+ os.chown(filename, getpwnam(uid).pw_uid, getgrnam(gid).gr_gid)
+ if mode:
+ logging.debug("Setting permissions of %s to: %d", filename, mode)
+ os.chmod(filename, mode)
-def setup_grub(svc_name, image_path, image_info, srv_address, menu_path,
- bootargs):
- '''Configure GRUB for this AIService instance.
-
- Input:
- svc_name - service name
- image_path - image path for service
- image_info - dict of image info
- srv_address - address from service.py
- menu_path - where to put menu.lst file
- bootargs - string of user specified bootargs or '' if none
-
+class AIGrubCfg(object):
+ '''AIGrubCfg interacts with the bootmgmt classes to create and
+ modify the AI boot related service configuration files.
'''
- # Move the install media's grub menu out of the way so we do not boot it.
- media_grub = os.path.join(image_path, "boot/grub", MENULST)
- if os.path.exists(media_grub):
- os.remove(media_grub)
+
+ def __init__(self, name, path=None, image_info=None, srv_address=None,
+ config_dir=None, bootargs=None):
+ '''Constructor for class
+ Input:
+ name - service name
+ path - image path for service
+ image_info - dict of image info
+ srv_address - srv_address from AIService
+ config_dir - service config dir (/var/ai/service/<svcname>)
+ bootargs - string of user specified bootargs or '' if none
+
+ '''
+ # Used for string token substitution of pybootgmmt return data
+ self.boot_tokens = dict()
+ self.netbc = None
+ self.svcname = name
+ self.img_path = path
+ self.image_info = image_info
+ self.srv_address = srv_address
+ self.svc_config_dir = config_dir
+ self.bootargs = bootargs
+ self.tftp_subdir = '/' + name
+ self.mac_address = None
+
+ def setup_grub(self):
+ '''Setup grub files'''
+ self.init_netboot_config()
+ self.build_default_entries()
+ netconfig_files, config_files, boot_tuples = self.handle_boot_config()
+
+ logging.debug('setup_grub returning:\n netconfig_files: %s\n '
+ 'config_files: %s\n boot_tuples: %s',
+ netconfig_files, config_files, boot_tuples)
+ return (netconfig_files, config_files, boot_tuples)
+
+ def init_netboot_config(self):
+ '''Instantiates the bootmgmt.bootConfig.NetBootConfig subclass
+ object for this class
+ '''
+ logging.debug('in init_boot_config')
+ self.netbc = NetBootConfig([BootConfig.BCF_CREATE],
+ platform=('x86', None),
+ osimage_root=self.img_path,
+ data_root=self.svc_config_dir,
+ tftproot_subdir=self.tftp_subdir)
+
+ # Set props to disable graphical boot splash, apply boot timeout
+ # property to boot loader, and set min_mem64.
+ propdict = {BootLoader.PROP_CONSOLE: BootLoader.PROP_CONSOLE_TEXT}
+ if "grub_min_mem64" in self.image_info:
+ minmem64 = self.image_info["grub_min_mem64"]
+ propdict[BootLoader.PROP_MINMEM64] = minmem64
+ for key in propdict:
+ try:
+ self.netbc.boot_loader.setprop(key, propdict[key])
+ except BootmgmtUnsupportedPropertyError:
+ logging.debug("Boot loader type '%s' does not support the "
+ "'%s' property. Ignoring.",
+ self.netbc.boot_loader.name, key)
+
+ def build_default_entries(self):
+ '''Construct the default boot entries and insert them into the
+ NetBootConfig object's boot_instances list
+ '''
+ logging.debug('in build_default_entries')
+
+ # Create net boot instance for Text Installer
+ ti_title = self.image_info.get("no_install_grub_title",
+ "Text Installer and command line")
+ ti_nbi = SolarisNetBootInstance(None, platform='x86', title=ti_title)
+
+ # Modify kernel and boot_archive lines if 32 bit
+ unix_32bit = 'platform/i86pc/kernel/unix'
+ if os.path.exists(os.path.join(self.img_path, unix_32bit)):
+ ti_nbi.kernel = '/platform/i86pc/kernel/%(karch)s/unix'
+ ti_nbi.boot_archive = '/platform/i86pc/%(karch)s/boot_archive'
+
+ # prepend kernel and boot_archive lines with /<svcname>
+ ti_nbi.kernel = '/' + self.svcname + ti_nbi.kernel
+ ti_nbi.boot_archive = '/' + self.svcname + ti_nbi.boot_archive
+ ti_nbi.kargs = ("-B %sinstall_media=http://%s/%s,install_service=%s,"
+ "install_svc_address=%s" %
+ (self.bootargs, self.srv_address, self.img_path,
+ self.svcname, self.srv_address))
+ self.netbc.add_boot_instance(ti_nbi)
+
+ # Create net boot instance for AI
+ ai_title = self.image_info.get("grub_title",
+ "Solaris " + self.svcname) + ' Automated Install'
+ ai_nbi = SolarisNetBootInstance(None, platform='x86', title=ai_title)
+ ai_nbi.kernel = ti_nbi.kernel
+ ai_nbi.boot_archive = ti_nbi.boot_archive
+ ai_nbi.kargs = ("-B %sinstall=true,install_media=http://%s/%s,"
+ "install_service=%s,install_svc_address=%s" %
+ (self.bootargs, self.srv_address, self.img_path,
+ self.svcname, self.srv_address))
+ self.netbc.add_boot_instance(ai_nbi)
- # Create a new menu.lst for the AI client to use during boot
- # menu.lst should be created with right permissions
- orig_umask = os.umask(0022)
- with open(os.path.join(menu_path, MENULST), "w") as menu:
- # First setup the the global environment variables
- menu.write("default=0\n")
- menu.write("timeout=30\n")
+ def handle_boot_config(self):
+ '''Call commit_boot_config and process the resulting tuple list'''
+ logging.debug('in handle_boot_config')
+ tuple_list = self.netbc.commit_boot_config()
+ netconfig_files, config_files, boot_tuples = \
+ self._handle_boot_tuple_list(tuple_list)
+
+ # Remove the install image's cfgfile so we do not boot it.
+ for file in netconfig_files + config_files:
+ cfgfile = os.path.basename(file)
+ media_grub = os.path.join(self.img_path, "boot/grub", cfgfile)
+ if os.path.exists(media_grub):
+ logging.debug('removing %s', media_grub)
+ os.remove(media_grub)
+ return (netconfig_files, config_files, boot_tuples)
+
+ def _handle_boot_tuple_list(self, boot_config_list):
+ '''Process returned tuple list from commit_boot_config()
+ and handle entries appropriately.
+ Returns tuple containing:
+ netconfig_files: List of boot loader configuration files
+ e.g. /etc/netboot/<svcname>/[menu.lst|grub.cfg]
+ config_files: List of other bootmgmt configuration files
+ boot_tuples: list of tuples consisting of:
+ (<type>, <archval>, <relative path of bootfile in tftproot>)
+ e.g., ('00:00', 'bios', '<svcname>/boot/grub/pxegrub2')
+ '''
+ logging.debug('in _handle_boot_tuple_list tuples are %s',
+ boot_config_list)
+ # Add dictionary mappings for tokens that commit_boot_config() might
+ # return.
+ self.boot_tokens[NetBootConfig.TOKEN_TFTP_ROOT] = com.BOOT_DIR
+ boot_tuples = list()
+ netconfig_files = list()
+ config_files = list()
+ for boot_config in boot_config_list:
+ # boot-config elements can be either tuples or lists of tuples.
+ # Cast tuples into a list for consistent iteration loop below.
+ if not isinstance(boot_config, list):
+ boot_config = [boot_config]
+ for config_set in boot_config:
+ ftype = config_set[0]
+ if ftype == BootConfig.OUTPUT_TYPE_NETCONFIG:
+ dest_file = self._handle_file_type(config_set)
+ netconfig_files.append(dest_file)
+ elif ftype == BootConfig.OUTPUT_TYPE_FILE:
+ dest_file = self._handle_file_type(config_set)
+ config_files.append(dest_file)
+ elif ftype in [BootConfig.OUTPUT_TYPE_BIOS_NBP,
+ BootConfig.OUTPUT_TYPE_UEFI64_NBP]:
+ boot_entry = self._handle_nbp_type(config_set)
+ boot_tuples.append(boot_entry)
+ else:
+ raise GrubError("Unexpected boot loader file "
+ "type: %s" % ftype)
+
+ logging.debug('_handle_boot_tuple_list returning netconfig_files: %s',
+ netconfig_files)
+ logging.debug('_handle_boot_tuple_list returning config_files: %s',
+ config_files)
+ logging.debug('_handle_boot_tuple_list returning boot_tuples: %s',
+ boot_tuples)
+ return (netconfig_files, config_files, boot_tuples)
+
+ def _handle_file_type(self, config):
+ '''Method that handles tuple entry of type BootConfig.OUTPUT_TYPE_FILE
+ Returns 'dest' from tuple, after updating '%(tftproot)s' token
+ with appropriate value.
+
+ '''
+ ftype, src, objref, dest, uid, gid, mode = config
+
+ if src is None:
+ raise GrubError("Error: No source path defined for boot file.")
+ if dest is None:
+ raise GrubError("Error: No destination path defined for boot "
+ "file.")
+ # handle ownership/permissions
+ if uid is None:
+ uid = -1
+ if gid is None:
+ gid = -1
+ set_perms(src, uid, gid, mode)
+
+ # for a client, use the src as the dest, because the file is
+ # created directly in tftproot
+ if self.mac_address is not None:
+ real_dest = src
+ else:
+ real_dest = dest % self.boot_tokens
+ return (real_dest)
+
+ def _handle_nbp_type(self, config):
+ '''Method that handles tuple entry of type
+ BootConfig.OUTPUT_TYPE_BIOS_NBP or OUTPUT_TYPE_UEFI64_NBP
+ Returns tuple:
+ (dhcparch, archtype, relpath to nbp)
+ ('00:00', 'bios', mysvc/boot/grub/pxegrub2')
+ '''
+ ftype, src, objref, dest, uid, gid, mode = config
+
+ if src is None:
+ raise GrubError("Error: No source path defined for nbp file.")
- if "grub_min_mem64" in image_info:
- menu.write("min_mem64=%s\n\n" % image_info["grub_min_mem64"])
+ # ftype will be 'nbp-platform-0xnnnn', such as 'nbp-platform-0x0007'
+ # strip off the '0x'
+ archval = ftype.rpartition('-0x')[2]
+ archtype = archval
+ if archval in NBP_TYPE:
+ archtype = (NBP_TYPE[archval])
+ dhcparch = archval[0:2] + ':' + archval[2:4]
+
+ # create the relative path to the nbp (from /etc/netboot)
+ relpath = self.svcname + src.partition(self.img_path)[2]
+ return (dhcparch, archtype, relpath)
+
+ def _read_current_config(self, mac_address=None):
+ '''Read in current config and set self.netbc'''
+ logging.debug('_read_current_config: mac_address is %s', mac_address)
+
+ if mac_address is None:
+ # Read in service's config
+ self.netbc = NetBootConfig([],
+ platform=('x86', None),
+ osimage_root=self.img_path,
+ data_root=self.svc_config_dir,
+ tftproot_subdir=self.tftp_subdir)
+ else:
+ # Read in clients's config
+ self.netbc = NetBootConfig([],
+ platform=('x86', None),
+ osimage_root=self.img_path,
+ data_root=com.BOOT_DIR,
+ client_id=mac_address,
+ tftproot_subdir=self.tftp_subdir)
+ self.mac_address = mac_address
+
+ def update_imagepath(self, oldpath, newpath, mac_address=None):
+ '''Update the imagepath in the boot configfiles
+
+ Arguments:
+ oldpath - imagepath to replace
+ newpath - new imagepath
+ mac_address - client MAC address
+ '''
+ logging.debug('update_imagepath: oldpath=%s newpath=%s '
+ 'mac_address=%s', oldpath, newpath, mac_address)
+
+ self._read_current_config(mac_address=mac_address)
+
+ # Update imagepath in service's boot_instance kargs
+ for boot_instance in self.netbc.boot_instances:
+ kargs = boot_instance.kargs
+ boot_instance.kargs = update_kargs_imagepath(kargs, oldpath,
+ newpath)
+ tuple_list = self.netbc.commit_boot_config()
+ netconfig_files, config_files, boot_tuples = \
+ self._handle_boot_tuple_list(tuple_list)
+ return (netconfig_files, config_files, boot_tuples)
+
+ def update_svcname(self, oldname, newname, mac_address=None):
+ '''Update the service name in the boot configfiles
+
+ Arguments:
+ oldpath - imagepath to replace
+ newpath - new imagepath
+ mac_address - client MAC address
+ '''
+ logging.debug('in update_svcname oldname=%s newname=%s',
+ oldname, newname)
+ self._read_current_config(mac_address=mac_address)
+
+ # Update net_tftproot_subdir in NetBootConfig instance
+ self.netbc.net_tftproot_subdir = '/' + newname
+
+ # Update svcname in service's boot_instance kargs
+ for boot_instance in self.netbc.boot_instances:
+ boot_instance = update_boot_instance_svcname(boot_instance,
+ oldname, newname)
+
+ tuple_list = self.netbc.commit_boot_config()
+ netconfig_files, config_files, boot_tuples = \
+ self._handle_boot_tuple_list(tuple_list)
+ return (netconfig_files, config_files, boot_tuples)
+
+ def get_mountpt(self, mac_address=None):
+ '''Get the mountpt'''
+ logging.debug('in get_mountpt')
+ self._read_current_config(mac_address=mac_address)
+ kernel = self.netbc.boot_instances[0].kernel.lstrip('/')
+ parts = kernel.partition('/')
+ mountpt = ''
+ if parts[1]:
+ mountpt = parts[0]
+ logging.debug('get_mountpt returning %s', mountpt)
+ return mountpt
+
+ def setup_client(self, mac_address, bootargs='', service_bootargs=''):
+ '''Setup a client's config files, using service's config info
+ but allowing for client specific bootargs
- # Add the 'no install' entry. It may not have a title in
- # the image_info, so set a default.
- menu.write("title %s\n" %
- image_info.get("no_install_grub_title",
- "Text Installer and command line"))
- menu.write("\tkernel$ /%s/platform/i86pc/kernel/$ISADIR/unix -B "
- "%sinstall_media=http://%s/%s,install_service=%s,"
- "install_svc_address=%s\n" %
- (svc_name, bootargs, srv_address, image_path, svc_name,
- srv_address))
- menu.write("\tmodule$ /%s/platform/i86pc/$ISADIR/boot_archive\n\n" %
- svc_name)
+ Arguments:
+ mac_address - client MAC address
+ (either ABABABABABAB or AB:AB:AB:AB:AB:AB)
+ bootargs = bootargs of client
+ service_bootargs = bootargs of service
+ Returns:
+ Returns tuple (netconfig_files, config_files, boot_tuples):
+ netconfig_files: List of boot loader configuration files
+ e.g. /etc/netboot/<svcname>/[menu.lst|grub.cfg]
+ config_files: List of other bootmgmt configuration files
+ boot_tuples: list of tuples consisting of:
+ (<type>, <archval>, <relative path of bootfile>)
+ [('00:00', 'bios', 'myservice/boot/grub/pxegrub2'),
+ ('00:07', 'uefi', 'seth0126rename/boot/grub/grub2netx64.efi')]
+
+ '''
+ logging.debug('setup_client: mac_address=%s, bootargs=%s, '
+ 'service_bootargs=%s', mac_address, bootargs,
+ service_bootargs)
+ self.mac_address = mac_address
+
+ # Read in service's config
+ svc_netbc = NetBootConfig([],
+ platform=('x86', None),
+ osimage_root=self.img_path,
+ data_root=self.svc_config_dir,
+ tftproot_subdir=self.tftp_subdir)
+
+ # create client boot config
+ client_netbc = NetBootConfig([BootConfig.BCF_CREATE],
+ platform=('x86', None),
+ osimage_root=self.img_path,
+ data_root=com.BOOT_DIR,
+ client_id=mac_address,
+ tftproot_subdir=self.tftp_subdir)
+
+ # Copy service boot instances and add to client (update bootargs
+ # first, if they were specified by user)
+ svc_boot_instances = svc_netbc.boot_instances
+ for boot_instance in svc_boot_instances:
+ client_bi = boot_instance.copy()
+ if bootargs:
+ client_bi.kargs = update_kargs_bootargs(client_bi.kargs,
+ service_bootargs, bootargs)
+ client_netbc.add_boot_instance(client_bi)
+
+ # Set client boot loader props the same as in the service
+ # E.g., BootLoader.<PROP_CONSOLE/PROP_TIMEOUT/PROP_MINMEM64>
+ svc_bl = svc_netbc.boot_loader
+ for prop in svc_bl.SUPPORTED_PROPS:
+ client_netbc.boot_loader.setprop(prop, svc_bl.getprop(prop))
- # Finally, add the 'install' entry
- menu.write("title %s Automated Install\n" %
- image_info.get("grub_title", "Solaris " + svc_name))
- menu.write("\tkernel$ /%s/platform/i86pc/kernel/$ISADIR/unix -B "
- "%sinstall=true,install_media=http://%s/%s,"
- "install_service=%s,install_svc_address=%s\n" %
- (svc_name, bootargs, srv_address, image_path, svc_name,
- srv_address))
- menu.write("\tmodule$ /%s/platform/i86pc/$ISADIR/boot_archive\n" %
- svc_name)
- os.umask(orig_umask)
+ # Commit the configuration. This will result in the creation of
+ # the <cfgfile>.<clientid> file (e.g., menu.lst.01223344223344
+ # or grub.cfg.01223344223344) in tftproot.
+ tuple_list = client_netbc.commit_boot_config()
+ netconfig_files, config_files, boot_tuples = \
+ self._handle_boot_tuple_list(tuple_list)
+ return (netconfig_files, config_files, boot_tuples)
+
+ def update_basesvc(self, newbasesvc_netbc, newbasesvc_bootargs='',
+ alias_bootargs=''):
+ '''Update an alias's config files for a new basesvc
+
+ Arguments:
+ newbasesvc_netbc - AIGrubCfg object of new basesvc
+ bootargs = bootargs of client
+ service_bootargs = bootargs of service
+ Returns tuple (netconfig_files, config_files, boot_tuples):
+ netconfig_files: List of boot loader configuration files
+ e.g. /etc/netboot/<svcname>/[menu.lst|grub.cfg]
+ config_files: List of other bootmgmt configuration files
+ boot_tuples: list of tuples consisting of:
+ (<type>, <archval>, <relative path of bootfile>)
+
+ '''
+ logging.debug('update_basesvc: newbasesvc_bootargs=%s, '
+ 'alias_bootargs=%s' %
+ (newbasesvc_bootargs, alias_bootargs))
+
+ # Read in new baseservice's config
+ newbase_netbc = NetBootConfig([],
+ platform=('x86', None),
+ osimage_root=newbasesvc_netbc.img_path,
+ data_root=newbasesvc_netbc.svc_config_dir,
+ tftproot_subdir=newbasesvc_netbc.tftp_subdir)
+
+ # Create new alias config
+ alias_netbc = NetBootConfig([BootConfig.BCF_CREATE],
+ platform=('x86', None),
+ osimage_root=newbasesvc_netbc.img_path,
+ data_root=self.svc_config_dir,
+ tftproot_subdir=self.tftp_subdir)
+
+ # Copy new baseservice boot instances and use for alias, but
+ # use name and bootargs of alias
+ newbase_boot_instances = newbase_netbc.boot_instances
+ for boot_inst in newbase_boot_instances:
+ alias_bi = boot_inst.copy()
+ alias_bi = update_boot_instance_svcname(alias_bi,
+ newbasesvc_netbc.svcname, self.svcname)
+ alias_bi.kargs = update_kargs_bootargs(alias_bi.kargs,
+ newbasesvc_bootargs, alias_bootargs)
+ alias_netbc.add_boot_instance(alias_bi)
+
+ # Set alias boot loader props the same as in the base service
+ # E.g., BootLoader.<PROP_CONSOLE/PROP_TIMEOUT/PROP_MINMEM64>
+ newbase_bl = newbase_netbc.boot_loader
+ for prop in newbase_bl.SUPPORTED_PROPS:
+ alias_netbc.boot_loader.setprop(prop, newbase_bl.getprop(prop))
+
+ # Commit the configuration.
+ tuple_list = alias_netbc.commit_boot_config()
+ netconfig_files, config_files, boot_tuples = \
+ self._handle_boot_tuple_list(tuple_list)
+ return (netconfig_files, config_files, boot_tuples)
--- a/usr/src/cmd/installadm/installadm-convert.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/installadm/installadm-convert.py Fri Mar 23 04:40:57 2012 -0700
@@ -19,7 +19,7 @@
#
# CDDL HEADER END
#
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
#
"""
This file contains the code necessary to convert an AI server configuration
@@ -41,13 +41,13 @@
import osol_install.auto_install.AI_database as AIdb
import osol_install.auto_install.ai_smf_service as aismf
import osol_install.auto_install.dhcp as dhcp
-import osol_install.auto_install.grub as grub
import osol_install.auto_install.installadm_common as com
import osol_install.auto_install.service as svc
import osol_install.auto_install.service_config as config
import osol_install.libaiscf as smf
from osol_install.auto_install import create_client
+from osol_install.auto_install.grub import AIGrubCfg as grubcfg
from osol_install.auto_install.installadm_common import _, cli_wrap as cw
from osol_install.auto_install.service import AIService
from solaris_install import Popen, CalledProcessError
@@ -145,7 +145,7 @@
# Set up a regular expression to pull the last tuple from an IP
regexp = re.compile("^(\d{1,3}\.\d{1,3}\.\d{1,3})\.(\d{1,3})$")
- # Break the IP address into two string values
+ # Break the IP address into two string values
m = regexp.match(cur_ipaddr)
if m is None:
raise SUNDHCPData.DHCPError("generate_ip_ranges: bad IP "
@@ -154,7 +154,7 @@
cur_network_octet = m.group(1)
cur_host_octet = m.group(2)
- # Break the IP address into two string values;
+ # Break the IP address into two string values;
m = regexp.match(last_ipaddr)
if m is None:
raise SUNDHCPData.DHCPError("generate_ip_ranges: bad IP "
@@ -1251,7 +1251,7 @@
# Check to see if installadm-convert has been run before by
# testing for the existence of install.conf.bak and install.conf
- # existing as a sym link. If this is the case then remove the
+ # existing as a sym link. If this is the case then remove the
# link and copy install.conf.bak to install.conf. This will restore
# the file to its original contents and allow the convert to run
# successfully.
@@ -1379,7 +1379,10 @@
try:
print _(" Update menu.lst format")
- grub.update_svcname(new_grub, ai_service, ai_service)
+ svcgrub = grubcfg(ai_service, path=ipath,
+ config_dir=new_service_path)
+ old_mountpt = svcgrub.get_mountpt()
+ svcgrub.update_svcname(old_mountpt, ai_service)
except IOError as err:
raise ServiceConversionError(str(err))
@@ -1512,7 +1515,7 @@
svc.MountError) as err:
sys.stderr.write("Client conversion failed:\n")
sys.stderr.write(str(err) + "\n")
-
+
def copy_netboot_files(dry_run):
"""
--- a/usr/src/cmd/installadm/rename_service.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/installadm/rename_service.py Fri Mar 23 04:40:57 2012 -0700
@@ -19,7 +19,7 @@
#
# CDDL HEADER END
#
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
"""
AI rename-service
"""
@@ -34,6 +34,7 @@
from optparse import OptionParser
+from bootmgmt import BootmgmtError
from osol_install.auto_install.image import ImageError
from osol_install.auto_install.installadm_common import _, \
validate_service_name, cli_wrap as cw
@@ -81,7 +82,7 @@
def do_rename_service(cmd_options=None):
- '''Rename a service.
+ '''Rename a service and update all associated aliases and clients.
Note: Errors that occur during the various rename stages
are printed, but the other stages will continue, with the hopes
@@ -137,9 +138,12 @@
# Remove clients whose base service has been renamed
clients = config.get_clients(svcname)
for clientid in clients.keys():
- clientctrl.remove_client(clientid)
+ clientctrl.remove_client(clientid, suppress_dhcp_msgs=True)
- oldservice.rename(newsvcname)
+ try:
+ oldservice.rename(newsvcname)
+ except BootmgmtError as err:
+ raise SystemExit(err)
# Update aliases whose base service has been renamed
aliases = config.get_aliased_services(svcname)
@@ -166,13 +170,16 @@
bootargs = None
if config.BOOTARGS in clients[clientid]:
bootargs = clients[clientid][config.BOOTARGS]
- create_client.create_new_client(arch, newservice, client,
- bootargs=bootargs)
-
+ try:
+ create_client.create_new_client(arch, newservice, client,
+ bootargs=bootargs, suppress_dhcp_msgs=True)
+ except BootmgmtError as err:
+ failures.append(err)
+ print >> sys.stderr, (_('\nError: Unable to recreate client, '
+ '%s:\n%s') % (client, err))
if failures:
return 1
- else:
- return 0
+ return 0
if __name__ == '__main__':
--- a/usr/src/cmd/installadm/service.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/installadm/service.py Fri Mar 23 04:40:57 2012 -0700
@@ -41,7 +41,6 @@
import fileinput
import logging
import os
-import shutil
import socket
import sys
@@ -54,6 +53,7 @@
from osol_install.auto_install.data_files import DataFiles, insert_SQL, \
place_manifest, validate_file
+from osol_install.auto_install.grub import AIGrubCfg as grubcfg
from osol_install.auto_install.image import InstalladmImage, ImageError
from osol_install.auto_install.installadm_common import _, cli_wrap as cw
from solaris_install import Popen, CalledProcessError, force_delete
@@ -262,7 +262,7 @@
bootserver: If DHCP configuration is provided, optionally explicit
address for the bootfile server
alias: An AIService object to which this service will be aliased
- bootargs (x86 only): Additional bootargs to add to menu.lst
+ bootargs (x86 only): Additional bootargs to add to boot cfgfile
'''
service = cls(name, _check_version=False)
@@ -306,9 +306,6 @@
service._create_default_manifest()
- # Configure DHCP for this service
- service._configure_dhcp(ip_start, ip_count, bootserver)
-
# Supported client images will interpolate the bootserver's IP
# address (as retrieved from the DHCP server) in place of the
# '$serverIP' string. We choose to utilize this functionality when
@@ -347,6 +344,9 @@
else:
service._init_grub(srv_address)
+ # Configure DHCP for this service
+ service._configure_dhcp(ip_start, ip_count, bootserver)
+
return service
def __init__(self, name, image=None, image_class=InstalladmImage,
@@ -371,6 +371,8 @@
self._alias = None
self._bootargs = None
self._image_class = image_class
+ self._boot_tuples = None
+ self._netcfgfile = None
# _check_version should ONLY be used by AIService.create during service
# creation. Bypassing the version check when the service has already
@@ -454,10 +456,7 @@
return AIdb.DB(self.database_path)
def rename(self, new_name):
- '''Change this service's name to "new_name" updating all relevant
- files, aliases and clients.
-
- '''
+ '''Change this service's name to new_name '''
self._update_name_in_service_props(new_name)
new_svcdir = os.path.join(AI_SERVICE_DIR_PATH, new_name)
if self.arch == 'sparc':
@@ -478,11 +477,18 @@
# system.conf -> /etc/netboot/<defaultsvc>/system.conf
# by calling: self.do_default_sparc_symlinks(new_name)
else:
- # Update the menu.lst file with the new service name
+ # Update the grub config files with the new service name
# Note that the new svcname is assumed to be the mountpoint
# in /etc/netboot
- menulstpath = os.path.join(self.config_dir, MENULST)
- grub.update_svcname(menulstpath, new_name, new_name)
+ svcgrub = grubcfg(self.name, path=self.image.path,
+ config_dir=self.config_dir)
+ netconfig_files, config_files, self._boot_tuples = \
+ svcgrub.update_svcname(self.name, new_name)
+
+ # update boot_cfgfile in .config file
+ self._netcfgfile = netconfig_files[0]
+ props = {config.PROP_BOOT_CFGFILE: self._netcfgfile}
+ config.set_service_props(self.name, props)
self._migrate_service_dir(new_svcdir)
self._setup_manifest_dir(new_name=new_name)
@@ -600,41 +606,63 @@
'''
if self.arch == 'i386':
- return os.path.join(self.mountpoint, MENULST)
+ mtpt = self.boot_cfgfile
+ if not mtpt:
+ # Fall back to menu.lst
+ mtpt = os.path.join(self.mountpoint, MENULST)
+ return mtpt
else:
return os.path.join(self.mountpoint, SYSTEMCONF)
def all_bootmountpts(self):
- return [os.path.join(self.mountpoint, MENULST),
- os.path.join(self.mountpoint, SYSTEMCONF)]
+ mountpts = [os.path.join(self.mountpoint, MENULST),
+ os.path.join(self.mountpoint, SYSTEMCONF)]
+ mountpts.append(self.bootmountpt)
+ return mountpts
@property
def bootsource(self):
'''Returns the path to the file that should be mounted at
- self.bootmountpt
-
+ self.bootmountpt (e.g., /var/ai/service/<svcname>/<grub_cfg_file>)
'''
if self.arch == 'i386':
- return os.path.join(self.config_dir, MENULST)
+ mtpt = self.bootmountpt
+ cfgfile = os.path.basename(mtpt)
+ return os.path.join(self.config_dir, cfgfile)
else:
return os.path.join(self.config_dir, SYSTEMCONF)
@property
- def dhcp_bootfile(self):
- '''Returns a string that represents what should be added to DHCP
- as this service's bootfile
+ def boot_tuples(self):
+ '''Boot tuples created by bootmgmt'''
+ return self._boot_tuples
+
+ @property
+ def boot_cfgfile(self):
+ '''Look up and return the name of where to mount config file
+ created by bootmgmt (e.g., /etc/netboot/mysvc/<grub_cfg_file>)
'''
- if self.arch == 'sparc':
- http_port = libaimdns.getinteger_property(com.SRVINST,
- com.PORTPROP)
- # Always use $serverIP keyword as setup-dhcp will
- # substitute in the correct IP addresses.
- dhcpbfile = 'http://%s:%u/%s' % ("$serverIP", http_port,
- com.WANBOOTCGI)
- else:
- abs_dhcpbfile = os.path.join(self.mountpoint, self.X86_BOOTFILE)
- dhcpbfile = os.path.relpath(abs_dhcpbfile, self.MOUNT_ON)
+ props = config.get_service_props(self.name)
+ try:
+ return props[config.PROP_BOOT_CFGFILE]
+ except (KeyError, ValueError, TypeError):
+ return None
+
+ @property
+ def dhcp_bootfile_sparc(self):
+ '''Returns a string that represents what should be added to DHCP
+ as this sparc service's bootfile
+
+ '''
+ if self.arch != 'sparc':
+ return None
+
+ http_port = libaimdns.getinteger_property(com.SRVINST, com.PORTPROP)
+ # Always use $serverIP keyword as setup-dhcp will substitute in
+ # the correct IP addresses.
+ dhcpbfile = 'http://%s:%u/%s' % ("$serverIP", http_port,
+ com.WANBOOTCGI)
return dhcpbfile
def _lofs_mount(self, from_path, to_mountpoint):
@@ -671,8 +699,8 @@
/etc/netboot/<svcname>/system.conf is an lofs mount of
AI_SERVICE_DIR_PATH/<svcname>/system.conf
For x86:
- /etc/netboot/<svcname>/menu.lst is an lofs mount of
- AI_SERVICE_DIR_PATH/<svcname>/menu.lst
+ /etc/netboot/<svcname>/<cfgfile> is an lofs mount of
+ AI_SERVICE_DIR_PATH/<svcname>/<grub_cfg_file>
'''
# Verify the image and ensure the mountpoints exist
@@ -687,8 +715,12 @@
# Mount service's image to /etc/netboot/<svcname>
self._lofs_mount(self.image.path, self.mountpoint)
- # Do second lofi mount of menu.lst(x86) or system.conf(sparc)
- self._lofs_mount(self.bootsource, self.bootmountpt)
+ # Do second lofi mount of grub config file for x86 or
+ # system.conf (for sparc)
+ bootsource = self.bootsource
+ bootmount = self.bootmountpt
+ logging.debug('lofs mount of %s, %s', bootsource, bootmount)
+ self._lofs_mount(bootsource, bootmount)
def unmount(self, force=False, arch_safe=False):
'''Unmount the service, reversing the effects of AIService.mount()
@@ -795,26 +827,10 @@
All:
o update aliasof property in .config to newbasesvc_name
o disable alias
- x86 only:
- o Copy over new base services's menu.lst to alias' menu.lst
- o reinstate alias service name in alias' menu.lst
- o Replace base service bootargs with alias' bootargs in
- alias' menu.lst
- o For each dependent alias of alias:
- o Copy alias' revised menu.lst to dependent alias' menu.lst
- o reinstate dependent alias name in dependent alias'
- menu.lst
- o If bootargs were specified for dependent alias,
- replace alias bootargs with dependent alias bootargs
- o For each client of alias:
- o Copy over alias' revised menu.lst to menu.lst.<clientid>
- o reinstate alias service_name in menu.lst.<clientid>
- o If client bootargs were specified during create-client,
- replace alias bootargs with client bootargs in
- menu.lst.<clientid>. Otherwise, alias bootargs are
- inherited.
+ o x86 only: Recreate grub config for alias and dependent aliases
o revalidate manifests and profiles
o enable alias
+ Dependent clients are handled in set_service.
Input: newbasesvc_name - name of new base service
@@ -842,26 +858,23 @@
self.disable(force=True)
if self.arch == 'i386':
- self_menulstpath = os.path.join(self.config_dir, MENULST)
-
all_aliases = config.get_aliased_services(self.name, recurse=True)
- all_clients = config.get_clients(self.name).keys()
- for alias in all_aliases:
- all_clients.extend(config.get_clients(alias).keys())
- # Copy over the new base service's menu.lst file for the alias
- # to use. Replace the base service's name with the alias' name
- # as well as the base service's bootargs with the alias' bootargs
- # in the alias' menu.lst file.
- newbasesvc_menulst = os.path.join(newbasesvc.config_dir, MENULST)
- logging.debug("update_basesvc: copying new menu.lst from %s to %s",
- newbasesvc_menulst, self_menulstpath)
- shutil.copy(newbasesvc_menulst, self_menulstpath)
- grub.update_svcname(self_menulstpath, self.name, self.name)
- grub.update_bootargs(self_menulstpath, newbasesvc.bootargs,
- self.bootargs)
+ # Update the grub config with the new base service info.
+ newbasegrub = grubcfg(newbasesvc.name, path=newbasesvc.image.path,
+ config_dir=newbasesvc.config_dir)
+ aliasgrub = grubcfg(self.name, path=newbasesvc.image.path,
+ config_dir=self.config_dir)
+ netconfig_files, config_files, self._boot_tuples = \
+ aliasgrub.update_basesvc(newbasegrub, newbasesvc.bootargs,
+ self.bootargs)
- # Recreate menu.lst files of all aliases of this alias
+ # update boot_cfgfile in .config file
+ self._netcfgfile = netconfig_files[0]
+ props = {config.PROP_BOOT_CFGFILE: self._netcfgfile}
+ config.set_service_props(self.name, props)
+
+ # Recreate grub config files of all aliases of this alias
# (and descendants). Use the sub alias' bootargs instead
# of the ones specified in the base service.
for alias in all_aliases:
@@ -871,35 +884,13 @@
sub_alias_was_mounted = True
aliassvc.disable(force=True)
- sub_alias_menulst = os.path.join(aliassvc.config_dir, MENULST)
- logging.debug("update_basesvc: copying new menu.lst from %s "
- "to %s", self_menulstpath, sub_alias_menulst)
- shutil.copy(self_menulstpath, sub_alias_menulst)
- grub.update_svcname(sub_alias_menulst, aliassvc.name,
- aliassvc.name)
- grub.update_bootargs(sub_alias_menulst, self.bootargs,
- aliassvc.bootargs)
+ aliasgrub = grubcfg(alias, path=aliassvc.image.path,
+ config_dir=aliassvc.config_dir)
+ aliasgrub.update_basesvc(newbasegrub, newbasesvc.bootargs,
+ aliassvc.bootargs)
if sub_alias_was_mounted:
aliassvc.enable()
- # recreate menu.lst files for clients
- for clientid in all_clients:
- (service, datadict) = config.find_client(clientid)
- client_bootargs = datadict.get(config.BOOTARGS, '')
- client_menulst = get_client_menulst(clientid)
-
- # copy alias' menu.lst file to menu.lst.<clientid>
- logging.debug("update_basesvc: copying new menu.lst from %s "
- "to %s", self_menulstpath, client_menulst)
- shutil.copy(self_menulstpath, client_menulst)
- grub.update_svcname(client_menulst, self.name, self.name)
-
- # if the client has bootargs, use them. Otherwise, inherit
- # the bootargs specified in the alias (do nothing)
- if client_bootargs:
- grub.update_bootargs(client_menulst, self.bootargs,
- client_bootargs)
-
# Turning point: Function calls prior to this line will reference
# the 'old' base service's image. Function calls after this line
# will reference the *new* base service's image. Keep that in mind
@@ -918,6 +909,9 @@
if was_mounted:
self.enable()
+ # Configure DHCP for this service
+ self._configure_dhcp(None, None, None)
+
@property
def manifest_dir(self):
'''Full path to the directory where this service's
@@ -1081,16 +1075,22 @@
def _configure_dhcp(self, ip_start, ip_count, bootserver):
'''Add DHCP configuration elements for this service.'''
- logging.debug('Calling setup_dhcp_server %s %s %s %s %s',
- ip_start, ip_count, bootserver, self.dhcp_bootfile, self.arch)
+ logging.debug('Calling setup_dhcp_server %s %s %s',
+ ip_start, ip_count, bootserver)
setup_dhcp_server(self, ip_start, ip_count, bootserver)
def _init_grub(self, srv_address):
- '''Initialize grub (menu.lst) for this service'''
- grub.setup_grub(self.name, self.image.path,
- self.image.read_image_info(), srv_address,
- self.config_dir, self._bootargs)
+ '''Initialize grub for this service'''
+ svcgrub = grubcfg(self.name, path=self.image.path,
+ image_info=self.image.read_image_info(),
+ srv_address=srv_address, config_dir=self.config_dir,
+ bootargs=self._bootargs)
+
+ netconfig_files, config_files, self._boot_tuples = svcgrub.setup_grub()
+ self._netcfgfile = netconfig_files[0]
+ props = {config.PROP_BOOT_CFGFILE: self._netcfgfile}
+ config.set_service_props(self.name, props)
def _init_wanboot(self, srv_address):
'''Create system.conf file in AI_SERVICE_DIR_PATH/<svcname>'''
@@ -1291,8 +1291,8 @@
'''Change the location of a service's image:
o disable the service and all dependent aliases
o update wanboot.conf for the service (sparc)
- o update menu.lst files of service and its clients and
- aliases (x86)
+ o update grub config files of service and its clients
+ and aliases (x86)
o update service .config file
o move the image and update webserver symlink
o enable service and dependent aliases
@@ -1314,28 +1314,31 @@
enabled_aliases.append(alias)
aliassvc.disable(force=True)
- # For x86, update menu.lst files (of service, clients, and aliases)
- # with the new imagepath.
+ # For x86, update grub config files of service, clients, and
+ # aliases with the new imagepath.
# For sparc, update wanboot.conf with the new imagepath.
# There is nothing to do for sparc aliases or clients because they
# reference the service's wanboot.conf file.
if self.arch == 'i386':
- self_menulstpath = os.path.join(self.config_dir, MENULST)
- grub.update_imagepath(self_menulstpath, self.image.path, new_path)
+ svcgrub = grubcfg(self.name, path=self.image.path,
+ config_dir=self.config_dir)
+
+ svcgrub.update_imagepath(self.image.path, new_path)
all_clients = config.get_clients(self.name).keys()
for alias in all_aliases:
all_clients.extend(config.get_clients(alias).keys())
for clientid in all_clients:
- client_menulstpath = get_client_menulst(clientid)
- grub.update_imagepath(client_menulstpath, self.image.path,
- new_path)
+ clientgrub = grubcfg(self.name, path=self.image.path,
+ config_dir=self.config_dir)
+ clientgrub.update_imagepath(self.image.path, new_path,
+ mac_address=clientid[2:])
for alias in all_aliases:
aliassvc = AIService(alias)
- alias_menulstpath = os.path.join(aliassvc.config_dir, MENULST)
- grub.update_imagepath(alias_menulstpath, aliassvc.image.path,
- new_path)
+ aliasgrub = grubcfg(alias, path=aliassvc.image.path,
+ config_dir=aliassvc.config_dir)
+ aliasgrub.update_imagepath(aliassvc.image.path, new_path)
elif self.arch == 'sparc':
self.update_wanboot_imagepath(self.image.path, new_path)
@@ -1392,11 +1395,6 @@
return port
-def get_client_menulst(clientid):
- '''get path to client menu.lst.<clientid> file'''
- return (os.path.join(NETBOOT, MENULST + '.' + clientid))
-
-
_DHCP_MSG = """No local DHCP configuration found. This service is the default
alias for all %s clients. If not already in place, the following should
be added to the DHCP configuration:"""
@@ -1409,11 +1407,9 @@
"DHCP configuration has not been completed. Please "
"see dhcpd(8) for further information.\n"))
- bootfile = service.dhcp_bootfile
arch = service.arch
-
- logging.debug("setup_dhcp_server: ip_start=%s, ip_count=%s, "
- "bootfile=%s, arch=%s", ip_start, ip_count, bootfile, arch)
+ logging.debug("setup_dhcp_server: ip_start=%s, ip_count=%s, arch=%s",
+ ip_start, ip_count, arch)
server = dhcp.DHCPServer()
svc_cmd = False
@@ -1448,35 +1444,54 @@
# creating a new default alias for this architecture, set this service's
# bootfile as the default in the DHCP local server.
if arch == 'sparc':
+ bootfile = service.dhcp_bootfile_sparc
bfile = dhcp.fixup_sparc_bootfile(bootfile)
client_type = 'SPARC'
else:
- bfile = bootfile
+ # create new tuple list containing only the arch val and relpath
+ # i.e., go from entries of (dhcparch, archtype, relpath to nbp) to
+ # (dhcparch, relpath to nbp)
+ bootfile = service.boot_tuples
+ bootfile_tuples = zip(*service.boot_tuples)
+ bootfile_tuples = zip(bootfile_tuples[0], bootfile_tuples[2])
+ bfile = ''
+ for archval, archtype, relpath in service.boot_tuples:
+ line = (' ' + archtype + ' clients (arch ' + archval + '): ' +
+ relpath + '\n')
+ bfile = bfile + line
client_type = 'PXE'
+ logging.debug("setup_dhcp_server: bootfile=%s, bfile=\n%s",
+ bootfile, bfile)
+
if server.is_configured():
if service.is_default_arch_service():
try:
+ if arch == 'i386':
+ # add the arch option if needed
+ server.add_option_arch()
if server.arch_class_is_set(arch):
# There is a class for this architecture already in place
cur_bootfile = server.get_bootfile_for_arch(arch)
+ logging.debug('cur_bootfile is %s', cur_bootfile)
if cur_bootfile is None:
# No bootfile set for this arch
- print cw(_("Setting the default %s bootfile in the "
- "local DHCP configuration to '%s'\n") %
- (client_type, bfile))
+ print cw(_("Setting the default %s bootfile(s) in "
+ "the local DHCP configuration to:"
+ "\n%s\n") % (client_type, bfile))
server.add_bootfile_to_arch(arch, bootfile)
- elif cur_bootfile != bfile:
+ elif ((arch == 'sparc' and cur_bootfile != bfile) or
+ (arch == 'i386' and sorted(bootfile_tuples) !=
+ sorted(cur_bootfile))):
# Update the existing bootfile to our default
- print cw(_("Updating the default %s bootfile in the "
- "local DHCP configuration from '%s' to "
- "'%s'\n") %
- (client_type, cur_bootfile, bfile))
+ print cw(_("Updating the default %s bootfile(s) in "
+ "the local DHCP configuration to:\n%s\n") %
+ (client_type, bfile))
server.update_bootfile_for_arch(arch, bootfile)
else:
# Set up a whole new architecture class
- print cw(_("Setting the default %s bootfile in the local "
- "DHCP configuration to '%s'\n") %
+ print cw(_("Setting the default %s bootfile(s) in the "
+ "local DHCP configuration to:\n%s\n") %
(client_type, bfile))
server.add_arch_class(arch, bootfile)
@@ -1515,9 +1530,10 @@
if arch == 'i386':
# Boot server IP is not needed for SPARC clients
- print _("\t%-20s : %s" % ("Boot server IP", server_ip))
-
- print _("\t%-20s : %s\n" % ("Boot file", bfile))
+ print _("%s: %s" % ("Boot server IP", server_ip))
+ print _("%s:\n%s" % ("Boot file(s)", bfile))
+ else:
+ print _("%s: %s\n" % ("Boot file", bfile))
if len(valid_nets) > 1 and arch == 'i386':
print cw(_("\nNote: determined more than one IP address "
--- a/usr/src/cmd/installadm/service_config.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/installadm/service_config.py Fri Mar 23 04:40:57 2012 -0700
@@ -44,6 +44,7 @@
PROP_BOOT_ARGS = 'boot_args'
PROP_BOOT_FILE = 'boot_file'
PROP_BOOT_MENU = 'boot_menu'
+PROP_BOOT_CFGFILE = 'boot_cfgfile'
PROP_DEFAULT_MANIFEST = 'default-manifest'
PROP_GLOBAL_MENU = 'global_menu'
PROP_IMAGE_PATH = 'image_path'
@@ -169,19 +170,19 @@
'''
logging.log(com.XDEBUG, '**** START service_config.get_service_props ****')
-
+
cfgp = _read_config_file(service_name)
if cfgp is None:
return None
-
+
props = dict()
if cfgp.has_section(SERVICE):
section_dict = dict(cfgp.items(SERVICE))
props.update(section_dict)
-
+
for prop in props:
logging.log(com.XDEBUG, ' property: %s=%s', prop, props[prop])
-
+
return props
@@ -482,18 +483,18 @@
Raises:
ServiceCfgError if service doesn't exist or if there is a
problem with the txt_record.
-
+
'''
# get the port from the service's txt_record
try:
txt_rec = get_service_props(svcname)[PROP_TXT_RECORD]
except KeyError as err:
raise ServiceCfgError(err)
-
+
# txt_record is of the form "aiwebserver=example:46503" so split
# on ":" and take the trailing portion for the port number
port = txt_rec.rsplit(':')[-1]
-
+
return port
@@ -623,7 +624,7 @@
Input:
smf_port - webserver port from smf
- Return:
+ Return:
0 - New COMPATIBILITY_PORTS file has been put into place.
1 - Existing COMPATIBILITY_PORTS file was not touched.
'''
@@ -649,7 +650,7 @@
# Work file, as a tempfile.TemporaryFile object,
# is automatically deleted upon close
work_file.close()
-
+
if os.path.exists(COMPATIBILITY_PORTS):
with open(COMPATIBILITY_PORTS, 'r') as compat:
compat_data = compat.read()
@@ -688,14 +689,14 @@
for ip in valid_networks:
listen = 'Listen %s:%s\n' % (ip, smf_port)
work_file.write(listen)
-
+
work_file.seek(0)
work_data = work_file.read()
finally:
# Work file, as a tempfile.TemporaryFile object,
# is automatically deleted upon close
work_file.close()
-
+
if os.path.exists(LISTEN_ADDRESSES):
with open(LISTEN_ADDRESSES, 'r') as listen:
listen_data = listen.read()
@@ -705,7 +706,7 @@
if work_data == listen_data:
# use existing configuration file
return 1
-
+
# Truncate existing file, write new data
with open(LISTEN_ADDRESSES, "w") as listen_file:
listen_file.write(work_data)
@@ -801,7 +802,7 @@
# Add version if new file
if newfile and cfg.has_section(SERVICE):
cfg.set(SERVICE, PROP_VERSION, CURRENT_VERSION)
-
+
if logging.root.isEnabledFor(com.XDEBUG):
for section in cfg.sections():
logging.log(com.XDEBUG, '%s %s items are: %s', service_name,
--- a/usr/src/cmd/installadm/set_service.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/installadm/set_service.py Fri Mar 23 04:40:57 2012 -0700
@@ -19,7 +19,7 @@
#
# CDDL HEADER END
#
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
"""
AI set-service
"""
@@ -29,11 +29,14 @@
import sys
import osol_install.auto_install.ai_smf_service as aismf
+import osol_install.auto_install.create_client as create_client
+import osol_install.auto_install.client_control as clientctrl
import osol_install.auto_install.service as svc
import osol_install.auto_install.service_config as config
from optparse import OptionParser
+from bootmgmt import BootmgmtError
from osol_install.auto_install.installadm_common import _, \
validate_service_name, cli_wrap as cw
@@ -166,19 +169,52 @@
raise SystemExit(cw(_("\nError: %s can not be made an alias of %s "
"because %s is dependent on %s\n") % (aliasname,
basesvcname, basesvcname, aliasname)))
+
+ # Remove clients of alias
+ clients = config.get_clients(aliasname)
+ for clientid in clients.keys():
+ clientctrl.remove_client(clientid, suppress_dhcp_msgs=True)
+
+ failures = list()
try:
aliassvc.update_basesvc(basesvcname)
- except (OSError, config.ServiceCfgError) as err:
- raise SystemExit(_("Failed to set 'aliasof' property of : %s") %
- aliasname)
+ except (OSError, config.ServiceCfgError, BootmgmtError) as err:
+ print >> sys.stderr, (_("Failed to set 'aliasof' property of : %s") %
+ aliasname)
+ print >> sys.stderr, err
+ failures.append(err)
except svc.MultipleUnmountError as err:
print >> sys.stderr, _("Failed to disable alias")
- raise SystemExit(err)
+ print >> sys.stderr, err
+ failures.append(err)
except svc.MountError as err:
print >> sys.stderr, _("Failed to enable alias")
- raise SystemExit(err)
+ print >> sys.stderr, err
+ failures.append(err)
except svc.UnsupportedAliasError as err:
- raise SystemExit(err)
+ print >> sys.stderr, err
+ failures.append(err)
+
+ # Re-add clients to updated alias
+ arch = aliassvc_arch
+ for clientid in clients.keys():
+ # strip off leading '01'
+ client = clientid[2:]
+ bootargs = None
+ if config.BOOTARGS in clients[clientid]:
+ bootargs = clients[clientid][config.BOOTARGS]
+ # Don't suppress messages, because user may need to update
+ # DHCP configuration
+ try:
+ create_client.create_new_client(arch, aliassvc, client,
+ bootargs=bootargs, suppress_dhcp_msgs=False)
+ except BootmgmtError as err:
+ failures.append(err)
+ print >> sys.stderr, (_('\nError: Unable to recreate client, '
+ '%s:\n%s') % (client, err))
+ if failures:
+ return 1
+ return 0
def set_imagepath(options):
@@ -207,7 +243,7 @@
new_imagepath = new_imagepath.rstrip('/')
try:
service.relocate_imagedir(new_imagepath)
- except (svc.MountError, aismf.ServicesError) as error:
+ except (svc.MountError, aismf.ServicesError, BootmgmtError) as error:
raise SystemExit(error)
--- a/usr/src/cmd/installadm/test/test_create_service.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/installadm/test/test_create_service.py Fri Mar 23 04:40:57 2012 -0700
@@ -29,14 +29,10 @@
'''
-import os
+import unittest
+
import osol_install.auto_install.create_service as create_service
-import osol_install.auto_install.dhcp as dhcp
-import osol_install.auto_install.grub as grub
import osol_install.auto_install.installadm_common as com
-import shutil
-import tempfile
-import unittest
class ParseOptions(unittest.TestCase):
@@ -124,42 +120,5 @@
"pkg:/install-image/solaris-auto-install")
-class CreateTestService(unittest.TestCase):
- '''Tests for install service set up.'''
-
- def setUp(self):
- '''unit test set up'''
- self.svc_name = 'my-test-service'
- self.image_path = tempfile.mkdtemp(dir="/tmp")
- self.image_info = {'image_version': '3.0', 'grub_min_mem64': '0',
- 'service_name': 'solaris11u1-i386-05',
- 'grub_do_safe_default': 'true',
- 'grub_title': 'Oracle Solaris 11',
- 'image_size': '744619',
- 'no_install_grub_title': 'Oracle Solaris 11'}
- self.srv_address = '$serverIP:5555'
- self.menu_path = self.image_path
- self.bootargs = ''
-
- def tearDown(self):
- '''teardown'''
- if os.path.exists(self.image_path):
- shutil.rmtree(self.image_path)
-
- def test_menu_permissions(self):
- '''Ensure that menu.lst is created with correct permissions'''
-
- # save original umask
- orig_umask = os.umask(0022)
- # set too restrictive and too open umask
- for mask in (0066, 0000):
- os.umask(mask)
- grub.setup_grub(self.svc_name, self.image_path, self.image_info,
- self.srv_address, self.menu_path, self.bootargs)
- mode = os.stat(self.menu_path + '/' + 'menu.lst').st_mode
- self.assertEqual(mode, 0100644)
- # return umask to the original value
- os.umask(orig_umask)
-
if __name__ == '__main__':
unittest.main()
--- a/usr/src/cmd/installadm/test/test_dhcp.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/installadm/test/test_dhcp.py Fri Mar 23 04:40:57 2012 -0700
@@ -97,13 +97,15 @@
'''Test correct permissions of dhcpd4.conf'''
dhcpsrv = dhcp.DHCPServer()
- dhcpsrv.add_arch_class('i386', 'some-test-string')
+ dhcpsrv.add_arch_class('i386',
+ [('00:00', 'bios', 'some-test-string')])
# save original umask
orig_umask = os.umask(0022)
# set too restrictive and too open umask
for mask in (0066, 0000):
os.umask(mask)
- dhcpsrv.update_bootfile_for_arch('i386', 'some-other-test-string')
+ dhcpsrv.update_bootfile_for_arch('i386',
+ [('00:07', 'uefi', 'some-other-test-string')])
mode = os.stat(self.dhcp_dir + '/' + 'dhcpd4.conf').st_mode
self.assertEqual(mode, 0100644)
# return umask to the original value
--- a/usr/src/cmd/installadm/test/test_grub.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/cmd/installadm/test/test_grub.py Fri Mar 23 04:40:57 2012 -0700
@@ -19,7 +19,7 @@
#
# CDDL HEADER END
#
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
#
'''
@@ -28,88 +28,127 @@
must be rebuilt for these tests to pick up any changes in the tested code.
'''
-import unittest
+
+import grp
import os
+import pwd
import tempfile
+import unittest
import osol_install.auto_install.grub as grub
+class BootInstance(object):
+ '''Class for fake BootInstance (instead of SolarisNetBootInstance)'''
+ def __init__(self, name='mysvc', path='/export/myimage', bootargs=''):
+ self.svcname = name
+ self.path = path
+ self.bootargs = bootargs
+ self.kargs = ("-B %(args)s,install=true,"
+ "install_media=http://$serverIP:5555/%(path)s,"
+ "install_service=%(name)s,install_svc_address=$serverIP:5555\n" %
+ {'args': self.bootargs, 'path': self.path, 'name': self.svcname})
+
+ self.kernel = ("\tkernel$ /%(name)s/platform/i86pc/kernel/$ISADIR/unix"
+ % {'name': self.svcname})
+
+ self.boot_archive = ("\tmodule$ /%(name)s/platform/i86pc/$ISADIR/"
+ "boot_archive\n" % {'name': self.svcname})
+
+
class TestGrub(unittest.TestCase):
'''Tests for grub'''
def setUp(self):
'''unit test set up'''
- self.path = '/foo/auto_install/x86iso'
+ self.path = '/foo/auto_install/x86path'
self.svcname = 'myservice'
self.bootargs = 'console=ttya'
- # create original menu.lst file
- self.menulst_txt = (
- "default=0\n"
- "timeout=30\n"
- "min_mem64=0\n\n"
-
- "title Oracle Solaris 11 11/11 Text Installer and command line\n"
- "\tkernel$ /%(name)s/platform/i86pc/kernel/$ISADIR/unix -B "
- "%(args)s,install_media=http://$serverIP:5555/%(path)s,"
- "install_service=%(name)s,install_svc_address=$serverIP:5555\n"
- "\tmodule$ /%(name)s/platform/i86pc/$ISADIR/boot_archive\n\n"
-
- "title Oracle Solaris 11 11/11 Automated Install\n"
- "\tkernel$ /%(name)s/platform/i86pc/kernel/$ISADIR/unix -B "
- "%(args)s,install=true,"
+ # create original boot instance strings
+ self.kargs = ("-B %(args)s,install=true,"
"install_media=http://$serverIP:5555/%(path)s,"
- "install_service=%(name)s,install_svc_address=$serverIP:5555\n"
- "\tmodule$ /%(name)s/platform/i86pc/$ISADIR/boot_archive\n") % \
- {'args': self.bootargs, 'path': self.path, 'name': self.svcname}
-
- (tfp, self.mymenulst) = tempfile.mkstemp()
- os.write(tfp, self.menulst_txt)
- os.close(tfp)
-
- def tearDown(self):
- '''unit test tear down'''
- os.remove(self.mymenulst)
+ "install_service=%(name)s,install_svc_address=$serverIP:5555\n" %
+ {'args': self.bootargs, 'path': self.path, 'name': self.svcname})
+ self.kernel = ("\tkernel$ /%(name)s/platform/i86pc/kernel/$ISADIR/unix"
+ % {'name': self.svcname})
+ self.boot_archive = ("\tmodule$ /%(name)s/platform/i86pc/$ISADIR/"
+ "boot_archive\n" % {'name': self.svcname})
def test_update_imagepath(self):
- '''verify update_imagepath updates imagepath correctly'''
+ '''verify update_kargs_imagepath updates imagepath correctly'''
- # update path in menu.lst file, read file back in
- # and ensure file updated properly
newpath = '/export/mydir/myimage'
- grub.update_imagepath(self.mymenulst, self.path, newpath)
- with open(self.mymenulst, 'r') as menulst_file:
- newmenulst = menulst_file.read()
- expected_text = self.menulst_txt.replace(self.path, newpath)
- self.assertEqual(newmenulst, expected_text)
+ new_kargs = grub.update_kargs_imagepath(self.kargs, self.path, newpath)
+ expected_text = self.kargs.replace(self.path, newpath)
+ self.assertEqual(new_kargs, expected_text)
+
+ def test_update_boot_instance_svcname(self):
+ '''verify update_boot_instance_svcname updates svcname correctly'''
+ newname = 'newsvcname'
+ boot_inst = BootInstance(name=self.svcname, path=self.path,
+ bootargs=self.bootargs)
+ boot_inst = grub.update_boot_instance_svcname(boot_inst,
+ self.svcname, 'newsvcname')
+ boot_inst2 = BootInstance(name=newname, path=self.path,
+ bootargs=self.bootargs)
+ self.assertEqual(boot_inst.kargs, boot_inst2.kargs)
+ self.assertEqual(boot_inst.kernel, boot_inst2.kernel)
+ self.assertEqual(boot_inst.boot_archive, boot_inst2.boot_archive)
- def test_update_svcname(self):
- '''verify update_svcname updates svcname correctly'''
+ def test_update_svcname_functions(self):
+ '''verify individual update_svcname functions work correctly'''
- # update svcname in menu.lst file, read file back in
- # and ensure file updated properly
newsvcname = 'new_service'
- grub.update_svcname(self.mymenulst, newsvcname, newsvcname)
- with open(self.mymenulst, 'r') as menulst_file:
- newmenulst = menulst_file.read()
- expected_text = self.menulst_txt.replace('/' + self.svcname + '/',
- '/' + newsvcname + '/')
- expected_text = expected_text.replace(
- 'install_service=' + self.svcname,
- 'install_service=' + newsvcname)
- self.assertEqual(newmenulst, expected_text)
+
+ # test using kernel string
+ new_kernel = grub.update_kernel_ba_svcname(self.kernel, self.svcname,
+ newsvcname)
+ expected_text = self.kernel.replace('/' + self.svcname + '/',
+ '/' + newsvcname + '/')
+ self.assertEqual(new_kernel, expected_text)
+
+ # test using module string
+ new_boot_archive = grub.update_kernel_ba_svcname(self.boot_archive,
+ self.svcname, newsvcname)
+ expected_text = self.boot_archive.replace('/' + self.svcname + '/',
+ '/' + newsvcname + '/')
+ self.assertEqual(new_boot_archive, expected_text)
+
+ new_kargs = grub.update_kargs_install_service(self.kargs,
+ self.svcname, newsvcname)
+ expected_text = self.kargs.replace('install_service=' + self.svcname,
+ 'install_service=' + newsvcname)
+ self.assertEqual(new_kargs, expected_text)
def test_update_bootargs(self):
- '''verify update_bootargs updates bootargs correctly'''
+ '''verify update_kargs_bootargs updates bootargs correctly'''
# update bootargs in menu.lst file, read file back in
# and ensure file updated properly
newbootargs = 'console=ttyb'
- grub.update_bootargs(self.mymenulst, self.bootargs, newbootargs)
- with open(self.mymenulst, 'r') as menulst_file:
- newmenulst = menulst_file.read()
- expected_text = self.menulst_txt.replace(self.bootargs, newbootargs)
- self.assertEqual(newmenulst, expected_text)
+ new_kargs = grub.update_kargs_bootargs(self.kargs, self.bootargs,
+ newbootargs)
+ expected_text = self.kargs.replace(self.bootargs, newbootargs)
+ self.assertEqual(new_kargs, expected_text)
+
+ def test_set_perms(self):
+ '''Ensure that set_perms sets permissions properly'''
+
+ # save original umask
+ orig_umask = os.umask(0022)
+ # set too restrictive and too open umask
+ for mask in (0066, 0000):
+ tmpfile = tempfile.mktemp()
+ with open(tmpfile, 'w'):
+ pass
+ os.umask(mask)
+ grub.set_perms(tmpfile, pwd.getpwuid(os.getuid()).pw_name,
+ grp.getgrgid(os.getgid()).gr_name, 420)
+ mode = os.stat(tmpfile).st_mode
+ self.assertEqual(mode, 0100644)
+ os.remove(tmpfile)
+ # return umask to the original value
+ os.umask(orig_umask)
if __name__ == '__main__':
--- a/usr/src/lib/install_boot/boot.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/lib/install_boot/boot.py Fri Mar 23 04:40:57 2012 -0700
@@ -21,7 +21,7 @@
#
#
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
#
""" boot.py -- Installs and configures boot loader and boot menu
onto physical systems and installation images.
@@ -41,7 +41,8 @@
from pwd import getpwnam
from shutil import move, copyfile, rmtree
-from bootmgmt import bootconfig, BootmgmtUnsupportedPropertyError
+from bootmgmt import bootconfig, BootmgmtUnsupportedPlatformError, \
+ BootmgmtPropertyWriteError, BootmgmtUnsupportedPropertyError
from bootmgmt.bootconfig import BootConfig, DiskBootConfig, ODDBootConfig, \
SolarisDiskBootInstance, SolarisODDBootInstance, ChainDiskBootInstance
from bootmgmt.bootloader import BootLoader
@@ -55,13 +56,24 @@
from solaris_install.logger import INSTALL_LOGGER_NAME as ILN
from solaris_install.target import Target
from solaris_install.target.logical import be_list, BE, Zpool
-from solaris_install.target.physical import Disk, Slice
+from solaris_install.target.physical import Disk, FDISK, \
+ Partition, Slice
from solaris_install.transfer.media_transfer import get_image_grub_title
BOOT_ENV = "boot-env"
# Character boot device paths eg. c0t0d0s0
DEVS = "devs"
+# Stuff used to set up a USB media image with grub2
+GRUB_SETUP = "/usr/lib/grub2/bios/sbin/grub-setup"
+LOFIADM = "/usr/sbin/lofiadm"
+MBR_IMG_RPATH = "boot/mbr.img"
+
+# For USB we assume a 512b block size. Seems most reasonable for a USB stick
+BLOCKSIZE = 512
+KB = 1024
+MB = (KB * KB)
+
class BootMenu(Checkpoint):
""" Abstract class for BootMenu checkpoint
@@ -210,9 +222,9 @@
ftype = config_set[0]
if ftype == BootConfig.OUTPUT_TYPE_FILE:
self._handle_file_type(config_set, dry_run)
- elif ftype in [BootConfig.OUTPUT_TYPE_BIOS_ELTORITO]:
- # UEFI - add handlers for the following when supported:
- # BootConfig.OUTPUT_TYPE_UEFI_ELTORITO
+ elif ftype in [BootConfig.OUTPUT_TYPE_BIOS_ELTORITO,
+ BootConfig.OUTPUT_TYPE_UEFI_ELTORITO]:
+ # XXX add handlers for the following when supported:
# BootConfig.OUTPUT_TYPE_HSFS_BOOTBLK
self._handle_iso_boot_image_type(config_set, dry_run)
else:
@@ -922,9 +934,11 @@
""" Constructor for class
"""
super(ISOImageBootMenu, self).__init__(name)
+ self.ba_build = None
self.dc_dict = dict()
self.dc_pers_dict = dict()
self.pkg_img_path = None
+ self.tmp_dir = None
def init_boot_config(self, autogen=True):
""" Instantiates the appropriate bootmgmt.bootConfig subclass object
@@ -934,18 +948,49 @@
bootconfig.BootConfig.BCF_ONESHOT)
self.config = ODDBootConfig(bc_flags,
oddimage_root=self.pkg_img_path)
- # UEFI Note that we will have to specify additional firmware
- # targets (uefi64 & SPARC OBP) when pybootmgmt supports them.
- self.config.boot_loader.setprop('boot-targets', 'bios')
+
+ # Inspect boot loader to determine if it can support creation of a
+ # hybrid BIOS/UEFI bootable ISO image.
+ try:
+ self.config.boot_loader.setprop(BootLoader.PROP_BOOT_TARGS,
+ ['bios', 'uefi64'])
+ except BootmgmtUnsupportedPlatformError:
+ self.logger.warning("Boot loader '%s' does not support both " \
+ "BIOS and UEFI64 platforms." % (self.config.boot_loader.name))
+
+ fw_targs = self.config.boot_loader.getprop(BootLoader.PROP_BOOT_TARGS)
+ if isinstance(fw_targs, str):
+ fw_targs = [fw_targs]
+ self.logger.info("Creating ISO boot configuration for firmware " \
+ "targets: " + ', '.join(fw_targs))
# Apply boot timeout property to boot loader.
try:
- self.config.boot_loader.setprop('timeout',
+ self.config.boot_loader.setprop(BootLoader.PROP_TIMEOUT,
self.boot_timeout)
except BootmgmtUnsupportedPropertyError:
self.logger.warning("Boot loader type %s does not support the \
- 'timeout' property. Ignoring." \
- % self.config.boot_loader.name)
+ 'timeout' property. Ignoring." \
+ % self.config.boot_loader.name)
+
+ # Apply ident file property if required by boot loader:
+ # GRUB2 requires this file, Legacy GRUB doesn't. No big deal
+ if BootLoader.PROP_IDENT_FILE in \
+ self.config.boot_loader.SUPPORTED_PROPS:
+ # read .volsetid and derive a unique ident file name from it
+ vpath = os.path.join(self.ba_build, ".volsetid")
+ with open(vpath, "r") as vpath_fh:
+ volsetid = vpath_fh.read().strip()
+
+ identfile = "." + volsetid
+ identpath = os.path.join(self.pkg_img_path, identfile)
+ self.logger.info("Boot loader unique ident file name: %s" \
+ % identfile)
+ with open(identpath, "a+") as identf:
+ identf.write("# This file identifies the image root for GRUB2")
+
+ self.config.boot_loader.setprop(BootLoader.PROP_IDENT_FILE,
+ "/" + identfile)
def _get_rel_file_path(self):
"""
@@ -997,7 +1042,9 @@
not_found_is_err=True)[0].data_dict
try:
+ self.ba_build = self.dc_dict["ba_build"]
self.pkg_img_path = self.dc_dict["pkg_img_path"]
+ self.tmp_dir = self.dc_dict["tmp_dir"]
except KeyError, msg:
raise RuntimeError("Error retrieving a value from the DOC: " + \
str(msg))
@@ -1009,12 +1056,99 @@
# parsing the BootMods tree of the DOC
self.boot_title = self.rel_file_title
+ def _prepare_usb_boot(self, dry_run=False):
+ """ Creates an embeddable boot image for GRUB2 based USB boot media
+ """
+ # Return if this is not GRUB2
+ if self.config.boot_loader.name != "GRUB2":
+ return
+
+ self.logger.info("Preparing MBR embedded GRUB2 boot image for USB")
+
+ # Create a nice big 10MB lofi file.
+ imagesize = (10 * MB)
+ imagename = os.path.join(self.tmp_dir, "mbr_embedded_grub2")
+
+ with open(imagename, 'w') as imagefile:
+ imagefile.seek(imagesize - 1)
+ imagefile.write('\0')
+
+ if not dry_run:
+ # Create the lofi device
+ lofi_dev = None
+ lofi_rdev = None
+ cmd = [LOFIADM, "-a", imagename]
+ try:
+ p = run(cmd)
+ lofi_dev = p.stdout.strip()
+ lofi_rdev = lofi_dev.replace("lofi", "rlofi")
+
+ uefi_image_size = os.stat(self._uefi_image_name).st_size
+ self.logger.debug("EFI system partition image: %s",
+ self._uefi_image_name)
+
+ # write the uefi.img to the lofi character dev at 1MB offset
+ # in 1KB increments, instead of allocating one giant buffer.
+ self.logger.debug("Writing EFI system partition image to: %s",
+ lofi_rdev)
+ with open(self._uefi_image_name, 'r') as src:
+ with open(lofi_rdev, 'w') as dst:
+ dst.seek(MB)
+ for i in range(uefi_image_size / KB):
+ dst.write(src.read(KB))
+ tail = uefi_image_size % KB
+ if tail > 0:
+ dst.write(src.read(tail))
+
+ # Write out an fdisk table. We don't use the standard install
+ # target modules because we want precise control over the
+ # geometry, plus it's not well suited to lofi devices.
+ # Layout:
+ # type ESP, at 1M offset, partition size = uefi_image size
+ esptype = Partition.name_to_num("EFI System")
+ startblock = MB / BLOCKSIZE
+ uefi_image_nblocks = uefi_image_size / BLOCKSIZE
+ fdiskentry = "%d:0:0:0:0:0:0:0:%d:%d" \
+ % (esptype, startblock, uefi_image_nblocks)
+ self.logger.debug("Creating fdisk table on %s:\n\t%s",
+ lofi_rdev, fdiskentry)
+ cmd = [FDISK, "-A", fdiskentry, lofi_rdev]
+ run(cmd)
+
+ # Install the BIOS grub2 core image in the MBR of the lofi dev
+ self.logger.debug("Installing GRUB2 on %s", lofi_rdev)
+ bios_grub_dir = os.path.join(self.pkg_img_path,
+ "boot/grub/i386-pc")
+ cmd = [GRUB_SETUP, "-d", bios_grub_dir, "-M", lofi_rdev]
+ run(cmd)
+
+ # Write the first 1MB of the lofi image out to file and store
+ # it in the pkg_img area for inclusion in the ISO
+ usb_image_name = os.path.join(self.pkg_img_path,
+ MBR_IMG_RPATH)
+ self.logger.debug("Writing GRUB2 MBR image to: %s",
+ usb_image_name)
+ with open(lofi_rdev, 'r') as src:
+ with open(usb_image_name, 'w') as dst:
+ for i in range(KB):
+ dst.write(src.read(KB))
+ finally:
+ # remove the lofi device
+ if lofi_dev is not None:
+ cmd = [LOFIADM, "-d", lofi_dev]
+ run(cmd)
+
+ # Remove the backing file for the lofi image
+ os.unlink(imagename)
+ self.logger.info("MBR embedded GRUB2 boot image complete")
+
def execute(self, dry_run=False):
""" Primary execution method used by the Checkpoint parent class
"""
self.logger.info("=== Executing Boot Loader Setup Checkpoint ===")
super(ISOImageBootMenu, self).execute(dry_run)
self.update_img_info_path()
+ self._prepare_usb_boot(dry_run)
def install_boot_loader(self, dry_run=False):
""" Install the boot loader and associated boot configuration files.
@@ -1024,9 +1158,12 @@
tempfile.mkdtemp(dir="/tmp", prefix="iso_boot_config_")
else:
temp_dir = self.pkg_img_path
+
# Add dictionary mappings for tokens that commit_boot_config() might
# return.
self.boot_tokens[BootConfig.TOKEN_SYSTEMROOT] = self.pkg_img_path
+ self.boot_tokens[ODDBootConfig.TOKEN_ODD_ROOT] = self.pkg_img_path
+
self.logger.debug("Writing boot configuration to %s" % (temp_dir))
boot_config_list = self.config.commit_boot_config(temp_dir, None)
self._handle_boot_config_list(boot_config_list, dry_run)
@@ -1049,13 +1186,41 @@
""" Method that copies ISO El Torito and HSFS bootblockimage file types
to their appropriate targets.
"""
+
+ # XXX: Add in key/val pair to handle the SPARC HSFS BOOTBLK
+ # type here when pybootmgmt supports this firmware type.
+ # For now just it just does BIOS & UEFI ELTORITO.
+ eltorito_dict = {
+ BootConfig.OUTPUT_TYPE_BIOS_ELTORITO: \
+ ("bios-eltorito-img", "BIOS"),
+ BootConfig.OUTPUT_TYPE_UEFI_ELTORITO: \
+ ("uefi-eltorito-img", "UEFI")
+ }
+
# Store the ISO boot image path in DC's dictionary for later use
# when constructing the ISO image
img_type = config[0]
src = config[1]
+ if config[3] is None:
+ dst = src
+ else:
+ dst = config[3] % self.boot_tokens
+
+ img_val = eltorito_dict.get(img_type)
+ if img_val is None:
+ raise RuntimeError("Unrecognised ISO boot loader image type: %s" \
+ % img_type)
+ else:
+ img_id, firmware_id = img_val
+
if not os.path.exists(src):
raise RuntimeError("Expected boot image type \'%s\' does not " \
"exist at path: %s" % (img_type, src))
+
+ # Keep a reference to the full path name of the UEFI image. We need it
+ # later to set up USB boot.
+ if img_type == BootConfig.OUTPUT_TYPE_UEFI_ELTORITO:
+ self._uefi_image_name = dst
if dry_run is True:
self.logger.debug("Deleting El Torito image: %s" % src)
os.unlink(src)
@@ -1063,40 +1228,24 @@
if not os.path.abspath(src).startswith(self.pkg_img_path):
raise RuntimeError("El Torito boot image \'%s\' mislocated "
- "outside of image root: %s" \
- % (src, self.pkg_img_path))
- if img_type == BootConfig.OUTPUT_TYPE_BIOS_ELTORITO:
- # bios-eltorito-img name is randomised by pybootmgmt to avoid
- # name collisions. Rename it to something less conspicuous.
- dst = os.path.join(self.pkg_img_path,
- 'boot',
- '.bios-eltorito-img')
+ "outside of image root: %s" \
+ % (src, self.pkg_img_path))
+ if src != dst:
os.rename(src, dst)
- # bios-eltorito-img needs to live in the persistent section
- # of the DOC to ensure pause/resume works correctly.
- #
- # Update the DC_PERS_LABEL DOC object with an entry for
- # bios-eltorito-img
- if self.dc_pers_dict:
- self.doc.persistent.delete_children(name=DC_PERS_LABEL)
+ if self.dc_pers_dict:
+ self.doc.persistent.delete_children(name=DC_PERS_LABEL)
- # Strip out the pkg_img_path prefix from dst. Otherwise
- # mkisofs will choke because it requires a relative rather
- # than an absolute path for the eltorito image argument
- self.dc_pers_dict["bios-eltorito-img"] = \
- dst.split(self.pkg_img_path + os.sep)[1]
- self.doc.persistent.insert_children(
- DataObjectDict(DC_PERS_LABEL,
- self.dc_pers_dict, generate_xml=True))
- self.logger.debug("BIOS El Torito boot image: %s" \
- % self.dc_pers_dict["bios-eltorito-img"])
- # UEFI / Pybootmgmt.
- # Add in blocks to handle UEFI ELTORIO and SPARC HSFS BOOTBLK
- # types here when pybootmgmt supports these firmware types.
- # For now just it just does BIOS ELTORITO above.
- else:
- raise RuntimeError("Unrecognised ISO boot loader image type: %s" \
- % img_type)
+ # Strip out the pkg_img_path prefix from dst. Otherwise
+ # mkisofs will choke because it requires a relative rather
+ # than an absolute path for the eltorito image argument
+ self.dc_pers_dict[img_id] = \
+ dst.split(self.pkg_img_path + os.sep)[1]
+ self.doc.persistent.insert_children(
+ DataObjectDict(DC_PERS_LABEL,
+ self.dc_pers_dict, generate_xml=True))
+
+ self.logger.info("%s El Torito boot image name: %s" \
+ % (firmware_id, self.dc_pers_dict[img_id]))
class AIISOImageBootMenu(ISOImageBootMenu):
@@ -1185,6 +1334,17 @@
"""
super(LiveCDISOImageBootMenu, self).__init__(name)
+ def init_boot_config(self, autogen=True):
+ """ Instantiates the appropriate bootmgmt.bootConfig subclass object
+ for LiveCD
+ """
+ super(LiveCDISOImageBootMenu, self).init_boot_config(autogen)
+ # LiveCD specific global bootloader configuration:
+ # - Enable graphical boot splash
+ self.config.boot_loader.setprop(
+ BootLoader.PROP_CONSOLE,
+ BootLoader.PROP_CONSOLE_GFX)
+
def build_default_entries(self):
""" Constructs the default boot entries and inserts them into the
bootConfig object's boot_instances list
--- a/usr/src/lib/install_boot/test/test_boot.py Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/lib/install_boot/test/test_boot.py Fri Mar 23 04:40:57 2012 -0700
@@ -21,7 +21,7 @@
#
#
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
#
""" Boot checkpoint Unit Tests
@@ -35,7 +35,7 @@
import pwd
import grp
-from shutil import rmtree, copyfile
+from shutil import rmtree, copyfile, copytree
from lxml import etree
from bootmgmt import BootmgmtUnsupportedPropertyError
@@ -421,25 +421,37 @@
rel_file = os.path.join(rel_file_dir, 'release')
with open(rel_file, 'w') as file_handle:
file_handle.write('Solaris Boot Test')
- doc_dict = {"pkg_img_path": temp_dir}
+ # Under normal DC circumstances pkg_img_path and ba_build are
+ # different paths, but this is irrelevant to the boot checkpoint
+ doc_dict = {"pkg_img_path": temp_dir,
+ "ba_build": temp_dir,
+ "tmp_dir": temp_dir}
self.temp_dir = temp_dir
+ with open(os.path.join(temp_dir, ".volsetid"), "w") as volsetid:
+ volsetid.write("boot_checkpoint_ISO_unit_test")
self.doc.volatile.insert_children(DataObjectDict("DC specific",
doc_dict,
generate_xml=True))
- # Create a dummy boot and grub menu.ls environment
- # so that pybootmgmt can instantiate a legacyGrub
- # boot loader under the bootconfig object
- # UEFI - this needs updating for SPARC, GRUB2 & UEFI
- # when support gets added to pybootmgmt
+ # Create a dummy boot and grub menu.lst environment so that pybootmgmt
+ # can instantiate a Legacy GRUB or GRUB2 boot loader under the
+ # bootconfig object.
+ # XXX - this needs updating for SPARC
if platform.processor() == 'i386':
- os.makedirs(os.path.join(temp_dir, 'boot/grub'))
- copyfile('/boot/grub/menu.lst',
- os.path.join(temp_dir, 'boot/grub/menu.lst'))
- copyfile('/boot/grub/stage2_eltorito',
- os.path.join(temp_dir, 'boot/grub/stage2_eltorito'))
- os.makedirs(os.path.join(temp_dir, 'boot/solaris'))
- copyfile('/boot/solaris/bootenv.rc',
- os.path.join(temp_dir, 'boot/solaris/bootenv.rc'))
+ # Create a dummy legacy GRUB environment if that's what the build
+ # host has
+ if os.path.exists('/boot/grub/stage2_eltorito'):
+ os.makedirs(os.path.join(temp_dir, 'boot/grub'))
+ copyfile('/boot/grub/menu.lst',
+ os.path.join(temp_dir, 'boot/grub/menu.lst'))
+ copyfile('/boot/grub/stage2_eltorito',
+ os.path.join(temp_dir, 'boot/grub/stage2_eltorito'))
+ os.makedirs(os.path.join(temp_dir, 'boot/solaris'))
+ copyfile('/boot/solaris/bootenv.rc',
+ os.path.join(temp_dir, 'boot/solaris/bootenv.rc'))
+ # If it has GRUB 2, copy over the entire GRUB2 tree
+ if os.path.exists('/usr/lib/grub2'):
+ copytree('/usr/lib/grub2',
+ os.path.join(temp_dir, 'usr/lib/grub2'))
def tearDown(self):
""" Cleans up after each unit test is executed
--- a/usr/src/pkg/manifests/install-distribution-constructor.mf Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/pkg/manifests/install-distribution-constructor.mf Fri Mar 23 04:40:57 2012 -0700
@@ -19,7 +19,7 @@
# CDDL HEADER END
#
#
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
#
set name=pkg.fmri value=pkg:/install/distribution-constructor@$(PKGVERS)
@@ -180,6 +180,9 @@
depend type=require fmri=compress/p7zip
# /usr/bin/mkisofs
depend type=require fmri=media/cdrtools
+# Add manual boot-management version 13 dependency for UEFI/GRUB2 compat.
+depend type=require \
+ fmri=pkg:/system/library/[email protected],5.11-0.175.1.0.0.13
# /usr/bin/rmformat
depend type=require fmri=service/storage/media-volume-manager
# /usr/bin/awk
--- a/usr/src/pkg/manifests/install-installadm.mf Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/pkg/manifests/install-installadm.mf Fri Mar 23 04:40:57 2012 -0700
@@ -19,54 +19,27 @@
# CDDL HEADER END
#
#
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
#
set name=pkg.fmri value=pkg:/install/installadm@$(PKGVERS)
+set name=pkg.summary value="installadm utility"
set name=pkg.description value="Automatic Installation Server Setup Tools"
-set name=pkg.summary value="installadm utility"
set name=info.classification \
value="org.opensolaris.category.2008:System/Administration and Configuration"
set name=variant.arch value=$(ARCH)
set name=variant.opensolaris.zone value=global value=nonglobal
-dir path=lib
-dir path=lib/svc
-dir path=lib/svc/manifest group=sys
-dir path=lib/svc/manifest/system group=sys
-dir path=lib/svc/manifest/system/install group=sys
-dir path=lib/svc/method
-dir path=usr group=sys
-dir path=usr/lib
-dir path=usr/lib/installadm
-dir path=usr/lib/python2.6
-dir path=usr/lib/python2.6/vendor-packages
-dir path=usr/lib/python2.6/vendor-packages/osol_install
-dir path=usr/lib/python2.6/vendor-packages/osol_install/auto_install
-dir path=usr/sbin
-dir path=usr/share group=sys
-dir path=usr/share/auto_install group=sys
-dir path=usr/share/man
-dir path=usr/share/man/ja_JP.UTF-8
-dir path=usr/share/man/ja_JP.UTF-8/man1m
-dir path=usr/share/man/man1m
-dir path=usr/share/man/zh_CN.UTF-8
-dir path=usr/share/man/zh_CN.UTF-8/man1m
-dir path=usr/share/xml
-dir path=var group=sys
-dir path=var/ai group=sys
-dir path=var/ai/image-server group=sys
-dir path=var/ai/image-server/cgi-bin group=sys
-dir path=var/ai/image-server/images group=sys
-dir path=var/ai/image-server/logs group=sys
-dir path=var/ai/profile group=webservd mode=0700 owner=webservd
-dir path=var/ai/service group=sys
-dir path=var/installadm group=sys
-dir path=var/installadm/ai-webserver group=sys
-dir path=var/installadm/ai-webserver/AI_data group=sys
-dir path=var/installadm/ai-webserver/AI_files group=sys
-dir path=var/installadm/ai-webserver/compatibility-configuration group=sys
+dir path=lib
+dir path=lib/svc
+dir path=lib/svc/manifest group=sys
+dir path=lib/svc/manifest/system group=sys
+dir path=lib/svc/manifest/system/install group=sys
file path=lib/svc/manifest/system/install/server.xml group=sys
+dir path=lib/svc/method
file path=lib/svc/method/svc-install-server mode=0555
+dir path=usr group=sys
+dir path=usr/lib
+dir path=usr/lib/installadm
file path=usr/lib/installadm/aimdns.py
file path=usr/lib/installadm/aimdnsd.py
file path=usr/lib/installadm/check-server-setup
@@ -74,6 +47,10 @@
file path=usr/lib/installadm/setup-image
file path=usr/lib/installadm/setup-service
file path=usr/lib/installadm/setup-sparc
+dir path=usr/lib/python2.6
+dir path=usr/lib/python2.6/vendor-packages
+dir path=usr/lib/python2.6/vendor-packages/osol_install
+dir path=usr/lib/python2.6/vendor-packages/osol_install/auto_install
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/AI_database.py \
group=sys
@@ -92,9 +69,11 @@
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/common_profile.pyc \
group=sys
+# Add bypass generate to allow manual boot-management version 13 dependency
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/create_client.py \
- group=sys
+ group=sys \
+ pkg.depend.bypass-generate=^usr/lib/python2.6/vendor-packages/bootmgmt/.*$
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/create_client.pyc \
group=sys
@@ -104,9 +83,11 @@
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/create_profile.pyc \
group=sys
+# Add bypass generate to allow manual boot-management version 13 dependency
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/create_service.py \
- group=sys
+ group=sys \
+ pkg.depend.bypass-generate=^usr/lib/python2.6/vendor-packages/bootmgmt/.*$
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/create_service.pyc \
group=sys
@@ -150,8 +131,10 @@
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/export.pyc \
group=sys
+# Add bypass generate to allow manual boot-management version 13 dependency
file path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/grub.py \
- group=sys
+ group=sys \
+ pkg.depend.bypass-generate=^usr/lib/python2.6/vendor-packages/bootmgmt/.*$
file path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/grub.pyc \
group=sys
file path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/image.py \
@@ -169,9 +152,11 @@
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/publish_manifest.pyc \
group=sys
+# Add bypass generate to allow manual boot-management version 13 dependency
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/rename_service.py \
- group=sys
+ group=sys \
+ pkg.depend.bypass-generate=^usr/lib/python2.6/vendor-packages/bootmgmt/.*$
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/rename_service.pyc \
group=sys
@@ -187,9 +172,11 @@
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/set_criteria.pyc \
group=sys
+# Add bypass generate to allow manual boot-management version 13 dependency
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/set_service.py \
- group=sys
+ group=sys \
+ pkg.depend.bypass-generate=^usr/lib/python2.6/vendor-packages/bootmgmt/.*$
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/set_service.pyc \
group=sys
@@ -205,19 +192,46 @@
file \
path=usr/lib/python2.6/vendor-packages/osol_install/auto_install/verifyXML.pyc \
group=sys
+dir path=usr/sbin
file path=usr/sbin/installadm mode=0555
file path=usr/sbin/installadm-convert mode=0555
+dir path=usr/share group=sys
+dir path=usr/share/auto_install group=sys
file path=usr/share/auto_install/criteria_schema.rng group=sys
+dir path=usr/share/man
+dir path=usr/share/man/ja_JP.UTF-8
+dir path=usr/share/man/ja_JP.UTF-8/man1m
file path=usr/share/man/ja_JP.UTF-8/man1m/installadm.1m mode=0444
+dir path=usr/share/man/man1m
file path=usr/share/man/man1m/installadm.1m mode=0444
+dir path=usr/share/man/zh_CN.UTF-8
+dir path=usr/share/man/zh_CN.UTF-8/man1m
file path=usr/share/man/zh_CN.UTF-8/man1m/installadm.1m mode=0444
+dir path=usr/share/xml
+dir path=var group=sys
+dir path=var/ai group=sys
+dir path=var/ai/image-server group=sys
+dir path=var/ai/image-server/cgi-bin group=sys
file path=var/ai/image-server/cgi-bin/cgi_get_manifest.py mode=0555
+dir path=var/ai/image-server/images group=sys
+dir path=var/ai/image-server/logs group=sys
+dir path=var/ai/profile owner=webservd group=webservd mode=0700
+dir path=var/ai/service group=sys
+dir path=var/installadm group=sys
+dir path=var/installadm/ai-webserver group=sys
file path=var/installadm/ai-webserver/AI.db
+dir path=var/installadm/ai-webserver/AI_data group=sys
+dir path=var/installadm/ai-webserver/AI_files group=sys
file path=var/installadm/ai-webserver/ai-httpd.conf
+dir path=var/installadm/ai-webserver/compatibility-configuration group=sys
license cr_Sun license=cr_Sun
-depend fmri=database/sqlite-3 type=require
-depend fmri=service/network/dhcp/isc-dhcp type=require
-depend fmri=service/network/dns/mdns type=require
-depend fmri=service/network/tftp type=require
-depend fmri=system/install/locale type=require
-depend fmri=web/server/apache-22 type=require
+depend type=require fmri=database/sqlite-3
+# Add manual boot-management version 13 dependency for UEFI/GRUB2 compat.
+# Make sure to remove the pkg.depend.bypass-genererate actions if removing.
+depend type=require \
+ fmri=pkg:/system/library/[email protected],5.11-0.175.1.0.0.13
+depend type=require fmri=service/network/dhcp/isc-dhcp
+depend type=require fmri=service/network/dns/mdns
+depend type=require fmri=service/network/tftp
+depend type=require fmri=system/install/locale
+depend type=require fmri=web/server/apache-22
--- a/usr/src/pkg/manifests/system-library-install.mf Thu Mar 22 00:08:34 2012 -0600
+++ b/usr/src/pkg/manifests/system-library-install.mf Fri Mar 23 04:40:57 2012 -0700
@@ -19,7 +19,7 @@
# CDDL HEADER END
#
#
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
#
set name=pkg.fmri value=pkg:/system/library/install@$(PKGVERS)
@@ -47,13 +47,17 @@
file path=usr/lib/python2.6/vendor-packages/osol_install/liberrsvc.py
file path=usr/lib/python2.6/vendor-packages/osol_install/liberrsvc.pyc
dir path=usr/lib/python2.6/vendor-packages/solaris_install
-file path=usr/lib/python2.6/vendor-packages/solaris_install/__init__.py
+# Add bypass generate to allow manual boot-management version 13 dependency
+file path=usr/lib/python2.6/vendor-packages/solaris_install/__init__.py \
+ pkg.depend.bypass-generate=^usr/lib/python2.6/vendor-packages/bootmgmt/.*$
file path=usr/lib/python2.6/vendor-packages/solaris_install/__init__.pyc
file path=usr/lib/python2.6/vendor-packages/solaris_install/_logger.so
dir path=usr/lib/python2.6/vendor-packages/solaris_install/boot
file path=usr/lib/python2.6/vendor-packages/solaris_install/boot/__init__.py
file path=usr/lib/python2.6/vendor-packages/solaris_install/boot/__init__.pyc
-file path=usr/lib/python2.6/vendor-packages/solaris_install/boot/boot.py
+# Add bypass generate to allow manual boot-management version 13 dependency
+file path=usr/lib/python2.6/vendor-packages/solaris_install/boot/boot.py \
+ pkg.depend.bypass-generate=^usr/lib/python2.6/vendor-packages/bootmgmt/.*$
file path=usr/lib/python2.6/vendor-packages/solaris_install/boot/boot.pyc
file path=usr/lib/python2.6/vendor-packages/solaris_install/boot/boot_spec.py
file path=usr/lib/python2.6/vendor-packages/solaris_install/boot/boot_spec.pyc
@@ -115,9 +119,15 @@
file path=usr/lib/python2.6/vendor-packages/solaris_install/target/__init__.pyc
file path=usr/lib/python2.6/vendor-packages/solaris_install/target/cgc.py
file path=usr/lib/python2.6/vendor-packages/solaris_install/target/cgc.pyc
-file path=usr/lib/python2.6/vendor-packages/solaris_install/target/controller.py
+# Add bypass generate to allow manual boot-management version 13 dependency
+file \
+ path=usr/lib/python2.6/vendor-packages/solaris_install/target/controller.py \
+ pkg.depend.bypass-generate=^usr/lib/python2.6/vendor-packages/bootmgmt/.*$
file path=usr/lib/python2.6/vendor-packages/solaris_install/target/controller.pyc
-file path=usr/lib/python2.6/vendor-packages/solaris_install/target/discovery.py
+# Add bypass generate to allow manual boot-management version 13 dependency
+file \
+ path=usr/lib/python2.6/vendor-packages/solaris_install/target/discovery.py \
+ pkg.depend.bypass-generate=^usr/lib/python2.6/vendor-packages/bootmgmt/.*$
file path=usr/lib/python2.6/vendor-packages/solaris_install/target/discovery.pyc
file path=usr/lib/python2.6/vendor-packages/solaris_install/target/instantiation.py
file path=usr/lib/python2.6/vendor-packages/solaris_install/target/instantiation.pyc
@@ -180,7 +190,9 @@
file path=usr/lib/python2.6/vendor-packages/solaris_install/target/libnvpair/nvl.pyc
file path=usr/lib/python2.6/vendor-packages/solaris_install/target/logical.py
file path=usr/lib/python2.6/vendor-packages/solaris_install/target/logical.pyc
-file path=usr/lib/python2.6/vendor-packages/solaris_install/target/physical.py
+# Add bypass generate to allow manual boot-management version 13 dependency
+file path=usr/lib/python2.6/vendor-packages/solaris_install/target/physical.py \
+ pkg.depend.bypass-generate=^usr/lib/python2.6/vendor-packages/bootmgmt/.*$
file path=usr/lib/python2.6/vendor-packages/solaris_install/target/physical.pyc
dir path=usr/lib/python2.6/vendor-packages/solaris_install/target/shadow
file path=usr/lib/python2.6/vendor-packages/solaris_install/target/shadow/__init__.py
@@ -236,4 +248,8 @@
link path=usr/snadm/lib/libspmicommon.so target=libspmicommon.so.1
file path=usr/snadm/lib/libspmicommon.so.1
license cr_Sun license=cr_Sun
+# Add manual boot-management version 13 dependency for UEFI/GRUB2 compat.
+# Make sure to remove the pkg.depend.bypass-genererate actions if removing.
+depend type=require \
+ fmri=pkg:/system/library/[email protected],5.11-0.175.1.0.0.13
depend type=require fmri=system/boot-environment-utilities