--- a/usr/src/Makefile.master Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/Makefile.master Fri May 06 11:44:10 2011 +1000
@@ -100,6 +100,7 @@
ROOTPYTHONVENDORINSTALLPROF= $(ROOTPYTHONVENDORINSTALL)/profile
ROOTPYTHONVENDORINSTALLTI= $(ROOTPYTHONVENDORINSTALL)/text_install
ROOTPYTHONVENDORSOLINSTALL= $(ROOTPYTHONVENDOR)/solaris_install
+ROOTPYTHONVENDORSOLINSTALLBOOT = $(ROOTPYTHONVENDORSOLINSTALL)/boot
ROOTPYTHONVENDORSOLINSTALLDATACACHE= $(ROOTPYTHONVENDORSOLINSTALL)/data_object
ROOTPYTHONVENDORINSTALLDC= $(ROOTPYTHONVENDORSOLINSTALL)/distro_const
ROOTPYTHONVENDORINSTALLDCCHKPT= $(ROOTPYTHONVENDORINSTALLDC)/checkpoints
--- a/usr/src/Targetdirs Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/Targetdirs Fri May 06 11:44:10 2011 +1000
@@ -72,6 +72,7 @@
/usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints \
/usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/defaultfiles \
/usr/lib/python2.6/vendor-packages/solaris_install \
+ /usr/lib/python2.6/vendor-packages/solaris_install/boot \
/usr/lib/python2.6/vendor-packages/solaris_install/data_object \
/usr/lib/python2.6/vendor-packages/solaris_install/engine \
/usr/lib/python2.6/vendor-packages/solaris_install/engine/test \
--- a/usr/src/cmd/distro_const/__init__.py Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/cmd/distro_const/__init__.py Fri May 06 11:44:10 2011 +1000
@@ -48,6 +48,7 @@
from osol_install.install_utils import set_http_proxy
from osol_install.liberrsvc import ES_DATA_EXCEPTION
+from solaris_install.boot.boot_spec import BootMods
from solaris_install.data_object import DataObject, ObjectNotFoundError
from solaris_install.data_object.cache import DataObjectCache
from solaris_install.data_object.data_dict import DataObjectDict
--- a/usr/src/cmd/distro_const/checkpoints/Makefile Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/cmd/distro_const/checkpoints/Makefile Fri May 06 11:44:10 2011 +1000
@@ -20,7 +20,7 @@
#
#
-# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
#
include ../../Makefile.cmd
@@ -39,7 +39,6 @@
create_iso.py \
create_usb.py \
custom_script.py \
- grub_setup.py \
pkg_img_mod.py \
pre_pkg_img_mod.py
--- a/usr/src/cmd/distro_const/checkpoints/create_iso.py Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/cmd/distro_const/checkpoints/create_iso.py Fri May 06 11:44:10 2011 +1000
@@ -106,10 +106,16 @@
else:
self.distro_name = distro[0].name
- dc_pers_dict = self.doc.persistent.get_children(name=DC_PERS_LABEL,
- class_type=DataObjectDict)
+ # Look for DC persistent dictionary. Force an error if not found
+ # on X86 since the 'bios-eltorito-img' key is required.
+ dc_pers_dict = self.doc.persistent.get_children(
+ name = DC_PERS_LABEL,
+ class_type=DataObjectDict,
+ not_found_is_err=(self.arch=='i386'))
if dc_pers_dict:
self.dc_pers_dict = dc_pers_dict[0].data_dict
+ if self.arch == 'i386':
+ self.bios_eltorito = self.dc_pers_dict["bios-eltorito-img"]
except KeyError, msg:
raise RuntimeError("Error retrieving a value from the DOC: " + \
str(msg))
@@ -141,7 +147,7 @@
# set the mkisofs_cmd
if self.arch == "i386":
self.mkisofs_cmd = [cli.MKISOFS, "-quiet", "-o", self.dist_iso,
- "-b", "boot/grub/stage2_eltorito", "-c",
+ "-b", self.bios_eltorito, "-c",
".catalog", "-no-emul-boot",
"-boot-load-size", "4", "-boot-info-table",
"-N", "-l", "-R", "-U", "-allow-multidot",
--- a/usr/src/cmd/distro_const/checkpoints/grub_setup.py Thu May 05 13:47:10 2011 -0600
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,396 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-
-#
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-""" grub_setup.py - Creates a custom grub menu.lst file
-"""
-import abc
-import os
-import os.path
-
-from solaris_install.data_object import ObjectNotFoundError
-from solaris_install.data_object.data_dict import DataObjectDict
-from solaris_install.distro_const import DC_LABEL
-from solaris_install.distro_const.distro_spec import GrubMods, GrubEntry
-from solaris_install.engine import InstallEngine
-from solaris_install.engine.checkpoint import AbstractCheckpoint as Checkpoint
-
-
-class GrubSetup(Checkpoint):
- """ GrubSetup - class to customize the grub menu
- """
- DEFAULT_ENTRY = 0
- DEFAULT_TIMEOUT = 30
- # set the DEFAULT_MIN_MEM to 0
- DEFAULT_MIN_MEM = 0
-
- def __init__(self, name):
- """ constructor for class.
- """
- super(GrubSetup, self).__init__(name)
-
- # instance attributes
- self.doc = None
- self.dc_dict = {}
- self.pkg_img_path = None
- self.grub_mods = None
- self.grub_entry_list = None
- self.img_info_path = None
- self.menu_list = None
-
- def get_progress_estimate(self):
- """Returns an estimate of the time this checkpoint will take
- """
- return 1
-
- def parse_doc(self):
- """ class method for parsing data object cache (DOC) objects for use by
- the checkpoint.
- """
- self.doc = InstallEngine.get_instance().data_object_cache
-
- self.dc_dict = self.doc.volatile.get_children(name=DC_LABEL,
- class_type=DataObjectDict)[0].data_dict
-
- try:
- self.pkg_img_path = self.dc_dict["pkg_img_path"]
- except KeyError, msg:
- raise RuntimeError("Error retrieving a value from the DOC: " + \
- str(msg))
-
- self.img_info_path = os.path.join(self.pkg_img_path, ".image_info")
- self.menu_list = os.path.join(self.pkg_img_path, "boot/grub/menu.lst")
-
- # retrieve manifest specific grub configurations
- grub_mods = self.doc.volatile.get_descendants(class_type=GrubMods)
- if len(grub_mods) == 0:
- # if there are no GrubMods in the doc, create a new GrubMods
- # instance and allow it to be populated with default values
- # the DEFAULT_MIN_MEM is only used for setting up GRUB_MIN_MEM64
- # in AI .image_info for installadm backward compatibility
- self.grub_mods = GrubMods("grub mods")
- else:
- self.grub_mods = grub_mods[0]
-
- if self.grub_mods.default_entry is None:
- self.grub_mods.default_entry = self.DEFAULT_ENTRY
- if self.grub_mods.timeout is None:
- self.grub_mods.timeout = self.DEFAULT_TIMEOUT
- if self.grub_mods.min_mem is None:
- self.grub_mods.min_mem = self.DEFAULT_MIN_MEM
- if self.grub_mods.title is None:
- # set the default title
- rel_file = os.path.join(self.pkg_img_path, "etc/release")
- with open(rel_file, "r") as rel:
- # read the first line of /etc/release
- self.grub_mods.title = rel.readline().strip()
- else:
- # the manifest specified a special grub title. Record it in
- # .image_info
- with open(self.img_info_path, "a+") as fh:
- fh.write("GRUB_TITLE=" + self.grub_mods.title + "\n")
-
- # verify something is present
- if len(self.grub_mods.title) == 0:
- raise RuntimeError("Error finding or extracting non-empty " + \
- "release string from /etc/release")
-
- self.grub_entry_list = self.doc.volatile.get_descendants(
- class_type=GrubEntry)
-
- @abc.abstractmethod
- def build_entries(self):
- """ abstract method which is required by subclasses to implement
- """
- raise NotImplementedError
-
- def build_position_specific(self, entries):
- """ class method to insert position specific entries to the menu.lst
- file.
- """
- # walk each entry in the grub_entry_list
- for grub_entry in self.grub_entry_list:
- entry = [" ".join(["title", self.grub_mods.title,
- grub_entry.title_suffix])]
- for line in grub_entry.lines:
- entry.append("\t" + line)
-
- # place the entire new entry into the entries list
- if grub_entry.position is None:
- entries.append(entry)
- else:
- entries.insert(int(grub_entry.position), entry)
-
- return entries
-
- def write_entries(self, entries):
- """ class method to write out everything in the entries list to the
- menu.lst file.
- """
- with open(self.menu_list, "w") as menu_lst_fh:
- menu_lst_fh.write("default %s\n" % self.grub_mods.default_entry)
- menu_lst_fh.write("timeout %s\n" % self.grub_mods.timeout)
-
- # write out the entries
- for entry in entries:
- for line in entry:
- menu_lst_fh.write(line + "\n")
- menu_lst_fh.write("\n")
-
- def execute(self, dry_run=False):
- """ Primary execution method used by the Checkpoint parent class.
- dry_run is not used in DC
- """
- self.logger.info("=== Executing Grub Setup Checkpoint ===")
-
- # parse the DOC object
- self.parse_doc()
-
- # construct the entries list
- entries = self.build_entries()
-
- # add all position specific grub entries
- entries = self.build_position_specific(entries)
-
- # write out the default entry number and default timeout
- # values, along with everything in entries
- self.logger.info("Writing menu.lst")
- self.write_entries(entries)
-
-
-class AIGrubSetup(GrubSetup):
- """ AIGrubSetup - class to customize the grub menu for AI distributions
- """
-
- def __init__(self, name, arg=None):
- GrubSetup.__init__(self, name)
- if arg:
- self.__setup(**arg)
- else:
- self.__setup()
-
- def __setup(self, installadm_entry="boot image"):
- self.installadm_entry = installadm_entry
-
- def build_entries(self):
- """ class method for constructing the entries list.
- """
- entries = []
-
- title = "title " + self.grub_mods.title
- kernel = "\tkernel$ /platform/i86pc/kernel/$ISADIR/unix"
- module = "\tmodule$ /platform/i86pc/$ISADIR/boot_archive"
-
- # create lists of grub titles, kernels, and module lines to use
- ai_titles = [title + " Automated Install custom",
- title + " Automated Install",
- title + " Automated Install custom ttya",
- title + " Automated Install custom ttyb",
- title + " Automated Install ttya",
- title + " Automated Install ttyb"]
- ai_kernel = [kernel + " -B install=true,aimanifest=prompt",
- kernel + " -B install=true",
- kernel + " -B install=true,aimanifest=prompt," + \
- "console=ttya",
- kernel + " -B install=true,aimanifest=prompt," + \
- "console=ttyb",
- kernel + " -B install=true,console=ttya",
- kernel + " -B install=true,console=ttyb"]
- ai_module = len(ai_titles) * [module]
-
- # create a new list from zipper'ing each of the lists
- entries = zip(ai_titles, ai_kernel, ai_module)
-
- # boot from hd entry.
- entries.append(["title Boot from Hard Disk",
- "\trootnoverify (hd0)",
- "\tchainloader +1"])
- return entries
-
- def update_img_info_path(self):
- """ class method to write out the .img_info_path file.
- """
- self.logger.debug("updating %s" % self.img_info_path)
-
- # write out the GRUB_MIN_MEM64 and GRUB_DO_SAFE_DEFAULT lines
- with open(self.img_info_path, "a+") as iip:
- iip.write("GRUB_MIN_MEM64=%s\n" % self.grub_mods.min_mem)
- iip.write("GRUB_DO_SAFE_DEFAULT=true\n")
- iip.write("NO_INSTALL_GRUB_TITLE=%s\n" % self.installadm_entry)
-
- def execute(self, dry_run=False):
- """ Primary execution method used by the Checkpoint parent class.
- """
- self.logger.info("=== Executing Grub Setup Checkpoint ===")
-
- # parse the DOC object
- self.parse_doc()
-
- # update the .img_info_path file
- self.update_img_info_path()
-
- # construct the entries list
- entries = self.build_entries()
-
- # add all position specific grub entries
- entries = self.build_position_specific(entries)
-
- # write out the default entry number and default timeout
- # values, along with everything in entries
- self.logger.info("Writing menu.lst")
- self.write_entries(entries)
-
-
-class LiveCDGrubSetup(GrubSetup, Checkpoint):
- """ LiveCDGrubSetup - class to customize the grub menu for livecd
- distributions
- """
-
- def __init__(self, name):
- """ constructor for class.
- """
- super(LiveCDGrubSetup, self).__init__(name)
-
- def get_progress_estimate(self):
- """Returns an estimate of the time this checkpoint will take
- """
- return 1
-
- def build_position_specific(self, entries):
- """ class method to insert position specific entries to the menu.lst
- file.
- """
- # walk each entry in the grub_entry_list
- for grub_entry in self.grub_entry_list:
- entry = [" ".join(["title", self.grub_mods.title,
- grub_entry.title_suffix])]
- for line in grub_entry.lines:
- entry.append("\t" + line)
-
- # place the entire new entry into the entries list
- if grub_entry.position is None:
- entries.append(entry)
- else:
- entries.insert(int(grub_entry.position), entry)
-
- return entries
-
- def build_entries(self):
- """ class method for constructing the entries list.
- """
- entries = []
-
- title = "title " + self.grub_mods.title
- kernel = "\tkernel$ /platform/i86pc/kernel/$ISADIR/unix"
- module = "\tmodule$ /platform/i86pc/$ISADIR/boot_archive"
-
- # create lists of grub titles, kernels, and module lines to use
- lcd_titles = [title,
- title + " VESA driver",
- title + " text console",
- title + " Enable SSH"]
-
- lcd_kernel = [kernel,
- kernel + " -B livemode=vesa",
- kernel + " -B livemode=text",
- kernel + " -B livessh=enable"]
-
- lcd_module = len(lcd_titles) * [module]
-
- # create a new list from zipper'ing each of the lists
- entries = zip(lcd_titles, lcd_kernel, lcd_module)
-
- # boot from hd entry.
- entries.append(("title Boot from Hard Disk", "\trootnoverify (hd0)",
- "\tchainloader +1"))
-
- return entries
-
- def write_entries(self, entries):
- """ class method to write out everything in the entries list to the
- menu.lst file.
- """
- with open(self.menu_list, "w") as menu_lst_fh:
- menu_lst_fh.write("default %s\n" % self.grub_mods.default_entry)
- menu_lst_fh.write("timeout %s\n" % self.grub_mods.timeout)
-
- # livecd needs graphics entries
- menu_lst_fh.write("splashimage=/boot/grub/splash.xpm.gz\n")
- menu_lst_fh.write("foreground=343434\n")
- menu_lst_fh.write("background=F7FBFF\n")
-
- # write out the entries
- for entry in entries:
- for line in entry:
- menu_lst_fh.write(line + "\n")
- menu_lst_fh.write("\n")
-
-
-class TextGrubSetup(GrubSetup, Checkpoint):
- """ TextGrubSetup - class to customize the grub menu for text installer
- distributions
- """
-
- def __init__(self, name):
- """ constructor for class.
- """
- super(TextGrubSetup, self).__init__(name)
-
- def get_progress_estimate(self):
- """Returns an estimate of the time this checkpoint will take
- """
- return 1
-
- def build_entries(self):
- """ class method for constructing the entries list.
- """
- title = "title " + self.grub_mods.title
- kernel = "\tkernel$ /platform/i86pc/kernel/$ISADIR/unix"
- module = "\tmodule$ /platform/i86pc/$ISADIR/boot_archive"
- entries = [[title, kernel, module]]
-
- # boot from hd entry.
- entries.append(["title Boot from Hard Disk",
- "\trootnoverify (hd0)",
- "\tchainloader +1"])
- return entries
-
- def write_entries(self, entries):
- """ class method to write out everything in the entries list to the
- text install menu.lst file.
- """
- with open(self.menu_list, "w") as menu_lst_fh:
- menu_lst_fh.write("default %s\n" % self.grub_mods.default_entry)
- # set the timeout for text installs to 5 seconds
- menu_lst_fh.write("timeout 5\n")
-
- # text installer needs to hide the menu
- menu_lst_fh.write("hiddenmenu\n")
-
- # write out the entries
- for entry in entries:
- for line in entry:
- menu_lst_fh.write(line + "\n")
- menu_lst_fh.write("\n")
--- a/usr/src/cmd/distro_const/checkpoints/test/test_grub_setup.py Thu May 05 13:47:10 2011 -0600
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,349 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
-#
-
-""" test_grub_setup
-
- Test program for grub_setup
-"""
-
-import os
-import os.path
-import shutil
-import unittest
-
-import testlib
-
-from solaris_install.distro_const.checkpoints.grub_setup import AIGrubSetup, \
- LiveCDGrubSetup, TextGrubSetup
-from solaris_install.distro_const.distro_spec import GrubEntry, GrubMods
-from solaris_install.engine import InstallEngine
-
-
-class TestBuildPositionSpecific(unittest.TestCase):
- """ test case to test the build_position_specific() method of
- GrubSetup
- """
-
- def setUp(self):
- # create a dummy filesystem with some files created in the proper
- # location
- InstallEngine()
- self.filelist = ["/menu.lst", "/.image_info"]
- self.grubsetup = LiveCDGrubSetup("Test GrubSetup")
- self.grubsetup.pkg_img_path = testlib.create_filesystem(*self.filelist)
- self.grubsetup.img_info_path = \
- os.path.join(self.grubsetup.pkg_img_path, ".image_info")
- self.grubsetup.menu_list = \
- os.path.join(self.grubsetup.pkg_img_path, "menu.lst")
-
- grub_mods = GrubMods("GrubMods")
- grub_mods.min_mem = "1000"
- grub_mods.timeout = "30"
- grub_mods.default_entry = "0"
- grub_mods.title = "myentry"
- self.grubsetup.grub_mods = grub_mods
-
- def tearDown(self):
- shutil.rmtree(self.grubsetup.pkg_img_path, ignore_errors=True)
- InstallEngine._instance = None
-
- def test_position_begin(self):
- begin_entry = GrubEntry("Begin Entry")
- begin_entry.position = "0"
- begin_entry.title_suffix = "Sonia"
- begin_entry.lines = ["kernel$ SonKern", "module$ SonMod"]
-
- entries = self.grubsetup.build_entries()
- self.grubsetup.grub_entry_list = [begin_entry]
- self.grubsetup.build_position_specific(entries)
- self.grubsetup.write_entries(entries)
-
- try:
- with open(self.grubsetup.menu_list, "r") as fh:
- menu_lst_data = [line.strip() for line in fh.readlines()]
- fh.close()
- except IOError, msg:
- self.fail("unable to open the menu.list file: " + str(msg))
-
- # walk the menu.lst file and look for the first title
- for line in menu_lst_data:
- if line.startswith("title"):
- self.assert_("Sonia" in line, line)
- break
-
- def test_position_second(self):
- second_entry = GrubEntry("Second Entry")
- second_entry.position = "1"
- second_entry.title_suffix = "derp"
- second_entry.lines = ["kernel$ derpKern", "module$ derpKern"]
-
- entries = self.grubsetup.build_entries()
- self.grubsetup.grub_entry_list = [second_entry]
- self.grubsetup.build_position_specific(entries)
- self.grubsetup.write_entries(entries)
-
- try:
- with open(self.grubsetup.menu_list, "r") as fh:
- menu_lst_data = [line.strip() for line in fh.readlines()]
- fh.close()
- except IOError, msg:
- self.fail("unable to open the menu.list file: " + str(msg))
-
- # walk the menu.lst file and look for the first title
- title = 0
- for line in menu_lst_data:
- if line.startswith("title"):
- if title == 1:
- self.assert_("derp" in line, line)
- break
- else:
- title += 1
-
- # verify the default, timeout and min_mem64 values
- for line in menu_lst_data:
- if line.startswith("default"):
- self.assertEqual(line.split()[1], "0")
- if line.startswith("timeout"):
- self.assertEqual(line.split()[1], "30")
- if line.startswith("min_mem"):
- self.assertEqual(line.split()[1], "1000")
-
-
-class TestUpdateImgInfo(unittest.TestCase):
- """ test case to test the update_img_info_path() method of
- GrubSetup
- """
-
- def setUp(self):
- # create a dummy filesystem with some files created in the proper
- # location
- InstallEngine()
- self.filelist = ["/menu.lst", "/.image_info", "/etc/release"]
- self.grubsetup = AIGrubSetup("Test GrubSetup")
- self.grubsetup.pkg_img_path = testlib.create_filesystem(*self.filelist)
- self.grubsetup.img_info_path = \
- os.path.join(self.grubsetup.pkg_img_path, ".image_info")
- self.grubsetup.menu_list = \
- os.path.join(self.grubsetup.pkg_img_path, "menu.lst")
-
- grub_mods = GrubMods("GrubMods")
- grub_mods.min_mem = "1000"
- grub_mods.timeout = "30"
- grub_mods.default_entry = "0"
- grub_mods.title = "Sonia's entry"
- self.grubsetup.grub_mods = grub_mods
-
- def tearDown(self):
- shutil.rmtree(self.grubsetup.pkg_img_path, ignore_errors=True)
- InstallEngine._instance = None
-
- def test_ai_entries(self):
- self.grubsetup.update_img_info_path()
- iip = os.path.join(self.grubsetup.pkg_img_path, ".image_info")
- with open(iip, "r") as fh:
- image_info = fh.read().splitlines()
-
- # verify the AI specific entries
- line = "GRUB_MIN_MEM64=%s" % self.grubsetup.grub_mods.min_mem
- self.assert_(line in image_info)
- self.assert_("GRUB_DO_SAFE_DEFAULT=true" in image_info)
-
-
-class TestAIBuildEntries(unittest.TestCase):
- """ test case to test the build_entries() method of
- AIGrubSetup
- """
-
- def setUp(self):
- # create a dummy filesystem with some files created in the proper
- # location
- InstallEngine()
- self.filelist = ["/menu.lst"]
- self.grubsetup = AIGrubSetup("Test AIGrubSetup")
- self.grubsetup.pkg_img_path = testlib.create_filesystem(*self.filelist)
- self.grubsetup.menu_list = \
- os.path.join(self.grubsetup.pkg_img_path, "menu.lst")
-
- grub_mods = GrubMods("GrubMods")
- grub_mods.min_mem = "1000"
- grub_mods.timeout = "30"
- grub_mods.default_entry = "0"
- grub_mods.title = "Sonia's entry"
- self.grubsetup.grub_mods = grub_mods
-
- def tearDown(self):
- shutil.rmtree(self.grubsetup.pkg_img_path, ignore_errors=True)
- InstallEngine._instance = None
-
- def test_write_entries(self):
- second_entry = GrubEntry("Second Entry")
- second_entry.position = "1"
- second_entry.title_suffix = "derp"
- second_entry.lines = ["kernel$ derpKern", "module$ derpKern"]
-
- entries = self.grubsetup.build_entries()
- self.grubsetup.grub_entry_list = [second_entry]
- self.grubsetup.build_position_specific(entries)
- self.grubsetup.write_entries(entries)
-
- try:
- with open(self.grubsetup.menu_list, "r") as fh:
- menu_lst_data = [line.strip() for line in fh.readlines()]
- fh.close()
- except IOError, msg:
- self.fail("unable to open the menu.list file: " + str(msg))
-
- # verify the 'custom' and regular entry
- for line in menu_lst_data:
- if line.startswith("title") and \
- line.strip().endswith("Automated Install custom"):
- for innerline in menu_lst_data:
- if line.startswith("kernel"):
- self.assert_(
- innerline.strip().endswith("aimanifest=prompt"))
- break
-
- if line.startswith("title") and \
- line.strip().endswith("Automated Install"):
- for innerline in menu_lst_data:
- if line.startswith("kernel"):
- self.assert_("=" in innerline)
- break
-
-
-class TestLiveCDBuildEntries(unittest.TestCase):
- """ test case to test the build_entries() method of
- LiveCDGrubSetup
- """
-
- def setUp(self):
- # create a dummy filesystem with some files created in the proper
- # location
- InstallEngine()
- self.filelist = ["/menu.lst"]
- self.grubsetup = LiveCDGrubSetup("Test LiveCDGrubSetup")
- self.grubsetup.pkg_img_path = testlib.create_filesystem(*self.filelist)
- self.grubsetup.menu_list = \
- os.path.join(self.grubsetup.pkg_img_path, "menu.lst")
-
- grub_mods = GrubMods("GrubMods")
- grub_mods.min_mem = "1000"
- grub_mods.timeout = "30"
- grub_mods.default_entry = "0"
- grub_mods.title = "Sonia's entry"
- self.grubsetup.grub_mods = grub_mods
-
- def tearDown(self):
- shutil.rmtree(self.grubsetup.pkg_img_path, ignore_errors=True)
- InstallEngine._instance = None
-
- def test_write_entries(self):
- second_entry = GrubEntry("Second Entry")
- second_entry.position = "1"
- second_entry.title_suffix = "derp"
- second_entry.lines = ["kernel$ derpKern", "module$ derpKern"]
-
- entries = self.grubsetup.build_entries()
- self.grubsetup.grub_entry_list = [second_entry]
- self.grubsetup.build_position_specific(entries)
- self.grubsetup.write_entries(entries)
-
- try:
- with open(self.grubsetup.menu_list, "r") as fh:
- menu_lst_data = [line.strip() for line in fh.readlines()]
- fh.close()
- except IOError, msg:
- self.fail("unable to open the menu.list file: " + str(msg))
-
- # verify the 'vesa' and 'text console' entry
- for line in menu_lst_data:
- if line.startswith("title") and \
- line.strip().endswith("VESA driver"):
- for innerline in menu_lst_data:
- if line.startswith("kernel"):
- self.assert_(
- innerline.strip().endswith("-B livemode=vesa"))
- break
-
- if line.startswith("title") and \
- line.strip().endswith("text console"):
- for innerline in menu_lst_data:
- if line.startswith("kernel"):
- self.assert_(
- innerline.strip().endswith("-B livemode=text"))
- break
-
-
-class TestTextBuildEntries(unittest.TestCase):
- """ test case to test the build_entries() method of
- TextGrubSetup
- """
-
- def setUp(self):
- # create a dummy filesystem with some files created in the proper
- # location
- InstallEngine()
- self.filelist = ["/menu.lst"]
- self.grubsetup = TextGrubSetup("Test TextGrubSetup")
- self.grubsetup.pkg_img_path = testlib.create_filesystem(*self.filelist)
- self.grubsetup.menu_list = \
- os.path.join(self.grubsetup.pkg_img_path, "menu.lst")
-
- grub_mods = GrubMods("GrubMods")
- grub_mods.min_mem = "1000"
- grub_mods.timeout = "30"
- grub_mods.default_entry = "0"
- grub_mods.title = "Sonia's entry"
- self.grubsetup.grub_mods = grub_mods
-
- def tearDown(self):
- shutil.rmtree(self.grubsetup.pkg_img_path, ignore_errors=True)
- InstallEngine._instance = None
-
- def test_write_entries(self):
- second_entry = GrubEntry("Second Entry")
- second_entry.position = "1"
- second_entry.title_suffix = "derp"
- second_entry.lines = ["kernel$ derpKern", "module$ derpKern"]
-
- entries = self.grubsetup.build_entries()
- self.grubsetup.grub_entry_list = [second_entry]
- self.grubsetup.build_position_specific(entries)
- self.grubsetup.write_entries(entries)
-
- try:
- with open(self.grubsetup.menu_list, "r") as fh:
- menu_lst_data = [line.strip() for line in fh.readlines()]
- fh.close()
- except IOError, msg:
- self.fail("unable to open the menu.list file: " + str(msg))
-
- # verify that there's a 'hiddenmenu' line
- hiddenmenu = 0
- for line in menu_lst_data:
- if line.startswith("hiddenmenu"):
- hiddenmenu = 1
- break
-
- self.assert_(hiddenmenu == 1)
--- a/usr/src/cmd/distro_const/distro_spec.py Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/cmd/distro_const/distro_spec.py Fri May 06 11:44:10 2011 +1000
@@ -118,137 +118,6 @@
TAG_NAME = "vm_im"
-class GrubMods(SimpleXmlHandlerBase):
- TAG_NAME = "grub_mods"
- MIN_MEM_LABEL = "min_mem"
- TITLE_LABEL = "title"
- DEFAULT_ENTRY_LABEL = "default_entry"
- TIMEOUT_LABEL = "timeout"
-
- def __init__(self, name):
- super(GrubMods, self).__init__(name)
- self.min_mem = None
- self.title = None
- self.default_entry = None
- self.timeout = None
-
- def to_xml(self):
- element = etree.Element(GrubMods.TAG_NAME)
-
- if self.min_mem is not None:
- element.set(GrubMods.MIN_MEM_LABEL, self.min_mem)
- if self.title is not None:
- element.set(GrubMods.TITLE_LABEL, self.title)
- if self.default_entry is not None:
- element.set(GrubMods.DEFAULT_ENTRY_LABEL, self.default_entry)
- if self.timeout is not None:
- element.set(GrubMods.TIMEOUT_LABEL, self.timeout)
-
- return element
-
- @classmethod
- def can_handle(cls, element):
- if element.tag == cls.TAG_NAME:
- return True
- return False
-
- @classmethod
- def from_xml(cls, element):
- min_mem = element.get(cls.MIN_MEM_LABEL)
- title = element.get(cls.TITLE_LABEL)
- default_entry = element.get(cls.DEFAULT_ENTRY_LABEL)
- timeout = element.get(cls.TIMEOUT_LABEL)
-
- grub_mods = GrubMods(cls.TAG_NAME)
- if min_mem is not None:
- grub_mods.min_mem = min_mem
- if title is not None:
- grub_mods.title = title
- if default_entry is not None:
- grub_mods.default_entry = default_entry
- if timeout is not None:
- grub_mods.timeout = timeout
-
- return grub_mods
-
-
-class GrubEntry(SimpleXmlHandlerBase):
- TAG_NAME = "grub_entry"
- POSITION_LABEL = "position"
- TITLE_SUFFIX_LABEL = "title_suffix"
- LINE_LABEL = "line"
-
- def __init__(self, name):
- super(GrubEntry, self).__init__(name)
- self.position = None
- self.title_suffix = None
- self.lines = None
-
- def to_xml(self):
- element = etree.Element(GrubEntry.TAG_NAME)
- if self.position is not None:
- element.set(GrubEntry.POSITION_LABEL, self.position)
- if self.title_suffix is not None:
- title_element = etree.SubElement(element,
- GrubEntry.TITLE_SUFFIX_LABEL)
- title_element.text = self.title_suffix
- if self.lines is not None:
- for entry in self.lines:
- line_element = etree.SubElement(element, GrubEntry.LINE_LABEL)
- line_element.text = entry
-
- return element
-
- @classmethod
- def can_handle(cls, element):
- '''
- Returns True if element has:
- - the tag 'grub_entry'
- - a title_suffix element
- - a line element
-
- Otherwise returns False
- '''
- if element.tag != cls.TAG_NAME:
- return False
-
- title_suffix = None
- line = None
-
- for subelement in element.iterchildren():
- if subelement.tag == cls.TITLE_SUFFIX_LABEL:
- title_suffix = subelement.text
-
- if subelement.tag == cls.LINE_LABEL:
- line = subelement.text
-
- return True
-
- @classmethod
- def from_xml(cls, element):
- title_suffix = None
- lines = []
-
- position = element.get(cls.POSITION_LABEL)
-
- for subelement in element.iterchildren():
- if subelement.tag == cls.TITLE_SUFFIX_LABEL:
- title_suffix = subelement.text
-
- if subelement.tag == cls.LINE_LABEL:
- lines.append(subelement.text)
-
- grub_entry = GrubEntry(cls.TAG_NAME)
- if position is not None:
- grub_entry.position = position
- if title_suffix is not None:
- grub_entry.title_suffix = title_suffix
- if lines:
- grub_entry.lines = lines
-
- return grub_entry
-
-
class MaxSize(SimpleXmlHandlerBase):
TAG_NAME = "max_size"
--- a/usr/src/cmd/distro_const/manifest/dc_ai_x86.xml Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/cmd/distro_const/manifest/dc_ai_x86.xml Fri May 06 11:44:10 2011 +1000
@@ -39,38 +39,55 @@
<img_params>
<media_im>
<!--
- Grub menu modifications to be applied to the image.
+ Boot menu modifications to be applied to the image.
Optional attributes that can be specified here are:
- title - title for the specialized GRUB entry
+ title - title for the specialized boot entry
Default is to use the first line of /etc/release
- default_entry - which entry should be the default entry
- timeout - GRUB menu timeout value in seconds
+ timeout - boot loader timeout value before the default boot entry
+ is automatically activated.
-->
<!-- Uncomment before using
- <grub_mods title="myentry" default_entry="0" timeout="5">
+ <boot_mods title="myentry" timeout="5">
-->
- <!--
- Grub entries to add to the default grub menu in the image. Each
- entry is added sequentially to the menu.lst file in order of
- listing.
+ <!--
+ Boot entries to add to the default boot menu in the image. Each
+ entry is either prepended or appended sequentially to the boot
+ menu in order of listing based on each boot entry element's
+ "insert_at" attribute value ("start" or "end").
- An optional "position" attribute is the location in the menu.lst
- file to insert the entry. If omitted, simply append the entry
- to the end of the menu.lst file.
+ Optional attributes:
+ default_entry - If the boot_entry has this attribute set to
+ "true" then it will be the default boot entry
+ activated by the boot loader.
+ Note that if more than one boot entry has this
+ attribute set to "true", the last entry defined
+ as such will override preceeding default
+ boot_entry elements set to "true".
- title suffix - text string added to this specific entry title
- line - each line element will be added in order specified
+ insert_at - Optional attribute indicating the desired
+ insertion point relative to the existing list of
+ boot entries.
+ Valid values are "start" or "end" only. If
+ omitted the default action is to append the entry
+ to the end of the list.
+
+ Required sub-elements:
+ title_suffix - Text string appended to this specific entry's
+ title.
+
+ Optional sub-elements:
+ kernel_args - Optional kernel arguments passed to the kernel by
+ the boot loader.
-->
<!-- Uncomment before using
- <grub_entry position="1">
- <title_suffix>MyTitle</title_suffix>
- <line>kernel$ /platform/i86pc/kernel/$ISADIR/unix</line>
- <line>module$ /platform/i86pc/$ISADIR/boot_archive</line>
- </grub_entry>
+ <boot_entry default_entry="false" insert_at="end">
+ <title_suffix>My Title</title_suffix>
+ <kernel_args></kernel_args>
+ </boot_entry>
-->
<!-- Uncomment before using
- </grub_mods>
+ </boot_mods>
-->
</media_im>
</img_params>
@@ -260,13 +277,13 @@
<arg name="bytes_per_inode">0</arg>
</kwargs>
</checkpoint>
- <checkpoint name="grub-setup"
- desc="Setup GRUB menu"
- mod_path="solaris_install/distro_const/checkpoints/grub_setup"
- checkpoint_class="AIGrubSetup">
+ <checkpoint name="boot-setup"
+ desc="Setup boot menu"
+ mod_path="solaris_install/boot/boot"
+ checkpoint_class="AIISOImageBootMenu">
<kwargs>
<!-- The installadm_entry defines the title for the default
- Grub entry used by installadm when unpacking the image for use -->
+ boot entry used by installadm when unpacking the image for use -->
<arg name="installadm_entry">Text Installer and command line</arg>
</kwargs>
</checkpoint>
--- a/usr/src/cmd/distro_const/manifest/dc_livecd.xml Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/cmd/distro_const/manifest/dc_livecd.xml Fri May 06 11:44:10 2011 +1000
@@ -39,45 +39,64 @@
<img_params>
<media_im>
<!--
- Grub menu modifications to be applied to the image.
+ Boot menu modifications to be applied to the image.
Optional attributes that can be specified here are:
- title - title for the specialized GRUB entry
+ title - title for the specialized boot entry
Default is to use the first line of /etc/release
- default_entry - which entry should be the default entry
- timeout - GRUB menu timeout value in seconds
+
+ timeout - boot loader timeout value before the default boot entry
+ is automatically activated.
-->
- <!-- Uncomment before using
- <grub_mods title="myentry" default_entry="0" timeout="5">
+ <!-- Sample boot_mods with title and timeout attributes.
+ Uncomment and replace un-attributed boot_mods entry below
+ before using.
+ <boot_mods title="myentry" timeout="5">
-->
- <grub_mods>
- <!--
- Grub entries to add to the default grub menu in the image. Each
- entry is added sequentially to the menu.lst file in order of
- listing.
+ <boot_mods>
+ <!--
+ Boot entries to add to the default boot menu in the image. Each
+ entry is either prepended or appended sequentially to the boot
+ menu in order of listing based on each boot entry element's
+ "insert_at" attribute value ("start" or "end").
+
+ Optional attributes:
+ default_entry - If the boot_entry has this attribute set to
+ "true" then it will be the default boot entry
+ activated by the boot loader.
+ Note that if more than one boot entry has this
+ attribute set to "true", the last entry defined
+ as such will override preceeding default
+ boot_entry elements set to "true".
- An optional "position" attribute is the location in the menu.lst
- file to insert the entry. If omitted, simply append the entry
- to the end of the menu.lst file.
+ insert_at - Optional attribute indicating the desired
+ insertion point relative to the existing list of
+ boot entries.
+ Valid values are "start" or "end" only. If
+ omitted the default action is to append the entry
+ to the end of the list.
- title suffix - text string added to this specific entry title
- line - each line element will be added in order specified
-
+ Required sub-elements:
+ title_suffix - Text string appended to this specific entry's
+ title.
+
+ Optional sub-elements:
+ kernel_args - Optional kernel arguments passed to the kernel by
+ the boot loader.
+
NOTE: It is strongly suggested that any entries to be added
must be specified *before* the "with magnifier" entry
below.
-->
- <grub_entry>
+ <boot_entry>
<title_suffix>with magnifier</title_suffix>
- <line>kernel$ /platform/i86pc/kernel/$ISADIR/unix -B assistive_tech=magnifier</line>
- <line>module$ /platform/i86pc/$ISADIR/boot_archive</line>
- </grub_entry>
- <grub_entry>
+ <kernel_args>-B assistive_tech=magnifier</kernel_args>
+ </boot_entry>
+ <boot_entry>
<title_suffix>with screen reader</title_suffix>
- <line>kernel$ /platform/i86pc/kernel/$ISADIR/unix -B assistive_tech=reader</line>
- <line>module$ /platform/i86pc/$ISADIR/boot_archive</line>
- </grub_entry>
- </grub_mods>
+ <kernel_args>-B assistive_tech=reader</kernel_args>
+ </boot_entry>
+ </boot_mods>
</media_im>
</img_params>
</distro_spec>
@@ -273,10 +292,10 @@
<arg name="bytes_per_inode">0</arg>
</kwargs>
</checkpoint>
- <checkpoint name="grub-setup"
- desc="Setup GRUB menu"
- mod_path="solaris_install/distro_const/checkpoints/grub_setup"
- checkpoint_class="LiveCDGrubSetup"/>
+ <checkpoint name="boot-setup"
+ desc="Setup LiveCD boot menu"
+ mod_path="solaris_install/boot/boot"
+ checkpoint_class="LiveCDISOImageBootMenu"/>
<checkpoint name="pkg-img-mod"
desc="Pkg image area modification"
mod_path="solaris_install/distro_const/checkpoints/pkg_img_mod"
--- a/usr/src/cmd/distro_const/manifest/dc_text_x86.xml Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/cmd/distro_const/manifest/dc_text_x86.xml Fri May 06 11:44:10 2011 +1000
@@ -40,39 +40,56 @@
<img_params>
<media_im>
<!--
- Grub menu modifications to be applied to the image.
+ Boot menu modifications to be applied to the image.
Optional attributes that can be specified here are:
- title - title for the specialized GRUB entry
+ title - title for the specialized boot entry
Default is to use the first line of /etc/release
- default_entry - which entry should be the default entry
- timeout - GRUB menu timeout value in seconds
+
+ timeout - boot loader timeout value before the default boot entry
+ is automatically activated.
-->
<!-- Uncomment before using
- <grub_mods title="myentry" default_entry="0" timeout="5">
+ <boot_mods title="myentry" timeout="5">
-->
- <!--
- Grub entries to add to the default grub menu in the image. Each
- entry is added sequentially to the menu.lst file in order of
- listing.
+ <!--
+ Boot entries to add to the default boot menu in the image. Each
+ entry is either prepended or appended sequentially to the boot
+ menu in order of listing based on each boot entry element's
+ "insert_at" attribute value ("start" or "end").
- An optional "position" attribute is the location in the menu.lst
- file to insert the entry. If omitted, simply append the entry
- to the end of the menu.lst file.
+ Optional attributes:
+ default_entry - If the boot_entry has this attribute set to
+ "true" then it will be the default boot entry
+ activated by the boot loader.
+ Note that if more than one boot entry has this
+ attribute set to "true", the last entry defined
+ as such will override preceeding default
+ boot_entry elements set to "true".
- title suffix - text string added to this specific entry title
- line - each line element will be added in order specified
+ insert_at - Optional attribute indicating the desired
+ insertion point relative to the existing list of
+ boot entries.
+ Valid values are "start" or "end" only. If
+ omitted the default action is to append the entry
+ to the end of the list.
+ Required sub-elements:
+ title_suffix - Text string appended to this specific entry's
+ title.
+
+ Optional sub-elements:
+ kernel_args - Optional kernel arguments passed to the kernel by
+ the boot loader.
-->
<!-- Uncomment before using
- <grub_entry position="1">
- <title_suffix>MyTitle</title_suffix>
- <line>kernel$ /platform/i86pc/kernel/$ISADIR/unix</line>
- <line>module$ /platform/i86pc/$ISADIR/boot_archive</line>
- </grub_entry>
+ <boot_entry default_entry="false" insert_at="end">
+ <title_suffix>My Title</title_suffix>
+ <kernel_args></kernel_args>
+ </boot_entry>
-->
<!-- Uncomment before using
- </grub_mods>
+ </boot_mods>
-->
</media_im>
</img_params>
@@ -266,11 +283,10 @@
<arg name="bytes_per_inode">0</arg>
</kwargs>
</checkpoint>
- <checkpoint name="grub-setup"
- desc="Setup GRUB menu"
- mod_path="solaris_install/distro_const/checkpoints/grub_setup"
- checkpoint_class="TextGrubSetup">
- </checkpoint>
+ <checkpoint name="boot-setup"
+ desc="Setup boot menu"
+ mod_path="solaris_install/boot/boot"
+ checkpoint_class="TextISOImageBootMenu"/>
<checkpoint name="pkg-img-mod"
desc="Pkg image area modification"
mod_path="solaris_install/distro_const/checkpoints/pkg_img_mod"
--- a/usr/src/lib/Makefile Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/lib/Makefile Fri May 06 11:44:10 2011 +1000
@@ -51,6 +51,7 @@
COMSUBDIRS= liberrsvc_pymod \
liberrsvc \
+ install_boot \
install_doc \
install_engine \
install_ict \
--- a/usr/src/lib/Makefile.targ Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/lib/Makefile.targ Fri May 06 11:44:10 2011 +1000
@@ -112,6 +112,9 @@
$(ROOTPYTHONVENDORSOLINSTALL)/%: $$(PNAME)/$(ARCH)/%
$(INS.file)
+$(ROOTPYTHONVENDORSOLINSTALLBOOT):
+ $(INS.dir)
+
$(ROOTPYTHONVENDORSOLINSTALLDATACACHE):
$(INS.dir)
@@ -194,6 +197,9 @@
$(ROOTPYTHONVENDORSOLINSTALL)/%: %
$(CP_P.file)
+$(ROOTPYTHONVENDORSOLINSTALLBOOT)/%: %
+ $(CP_P.file)
+
$(ROOTPYTHONVENDORSOLINSTALLDATACACHE)/%: %
$(CP_P.file)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_boot/Makefile Fri May 06 11:44:10 2011 +1000
@@ -0,0 +1,61 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+PYMODS = __init__.py \
+ boot.py \
+ boot_spec.py
+
+PYCMODS = $(PYMODS:%.py=%.pyc)
+
+ROOTPYMODS = $(PYMODS:%=$(ROOTPYTHONVENDORSOLINSTALLBOOT)/%)
+
+ROOTPYCMODS = $(PYCMODS:%=$(ROOTPYTHONVENDORSOLINSTALLBOOT)/%)
+
+
+CLOBBERFILES = $(PYCMODS)
+CLEANFILES = $(CLOBBERFILES)
+
+include ../Makefile.lib
+
+HDRS = $(EXPHDRS) $(PRIVHDRS)
+
+python:
+ $(PYTHON) -m compileall -l $(@D)
+
+all: $(HDRS) python
+
+install_h:
+
+install: all .WAIT \
+ $(ROOTPYTHONVENDOR) \
+ $(ROOTPYTHONVENDORSOLINSTALL) \
+ $(ROOTPYTHONVENDORSOLINSTALLBOOT) \
+ $(ROOTPYMODS) $(ROOTPYCMODS)
+
+lint: lint_SRCS
+
+FRC:
+
+include ../Makefile.targ
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_boot/__init__.py Fri May 06 11:44:10 2011 +1000
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+"""init module for the boot checkpoints"""
+
+from solaris_install.data_object.cache import DataObjectCache
+from solaris_install.boot.boot import BootMods, BootEntry
+
+__all__ = ["boot", "boot_spec"]
+
+# Register DataObject sub-classes with the DOC
+DataObjectCache.register_class(BootMods)
+DataObjectCache.register_class(BootEntry)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_boot/boot.py Fri May 06 11:44:10 2011 +1000
@@ -0,0 +1,1046 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+""" boot.py -- Installs and configures boot loader and boot menu
+ onto physical systems and installation images.
+"""
+
+import abc
+import array
+import fcntl
+import linecache
+import os
+import platform
+import stat
+import struct
+import tempfile
+
+from grp import getgrnam
+from pwd import getpwnam
+from shutil import move, copyfile, rmtree
+
+from bootmgmt import bootconfig, BootmgmtUnsupportedPropertyError
+from bootmgmt.bootconfig import BootConfig, DiskBootConfig, ODDBootConfig, \
+ SolarisDiskBootInstance, SolarisODDBootInstance, ChainDiskBootInstance
+from solaris_install import CalledProcessError, Popen
+from solaris_install.boot.boot_spec import BootMods, BootEntry
+from solaris_install.data_object import ObjectNotFoundError
+from solaris_install.data_object.data_dict import DataObjectDict
+from solaris_install.engine import InstallEngine
+from solaris_install.engine.checkpoint import AbstractCheckpoint as Checkpoint
+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
+
+BOOT_ENV = "boot-env"
+# Character boot device paths eg. c0t0d0s0
+DEVS = "devs"
+
+
+class BootMenu(Checkpoint):
+ """ Abstract class for BootMenu checkpoint
+ """
+ __metaclass__ = abc.ABCMeta
+
+ def __init__(self, name):
+ """ Constructor for class
+ """
+ super(BootMenu, self).__init__(name)
+ self.arch = platform.processor()
+ self.boot_entry_list = list()
+ self.boot_mods = None
+ self.boot_title = None
+ # Used for string token substitution of pybootgmmt return data
+ self.boot_tokens = dict()
+ self.config = None
+ self.doc = InstallEngine.get_instance().data_object_cache
+ self.rel_file_title = None
+
+ def get_progress_estimate(self):
+ """ Returns an estimate of the time this checkpoint will
+ take to complete.
+ """
+ return 5
+
+ @abc.abstractmethod
+ def build_default_entries(self):
+ """ This method is required to be implemented by all subclasses.
+ Standard, pre-defined boot menu entries get created by
+ subclass implementors of this method.
+ """
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def build_custom_entries(self):
+ """ This method is required to be implemented by all subclasses.
+ Custom entries specified in the boot_mods section of the
+ XML manifest get created by subclass implementors of this method.
+ """
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def _get_rel_file_path(self):
+ """ This method is required to be implemented by all subclasses.
+ Returns file system path to the release file.
+ """
+ raise NotImplementedError
+
+ def parse_doc(self, dry_run=False):
+ """ Parse data object cache for values required by the checkpoint
+ execution.
+ """
+ # Break doc parsing down into 2 specific parts as the target
+ # configuration in the doc differs between DC (for ISO images)
+ # and and live system installation.
+ self._parse_doc_target(dry_run)
+ self._parse_doc_boot_mods()
+
+ def _parse_doc_boot_mods(self):
+ """ Parses data object cache (DOC) BootMods tree for configuration of
+ the checkpoint.
+ """
+ try:
+ # Check and retrieve the optional manifest
+ # specific boot configurations.
+ boot_mods = self.doc.get_descendants(class_type=BootMods)
+ if len(boot_mods) < 1:
+ return
+ self.boot_mods = boot_mods[0]
+ except ObjectNotFoundError as err:
+ raise RuntimeError("Error retrieving BootMods from the DOC: " + \
+ str(err))
+
+ if self.boot_mods.title and len(self.boot_mods.title) > 0:
+ self.logger.info("Setting boot title prefix from manifest value: \
+ '%s'" % self.boot_mods.title)
+ self.boot_title = self.boot_mods.title
+
+ try:
+ self.boot_entry_list = self.boot_mods.get_children(
+ class_type=BootEntry)
+ except ObjectNotFoundError:
+ # the manifest does not contain any custom boot entries
+ self.logger.debug("No custom boot entries found in the DOC")
+
+ @abc.abstractmethod
+ def _parse_doc_target(self, dry_run=False):
+ """ This method is required to be implemented by all subclasses.
+ Additional class specific parsing to determine the boot
+ configuration's target paths or devices get performed by
+ subclass implementors this method.
+
+ Input:
+ None
+ Output:
+ None
+ """
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def init_boot_config(self, autogen=True):
+ """ This method is required to be implemented by all subclasses.
+
+ Input:
+ None
+ Output:
+ None
+ """
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def install_boot_loader(self, dry_run=False):
+ """ This method is required to be implemented by all subclasses.
+
+ Input:
+ dry_run
+ - If True, the set of files that constitute the boot
+ configuration is written to a temporary directory (and not to
+ the official location(s)) When files are written to a
+ temporary directory, the list and format of the files written
+ is returned (see Output)
+ Output:
+ None
+ """
+ raise NotImplementedError
+
+ def _handle_boot_config_list(self, boot_config_list, dry_run):
+ """ Method that checks returned tuple list from commit_boot_config()
+ and copies over content as appropriate to their targets.
+ """
+ for boot_config in boot_config_list:
+ for config_set in boot_config:
+ 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
+ # BootConfig.OUTPUT_TYPE_HSFS_BOOTBLK
+ self._handle_iso_boot_image_type(config_set, dry_run)
+ else:
+ raise RuntimeError("Unrecognized boot loader " \
+ "file type: %s" % ftype)
+
+ def _handle_file_type(self, config, dry_run):
+ """ Method that copies generic file types to their appropriate targets.
+ """
+ ftype, src, objref, dest, uid, gid, mode = config
+ # Copy from 'src' to 'dest' and set uid:gid + mode
+ if src is None:
+ raise RuntimeError("No source path defined for boot" \
+ "file")
+ if dest is None:
+ raise RuntimeError("No destination path defined for boot" \
+ "file")
+ # XXX The 'dest' field, I believe, should be prepended with the
+ # string token '%(systemroot)s' but it currently appears not to
+ # be the case sometimes, and instead specifies an absolute path
+ # such as '/boot/grub/menu.lst'
+ # Check for a leading slash and prepend if necessary
+ if os.path.isabs(dest):
+ dest = '%(' + BootConfig.TOKEN_SYSTEMROOT + ')s' + dest
+
+ real_dest = dest % (self.boot_tokens)
+ par_dir = os.path.abspath(os.path.join(real_dest, os.path.pardir))
+ if dry_run is True:
+ os.unlink(src)
+ return
+ try:
+ if not os.path.exists(par_dir):
+ self.logger.debug("Creating boot configuration folder: %s" \
+ % par_dir)
+ os.makedirs(par_dir)
+ self.logger.debug("Moving boot configuration file: \n" \
+ "%s -> %s" % (src, real_dest))
+ move(src, real_dest)
+ if uid is None:
+ uid = -1
+ if gid is None:
+ gid = -1
+ self.logger.debug("Setting ownership of %s to: %s:%s" \
+ % (real_dest, uid, gid))
+ os.chown(real_dest,
+ getpwnam(uid).pw_uid,
+ getgrnam(gid).gr_gid)
+ if mode:
+ self.logger.debug("Setting permissions of %s to: %d" \
+ % (real_dest, mode))
+ os.chmod(real_dest, mode)
+ except OSError as err:
+ raise RuntimeError("Error copying boot configuration files to " \
+ "target: %s" % str(err))
+
+ def execute(self, dry_run=False):
+ """ Primary execution method used by the Checkpoint parent class
+ """
+ self.parse_doc(dry_run)
+ self.init_boot_config()
+ self.build_default_entries()
+ self.build_custom_entries()
+ self.install_boot_loader(dry_run)
+
+
+class SystemBootMenu(BootMenu):
+ """ Class for SystemBootMenu checkpoint. Suitable for automatic boot loader
+ and boot menu configuration for install applications that install onto
+ a physical system device such as gui-install, text install and AI
+ """
+
+ def __init__(self, name):
+ """ Constructor for class
+ """
+ super(SystemBootMenu, self).__init__(name)
+
+ # Dictionary for quick lookup of paramaters needed to configure and
+ # install the boot loader.
+ self.boot_target = dict()
+ self.boot_target[BOOT_ENV] = None
+ self.boot_target[DEVS] = list()
+
+ # Filesystem name of the target boot environment eg.
+ # rpool/ROOT/solaris-11-XYZ
+ self.target_bootfs = None
+ # Mountpoint of pool toplevel dataset
+ self.pool_tld_mount = None
+
+ def init_boot_config(self, autogen=True):
+ """ Instantiates the appropriate bootmgmt.bootConfig subclass object
+ for this class
+ """
+ bc_flags = [bootconfig.BootConfig.BCF_CREATE]
+ if autogen:
+ bc_flags.append(bootconfig.BootConfig.BCF_AUTOGEN)
+ bc_flags = tuple(bc_flags)
+ target_be = self.boot_target[BOOT_ENV]
+ # If the BE is unmounted for some reason we need to go and mount it.
+ if target_be.mountpoint == None:
+ tmp_be_mp = tempfile.mkdtemp(dir="/system/volatile",
+ prefix="be_mount_%s_" \
+ % (target_be.name))
+ self.logger.debug("Mounting BE %s at: %s" % \
+ (target_be.name, tmp_be_mp))
+ target_be.mount(tmp_be_mp, dry_run=False)
+
+ # Note that 'platform' tuple is not supplied here since we want to
+ # go with the running system arch/firmware, which bootmgmt defaults to.
+ self.config = DiskBootConfig(bc_flags,
+ rpname=target_be.parent.name,
+ tldpath=self.pool_tld_mount,
+ zfspath=target_be.mountpoint)
+
+ # Apply boot_mods manifest XML attributes to boot loader:
+ # - Override the default timeout if specifed in boot_mods
+ if self.boot_mods:
+ try:
+ if self.boot_mods.timeout:
+ self.config.boot_loader.setprop('timeout',
+ self.boot_mods.timeout)
+ except BootmgmtUnsupportedPropertyError:
+ self.logger.warning("Boot loader type %s does not support the \
+ 'timeout' property. Ignoring." \
+ % self.config.bootloader.name)
+
+ def execute(self, dry_run=False):
+ """ Primary execution method used by the Checkpoint parent class
+ """
+ # UEFI The divergent code paths for sparc and X86 is a temporary
+ # workaround for the lack of sparc support in the bootmgmt module.
+ # When this is addressed, I expect to revert to just calling the
+ # parent class method.
+ if self.arch == 'sparc':
+ # For sparc, generate a manual menu.lst and copy the bootlst
+ # binary into the boot dataset.
+ self.parse_doc(dry_run)
+ # Check to make sure that a non-dry run attempt is not
+ # executed on a live system
+ if self.boot_target[BOOT_ENV].mountpoint == '/' and \
+ not dry_run:
+ raise RuntimeError("Boot checkpoint can not be executed on" \
+ "live systems except with dry run mode")
+ self._set_bootfs_pool_prop(dry_run)
+ self._install_sparc_bootblk(dry_run)
+ self._set_sparc_prom_boot_device(dry_run)
+ self._create_sparc_boot_menu(dry_run)
+ self._copy_sparc_bootlst(dry_run)
+ else:
+ super(SystemBootMenu, self).execute(dry_run)
+
+ def _set_bootfs_pool_prop(self, dry_run):
+ """ Set the bootfs property on the boot pool, activating the BE.
+ XXX: This should really be ultimately handled by pybootmgmt
+ """
+ pool = self.boot_target[BOOT_ENV].parent
+ self.logger.debug("Setting bootfs zpool property on %s to %s" \
+ % (pool.name, self.target_bootfs))
+ pool.set("bootfs", self.target_bootfs, dry_run)
+
+ def _create_sparc_boot_menu(self, dry_run):
+ """ Create a boot menu.lst file on a SPARC system.
+ """
+ # Attempt to create the path to where the menu.lst file will reside
+ boot_menu_path = os.path.join(self.pool_tld_mount, 'boot')
+ if dry_run:
+ # Override boot_menu_path to temporary storage
+ temp_dir = tempfile.mkdtemp(dir="/tmp", prefix="sparc_boot_menu_")
+ boot_menu_path = os.path.join(temp_dir,
+ boot_menu_path.strip(os.path.sep))
+ boot_menu = os.path.join(boot_menu_path, 'menu.lst')
+ sparc_title_line = 'title %s\n' % self.boot_title
+ bootfs_line = 'bootfs ' + self.target_bootfs + '\n'
+ self.logger.debug("Creating SPARC boot menu file: %s" \
+ % boot_menu)
+ if not os.path.isdir(boot_menu_path):
+ os.makedirs(boot_menu_path, 0755)
+
+ try:
+ with open(boot_menu, 'w') as menu_lst:
+ menu_lst.write(sparc_title_line)
+ menu_lst.write(bootfs_line)
+ os.chmod(boot_menu,
+ stat.S_IREAD | stat.S_IWRITE | \
+ stat.S_IRGRP | stat.S_IROTH)
+ # Requires root privilige so bypass on dry_run
+ if not dry_run:
+ os.chown(boot_menu, 0, 3) # chown root:sys
+ except IOError, msg:
+ raise RuntimeError(msg)
+ if dry_run:
+ rmtree(temp_dir)
+
+ def _copy_sparc_bootlst(self, dry_run):
+ """ Copy the bootlst file on a SPARC system.
+ On SPARC systems a bootlst file is maintained at:
+ /platform/`uname -m`/bootlst
+
+ It needs to be copied to:
+ <rootpool>/platform/`uname -m`/bootlst
+ """
+ bootlst_src = os.path.join(self.boot_target[BOOT_ENV].mountpoint,
+ 'platform', platform.machine(),
+ 'bootlst')
+
+ # Copy file bootlst from basedir to the rootpool
+ bootlst_dir = os.path.join(self.pool_tld_mount,
+ 'platform', platform.machine())
+ if dry_run:
+ # Override bootlst_dir to temporary storage
+ temp_dir = tempfile.mkdtemp(dir="/tmp", prefix="sparc_bootlst_")
+ bootlst_dir = os.path.join(temp_dir,
+ bootlst_dir.strip(os.path.sep))
+ bootlst_dst = os.path.join(bootlst_dir, 'bootlst')
+ self.logger.debug("Copying SPARC bootlst file: %s" \
+ % bootlst_src)
+
+ # Create the destination directory if it does not already exist.
+ if not os.path.isdir(bootlst_dir):
+ os.makedirs(bootlst_dir, 0755)
+
+ # Copy the bootlst
+ copyfile(bootlst_src, bootlst_dst)
+ os.chmod(bootlst_dst,
+ stat.S_IREAD | stat.S_IWRITE | \
+ stat.S_IRGRP | stat.S_IROTH)
+ # Requires root privilige so bypass on dry_run
+ if dry_run:
+ rmtree(bootlst_dir)
+ else:
+ os.chown(bootlst_dst, 0, 3) # chown root:sys
+
+ def _install_sparc_bootblk(self, dry_run):
+ """ Runs installboot(1M) command on SPARC architectures to
+ install the zfs bootblock program.
+ """
+ # This method is only supported on SPARC platforms.
+ if self.arch != 'sparc':
+ raise RuntimeError("Can not install SPARC bootblk on a non-" \
+ "SPARC architecture system")
+ boot_devs = list()
+ boot_devs = [os.path.join('/dev/rdsk', dev) \
+ for dev in self.boot_target[DEVS]]
+
+ if len(boot_devs) < 1:
+ raise RuntimeError("No devices to install SPARC bootblk onto!")
+
+ self.logger.info("Installing SPARC bootblk to root pool devices: %s" \
+ % str(boot_devs))
+
+ bootblk_src = os.path.join(self.boot_target[BOOT_ENV].mountpoint,
+ 'platform', platform.machine(),
+ 'lib', 'fs', 'zfs', 'bootblk')
+ sub_cmd = ["/usr/sbin/installboot", "-F", "zfs", bootblk_src]
+
+ for dev in boot_devs:
+ cmd = sub_cmd[:]
+ cmd.append(dev)
+ self.logger.debug("Executing: %s" % cmd)
+ if not dry_run:
+ Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.STORE,
+ logger=ILN)
+
+ def _set_sparc_prom_boot_device(self, dry_run):
+ """ Set the SPARC boot-device parameter using eeprom.
+ If the root pool is mirrored, sets the boot-device
+ parameter as a sequence of the devices forming the
+ root pool.
+ """
+ self.logger.info("Setting openprom boot-device")
+ prom_device = "/dev/openprom"
+ #ioctl codes and OPROMMAXPARAM taken from /usr/include/sys/openpromio.h
+ oioc = ord('O') << 8
+ opromdev2promname = oioc | 15 # Convert devfs path to prom path
+ oprommaxparam = 32768
+
+ boot_devs = list()
+ boot_devs = [os.path.join('/dev/dsk', dev) \
+ for dev in self.boot_target[DEVS]]
+ if len(boot_devs) < 1:
+ raise RuntimeError("No boot devices identified!")
+
+ self.logger.debug("Opening prom device: %s" % prom_device)
+ try:
+ with open(prom_device, "r") as prom:
+ prom_arg_str = None
+ # Set boot-device as a sequence for mirrored root pools since
+ # the OBP will try to boot each successive device specifier
+ # in the list until something opens successfully.
+ for i, boot_dev in enumerate(boot_devs):
+ self.logger.debug("Boot device %d: %s" % (i, boot_dev))
+
+ # Set up a mutable array for ioctl to read from and write
+ # to. Standard Python objects are not usable here.
+ # fcntl.ioctl requires a mutable buffer pre-packed with the
+ # correct values (as determined by the device-driver).
+ # In this case,openprom(7D) describes the following C
+ # stucture as defined in <sys.openpromio.h>
+ # struct openpromio {
+ # uint_t oprom_size; /* real size of following data */
+ # union {
+ # char b[1]; /* NB: Adjacent, Null terminated */
+ # int i;
+ # } opio_u;
+ # };
+ dev = (boot_dev + "\0").ljust(oprommaxparam)
+ buf = array.array('c', struct.pack('I%ds' % oprommaxparam,
+ oprommaxparam, dev))
+
+ # use ioctl to query the prom device.
+ fcntl.ioctl(prom, opromdev2promname, buf, True)
+
+ # Unpack the mutable array, buf, which
+ # ioctl just wrote into.
+ new_oprom_size, new_dev = struct.unpack(
+ 'I%ds' % oprommaxparam,
+ buf)
+
+ # Device names are a list of null-terminated tokens, with a
+ # double null on the final token.
+ # We use only the first token.
+ prom_name = new_dev.split('\0')[0]
+ self.logger.debug("%s prom device name: %s" \
+ % (boot_dev, prom_name))
+ if prom_arg_str is None:
+ prom_arg_str = prom_name
+ else:
+ prom_arg_str += " " + prom_name
+ except StandardError as std:
+ # Treat this as non-fatal. It can be manually fixed later
+ # on reboot.
+ self.logger.warning("Failed to set openprom boot-device " \
+ "parameter:\n%s" % str(std))
+ return
+
+ # Set the boot device using eeprom
+ cmd = ["/usr/sbin/eeprom", "boot-device=%s" % prom_arg_str]
+ self.logger.debug("Executing: %s" % cmd)
+ if not dry_run:
+ try:
+ Popen.check_call(cmd,
+ stdout=Popen.STORE,
+ stderr=Popen.STORE,
+ logger=ILN)
+ except CalledProcessError as cpe:
+ # Treat this as non-fatal. It can be manually fixed later
+ # on reboot.
+ self.logger.warning("Failed to set openprom boot-device:")
+ self.logger.warning(str(cpe))
+
+ def _is_target_instance(self, instance):
+ """ Returns True if instance is the boot instance we just installed.
+ Otherwise returns False
+ Inputs:
+ - instance: A BootInstance object
+ """
+ # If not a SolarisDiskBootInstance it's not what we just installed
+ if not isinstance(instance, bootconfig.SolarisDiskBootInstance):
+ return False
+ # If the fstype of the instance is not zfs
+ if instance.fstype is not "zfs":
+ return False
+ # Check the bootfs property to see if it's a match
+ if instance.bootfs == self.target_bootfs:
+ return True
+
+ return False
+
+ def _set_as_default_instance(self, instance):
+ """ Sets instance as the default boot instance in a boot configuration
+ Inputs:
+ - instance: A BootInstance object
+ """
+ self.logger.debug("Marking '%s' as the default boot instance" \
+ % instance.title)
+ instance.default = True
+
+ def _set_instance_title(self, instance):
+ """ Sets the title of instance to match self.boot_title
+ Inputs:
+ - instance: A BootInstance object
+ """
+ self.logger.debug("Setting title of boot instance '%s' to '%s'" \
+ % (instance.title, self.boot_title))
+ instance.title = self.boot_title
+
+ def _get_rel_file_path(self):
+ """
+ Returns file system path to the release file.
+ """
+ return os.path.join(self.boot_target[BOOT_ENV].mountpoint,
+ "etc/release")
+
+ def _parse_doc_target(self, dry_run=False):
+ """ Parses the target objects in the DOC to determine the
+ installation target device for boot loader installation
+ """
+ try:
+ target = self.doc.get_descendants(Target.DESIRED, Target,
+ max_count=1, max_depth=2, not_found_is_err=True)[0]
+ except ObjectNotFoundError:
+ raise RuntimeError("No desired target element specified")
+
+ # Find the root pool(s).
+ root_pools = list()
+ for pool in target.get_descendants(class_type=Zpool):
+ if pool.is_root:
+ root_pools.append(pool)
+ if len(root_pools) < 1:
+ raise RuntimeError("No desired target Zpool specified")
+ root_pool = root_pools[0]
+ boot_env = root_pool.get_first_child(class_type=BE)
+ if boot_env is None:
+ raise RuntimeError("No BE specified in Target.Desired tree")
+ self.boot_target[BOOT_ENV] = boot_env
+
+ # If dry_run is True, the target BE might not exist so only bail out
+ # if not a dry run.
+ result = be_list(boot_env.name)
+ if len(result) != 1:
+ if dry_run == False:
+ raise RuntimeError("Target BE \'%s\' does not exist" \
+ % (boot_env.name))
+ else:
+ self.target_bootfs = result[0][2]
+
+ # Get root pool's top level filesystem mountpoint
+ pool = boot_env.parent
+ if pool.mountpoint:
+ self.pool_tld_mount = pool.mountpoint
+ # If mountpoint not defined by the zpool, look at its datasets
+ # for one with the same name as the zpool.
+ if self.pool_tld_mount is None:
+ for filesystem in pool.filesystems:
+ if filesystem.name == pool.name:
+ self.pool_tld_mount = filesystem.get("mountpoint")
+ break
+ # If no top level dataset definition exists then assume default zfs
+ # behaviour for pool top level dataset mount point as '/<pool.name>'
+ if self.pool_tld_mount is None:
+ self.pool_tld_mount = os.path.join('/', pool.name)
+
+ if not os.path.exists(self.pool_tld_mount):
+ raise RuntimeError("Expected mountpoint \'%\' of top level" \
+ "fileystem \'%s\' does not exist!" \
+ % (pool.name, self.pool_tld_mount))
+
+ # Figure out the boot device names in ctd notation
+ # XXX Will need to check for UEFI/GPT partitions here in future
+ for disk in target.get_children(class_type=Disk):
+ # Look for slices that are in the boot/root pool and store
+ # their ctd device names. "slice" is a python reserved word
+ # so use "slc" to represent slice instead.
+ slices = disk.get_descendants(class_type=Slice, max_depth=2)
+ boot_slices = [slc for slc in slices if \
+ slc.in_zpool == root_pool.name]
+ bdevs = ['%ss%s' % (disk.ctd, slc.name) for slc in boot_slices]
+ self.boot_target[DEVS].extend(bdevs)
+
+ title_line = linecache.getline(self._get_rel_file_path(), 1)
+ self.rel_file_title = title_line.strip()
+ # Set an initial boot_title value. It can be overwritten later when
+ # parsing the BootMods tree of the DOC
+ self.boot_title = self.rel_file_title
+
+ def build_default_entries(self):
+ """ Method for constructing the default entries list.
+ When installing onto physical target systems, the list of entries
+ is autogenerated based on discovery routines within bootmgmt.
+ The discovered entries should already be present from the
+ invocation of init_boot_config() so the only remaining tasks here
+ are to set the new boot environment as the default boot instance.
+ """
+ # Find the boot environment (BE) we just installed and
+ # make it the default boot instance
+ self.config.modify_boot_instance(self._is_target_instance,
+ self._set_as_default_instance)
+ # And set its boot title
+ self.config.modify_boot_instance(self._is_target_instance,
+ self._set_instance_title)
+
+ def build_custom_entries(self):
+ """ Currently only consumed by AI installer app. GUI & Text do not
+ consume a manifest XML file.
+ """
+ for entry in self.boot_entry_list:
+ instance = \
+ SolarisDiskBootInstance(self.boot_target[BOOT_ENV].mountpoint,
+ fstype='zfs',
+ bootfs=self.target_bootfs)
+ instance.title = self.boot_title + \
+ " " + entry.title_suffix
+ if entry.default_entry:
+ instance.default = True
+ else:
+ instance.default = False
+ instance.kargs = entry.kernel_args
+
+ if entry.insert_at == "start":
+ where = 0
+ else:
+ where = -1
+ self.logger.info("Adding custom boot entry: \'%s\'" \
+ % instance.title)
+ self.config.add_boot_instance(instance, where)
+
+ def install_boot_loader(self, dry_run=False):
+ """ Install the boot loader and associated boot
+ configuration files
+ """
+ boot_rdevs = [os.path.join('/dev/rdsk', dev) \
+ for dev in self.boot_target[DEVS]]
+
+ if dry_run == True:
+ # Write boot config to a temporary directory instead of to
+ # a physical target
+ temp_dir = \
+ tempfile.mkdtemp(dir="/tmp", prefix="boot_config_")
+ # Add dictionary mappings for tokens that commit_boot_config()
+ # might return.
+ self.boot_tokens[DiskBootConfig.TOKEN_ZFS_RPOOL_TOP_DATASET] = \
+ self.pool_tld_mount
+ self.boot_tokens[BootConfig.TOKEN_SYSTEMROOT] = \
+ self.boot_target[BOOT_ENV].mountpoint
+ self.logger.info("Installing boot loader configuration files 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)
+ else:
+ # Check to make sure that an installation attempt is not
+ # executed on a live system
+ if self.boot_target[BOOT_ENV].mountpoint == '/':
+ raise RuntimeError("Boot checkpoint can not be executed on " \
+ "a live system except with dry run mode")
+ # XXX Pybootmgmt ought to be creating this directory tree when
+ # doing a physical boot loader installation onto the disk.
+ # While it's being fixed, create the directories ourselves to
+ # make legacy Grub installation work.
+ if not dry_run and self.config.boot_loader.name == "Legacy GRUB":
+ grub_dir = os.path.join(self.pool_tld_mount, "boot", "grub")
+ if not os.path.exists(grub_dir):
+ self.logger.info("Creating Legacy GRUB config directory:" \
+ "\n\t%s" % (grub_dir))
+ os.makedirs(grub_dir, 755)
+
+ # Danger Will Robinson!!
+ self.logger.info("Installing boot loader to devices: %s" \
+ % str(boot_rdevs))
+ self.config.commit_boot_config(
+ boot_devices=boot_rdevs)
+ # XXX Pybootmgmt ought to do this but doesn't currently so
+ # set up the bootfs property on the boot pool.
+ self._set_bootfs_pool_prop(dry_run)
+ if dry_run:
+ rmtree(temp_dir)
+
+
+class ISOImageBootMenu(BootMenu):
+ """ Abstract base class for ISOImageBootMenu checkpoint
+ """
+ __metaclass__ = abc.ABCMeta
+
+ def __init__(self, name):
+ """ Constructor for class
+ """
+ super(ISOImageBootMenu, self).__init__(name)
+ self.dc_dict = dict()
+ self.dc_pers_dict = dict()
+ self.pkg_img_path = None
+ from solaris_install.distro_const import DC_LABEL, DC_PERS_LABEL
+ self.DC_LABEL = DC_LABEL
+ self.DC_PERS_LABEL = DC_PERS_LABEL
+
+ def init_boot_config(self, autogen=True):
+ """ Instantiates the appropriate bootmgmt.bootConfig subclass object
+ for this class
+ """
+ bc_flags = (bootconfig.BootConfig.BCF_CREATE,
+ 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')
+
+ def _get_rel_file_path(self):
+ """
+ Returns file system path to the release file.
+ """
+ return os.path.join(self.pkg_img_path, "etc/release")
+
+ def _add_chainloader_entry(self):
+ """ Adds a chainloader entry to the boot configuration.
+ """
+ instance = ChainDiskBootInstance(chaininfo=(0, 0))
+ instance.title = "Boot from Hard Disk"
+ self.config.add_boot_instance(instance)
+
+ def build_custom_entries(self):
+ """ Add custom defined boot entries from the manifest XML
+ """
+ for entry in self.boot_entry_list:
+ instance = SolarisODDBootInstance(self.pkg_img_path)
+ instance.title = self.boot_title + " " + entry.title_suffix
+ # The last boot entry in the list tagged as the default boot entry
+ # overrides the previous default if more than one is tagged as
+ # the default.
+ if entry.default_entry:
+ instance.default = True
+ else:
+ instance.default = False
+ instance.kargs = entry.kernel_args
+ if entry.insert_at == "start":
+ where = 0
+ else:
+ where = -1
+ self.config.add_boot_instance(instance, where)
+
+ def _parse_doc_target(self, dry_run=False):
+ """ Class method for parsing data object cache (DOC) objects for use by
+ the checkpoint.
+ """
+ try:
+ self.dc_pers_dict = self.doc.persistent.get_children(
+ name=self.DC_PERS_LABEL,
+ class_type=DataObjectDict,
+ not_found_is_err=True)[0].data_dict
+ except ObjectNotFoundError:
+ pass
+
+ self.dc_dict = self.doc.volatile.get_children(name=self.DC_LABEL,
+ class_type=DataObjectDict,
+ not_found_is_err=True)[0].data_dict
+
+ try:
+ self.pkg_img_path = self.dc_dict["pkg_img_path"]
+ except KeyError, msg:
+ raise RuntimeError("Error retrieving a value from the DOC: " + \
+ str(msg))
+
+ title_line = linecache.getline(self._get_rel_file_path(), 1)
+ self.rel_file_title = title_line.strip()
+ # Set an initial boot_title value. It can be overwritten later when
+ # parsing the BootMods tree of the DOC
+ self.boot_title = self.rel_file_title
+
+ 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)
+
+ def install_boot_loader(self, dry_run=False):
+ """ Install the boot loader and associated boot configuration files.
+ """
+ if dry_run == True:
+ temp_dir = \
+ 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.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)
+ if dry_run:
+ rmtree(temp_dir)
+
+ def _handle_iso_boot_image_type(self, config, dry_run):
+ """ Method that copies ISO El Torito and HSFS bootblockimage file types
+ to their appropriate targets.
+ """
+ # 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 not os.path.exists(src):
+ raise RuntimeError("Expected boot image type \'%s\' does not " \
+ "exist at path: %s" % (img_type, src))
+ if dry_run is True:
+ self.logger.debug("Deleting El Torito image: %s" % src)
+ os.unlink(src)
+ return
+
+ 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 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=self.DC_PERS_LABEL)
+
+ # Strip out the pkg_img_path prefix from src. 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"] = \
+ src.split(self.pkg_img_path + os.sep)[1]
+ self.doc.persistent.insert_children(
+ DataObjectDict(self.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)
+
+
+class AIISOImageBootMenu(ISOImageBootMenu):
+ """ Class for AIISOImageBootMenu checkpoint.
+ Used by Distro Constructor for creation of Automated Install ISO image.
+ """
+
+ def __init__(self, name, arg=None):
+ """ Constructor for class
+ """
+ super(AIISOImageBootMenu, self).__init__(name)
+ self.img_info_path = None
+ self.installadm_entry = None
+ if arg:
+ self.__setup(**arg)
+ else:
+ self.__setup()
+
+ def __setup(self, installadm_entry="boot image"):
+ """ Setup checkpoint with any kwargs in manifest XML
+ """
+ self.installadm_entry = installadm_entry
+
+ def build_default_entries(self):
+ """ Constructs the default boot entries and inserts them into the
+ bootConfig object's boot_instances list
+ """
+ ai_titles = [self.boot_title + " Automated Install custom",
+ self.boot_title + " Automated Install",
+ self.boot_title + " Automated Install custom ttya",
+ self.boot_title + " Automated Install custom ttyb",
+ self.boot_title + " Automated Install ttya",
+ self.boot_title + " Automated Install ttyb"]
+ ai_kargs = ["-B aimanifest=prompt",
+ None,
+ "-B install=true,aimanifest=prompt,console=ttya",
+ "-B install=true,aimanifest=prompt,console=ttyb",
+ "-B install=true,console=ttya",
+ "-B install=true,console=ttyb"]
+ for i, title in enumerate(ai_titles):
+ instance = SolarisODDBootInstance(self.pkg_img_path)
+ instance.title = title
+ # Make the first entry the default
+ if i == 0:
+ instance.default = True
+ instance.kargs = ai_kargs[i]
+ self.config.add_boot_instance(instance)
+
+ # Create a chainloader boot from HD entry
+ self._add_chainloader_entry()
+
+ def execute(self, dry_run=False):
+ """ Primary execution method used by the Checkpoint parent class
+ """
+ super(AIISOImageBootMenu, self).execute(dry_run)
+ self.update_img_info_path()
+
+ def update_img_info_path(self):
+ """ Method to write out the .img_info_path file.
+ """
+ self.img_info_path = os.path.join(self.pkg_img_path, ".image_info")
+ self.logger.info("Updating %s" % self.img_info_path)
+
+ # write out the GRUB_TITLE line
+ with open(self.img_info_path, "a+") as iip:
+ try:
+ iip.write("GRUB_TITLE=" + self.boot_title + "\n")
+ iip.write("GRUB_MIN_MEM64=%d\n" % 1000)
+ iip.write("GRUB_DO_SAFE_DEFAULT=true\n")
+ iip.write("NO_INSTALL_GRUB_TITLE=%s\n" % self.installadm_entry)
+ except IOError, msg:
+ raise RuntimeError(msg)
+
+
+class LiveCDISOImageBootMenu(ISOImageBootMenu):
+ """ Class for LiveCDISOImageBootMenu checkpoint.
+ Used by Distro Constructor for creation of LiveCD
+ ISO image.
+ """
+
+ def __init__(self, name):
+ """ Constructor for class
+ """
+ super(LiveCDISOImageBootMenu, self).__init__(name)
+
+ def build_default_entries(self):
+ """ Constructs the default boot entries and inserts them into the
+ bootConfig object's boot_instances list
+ """
+ # create lists of boot titles and kernel args to use
+ lcd_titles = [self.boot_title,
+ self.boot_title + " VESA driver",
+ self.boot_title + " text console"]
+ lcd_kargs = [None,
+ "-B livemode=vesa",
+ "-B livemode=text"]
+ for i, title in enumerate(lcd_titles):
+ instance = SolarisODDBootInstance(self.pkg_img_path)
+ instance.title = title
+ # Make the first entry the default
+ if i == 0:
+ instance.default = True
+ instance.kargs = lcd_kargs[i]
+ self.config.add_boot_instance(instance)
+
+ # Create a chainloader boot from HD entry
+ self._add_chainloader_entry()
+
+
+class TextISOImageBootMenu(ISOImageBootMenu):
+ """ Class for TextISOImageBootMenu checkpoint.
+ Used by Distro Constructor for creation of Text installer ISO image.
+ """
+
+ def __init__(self, name):
+ """ Constructor for class
+ """
+ super(TextISOImageBootMenu, self).__init__(name)
+
+ def build_default_entries(self):
+ """ Constructs the default boot entries and inserts them into the
+ bootConfig object's boot_instances list
+ """
+ instance = SolarisODDBootInstance(self.pkg_img_path)
+ instance.title = self.boot_title
+ # Make this entry the default
+ instance.default = True
+ self.config.add_boot_instance(instance)
+
+ # Create a chainloader boot from HD entry
+ self._add_chainloader_entry()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_boot/boot_spec.py Fri May 06 11:44:10 2011 +1000
@@ -0,0 +1,202 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+""" boot_spec.py -- library containing class definitions for boot DOC objects,
+ including BootMods and BootEntry.
+"""
+
+from lxml import etree
+
+from solaris_install.data_object import DataObject
+
+
+class BootMods(DataObject):
+ """ Subclass of DataObject to contain the boot_mods
+ information in the Data Object Cache.
+ """
+ BOOT_MODS_LABEL = "boot_mods"
+ TITLE_LABEL = "title"
+ TIMEOUT_LABEL = "timeout"
+
+ def __init__(self, name):
+ """ Initialize the DataObject object with name.
+ """
+ super(BootMods, self).__init__(name)
+ self.title = None
+ self.timeout = None
+
+ def __repr__(self):
+ """ String representation of a BootMods object.
+ """
+ rep = "BootMods: "
+ if self.title is not None:
+ rep += 'title="%s"; ' % self.title
+ if self.timeout is not None:
+ rep += 'timeout=%d' % self.timeout
+ return rep
+
+ def to_xml(self):
+ """ Method to create the xml boot_mods element.
+ """
+ element = etree.Element(BootMods.BOOT_MODS_LABEL)
+
+ if self.title is not None:
+ element.set(BootMods.TITLE_LABEL, self.title)
+
+ if self.timeout is not None:
+ element.set(BootMods.TIMEOUT_LABEL, str(self.timeout))
+
+ return element
+
+ @classmethod
+ def can_handle(cls, element):
+ """ Returns True if element has tag: "boot_mods".
+ Returns False otherwise.
+ """
+ if element.tag == BootMods.BOOT_MODS_LABEL:
+ return True
+ return False
+
+ @classmethod
+ def from_xml(cls, element):
+ """ Method to create the DOC boot_mods element.
+ """
+ title = element.get(BootMods.TITLE_LABEL)
+ timeout = element.get(BootMods.TIMEOUT_LABEL)
+ boot_mods = BootMods(BootMods.BOOT_MODS_LABEL)
+
+ if title is not None:
+ boot_mods.title = title
+ if timeout is not None:
+ boot_mods.timeout = int(timeout)
+
+ return boot_mods
+
+
+class BootEntry(DataObject):
+ """ Subclass of DataObject to contain a boot_entry objects
+ information in the Data Object Cache.
+ """
+ BOOT_ENTRY_LABEL = "boot_entry"
+ DEFAULT_ENTRY_LABEL = "default_entry"
+ INSERT_AT_LABEL = "insert_at"
+ TITLE_SUFFIX_LABEL = "title_suffix"
+ KERNEL_ARGS_LABEL = "kernel_args"
+
+ def __init__(self, name):
+ """ Initialize the DataObject object with name.
+ """
+ super(BootEntry, self).__init__(name)
+ self.default_entry = None
+ self.insert_at = None
+ self.title_suffix = None
+ self.kernel_args = None
+
+ def __repr__(self):
+ """ String representation of a BootEntry object.
+ """
+ rep = 'BootEntry: '
+ if self.title_suffix is not None:
+ rep += 'title_suffix="%s"; ' % self.title_suffix
+ if self.default_entry is not None:
+ rep += 'default=%s; ' % self.default_entry
+ if self.insert_at is not None:
+ rep += 'insert_at=%s; ' % self.insert_at
+ if self.kernel_args is not None:
+ rep += 'kernel_args="%s"' % self.kernel_args
+ return rep
+
+ def to_xml(self):
+ """ Method to create the xml boot_entry element.
+ """
+ element = etree.Element(BootEntry.BOOT_ENTRY_LABEL)
+
+ if self.default_entry is not None:
+ element.set(BootEntry.DEFAULT_ENTRY_LABEL,
+ (str(self.default_entry).lower()))
+ if self.insert_at is not None:
+ element.set(BootEntry.INSERT_AT_LABEL, self.insert_at)
+
+ if self.title_suffix is not None:
+ title_element = etree.SubElement(element,
+ BootEntry.TITLE_SUFFIX_LABEL)
+ title_element.text = self.title_suffix
+ if self.kernel_args is not None:
+ kargs_element = etree.SubElement(element,
+ BootEntry.KERNEL_ARGS_LABEL)
+ kargs_element.text = self.kernel_args
+
+ return element
+
+ @classmethod
+ def can_handle(cls, element):
+ """ Returns True if element has the tag "boot_entry",
+ and a sub-elemenent that has the tag: "title_suffix".
+ Returns False otherwise.
+ """
+ if element.tag != BootEntry.BOOT_ENTRY_LABEL:
+ return False
+
+ title_suffix = None
+
+ for subelement in element.iterchildren():
+ if subelement.tag == BootEntry.TITLE_SUFFIX_LABEL:
+ title_suffix = subelement.text
+
+ if title_suffix is None:
+ return False
+
+ return True
+
+ @classmethod
+ def from_xml(cls, element):
+ """ Method to create a DOC boot_entry element.
+ """
+ title_suffix = None
+ kernel_args = None
+ default_entry = element.get(BootEntry.DEFAULT_ENTRY_LABEL)
+ insert_at = element.get(BootEntry.INSERT_AT_LABEL)
+
+ for subelement in element.iterchildren():
+ if subelement.tag == BootEntry.TITLE_SUFFIX_LABEL:
+ title_suffix = subelement.text
+
+ if subelement.tag == BootEntry.KERNEL_ARGS_LABEL:
+ kernel_args = subelement.text
+
+ boot_entry = BootEntry(BootEntry.BOOT_ENTRY_LABEL)
+ if insert_at is not None:
+ boot_entry.insert_at = insert_at
+ if title_suffix is not None:
+ boot_entry.title_suffix = title_suffix
+ if kernel_args is not None:
+ boot_entry.kernel_args = kernel_args
+ if default_entry is not None:
+ if default_entry.lower() == "true":
+ boot_entry.default_entry = True
+ elif default_entry.lower() == "false":
+ boot_entry.default_entry = False
+
+ return boot_entry
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_boot/test/test_boot.py Fri May 06 11:44:10 2011 +1000
@@ -0,0 +1,646 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+""" Boot checkpoint Unit Tests
+"""
+
+import os
+import abc
+import tempfile
+import unittest
+import platform
+import pwd
+import grp
+
+from shutil import rmtree, copyfile
+from lxml import etree
+
+from bootmgmt.bootconfig import BootConfig
+from solaris_install.boot.boot import SystemBootMenu, AIISOImageBootMenu, \
+ TextISOImageBootMenu, LiveCDISOImageBootMenu, BOOT_ENV, DEVS
+from solaris_install.data_object.data_dict import DataObjectDict
+from solaris_install.engine import InstallEngine
+from solaris_install.engine.test.engine_test_utils import \
+ get_new_engine_instance, reset_engine
+from solaris_install.target.logical import be_list, BE, Filesystem
+
+TMP_TEST_DIR = "/tmp/boot-test"
+
+BOOT_MODS_XML = '''
+<root>
+ <boot_mods timeout="20" title="Boot Testing:">
+ <boot_entry default_entry="false" insert_at="end">
+ <title_suffix>2nd last Boot Entry</title_suffix>
+ <kernel_args>test_kernel_args=true</kernel_args>
+ </boot_entry>
+ <boot_entry default_entry="true" insert_at="start">
+ <title_suffix>1st (default) Boot Entry</title_suffix>
+ <kernel_args>test_kernel_args=true</kernel_args>
+ </boot_entry>
+ <boot_entry>
+ <title_suffix>Last Boot Entry</title_suffix>
+ </boot_entry>
+ <boot_entry>
+ </boot_entry>
+ </boot_mods>
+</root>'''
+
+# Physical target representation for X86 tests
+PHYS_X86_XML = \
+'''<disk whole_disk="false">
+ <disk_name name="c600d600" name_type="ctd"/>
+ <disk_prop dev_type="FIXED" dev_size="666666666"/>
+ <disk_keyword key="boot_disk"/>
+ <partition action="preserve" name="1" part_type="191">
+ <size val="66666666secs" start_sector="16065"/>
+ <slice name="0" action="preserve" force="false" in_zpool="rpool">
+ <size val="33479459secs" start_sector="16065"/>
+ </slice>
+ </partition>
+ </disk>
+ <disk whole_disk="false">
+ <disk_name name="c700d700" name_type="ctd"/>
+ <disk_prop dev_type="FIXED" dev_size="777777777"/>
+ <partition action="preserve" name="1" part_type="191">
+ <size val="33527655secs" start_sector="16065"/>
+ <slice name="0" action="preserve" force="false" in_zpool="rpool">
+ <size val="33479459secs" start_sector="16065"/>
+ </slice>
+ </partition>
+ </disk>'''
+
+# Physical target representation for SPARC tests
+PHYS_SPARC_XML = '''
+<root>
+ <disk whole_disk="false">
+ <disk_name name="c600d600" name_type="ctd"/>
+ <disk_prop dev_type="FIXED" dev_size="666666666"/>
+ <disk_keyword key="boot_disk"/>
+ <slice name="0" action="preserve" force="false" in_zpool="rpool">
+ <size val="33479459secs" start_sector="16065"/>
+ </slice>
+ </disk>
+ <disk whole_disk="false">
+ <disk_name name="c700d700" name_type="ctd"/>
+ <disk_prop dev_type="FIXED" dev_size="777777777"/>
+ <slice name="0" action="preserve" force="false" in_zpool="rpool">
+ <size val="33479459secs" start_sector="16065"/>
+ </slice>
+ </disk>
+</root>'''
+
+# This is a template logical target specification. The physical part is
+# populated with the appropriate architecture specific physical specification
+# above. The logical part is populated based on a run time inspection of the
+# live system's active BE and pool(s).
+DYNAMIC_DESIRED_XML = '''
+<root>
+<target name="desired">
+ %(phys_xml)s
+ <logical noswap="true" nodump="true">
+ <zpool name="%(rpoolname)s" action="preserve" is_root="true">
+ <be name="%(bename)s" mountpoint="%(bemount)s"/>
+ </zpool>
+ </logical>
+</target>
+</root>'''
+
+
+class BootMenuTestCaseBase(unittest.TestCase):
+ """ Abstracy base class for unit tests that doesn't run any tests of
+ its own, but sets up an environment for boot unit tests
+ """
+ __metaclass__ = abc.ABCMeta
+
+ def __init__(self, test_case_name):
+ """ Constructor for class so that architecture can be overriden if
+ necessary
+ """
+ super(BootMenuTestCaseBase, self).__init__(test_case_name)
+ self.arch = platform.processor()
+
+ def setUp(self):
+ """ Common unit test setup method for all subclasses
+ """
+ self.engine = get_new_engine_instance()
+ self.doc = InstallEngine.get_instance().data_object_cache
+ if not os.path.exists(TMP_TEST_DIR):
+ os.makedirs(TMP_TEST_DIR)
+
+ def tearDown(self):
+ """ Common unit test tear down method for all subclasses
+ """
+ if self.engine:
+ reset_engine(self.engine)
+ self.doc = None
+ self.boot_mods = None
+ self.boot_menu = None
+ self.arch = None
+
+
+class SystemBootMenuTestCase(BootMenuTestCaseBase):
+ """ Class for unit testing of SystemBootMenu class
+ """
+
+ def setUp(self):
+ """ Initialises a runtime environment for execution of
+ SystemBootMenu unit tests.
+ """
+ super(SystemBootMenuTestCase, self).setUp()
+
+ desired_dict = {}
+ # For the purposes of having a sane environment for the
+ # test case, use the active BE. This allows pybootmgt
+ # initialisation to locate all the bits it expects.
+ for self.be_name, be_pool, be_root_ds in be_list():
+ be_fs = Filesystem(be_root_ds)
+ if be_fs.get('mounted') == 'yes' and \
+ be_fs.get('mountpoint') == '/':
+ desired_dict["rpoolname"] = be_pool
+ desired_dict["rpoolmount"] = \
+ Filesystem(be_pool).get('mountpoint')
+ desired_dict["bename"] = self.be_name
+ desired_dict["bemount"] = '/'
+ break
+ if self.arch == "sparc":
+ desired_dict["phys_xml"] = PHYS_SPARC_XML
+ else:
+ desired_dict["phys_xml"] = PHYS_X86_XML
+ # Make sure the active BE was found before proceeding with the test
+ try:
+ desired_dict["bemount"]
+ except KeyError:
+ raise RuntimeError("Couldn't find active BE to use in unit test")
+ boot_mods_dom = etree.fromstring(BOOT_MODS_XML)
+ self.doc.import_from_manifest_xml(boot_mods_dom, volatile=True)
+ target_desired_dom = etree.fromstring(DYNAMIC_DESIRED_XML \
+ % desired_dict)
+ self.doc.import_from_manifest_xml(target_desired_dom, volatile=True)
+
+ # logical BE:from_xml() method ignores the mountpoint property in the
+ # XML so manually override its None value.
+ boot_env = self.doc.volatile.get_descendants(class_type=BE)[0]
+ boot_env.mountpoint = '/'
+ self.boot_menu = SystemBootMenu("Test SystemBootMenu Checkpoint")
+ # Force the arch in SystemBootMenu to match self.arch in case we
+ # are trying to override it.
+ self.boot_menu.arch = self.arch
+
+ def test_parse_doc_target(self):
+ """ Tests parsing of Target.Desired in the Data Object Cache
+ """
+ for arch in ['sparc', 'i386']:
+ self.tearDown()
+ self.arch = arch
+ self.setUp()
+
+ # Invoke target parsing method and verify it matches
+ # expected values
+ self.boot_menu._parse_doc_target(dry_run=True)
+ boot_targets = self.boot_menu.boot_target
+
+ # Check that the vtoc slice object was identified as a boot device
+ # Slice should be slice 0 belonging to disk c600d600
+ # ie. c600d700s0
+
+ boot_slice = boot_targets[DEVS][0]
+ self.failUnlessEqual(boot_slice, 'c600d600s0',
+ "%s target boot device slice object doesn't match "\
+ "manifest entry:\n" \
+ "(Manifest slice device: c600d600s0, SystemBootMenu slice " \
+ "slice device: %s)\n" % (arch, boot_slice))
+
+ # UEFI Further checks for gpt_partitions should be included here.
+
+ # Check the BE sub element of the logical node of target too
+ boot_env = boot_targets[BOOT_ENV]
+ self.failUnlessEqual(boot_env.name, self.be_name,
+ "Target boot environment object doesn't match " \
+ "manifest entry:\n" \
+ "(Manifest boot environment: \'foobar\', "
+ "SystemBootMenu boot " \
+ "environment: %s)\n" % (boot_env.name))
+
+ def test_get_progress_estimate(self):
+ """ Tests return value of the boot checkpoint's
+ get_progress_estimate() method
+ """
+ estimate = self.boot_menu.get_progress_estimate()
+ self.failUnlessEqual(estimate, 5,
+ "BootMenu:get_progress_estimate() returned unexpected " \
+ "estimate:\n(Expected estimate: \'5\', Actual estimate '%d'\n" \
+ % (estimate))
+
+ def test_build_custom_entries(self):
+ """ Unit tests custom boot menu entry creation from manifest XML
+ """
+ self.boot_menu.parse_doc()
+ self.boot_menu.init_boot_config()
+ self.boot_menu.build_custom_entries()
+
+ def test_build_default_entries(self):
+ """ Unit tests default boot menu entry creation (autogenerated)
+ """
+ self.boot_menu.parse_doc()
+ self.boot_menu.init_boot_config()
+ # Force method to construct a boot title
+ self.boot_menu.boot_title = None
+ self.boot_menu.build_default_entries()
+
+ def test_install_boot_loader(self):
+ """ Unit tests boot loader installation method
+ """
+ self.boot_menu.parse_doc()
+ self.boot_menu.init_boot_config()
+ self.boot_menu.build_default_entries()
+ self.boot_menu.install_boot_loader(dry_run=True)
+
+ def test_copy_sparc_bootlst(self):
+ """ Unit tests copying of the SPARC bootlst binary.
+ Can only be partially tested on X86.
+ """
+ if self.arch == 'sparc':
+ self.boot_menu._parse_doc()
+ self.boot_menu._copy_sparc_bootlst(dry_run=True)
+
+ def test_create_sparc_boot_menu(self):
+ """ Test creation of the SPARC menu.lst file
+ """
+ self.tearDown()
+ self.arch = 'sparc'
+ self.setUp()
+ self.boot_menu._parse_doc_target()
+ self.boot_menu._create_sparc_boot_menu(dry_run=True)
+ self.tearDown()
+
+ def test_install_sparc_bootblk(self):
+ """ Test installation of the SPARC bootblk binary via installboot(1M)
+ """
+ # Make sure the method thinks it's runnning on SPARC
+ self.tearDown()
+ self.arch = 'sparc'
+ self.setUp()
+ self.boot_menu._parse_doc_target()
+ self.boot_menu._install_sparc_bootblk(dry_run=True)
+ # Make sure it raises a RuntimeError on X86
+ self.boot_menu.arch = 'i386'
+ self.assertRaises(RuntimeError,
+ self.boot_menu._install_sparc_bootblk,
+ dry_run=True)
+
+ def test_set_sparc_prom_boot_device(self):
+ """ Tests setting of the prom boot-device variable on SPARC
+ This test is limited because reading from /dev/openprom
+ requires root priviliges which causes a non-fatal exception
+ in the called method.
+ """
+ # Make sure the method thinks it's runnning on SPARC
+ self.tearDown()
+ self.arch = 'sparc'
+ self.setUp()
+ self.boot_menu._parse_doc_target()
+ self.boot_menu._set_sparc_prom_boot_device(dry_run=True)
+
+ def test_execute(self):
+ """ Test that runs the entire checkpoint as an install application
+ would. Essentially an aggregation of the above tests.
+ """
+ self.boot_menu.execute(dry_run=True)
+
+
+class ISOBootMenuTestCase(BootMenuTestCaseBase):
+ """ Abstract unit test base class for ISO image derived classes such as
+ AI, Text and LiveCD ISO BootMenu classes.
+ Doesn't directly perform any unit tests
+ """
+ __metaclass__ = abc.ABCMeta
+
+ def setUp(self):
+ """ Initialises a runtime environment for execution of
+ ISOImageBootMenu subclassed unit tests. Currently needs
+ an X86 legacy GRUB environment.
+ """
+ super(ISOBootMenuTestCase, self).setUp()
+ temp_dir = tempfile.mkdtemp(dir=TMP_TEST_DIR,
+ prefix="install_boot_test_")
+ rel_file_dir = os.path.join(temp_dir, "etc")
+ os.makedirs(rel_file_dir)
+ 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}
+ self.temp_dir = temp_dir
+ 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
+ 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'))
+
+ def tearDown(self):
+ """ Cleans up after each unit test is executed
+ """
+ super(ISOBootMenuTestCase, self).tearDown()
+ rmtree(self.temp_dir)
+
+ def _test_build_custom_entries(self):
+ """ Implements a custom boot menu entry listtest for derived classes
+ """
+ boot_mods_dom = etree.fromstring(BOOT_MODS_XML)
+ self.doc.import_from_manifest_xml(boot_mods_dom, volatile=True)
+ self.boot_menu.parse_doc()
+ self.boot_menu.init_boot_config()
+ self.boot_menu.build_default_entries()
+ self.boot_menu.build_custom_entries()
+ config = self.boot_menu.config
+
+ sorted_entry_list = []
+ for entry in self.boot_menu.boot_entry_list:
+ if entry.insert_at == "start":
+ sorted_entry_list.insert(0, entry)
+ else:
+ sorted_entry_list.append(entry)
+
+ # Map manifest defined custom entries to their expected positions in
+ # the bootConfig objects boot_instances list and compare.
+ num_entries = len(config.boot_instances)
+ j = 0
+ for i in [0, num_entries - 2, num_entries - 1]:
+ full_title = self.boot_menu.boot_title + \
+ " " + \
+ sorted_entry_list[j].title_suffix
+ self.failUnlessEqual(full_title, config.boot_instances[i].title,
+ "Resulting BootConfig doesn't match manifest.\n\
+ Mismatched boot entry titles at entry: %d\n\
+ BootConfig Title: %s\n\
+ Manifest Title: %s\n"\
+ % (i, config.boot_instances[i].title, full_title))
+ j += 1
+
+ def _test_build_default_entries(self, titles, kargs, chainloader=True):
+ """ Implements a default menu entry unit test for sub-classes
+ """
+ # How many boot entries the TextCD ISO defines by default. Needs to
+ # be adjusted along with expected entry contents if the defaults
+ # change.
+ exp_len = len(titles)
+ if chainloader:
+ exp_len += 1
+ self.boot_menu.parse_doc()
+ self.boot_menu.init_boot_config()
+ self.boot_menu.build_default_entries()
+
+ instances = self.boot_menu.config.boot_instances
+ self.assertEqual(len(titles), len(kargs),
+ "Arguments mismatch. titles and kargs arguments must be the " \
+ "same length")
+ self.assertEqual(len(instances), exp_len,
+ "Unexpected number of boot entries in default menu:"
+ "\n\tACTUAL: %d\tExpected: %d" % (len(instances), exp_len))
+
+ # Only one element for now, but making it a list makes it easier to
+ # expand later, even if it looks silly.
+ self.boot_title = self.boot_menu.boot_title
+
+ for i, exp_title in enumerate(titles):
+ actual_title = self.boot_menu.config.boot_instances[i].title
+ self.assertEqual(actual_title,
+ exp_title,
+ "Unexpected boot entry title at index %d:" \
+ "\nACTUAL:\t%s\nEXPECTED:\t%s" \
+ % (i, actual_title, exp_title))
+ actual_kargs = self.boot_menu.config.boot_instances[i].kargs
+ exp_kargs = kargs[i]
+ self.assertEqual(actual_kargs,
+ exp_kargs,
+ "Unexpected boot entry kernel args at index %d:" \
+ "\nACTUAL:\t%s\nEXPECTED:\t%s" \
+ % (i, actual_kargs, exp_kargs))
+ # Finally, check the chainloader entry
+ if chainloader:
+ i += 1
+ actual_title = self.boot_menu.config.boot_instances[i].title
+ exp_title = "Boot from Hard Disk"
+ self.assertEqual(actual_title,
+ exp_title,
+ "Unexpected chainloader title at index %d:" \
+ "\nACTUAL:\t%s\nEXPECTED:\t%s" \
+ % (i, actual_title, exp_title))
+ actual_cinfo = self.boot_menu.config.boot_instances[i].chaininfo
+ exp_cinfo = (0, 0)
+ self.assertEqual(actual_cinfo,
+ exp_cinfo,
+ "Unexpected chainloader chaininfo at index %d:" \
+ "\nACTUAL:\t%s\nEXPECTED:\t%s" \
+ % (i, str(actual_cinfo), str(exp_cinfo)))
+
+ def _test_install_boot_loader(self):
+ """ Implements a boot loader installation unit test for sub-classes
+ """
+ boot_mods_dom = etree.fromstring(BOOT_MODS_XML)
+ self.doc.import_from_manifest_xml(boot_mods_dom, volatile=True)
+ self.boot_menu.parse_doc()
+ self.boot_menu.init_boot_config()
+ self.boot_menu.build_default_entries()
+ self.boot_menu.build_custom_entries()
+ self.boot_menu.install_boot_loader(dry_run=True)
+
+ def _test_handle_boot_config_list(self):
+ """ Implemnts a unit test to validate that returned boot config data
+ from bootmgmt is handled correctly. Used by sub-classes
+ Checks file and bios eltorito boot images for now.
+ """
+ self.boot_menu.parse_doc()
+ self.boot_menu.boot_tokens[BootConfig.TOKEN_SYSTEMROOT] = self.temp_dir
+ dst_file = "%(" + BootConfig.TOKEN_SYSTEMROOT + ")s" + "/dst-file"
+ dst_blt = "%(" + BootConfig.TOKEN_SYSTEMROOT + ")s" + \
+ "/dst-bios-eltorito-img"
+
+ # Assign matching user and group names so file ownership can be
+ # processed.
+ user = pwd.getpwuid(os.getuid())[0]
+ group = grp.getgrgid(os.getgid())[0]
+
+ # Test standard file handle type first
+ src_dir = tempfile.mkdtemp(dir=TMP_TEST_DIR, prefix="boot_config_src_")
+ src_file = os.path.join(src_dir, "src-file")
+ with open(src_file, 'w') as file_handle:
+ file_handle.write("Rhubarb")
+ boot_files = [[(BootConfig.OUTPUT_TYPE_FILE,
+ src_file, None,
+ dst_file,
+ user,
+ group,
+ 420)]]
+ self.boot_menu._handle_boot_config_list(boot_files, dry_run=False)
+ os.unlink(os.path.join(self.temp_dir, "dst-file"))
+
+ # Test BIOS eltorito image type. mkisofs(1M) requires that the
+ # eltorito image file be underneath the top level folder of the
+ # ISO filesystem tree.
+ src_blt = os.path.join(self.temp_dir, "src-bios-eltorito-img")
+ with open(src_blt, 'w') as file_handle:
+ file_handle.write("BLT")
+ boot_files = [[(BootConfig.OUTPUT_TYPE_BIOS_ELTORITO,
+ src_blt, None,
+ dst_blt,
+ user,
+ group,
+ 420)]]
+ self.boot_menu._handle_boot_config_list(boot_files, dry_run=False)
+ os.unlink(src_blt)
+
+ # Test BIOS eltorito image type with a bad src directory
+ bad_blt_dir = tempfile.mkdtemp(dir=TMP_TEST_DIR, prefix="bad_blt_")
+ src_blt = os.path.join(bad_blt_dir, "src-bios-eltorito-img")
+ with open(src_blt, 'w') as file_handle:
+ file_handle.write("BLT")
+ boot_blt = [[(BootConfig.OUTPUT_TYPE_BIOS_ELTORITO,
+ src_blt, None,
+ dst_blt,
+ user,
+ group,
+ 420)]]
+ self.assertRaises(RuntimeError,
+ self.boot_menu._handle_boot_config_list,
+ boot_config_list=boot_blt, dry_run=False)
+ # Test with an invalid file type.
+ boot_unsup = [[("DEADBEEF",
+ src_blt, None,
+ dst_blt,
+ user,
+ group,
+ 420)]]
+ self.assertRaises(RuntimeError,
+ self.boot_menu._handle_boot_config_list,
+ boot_config_list=boot_unsup, dry_run=False)
+ os.unlink(src_blt)
+ rmtree(bad_blt_dir)
+ rmtree(src_dir)
+
+
+class AIISOBootMenuTestCase(ISOBootMenuTestCase):
+ """ Unit test class for AIIsoBootMenu class
+ """
+
+ def test_build_custom_entries(self):
+ self.boot_menu = AIISOImageBootMenu("Test AI Boot Checkpoint")
+ self._test_build_custom_entries()
+
+ def test_update_img_info_path(self):
+ self.boot_menu = AIISOImageBootMenu("Test AI Boot Checkpoint")
+ boot_mods_dom = etree.fromstring(BOOT_MODS_XML)
+ self.doc.import_from_manifest_xml(boot_mods_dom, volatile=True)
+ self.boot_menu.parse_doc()
+ self.boot_menu.img_info_path = tempfile.mktemp(dir=TMP_TEST_DIR,
+ prefix="img_info_")
+ self.boot_menu.update_img_info_path()
+ os.unlink(self.boot_menu.img_info_path)
+
+ def test_install_boot_loader(self):
+ self.boot_menu = AIISOImageBootMenu("Test AIISOBootMenu Checkpoint")
+ self._test_install_boot_loader()
+
+ def test_handle_boot_config_list(self):
+ self.boot_menu = AIISOImageBootMenu("Test AIISOBootMenu Checkpoint")
+ self._test_handle_boot_config_list()
+
+ def test_build_default_entries(self):
+ """ Default menu entry unit test for AI ISO
+ """
+ self.boot_menu = AIISOImageBootMenu("Test AIISOBootMenu Checkpoint")
+ self.boot_menu.parse_doc()
+
+ # This assignment permits convenient copy and paste from the
+ # AIISOImageBootMenu checkpoint codes ai_titles and ai_kargs values
+ self.boot_title = self.boot_menu.boot_title
+ ai_titles = [self.boot_title + " Automated Install custom",
+ self.boot_title + " Automated Install",
+ self.boot_title + " Automated Install custom ttya",
+ self.boot_title + " Automated Install custom ttyb",
+ self.boot_title + " Automated Install ttya",
+ self.boot_title + " Automated Install ttyb"]
+ ai_kargs = ["-B aimanifest=prompt",
+ None,
+ "-B install=true,aimanifest=prompt,console=ttya",
+ "-B install=true,aimanifest=prompt,console=ttyb",
+ "-B install=true,console=ttya",
+ "-B install=true,console=ttyb"]
+
+ self._test_build_default_entries(ai_titles, ai_kargs)
+
+
+class LiveCDISOBootMenuTestCase(ISOBootMenuTestCase):
+
+ def test_install_boot_loader(self):
+ self.boot_menu = LiveCDISOImageBootMenu("Test LiveCD Boot Checkpoint")
+ self.boot_menu.execute(dry_run=True)
+
+ def test_build_default_entries(self):
+ """ Default menu entry unit test for LiveCD ISO
+ """
+ self.boot_menu = LiveCDISOImageBootMenu("Test LiveCD Boot Checkpoint")
+ self.boot_menu.parse_doc()
+
+ # This assignment permits convenient copy and paste from the
+ # LiveCDISOImageBootMenu checkpoint codes ai_titles and ai_kargs values
+ self.boot_title = self.boot_menu.boot_title
+ lcd_titles = [self.boot_title,
+ self.boot_title + " VESA driver",
+ self.boot_title + " text console"]
+ lcd_kargs = [None,
+ "-B livemode=vesa",
+ "-B livemode=text"]
+ self._test_build_default_entries(lcd_titles, lcd_kargs)
+
+
+class TextISOBootMenuTestCase(ISOBootMenuTestCase):
+ def test_build_default_entries(self):
+ """ Default menu entry unit test for Text ISO
+ """
+ # How many boot entries the TextCD ISO defines by default. Needs to
+ # be adjusted along with expected entry contents if the defaults
+ # change.
+ self.boot_menu = TextISOImageBootMenu("Test Text Boot Checkpoint")
+ self.boot_menu.parse_doc()
+
+ titles = [self.boot_menu.boot_title]
+ kargs = [None]
+ self._test_build_default_entries(titles, kargs)
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_boot/test/test_boot_spec.py Fri May 06 11:44:10 2011 +1000
@@ -0,0 +1,277 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+"""boot_spec Unit Tests"""
+
+import unittest
+
+from lxml import etree
+
+from solaris_install.boot.boot_spec import BootMods, BootEntry
+from solaris_install.engine import InstallEngine
+from solaris_install.engine.test.engine_test_utils import \
+ get_new_engine_instance, reset_engine
+
+
+indentation = '''\
+ '''
+
+
+class TestBootMods(unittest.TestCase):
+ """ Test case to test the from_xml() method of
+ BootMods
+ """
+
+ BOOT_MODS_XML = '''
+ <root>
+ <boot_mods timeout="20" title="Boot Testing"/>
+ </root>'''
+
+ BOOT_MODS_NOTITLE_XML = '''
+ <root>
+ <boot_mods timeout="20"/>
+ </root>'''
+
+ BOOT_MODS_NOTIMEOUT_XML = '''
+ <root>
+ <boot_mods title="Boot Testing"/>
+ </root>'''
+
+ def setUp(self):
+ """ Initialises a runtime environment for execution of
+ BootMods and BootMods unit tests.
+ """
+ self.engine = get_new_engine_instance()
+
+ def tearDown(self):
+ """ Cleans up after the previous unit test execution.
+ """
+ if self.engine is not None:
+ reset_engine(self.engine)
+ self.doc = None
+ self.boot_mods = None
+
+ def _run_manifest_parser(self, test_xml, expected_xml):
+ """ Support method to import manifest XML into the DOC and extract it
+ back out. Compares the two and raises and assertEqual exception if
+ they do not match.
+ Inputs:
+ - text_xml: The xml string representation imported into the DOC
+ - expected_xml: The xml representation out from the DOC
+ """
+ self.doc = InstallEngine.get_instance().data_object_cache
+ boot_mods_dom = etree.fromstring(test_xml)
+ self.doc.import_from_manifest_xml(boot_mods_dom, volatile=True)
+
+ boot_mods = self.doc.volatile.get_first_child(class_type=BootMods)
+ xml_str = boot_mods.get_xml_tree_str()
+ self.assertEqual(xml_str, expected_xml,
+ "Resulting XML doesn't match expected (len=%d != %d):\
+ \nGOT:\n'%s'\nEXPECTED:\n'%s'\n" %
+ (len(xml_str), len(expected_xml), xml_str, expected_xml))
+
+ def test_manifest_xml(self):
+ """ Tests the from_xml() and to_xml() methods of BootMods with
+ both timeout and title specfied.
+ """
+ expected_xml = '''\
+ <boot_mods title="Boot Testing" timeout="20"/>
+ '''.replace(indentation, "")
+
+ self._run_manifest_parser(self.BOOT_MODS_XML, expected_xml)
+
+ def test_manifest_notitle_xml(self):
+ """ Tests the from_xml() and to_xml() methods of BootMods with
+ no title specfied.
+ """
+ expected_xml = '''\
+ <boot_mods timeout="20"/>
+ '''.replace(indentation, "")
+ self._run_manifest_parser(self.BOOT_MODS_NOTITLE_XML, expected_xml)
+
+ def test_manifest_notimeout_xml(self):
+ """ Tests the from_xml() and to_xml() methods of BootMods with
+ no timeout specfied.
+ """
+ expected_xml = '''\
+ <boot_mods title="Boot Testing"/>
+ '''.replace(indentation, "")
+ self._run_manifest_parser(self.BOOT_MODS_NOTIMEOUT_XML, expected_xml)
+
+
+class TestBootEntry(unittest.TestCase):
+ """ Test case to test the from_xml() method of
+ BootMods
+ """
+ BOOT_ENTRY_XML = '''
+ <root>
+ <boot_entry>
+ <title_suffix>Basic Boot Entry</title_suffix>
+ </boot_entry>
+ </root>'''
+
+ BOOT_ENTRY_DEFAULT_XML = '''
+ <root>
+ <boot_entry default_entry="true">
+ <title_suffix>Default Boot Entry</title_suffix>
+ </boot_entry>
+ </root>'''
+
+ BOOT_ENTRY_NOTDEFAULT_XML = '''
+ <root>
+ <boot_entry default_entry="false">
+ <title_suffix>Non-Default Boot Entry</title_suffix>
+ </boot_entry>
+ </root>'''
+
+ BOOT_ENTRY_START_XML = '''
+ <root>
+ <boot_entry insert_at="start">
+ <title_suffix>First Boot Entry</title_suffix>
+ </boot_entry>
+ </root>'''
+
+ BOOT_ENTRY_END_XML = '''
+ <root>
+ <boot_entry insert_at="end">
+ <title_suffix>Last Boot Entry</title_suffix>
+ </boot_entry>
+ </root>'''
+
+ BOOT_ENTRY_KARGS_XML = '''
+ <root>
+ <boot_entry>
+ <title_suffix>Kernel Args Boot Entry</title_suffix>
+ <kernel_args>test_kernel_args=rhubarb</kernel_args>
+ </boot_entry>
+ </root>'''
+
+ def setUp(self):
+ """ Initialises a runtime environment for execution of
+ BootMods and BootMods unit tests.
+ """
+ self.engine = get_new_engine_instance()
+
+ def tearDown(self):
+ """ Cleans up after the previous unit test execution.
+ """
+ if self.engine is not None:
+ reset_engine(self.engine)
+ self.doc = None
+
+ def _run_manifest_parser(self, test_xml, expected_xml):
+ """ Support method to import manifest XML into the DOC and extract it
+ back out. Compares the two and raises and assertEqual exception if
+ they do not match.
+ Inputs:
+ - text_xml: The xml string representation imported into the DOC
+ - expected_xml: The xml representation out from the DOC
+ """
+ self.doc = InstallEngine.get_instance().data_object_cache
+ boot_entry_dom = etree.fromstring(test_xml)
+ self.doc.import_from_manifest_xml(boot_entry_dom, volatile=True)
+
+ boot_entry = self.doc.volatile.get_first_child(class_type=BootEntry)
+ xml_str = boot_entry.get_xml_tree_str()
+ self.assertEqual(xml_str, expected_xml,
+ "Resulting XML doesn't match expected (len=%d != %d):\
+ \nGOT:\n'%s'\nEXPECTED:\n'%s'\n" %
+ (len(xml_str), len(expected_xml), xml_str, expected_xml))
+
+ def test_manifest_basic_entry_xml(self):
+ """Tests the from_xml() and to_xml() with a basic barebones entry.
+ """
+ expected_xml = '''\
+ <boot_entry>
+ ..<title_suffix>Basic Boot Entry</title_suffix>
+ </boot_entry>
+ '''.replace(indentation, "").replace(".", " ")
+ self._run_manifest_parser(self.BOOT_ENTRY_XML, expected_xml)
+
+ def test_manifest_default_entry_xml(self):
+ """Tests the from_xml() and to_xml() methods of BootEntry with
+ default_entry = "true"
+ """
+
+ expected_xml = '''\
+ <boot_entry default_entry="true">
+ ..<title_suffix>Default Boot Entry</title_suffix>
+ </boot_entry>
+ '''.replace(indentation, "").replace(".", " ")
+ self._run_manifest_parser(self.BOOT_ENTRY_DEFAULT_XML, expected_xml)
+
+ def test_manifest_notdefault_entry_xml(self):
+ """Tests the from_xml() and to_xml() methods of BootEntry with
+ default_entry = "false"
+ """
+
+ expected_xml = '''\
+ <boot_entry default_entry="false">
+ ..<title_suffix>Non-Default Boot Entry</title_suffix>
+ </boot_entry>
+ '''.replace(indentation, "").replace(".", " ")
+ self._run_manifest_parser(self.BOOT_ENTRY_NOTDEFAULT_XML, expected_xml)
+
+ def test_manifest_start_entry_xml(self):
+ """Tests the from_xml() and to_xml() methods of BootEntry with
+ insert_at = "start"
+ """
+
+ expected_xml = '''\
+ <boot_entry insert_at="start">
+ ..<title_suffix>First Boot Entry</title_suffix>
+ </boot_entry>
+ '''.replace(indentation, "").replace(".", " ")
+ self._run_manifest_parser(self.BOOT_ENTRY_START_XML, expected_xml)
+
+ def test_manifest_end_entry_xml(self):
+ """Tests the from_xml() and to_xml() methods of BootEntry with
+ insert_at = "end"
+ """
+
+ expected_xml = '''\
+ <boot_entry insert_at="end">
+ ..<title_suffix>Last Boot Entry</title_suffix>
+ </boot_entry>
+ '''.replace(indentation, "").replace(".", " ")
+ self._run_manifest_parser(self.BOOT_ENTRY_END_XML, expected_xml)
+
+ def test_manifest_kargs_entry_xml(self):
+ """Tests the from_xml() and to_xml() methods of BootEntry with
+ kernel_args specified
+ """
+
+ expected_xml = '''\
+ <boot_entry>
+ ..<title_suffix>Kernel Args Boot Entry</title_suffix>
+ ..<kernel_args>test_kernel_args=rhubarb</kernel_args>
+ </boot_entry>
+ '''.replace(indentation, "").replace(".", " ")
+ self._run_manifest_parser(self.BOOT_ENTRY_KARGS_XML, expected_xml)
+
+
+if __name__ == '__main__':
+ unittest.main()
--- a/usr/src/lib/install_manifest/dtd/Makefile Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/lib/install_manifest/dtd/Makefile Fri May 06 11:44:10 2011 +1000
@@ -21,7 +21,7 @@
#
#
-# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
#
include ../../Makefile.lib
@@ -32,6 +32,7 @@
install:= TARGET= install
DTD_FILES= ai.dtd \
+ boot_mods.dtd \
dc.dtd \
configuration.dtd \
execution.dtd \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_manifest/dtd/boot_mods.dtd Fri May 06 11:44:10 2011 +1000
@@ -0,0 +1,79 @@
+<!--
+ CDDL HEADER START
+
+ The contents of this file are subject to the terms of the
+ Common Development and Distribution License (the "License").
+ You may not use this file except in compliance with the License.
+
+ You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ or http://www.opensolaris.org/os/licensing.
+ See the License for the specific language governing permissions
+ and limitations under the License.
+
+ When distributing Covered Code, include this CDDL HEADER in each
+ file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ If applicable, add the following below this CDDL HEADER, with the
+ fields enclosed by brackets "[]" replaced with your own identifying
+ information: Portions Copyright [yyyy] [name of copyright owner]
+
+ CDDL HEADER END
+
+ Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+
+-->
+
+<!--
+Boot menu modifications to be applied to the image (DC) or on the installed
+system (AI) on X86.
+
+NOTE: On SPARC, only the "title" element is supported and only then when using
+ the Automated Installer (AI), "title" will be applied to boot menu file,
+ '/boot/menu.lst'
+
+Optional attributes that can be specified here are:
+
+title - title for the specialized boot entry
+ Default is to use the first line of /etc/release
+
+timeout
+ - boot loader timeout value before the default boot entry
+ is automatically activated. Ignored on OBP boot based
+ SPARC systems
+-->
+<!ELEMENT boot_mods (boot_entry*)>
+<!ATTLIST boot_mods title CDATA #IMPLIED>
+<!ATTLIST boot_mods timeout CDATA #IMPLIED>
+
+<!--
+Boot entries to add to the default boot menu in the image (DC) or
+on the installed system (AI) on X86 systems.
+
+NOTE: This XML section is *not* supported on OBP boot based SPARC
+ systems and will be ignored.
+
+default_entry - If the boot_entry has this attribute set to "true" then it
+ will be the default boot entry activated by the boot loader.
+ Note that if more than one boot entry has this attribute set
+ to "true", the last entry defined as such will override
+ preceeding default boot_entry elements set to "true".
+
+insert_at - Optional attribute indicating the desired insertion point
+ relative to the existing list of boot entries. Valid values
+ are "start" or "end" only. If omitted the default action
+ is to append the entry to the end of the list.
+-->
+<!ELEMENT boot_entry (title_suffix, kernel_args?)>
+<!ATTLIST boot_entry default_entry (true|false) "false">
+<!ATTLIST boot_entry insert_at (start|end) "end">
+
+<!--
+title_suffix - Text string appended to this specific entry's title.
+-->
+<!ELEMENT title_suffix (#PCDATA)>
+
+<!--
+kernel_args - Optional kernel arguments passed to the kernel by the
+ boot loader.
+-->
+<!ELEMENT kernel_args (#PCDATA)>
+
--- a/usr/src/lib/install_manifest/dtd/dc.dtd Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/lib/install_manifest/dtd/dc.dtd Fri May 06 11:44:10 2011 +1000
@@ -1,5 +1,32 @@
+<!--
+ CDDL HEADER START
+
+ The contents of this file are subject to the terms of the
+ Common Development and Distribution License (the "License").
+ You may not use this file except in compliance with the License.
+
+ You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ or http://www.opensolaris.org/os/licensing.
+ See the License for the specific language governing permissions
+ and limitations under the License.
+
+ When distributing Covered Code, include this CDDL HEADER in each
+ file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ If applicable, add the following below this CDDL HEADER, with the
+ fields enclosed by brackets "[]" replaced with your own identifying
+ information: Portions Copyright [yyyy] [name of copyright owner]
+
+ CDDL HEADER END
+
+ Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+
+-->
+
<!ELEMENT dc (distro)>
+<!ENTITY % boot_mods SYSTEM "boot_mods.dtd">
+%boot_mods;
+
<!ENTITY % target SYSTEM "target.dtd">
%target;
@@ -31,17 +58,8 @@
<!ELEMENT img_params (media_im|vm_im)>
-<!ELEMENT media_im (grub_mods*, max_size?)>
-<!ELEMENT grub_mods (grub_entry*)>
-<!ATTLIST grub_mods min_mem CDATA #IMPLIED>
-<!ATTLIST grub_mods title CDATA #IMPLIED>
-<!ATTLIST grub_mods default_entry CDATA #IMPLIED>
-<!ATTLIST grub_mods timeout CDATA #IMPLIED>
+<!ELEMENT media_im (boot_mods?, max_size?)>
-<!ELEMENT grub_entry (title_suffix, line+)>
-<!ATTLIST grub_entry position CDATA #IMPLIED>
-<!ELEMENT title_suffix (#PCDATA)>
-<!ELEMENT line (#PCDATA)>
<!--
max_size refers to the maximum size an image area can get,
--- a/usr/src/pkg/manifests/install-distribution-constructor.mf Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/pkg/manifests/install-distribution-constructor.mf Fri May 06 11:44:10 2011 +1000
@@ -109,8 +109,6 @@
file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/create_iso.pyc mode=0444
file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/create_usb.py mode=0444
file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/create_usb.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/grub_setup.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/grub_setup.pyc mode=0444
file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/pre_pkg_img_mod.py mode=0444
file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/pre_pkg_img_mod.pyc mode=0444
file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/pkg_img_mod.py mode=0444
--- a/usr/src/pkg/manifests/system-library-install.mf Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/pkg/manifests/system-library-install.mf Fri May 06 11:44:10 2011 +1000
@@ -35,6 +35,7 @@
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/solaris_install
+dir path=usr/lib/python2.6/vendor-packages/solaris_install/boot
dir path=usr/lib/python2.6/vendor-packages/solaris_install/data_object
dir path=usr/lib/python2.6/vendor-packages/solaris_install/engine
dir path=usr/lib/python2.6/vendor-packages/solaris_install/ict
@@ -64,6 +65,12 @@
file path=usr/lib/python2.6/vendor-packages/solaris_install/__init__.pyc
file path=usr/lib/python2.6/vendor-packages/solaris_install/logger.py
file path=usr/lib/python2.6/vendor-packages/solaris_install/logger.pyc
+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
+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
file path=usr/lib/python2.6/vendor-packages/solaris_install/data_object/__init__.py
file path=usr/lib/python2.6/vendor-packages/solaris_install/data_object/__init__.pyc
file path=usr/lib/python2.6/vendor-packages/solaris_install/data_object/cache.py
@@ -245,6 +252,7 @@
file path=usr/lib/python2.6/vendor-packages/bootmgmt/backend/loader/sbb.py
file path=usr/lib/python2.6/vendor-packages/bootmgmt/backend/loader/sbb.pyc
file path=usr/share/install/ai.dtd mode=0444 group=sys
+file path=usr/share/install/boot_mods.dtd mode=0444 group=sys
file path=usr/share/install/configuration.dtd mode=0444 group=sys
file path=usr/share/install/dc.dtd mode=0444 group=sys
file path=usr/share/install/execution.dtd mode=0444 group=sys