usr/src/cmd/js2ai/modules/conv.py
author Mary Ding <mary.ding@oracle.com>
Thu, 28 Jun 2012 11:30:49 -0700
changeset 1732 166f53ebcc4c
parent 1603 08318748ed0a
child 1761 c4dbae7a9d80
permissions -rwxr-xr-x
7180314 usr/src/cmd/js2ai/modules/conv.py had typo packge 7057701 js2ai results does not show up under link for install_unit_tests

#!/usr/bin/python2.6
#
# 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, 2012, Oracle and/or its affiliates. All rights reserved.
#
"""Conversion routines used to Solaris 10 convert rules and profile files to
the xml format used by the Solaris installer

"""

import gettext
import itertools
import os.path
import re
import sys

import pkg.client.api as api
import pkg.client.api_errors as apx
import pkg.client.progress as progress
import os

from solaris_install.js2ai import common
from solaris_install.js2ai.common import _
from solaris_install.js2ai.common import fetch_xpath_node
from solaris_install.js2ai.common import generate_error
from solaris_install.js2ai.common import LOG_KEY_FILE, LOG_KEY_LINE_NUM
from solaris_install.js2ai.common import LVL_CONVERSION, LVL_PROCESS
from solaris_install.js2ai.common import LVL_UNSUPPORTED, LVL_WARNING
from solaris_install.js2ai.common import RULES_FILENAME
from solaris_install.js2ai.default_xml import DEFAULT_XML_EMPTY
from lxml import etree
from StringIO import StringIO

from solaris_install import PKG5_API_VERSION


# These validation patterns were taken directly from the jumpstart
# check script

# Disk Patterns
# SPARC: cwtxdysz or cxdysz (c0t0d0s0 or c0d0s0)
# x86: cwtxdy or cxdy (c0t0d0 or c0d0)

# DISK1_PATTERN covers cxtydz (c0t0d0)
DISK1_PATTERN = re.compile("c[0-9][0-9]*t[0-9][0-9]*.*d[0-9][0-9]*$")
# DISK2_PATTERN covers cxdy (c0d0)
DISK2_PATTERN = re.compile("c[0-9][0-9]*.*d[0-9][0-9]*$")

# Slice Patterns: cwtxdysz or cxdysz (c0t0d0s0 or c0d0s0)
SLICE1_PATTERN = re.compile("(c[0-9][0-9]*t[0-9][0-9]*.*d[0-9][0-9]*)s[0-7]$")
SLICE2_PATTERN = re.compile("(c[0-9][0-9]*.*d[0-9][0-9]*)s[0-7]$")

NUM_PATTERN = re.compile("[0-9][0-9]*$")
SIZE_PATTERN = re.compile("([0-9][0-9]*)([g|m]?)$")

FILESYS_ARG_PATTERN = re.compile("..*:/..*")

DEFAULT_POOL_NAME = "rpool"
DEFAULT_VDEV_NAME = "rpool_vdev"
VDEV_SUFFIX = "_vdev"

DEFAULT_SWAP_POOL_NAME = "swap_pool"

# The text form when adding a localization facet
# in a manifest
# <software name="ips" type="IPS">
#      <destination>
#        <image>
#          <facet set="false">facet.locale.*</facet>
#          ....
#
FACET_LOCALE_FORM = "facet.locale.%s"

SOFTWARE_INSTALL = "install"
SOFTWARE_UNINSTALL = "uninstall"

REDUNDANCY_NONE = "none"
REDUNDANCY_MIRROR = "mirror"

DEVICE_ANY = "any"
SIZE_ALL = "all"
SIZE_AUTO = "auto"
SIZE_DELETE = "delete"
SIZE_EXISTING = "existing"
SIZE_FREE = "free"
SIZE_MAXFREE = "maxfree"

PARTITIONING_DEFAULT = "default"
PARTITIONING_EXPLICIT = "explicit"

# Prefix for keyword operations like fdisk that specify the <device> as
# rootdisk.
#
# fdisk rootdisk <type> <size>
#
PREFIX_ROOTDISK = "rootdisk"


# Prefix for keyword operations like filesys that specify the <slice> as
# a rootdisk slice.
#
# filesys rootdisk.s1 10000 swap
#
PREFIX_ROOTDISK_DOT = "rootdisk."

FILESYS_DEFAULT_MOUNT_POINT = "unnamed"

# Follow the same name scheme used for mirror pools using the old jumpstart
# scripts.  When not specified mirror names start with the letter "d" followed
# by a number between 0 and 127
DEFAULT_MIRROR_POOL_NAME = "d"


class XMLRuleData(object):
    """This object holds all the data read in from the rules file.  This data
    is converted into an xml document which then can be manipluated as needed.

    """

    def __init__(self, rule_dict, report):
        """Initialize the object

        Arguments:
        rule_dict - a dictionary containing the key values pairs read
                in from the rule file
        report - the error report

        """
        self._root = None
        self._report = report
        self.rule_dict = rule_dict
        self._extra_log_params = {LOG_KEY_FILE: RULES_FILENAME,
                                  LOG_KEY_LINE_NUM: 0}
        self.__process_rule()

    def __gen_err(self, lvl, message):
        """Log the specified error message at the specified level and
           increment the error count associated with that log level in
           the conversion report by 1

        """
        generate_error(lvl, self._report, message, self._extra_log_params)

    @property
    def report(self):
        """Conversion report associated with the object"""
        return self._report

    @property
    def root(self):
        """The xml root element"""
        return self._root

    def __unsupported_keyword(self, keyword, values):
        """Generate an unsupported keyword error message"""
        self.__gen_err(LVL_UNSUPPORTED,
                       _("unsupported keyword: %(key)s") % {"key": keyword})

    def __unsupported_negation(self):
        """Generate an unsupported negation error message"""
        self.__gen_err(LVL_UNSUPPORTED,
                       _("negation '!' not supported in manifests"))

    def __invalid_syntax(self, keyword):
        """Generate an invalid syntax error message"""
        self.__gen_err(LVL_PROCESS,
                       _("invalid syntax for keyword '%(key)s' "
                         "specified") % {"key": keyword})

    def __convert_arch(self, keyword, values):
        """Converts arch keyword and value from a line in the
        rules file into the xml format for outputting into the
        criteria file.

        """
        if len(values) != 1:
            self.__invalid_syntax(keyword)
            return
        if values[0] in ["sun4c", "sun4d", "sun4m"]:
            self.__gen_err(LVL_UNSUPPORTED,
                           _("Solaris 11 does not support the specified "
                             "arch '%(arch)s'") % {"arch": values[0]})
            return
        if values[0] == "sun4u":
            self.__gen_err(LVL_WARNING,
                           _("only a limited set of sun4u hardware is "
                             "supported by Solaris 11.  Consult the Solaris "
                             "11 documentation to ensure that the hardware "
                             "you wish to install on is supported"))
        self.__convert_common(keyword, values)

    def __convert_common(self, keyword, values):
        """Converts the specified keyword and value from a line in the
        rules file into the xml format for outputting into the
        criteria file.

        """
        if len(values) != 1:
            self.__invalid_syntax(keyword)
            return
        criteria_name = etree.SubElement(self._root,
                                         common.ELEMENT_AI_CRITERIA)
        try:
            criteria_name.set(common.ATTRIBUTE_NAME,
                              self.rule_keywd_conv_dict[keyword])
        except KeyError:
            self.__unsupported_keyword(keyword, values)
            # since we've already added an element to the tree we need to
            # cleanup that element due to the exception.
            self._root.remove(criteria_name)
            return
        crit_value = etree.SubElement(criteria_name, common.ELEMENT_VALUE)
        crit_value.text = values[0]

    def __convert_memsize(self, keyword, values):
        """Converts memsize value from the form 'value1-value2' to
        'value1 value2' and outputs the range node to the criteria file

        """
        if len(values) != 1:
            self.__invalid_syntax(keyword)
            return
        criteria_name = etree.SubElement(self._root,
                                         common.ELEMENT_AI_CRITERIA)
        criteria_name.set(common.ATTRIBUTE_NAME, "mem")
        if "-" in values[0]:
            crit_range = etree.SubElement(criteria_name, common.ELEMENT_RANGE)
            crit_range.text = values[0].replace("-", " ")
        else:
            crit_value = etree.SubElement(criteria_name, common.ELEMENT_VALUE)
            crit_value.text = values[0]

    def __convert_network(self, keyword, values):
        """Converts the network keyword and value from a line in the
        rules file into the xml format for outputting into the
        criteria file.

        """
        if len(values) != 1:
            self.__invalid_syntax(keyword)
            return
        criteria_name = etree.SubElement(self._root,
                                         common.ELEMENT_AI_CRITERIA)
        criteria_name.set(common.ATTRIBUTE_NAME, "ipv4")
        try:
            addr_a, addr_b, addr_c, _remainder = values[0].split(".", 3)
        except ValueError:
            self.__invalid_syntax(keyword)
            self._root.remove(criteria_name)
            # since we've already added an element to the tree we need to
            # cleanup that element due to the exception.
            return

        crit_range = etree.SubElement(criteria_name, common.ELEMENT_RANGE)
        net_range = ("%s %s.%s.%s.255") % (values[0], addr_a, addr_b, addr_c)
        crit_range.text = net_range

    def __process_rule(self):
        """Process the rules dictionary entries and convert to an xml doc"""
        if self.rule_dict is None:
            # There's nothing to convert.  This is a valid condition
            # for example if the file couldn't be read
            self._report.conversion_errors = 0
            self._report.unsupported_items = 0
            self._root = None
            return

        if self._root is not None:
            return

        self._root = etree.Element(common.ELEMENT_AI_CRITERIA_MANIFEST)

        key_dict = self.rule_dict.key_values_dict
        for key_values in key_dict.iterkeys():
            keyword = key_dict[key_values].key
            values = key_dict[key_values].values
            line_num = key_dict[key_values].line_num
            if line_num is None or values is None or keyword is None:
                raise ValueError
            self._extra_log_params[LOG_KEY_LINE_NUM] = line_num
            if "!" in keyword:
                self.__unsupported_negation()
                continue
            try:
                function_to_call = self.rule_conversion_dict[keyword]
            except KeyError:
                self.__unsupported_keyword(keyword, values)
            else:
                function_to_call(self, keyword, values)
        children = list(self._root)
        if len(children) == 0:
            self._root = None

    rule_conversion_dict = {
        "any": __unsupported_keyword,
        "arch": __convert_arch,
        "disksize": __unsupported_keyword,
        "domainname": __unsupported_keyword,
        "hostaddress": __convert_common,
        "hostname": __unsupported_keyword,
        "installed": __unsupported_keyword,
        "karch": __convert_arch,
        "memsize": __convert_memsize,
        "model": __convert_common,
        "network": __convert_network,
        "osname": __unsupported_keyword,
        "probe": __unsupported_keyword,
        "totaldisk": __unsupported_keyword,
        }

    rule_keywd_conv_dict = {
        "arch": "cpu",
        "hostaddress": "ipv4",
        "karch": "arch",
        "model": "platform",
        }


class XMLProfileData(object):
    """This object takes the profile data and converts it to an xml document"""

    def __init__(self, name, prof_dict, report, default_xml, local):
        """Initialize the object

        Arguments:
        name - the name of the profile
        prof_dict - a dictionary containing the key values pairs read
                in from the profile
        report - the error report
        default_xml - the XMLDefaultData object containing the xml tree
                hierachy that the prof_dict data will be merged into
        local - boolean flag for where package name looks is local only
        """
        self.profile_name = name
        self._report = report
        if default_xml is None:
            default_tree = etree.parse(StringIO(DEFAULT_XML_EMPTY))
        else:
            default_tree = common.tree_copy(default_xml.tree)
        self._tree = default_tree
        self._root = self._tree.getroot()

        xpath = "/auto_install/ai_instance"
        self._ai_instance = fetch_xpath_node(self._tree, xpath)
        if self._ai_instance is None:
            tree = etree.parse(StringIO(DEFAULT_XML_EMPTY))
            sys.stderr.write(etree.tostring(self._tree, pretty_print=True))
            expected_layout = etree.tostring(tree, pretty_print=True)
            raise ValueError(_("<ai_instance> node not found. "
                               "%(filename)s does not conform to the expected "
                               "layout of:\n\n%(layout)s") %
                               {"filename": default_xml.name, \
                                "layout": expected_layout})

        self._extra_log_params = {LOG_KEY_FILE: self.profile_name,
                                  LOG_KEY_LINE_NUM: 0}
        self._target = None
        self._image_node = None
        self._local = local
        self.inst_type = "ips"
        self.prof_dict = prof_dict
        self._partitioning = None
        self._usedisk = list()
        self._boot_device = None
        self._root_device = None
        #
        # _rootdisk will be used to hold to Jumpstart rootdisk keyword.
        # We won't be able to determine this automatically but we can follow
        # the initial priority rules as outlined in the
        # How JumpStart Determines a System's Root Disk (Initial Installation)
        #
        # 1. If the root_device keyword is specified in the profile, the
        #    JumpStart program sets rootdisk to the root device.
        # 2. If rootdisk is not set and the boot_device keyword is specified
        #    in the profile, the JumpStart program sets rootdisk to the boot
        #    device.
        # 3. If rootdisk is not set and a filesys cwtxdysz size / entry is
        #    specified in the profile, the JumpStart program sets rootdisk to
        #    the disk that is specified in the entry.
        #
        self._rootdisk = None
        self._rootdisk_set_by_keyword = None
        self._rootdisk_size = SIZE_ALL
        self._root_pool = None
        self._root_pool_create_via_keyword = None
        self._root_pool_name = DEFAULT_POOL_NAME
        self._arch = common.ARCH_GENERIC
        self._ai_instance.set(common.ATTRIBUTE_NAME, self.profile_name)
        self.__process_profile()

    def __gen_err(self, lvl, message):
        """Log the specified error message at the specified level and
           increment the error count associated with that log level in
           the conversion report by 1

        """
        generate_error(lvl, self._report, message, self._extra_log_params)

    def __device_name_conversion(self, device):
        """Takes a device and if that device is a slice specified device
        removes the slice portion of the device and returns the new device
        name.  If the device passed in is not a slice device it returns the
        original device passed in

        """
        match_pattern = SLICE1_PATTERN.match(device)
        if match_pattern:
            device = match_pattern.group(1)
        else:
            match_pattern = SLICE2_PATTERN.match(device)
            if match_pattern:
                device = match_pattern.group(1)
        return device

    def __duplicate_keyword(self, keyword):
        """Log a duplicate keyword error and add a process error to report"""
        self.__gen_err(LVL_PROCESS,
                       _("invalid entry, duplicate keyword encountered: "
                         "%(key)s") % {"key": keyword})

    def __is_valid_device_name(self, device):
        """ Validate the disk name based on the regexp used by the check script

         The disk name is either cXtXdX or cXdX
         For world wide name(MPXIO), disk is of the form
         cXtX[combination of alpha numeric characters]dX
         c[0-9][0-9]*t[0-9][0-9]*.*d[0-9][0-9]*$
         c[0-9][0-9]*.*d[0-9][0-9]*$

         Returns: True if valid, False otherwise

         """
        match_pattern = DISK1_PATTERN.match(device)
        if not match_pattern:
            match_pattern = DISK2_PATTERN.match(device)
            if not match_pattern:
                return False
        return True

    def __is_valid_device(self, device):
        """ Validate the disk name based on the regexp used by the check script

        A valid device is a string that is either a) a
        string that starts with /dev/dsk/ and ends with a
        valid slice name, or b) a valid slice name.

        Returns: True if valid, False otherwise

        """
        if device.startswith("/dev/dsk"):
            device = os.path.basename(device)
        return self.__is_valid_slice(device)

    def __is_valid_slice(self, slice_name):
        """Validate the slice name based on the regexp used by the check script

        The slice name is either cXtXdXsX or cXdXsX
        For world wide name(MPXIO), disk is of the form
        cXtX[combination of alpha numeric characters]dXsX

        Returns: True if valid, False otherwise

        """
        if not SLICE1_PATTERN.match(slice_name):
            if not SLICE2_PATTERN.match(slice_name):
                return False
        return True

    def __is_valid_mirror(self, keyword, devices, size,
                          root_conflict_check):
        """Check the devices that will make up the mirror to ensure there
        are no conflicts

        Arguments:
        line_num - the line being processed
        keyword - the keyword being processed
        devices - list of devices
        size - the size to assign to the mirror
        root_conflict_check - perform check to see if devices conflict with
                root_disk setting.  This should only be done if creating
                a root pool mirror
        """
        if DEVICE_ANY in devices:
            self.__gen_err(LVL_UNSUPPORTED,
                           _("use of the device entry 'any' is not "
                             "supported when a mirrored %(keyword)s is "
                             "specified") % {"keyword": keyword})
            return
        unique = set(devices)
        if len(unique) != len(devices):
            self.__gen_err(LVL_CONVERSION,
                           _("invalid syntax: mirror devices are not unique"))
            return False
        if size == SIZE_ALL:
            # When the size is all (entire disk) the underlining disk
            # associated with each slice must be unique
            unique = []
            for disk_slice in devices:
                disk, _slice_num = disk_slice.split("s")
                if disk in unique:
                    self.__gen_err(LVL_CONVERSION,
                                   _("invalid syntax: duplicate device "
                                     "%(disk)s found, underlying devices for "
                                     "mirror be different when a size of "
                                     "'all' is specified.") % {"disk": disk})
                    return False
                unique.append(disk)
        if root_conflict_check:
            for device in devices:
                # Check for conflict.
                if self.__rootdisk_slice_conflict_check(keyword, device):
                    # We've got a conditions like
                    #
                    # root_device cxtxdxs1
                    # pool newpool auto auto auto mirror cxtxdxs0 cxtxdxs1
                    #
                    # Warning message has been outputed
                    break
        return True

    @property
    def conversion_report(self):
        """Return the converstion report associated with this object"""
        return self._report

    @property
    def architecture(self):
        """Return the architecture for this profile.  A value of NONE
        indicates the architecture is unknown.  If known a value of
        common.ARCH_X86 or common.ARCH_SPARC will be returned"""
        return self._arch

    def __change_arch(self, arch):
        """Change the architecture setting that this profile is
           being generated for.  Check for the one possible conflict
           condition and update error report appropriately.  Returns True
           if change represents no conflict, False otherwise

        """
        if self._arch == common.ARCH_GENERIC:
            self._arch = arch
        elif arch == self._arch:
            # Already set
            return True
        else:
            # Error we've got a profile that is mixing x86 and sparc syntax
            # There only one way this can happen.
            self.__gen_err(LVL_CONVERSION,
                           _("architecuture conflict detected. fdisk is an "
                             "x86 only keyword operation. This conflicts "
                             "with 'boot_device %(dev)s' which was "
                             "specified using the SPARC device syntax "
                             "instead of the x86 device syntax of cwtxdy "
                             "or cxdy") % {"dev": self._boot_device})
            return False
        return True

    def __invalid_syntax(self, keyword):
        """Generate invalid keyword error"""
        self.__gen_err(LVL_PROCESS,
                       _("invalid syntax for keyword '%(key)s' specified") % \
                         {"key": keyword})

    def __unsupported_keyword(self, keyword, values):
        """Generate unsupported keyword error"""
        self.__gen_err(LVL_UNSUPPORTED,
                       _("unsupported keyword: %(key)s") % {"key": keyword})

    def __unsupported_value(self, keyword, value):
        """Generate unsupported value error"""
        self.__gen_err(LVL_UNSUPPORTED,
                        _("unsupported value for '%(key)s' specified: "
                          "%(val)s ") % {"val": value, "key": keyword})

    def __unsupported_syntax(self, keyword, msg):
        """Generate unsupported syntax error"""
        self.__gen_err(LVL_UNSUPPORTED,
                       _("unsupported syntax for '%(key)s' specified: "
                         "%(msg)s") % {"key": keyword, "msg": msg})

    def __root_pool_exists(self, keyword):
        """Check whether root pool has been created and generate error if the
        root pool already exists.  If exists adds a conversion error to report.
        Return True if exists, False otherwise

        """
        if self._root_pool is not None:
            # If the root pool already exists we reject the entry
            self.__gen_err(LVL_CONVERSION,
                           _("the ZFS root pool was already created using "
                             "the '%(created_by)s' keyword, ignoring "
                             "'%(keyword)s' definition") % \
                             {"keyword": keyword,
                              "created_by":
                              self._root_pool_create_via_keyword})
            return True
        return False

    def __create_logical(self, noswap, nodump):
        """Create the <logical noswap=$noswap nodump=$nodump> node"""
        logical = etree.SubElement(self._target,
                                         common.ELEMENT_LOGICAL)
        logical.set(common.ATTRIBUTE_NOSWAP, noswap)
        logical.set(common.ATTRIBUTE_NODUMP, nodump)
        return logical

    def __create_root_pool(self, created_by_keyword,
                           pool_name=DEFAULT_POOL_NAME,
                           noswap="false", nodump="true"):
        """Tests to see if a root pool currently exists.  If it exists a
        the existing root pool is returned. If no root pool exists the
        pool will be created with the specified pool name.

        Arguments:
        created_by_keyword - the keyword to associate with the creation of
                   the root pool.  This will be used in the error generation
                   for root pool already exists messages
        pool_name - the name to assign to the root pool
        noswap - noswap for logical component zpool resides in.
                 Expected value is "true"/"false"
        nodump - nodump for logical component zpool resides in.
                 Expected value is "true"/"false"

        """
        if self._root_pool is not None:
            return self._root_pool

        if self._target is None:
            self._target = etree.Element(common.ELEMENT_TARGET)
            self._ai_instance.insert(0, self._target)
        logical = self._target.find(common.ELEMENT_LOGICAL)
        if logical is None:
            logical = self.__create_logical(noswap, nodump)
        self._root_pool = self.__create_zfs_pool(pool_name)
        self._root_pool.set(common.ATTRIBUTE_IS_ROOT, "true")
        self._root_pool_create_via_keyword = created_by_keyword
        return self._root_pool

    def __create_vdev(self, parent, redundancy="none", name=DEFAULT_VDEV_NAME):
        """Tests to see if a vdev with the specified name currently exists.
        If it exists the existing vdev is returned. If no vdev exists the
        vdev will be created with the specified name and redundancy.

        Arguments:
        parent - the parent node of the vdev to create
        name - the name of the vdev
        redundancy - the vdev redundancy

        """
        xpath = "./vdev[@name='%s']" % name
        vdev = fetch_xpath_node(parent, xpath)
        if vdev is None:
            vdev = etree.SubElement(parent, common.ELEMENT_VDEV)
        vdev.set(common.ATTRIBUTE_NAME, name)
        vdev.set(common.ATTRIBUTE_REDUNDANCY, redundancy)

    def __create_zvol(self, parent, name, use, zvol_size):
        """Creates a zvol with the specifed name, size, and use.

        Arguments:
        parent - the parent node to create the vzol under
        name - the name of the vpool to create
        use - the use to specify for the zvol
        vpool_size - the size for the zvol.  Size should include measurement
                like mb, gb, etc

        """
        vpool = etree.SubElement(parent, common.ELEMENT_ZVOL)
        vpool.set(common.ATTRIBUTE_NAME, name)
        vpool.set(common.ATTRIBUTE_USE, use)
        size = etree.SubElement(vpool, common.ELEMENT_SIZE)
        size.set(common.ATTRIBUTE_VAL, zvol_size)

        # Depending on the use specified adjust the parents parent nodes
        # (<logical> node) attributes to indicate that a swap or dump
        # volume is available
        logical = parent.getparent()
        if logical is not None:
            if use == "swap":
                logical.set(common.ATTRIBUTE_NOSWAP, "false")
            elif use == "dump":
                logical.set(common.ATTRIBUTE_NODUMP, "false")

    def __create_zfs_pool(self, pool_name):
        """Create the <zpool> xml structure"""

        logical = self._target.find(common.ELEMENT_LOGICAL)
        if logical is None:
            logical = self.__create_logical(noswap="false", nodump="true")

        zpool = etree.SubElement(logical, common.ELEMENT_ZPOOL)
        zpool.set(common.ATTRIBUTE_NAME, pool_name)
        return zpool

    def __create_disk_node(self, disk_name, whole_disk):
        """Create the <disk><disk_name></disk> structure used to represent a
           disk in the system

           <disk whole_disk="true">
               <disk_name name="${disk_name}"/>
           </disk>

        """
        disk = etree.Element(common.ELEMENT_DISK)
        self._target.insert(0, disk)
        diskname_node = etree.SubElement(disk, common.ELEMENT_DISK_NAME)
        diskname_node.set(common.ATTRIBUTE_NAME, disk_name)
        diskname_node.set(common.ATTRIBUTE_NAME_TYPE, "ctd")
        if whole_disk:
            disk.set(common.ATTRIBUTE_WHOLE_DISK, "true")
        return disk

    def __add_device(self, device, size=None,
                     in_pool=DEFAULT_POOL_NAME,
                     in_vdev=DEFAULT_VDEV_NAME, is_swap=None):
        """Added device to the target xml hierachy

           Arguments:
           device - the device/slice to add.  "any" may be specified
           size = #size or all or none
           in_pool - the root pool to associate the device with if any.
           in_vdev - the vdev the the device is placed in
           is_swap - Indicates the device is a non ZFS swap device
                     May not be mixed with in_pool or in_vdev

        """
        if device == DEVICE_ANY:
            # We don't generate any structure for any
            # This tells the AI to automatically discover the root disk to use
            return
        if size == SIZE_ALL:
            delete_existing = True
        else:
            delete_existing = False
        try:
            disk_name, slice_num = device.split("s")
        except ValueError:
            disk_name = device
            # For Solaris 11 we default to s0 if no slice is specified
            slice_num = "0"
            delete_existing = True

        # Check to make sure that the device we are adding isn't in the
        # usedisk list.  If it is remove it so we don't try to use it later
        if self._usedisk.count(disk_name):
            self._usedisk.remove(disk_name)
            if self._partitioning == PARTITIONING_DEFAULT:
                delete_existing = True

        disk_node = self.__fetch_disk_node(disk_name)
        if disk_node is None:
            if self._arch == common.ARCH_GENERIC:
                # The architecture of the manifest represented by the xml tree
                # associated with this object is currently set as GENERIC.
                # The Jumpstart profile operation (via keyword) now being
                # processed cannot be performed in a generic fashion.  As such
                # when completed it will be necessary to generate 2 different
                # manifests.  One for SPARC and one for x86.  We accomplish
                # this by setting the _arch flag to None and then internally
                # generating the manifest as an x86 tree. The None value for
                # architecture returned via conv.arch() tells the caller
                # (in this case, __init__.py convert_profile()) that a call
                # to fetch both trees (x86, SPARC) via fetch_tree(arch)
                # will be necessary
                if not self.__change_arch(None):
                    return

            disk = self.__create_disk_node(disk_name, delete_existing)
            if self._arch == common.ARCH_SPARC:
                slice_parent_node = disk
            else:
                slice_parent_node = self.__add_partition(disk)
        else:
            if self._arch in [None, common.ARCH_X86]:
                # We return the partition
                xpath = "./partition[@action='create'][@part_type='191']"
                slice_parent_node = fetch_xpath_node(disk_node, xpath)
            else:
                slice_parent_node = disk_node
            if not self.__is_valid_to_add_slice(device, size):
                return
        self.__add_slice(slice_parent_node, slice_num, "create", size,
                         in_pool, in_vdev, is_swap)

    def __slice_exists(self, slice_node_parent,
                            device, slice_num):
        """Checks whether the specified slice can be added to structure
        If the slice already exists and error will be outputed

        """
        xpath = "./slice[@name='%s'][@action='create']" % slice_num
        slice_node = fetch_xpath_node(slice_node_parent, xpath)
        if slice_node is not None:
            # Slice already exists
            self.__gen_err(LVL_CONVERSION,
                           _("%(device)ss%(slice)s already exists") %
                             {"device": device, "slice": slice_num})
            return True
        return False

    def __is_valid_to_add_slice(self, device, size):
        """Perform some basic checks to prevent an invalid manifest from
        being generated.

        """
        # if device doesn't contain slice information 
        # return false
        if not 's' in device:
            return False
        disk_name, slice_num = device.split("s")
        diskname_node = self.__fetch_diskname_node(disk_name)
        if diskname_node is None:
            return True

        if self._arch is None or self._arch == common.ARCH_X86:
            # partition node node is only present on x86
            slice_node_parent = \
                diskname_node.getparent().find(common.ELEMENT_PARTITION)
            if slice_node_parent is None:
                return True
        else:
            slice_node_parent = diskname_node

        if self.__slice_exists(slice_node_parent,
                               disk_name, slice_num):
            return False

        # 2. If we are creating adding a slice to a disk with an
        #    existing slice make sure the slice size specification is
        #    compatible with the existing slice definition.  All though
        #    there may be multiple slices the 1st slice will give us
        #    all the data we need

        xpath = "./slice[@action='create']"
        slice_node = fetch_xpath_node(slice_node_parent, xpath)
        if slice_node is not None:
            size_node = slice_node.find(common.ELEMENT_SIZE)
            if  size_node is None:
                if size not in [SIZE_AUTO, SIZE_ALL, None]:
                    self.__gen_err(LVL_CONVERSION,
                                   _("can not create %(device)ss%(slice1)s. "
                                     "Conflicts with %(device)ss%(slice2)s "
                                     "that was created earlier via keyword "
                                     "'%(rp_kw)s' without a specified "
                                     "numeric size.") %
                                     {"device": disk_name,
                                      "slice1": slice_num,
                                      "slice2":
                                      slice_node.get(common.ATTRIBUTE_NAME),
                                      "rp_kw":
                                      self._root_pool_create_via_keyword})
                    return False
            elif size in [SIZE_AUTO, SIZE_ALL, None]:
                self.__gen_err(LVL_CONVERSION,
                               _("can not create %(device)ss%(slice1)s"
                                 " with a size of '%(size)s'. "
                                 "Conflicts with %(device)ss%(slice2)s "
                                 "that was created earlier via keyword "
                                 "'%(rp_kw)s' with a size of "
                                 "%(rp_size)s.") %
                                 {"device": disk_name,
                                  "slice1": slice_num,
                                  "slice2":
                                  slice_node.get(common.ATTRIBUTE_NAME),
                                  "size": size,
                                  "rp_kw": self._root_pool_create_via_keyword,
                                  "rp_size":
                                  size_node.get(common.ATTRIBUTE_VAL)})
                return False
        return True

    def __add_slice(self, parent, slice_num, action, size=None, in_pool=None,
                    in_vdev=None, is_swap=None):
        """Add the <slice> node with the specified attributes as a child
        of parent

        """
        # We can't mix in_pool or in_vdev with is_swap.  is_swap is only
        # for non ZFS swap
        if (in_vdev or in_pool) and is_swap:
            raise ValueError(_("is_swap can not be mixed with in_vdev or "
                               "in_pool"))
        slice_node = etree.SubElement(parent, common.ELEMENT_SLICE)
        slice_node.set(common.ATTRIBUTE_NAME, slice_num)
        slice_node.set(common.ATTRIBUTE_ACTION, action)
        slice_node.set(common.ATTRIBUTE_FORCE, "true")
        if in_pool is not None:
            slice_node.set(common.ATTRIBUTE_IN_ZPOOL, in_pool)
        if in_vdev is not None:
            slice_node.set(common.ATTRIBUTE_IN_VDEV, in_vdev)
        if is_swap is not None:
            slice_node.set(common.ATTRIBUTE_IS_SWAP, is_swap)
        if size not in [None, SIZE_AUTO, SIZE_ALL]:
            size_node = etree.SubElement(slice_node,
                                         common.ELEMENT_SIZE)
            size_node.set(common.ATTRIBUTE_VAL, size)

    def __add_partition(self, parent, name="1", part_type="191",
                        action="create"):
        """Add <partitition> node as a child of parent"""
        partition_node = parent.find(common.ELEMENT_PARTITION)
        if partition_node is None:
            partition_node = etree.SubElement(parent, common.ELEMENT_PARTITION)
            partition_node.set(common.ATTRIBUTE_ACTION, action)
            partition_node.set(common.ATTRIBUTE_NAME, name)
            partition_node.set(common.ATTRIBUTE_PART_TYPE, part_type)
        return partition_node

    def __fetch_solaris_software_node(self):
        """Fetch the software publisher node instance in the xml tree"""
        xpath = "./software[@type='IPS']/source/publisher[@name='solaris']"
        publisher = fetch_xpath_node(self._ai_instance, xpath)
        if publisher is not None:
            # We found the proper node
            # Return handle to software node
            return publisher.getparent().getparent()

        # Check to see if there is an software node with a publisher
        # If no publisher is specified it will default to solaris

        for software in self._ai_instance.findall(common.ELEMENT_SOFTWARE):
            if not software.find(common.ELEMENT_SOURCE):
                # We found our match
                return software

        # No match found create it
        software = etree.SubElement(self._ai_instance,
                                    common.ELEMENT_SOFTWARE)
        software.set(common.ATTRIBUTE_TYPE, "IPS")
        return software

    def __add_software_data(self, package, action):
        """Create a <name>$package</name> node and add it to the existing
        <software_data> node for the specified action.  If the node does not
        exist, create it and add it as a child of the specified parent node.

        Arguments:
        package - the name of the package element to add as a
            <name>$package</name> child node of <software_data>
        action - install or uninstall the package

        """
        orig_pwd = os.getcwd()
        prog_tracker = progress.CommandLineProgressTracker()
        api_inst = api.ImageInterface("/", PKG5_API_VERSION,
                                      prog_tracker, False, "js2ai")
        pkg_query = ":legacy:legacy_pkg:" + package
        query = [api.Query(pkg_query, False, True)]
        gettext.install("pkg", "/usr/share/locale")
        search_remote = api_inst.remote_search(query, servers=None,
                                               prune_versions=True)
        search_local = api_inst.local_search(query)
        pkg_name = None
        # Remote search is the default since this will often have a more
        # complete package catalog than that on an installed system.
        if not self._local:
            try:
                pkg_name = self.__do_pkg_search(search_remote)
            except Exception:
                # setting local so we'll retry with the local search
                self._local = True
                pkg_name = None
            if pkg_name != None:
                package = pkg_name

        if self._local and not pkg_name:
            try:
                pkg_name = self.__do_pkg_search(search_local)
            except Exception, msg:
                self.__gen_err(LVL_CONVERSION,
                               _("package name translation failed for "
                                 "'%(package)s': %(message)s") % \
                                 {"package": package, \
                                  "message": msg})
            if pkg_name != None:
                package = pkg_name

        # Because the pkg api call can change the working directory we need to
        # set it back to it's original directory.
        os.chdir(orig_pwd)

        software = self.__fetch_solaris_software_node()
        if pkg_name not in ["SUNWcs", "SUNWcsd"]:
            package = "pkg:/" + package
        xpath = "./software_data[@action='%s']"
        software_uninstall = fetch_xpath_node(software,
                                              xpath % SOFTWARE_UNINSTALL)
        software_install = fetch_xpath_node(software,
                                            xpath % SOFTWARE_INSTALL)

        # If we are doing an install and the package is listed as
        # in the uninstall list we want to remove it.  Vic version if we
        # are doing an uninstall
        if action == SOFTWARE_INSTALL:
            search = software_uninstall
        else:
            search = software_install

        if search is not None:
            match_node = None
            for child in search:
                if child.text == package:
                    # Found an entry
                    match_node = child
                    break
            if match_node is not None:
                # Remove the entry we found
                search.remove(match_node)

        if action == SOFTWARE_INSTALL:
            software_data = software_install
        else:
            software_data = software_uninstall

        if software_data is None:
            software_data = etree.SubElement(software,
                                             common.ELEMENT_SOFTWARE_DATA,
                                             action=action)

        name = etree.SubElement(software_data, common.ELEMENT_NAME)
        name.text = package

    def __do_pkg_search(self, search):
        """Grab the new package name returned from the ipkg search"""
        pkg_name = None
        search_values = itertools.chain(search)
        if search_values is not None:
            try:
                for raw_value in search_values:
                    _query_num, _pub, \
                        (_value, _return_type, pkg_info) = raw_value
                    pfmri = pkg_info[0]
                    pkg_name = pfmri.get_name()
                    # We ignore SUNWcs and SUNWcsd since these are system
                    # packages that can show up in the query due to
                    # dependecies.
                    if pkg_name in ["SUNWcs", "SUNWcsd"]:
                        continue
                    if pkg_name != None:
                        self.inst_type = "ips"
                        return pkg_name
            except apx.SlowSearchUsed, msg:
                self.__gen_err(LVL_WARNING,
                               _("package name lookup returned error: "
                                 "%(message)s") % {"message": msg})
        return pkg_name

    def __rootdisk_slice_conflict_check(self, keyword, disk_slice):
        """Checks the specified slice to see if it conflicts with the
        root_device or boot_device settings that may have been specified
        by the profile.  Returns True if conflict found, false otherwise

        """
        if self._rootdisk is None:
            return False
        if self._root_device is not None:
            # Both root_disk and are slice are have the same format
            # a direct comparision should be performed
            cmp_device = disk_slice
        else:
            if self._rootdisk_set_by_keyword == "fdisk":
                # fdisk keyword never represents a conflict
                # it may be overriden by any keyword that causes rootdisk
                # to be set
                return

            # root_disk wasn't set but boot_device was so our comparision
            # has to be made at the disk level instead of the slice level
            cmp_device = self.__device_name_conversion(disk_slice)
        if cmp_device != self._rootdisk:
            self.__gen_err(LVL_CONVERSION,
                           _("conflicting ZFS root pool definition: "
                             "%(keyword)s definition will be used instead of "
                             "'%(rd_kw)s %(rootdisk)s'") % \
                             {"keyword": keyword,
                              "rd_kw": self._rootdisk_set_by_keyword,
                              "rootdisk": str(self._rootdisk)})
            return True
        return False

    def __rootdisk_conversion(self, device, prefix):
        """Checks the 'device' for the presence of 'prefix'  if found
        and the root disk has been determined the 'prefix' will be
        replaced with the name of the root disk

        """
        # Jumpstart profiles may have commands in the format
        #
        # filesys rootdisk.s0 size mount_point
        # fdisk solaris rootdisk all
        #
        # This routines simply looks for that pattern in the device
        # and substitutes it for the rootdisk if we've determined
        # what that rootdisk is
        #
        if device is not None and device.startswith(prefix):
            # We can only support the rootdisk keyword if
            # root_device, boot_device, pool, or  filesys /
            # has been specified in the profile
            # If filesys / is used then it must proceed rootdisk usage
            # in order for this substitution to suceed
            if self._rootdisk is None:
                self.__gen_err(LVL_CONVERSION,
                               _("unable to convert '%(device)s'.  Replace"
                                 "'%(prefix)s' with actual device name") % \
                                 {"device": device,
                                  "prefix": prefix})
                return None
            # The root device specification has a slice associated with it
            # we need to strip this off before we substitute "rootdisk."
            # with it
            disk = self.__device_name_conversion(self._rootdisk)
            device = device.replace(prefix, disk, 1)
        return device

    def __convert_fdisk_entry(self, keyword, values):
        """Processes the fdisk keyword/values from the profile

        """

        # fdisk <diskname> <type> <size>
        if len(values) != 3:
            self.__invalid_syntax(keyword)
            return

        # check <diskname> syntax
        # valid values:         rootdisk
        #                       all
        #                       cx[ty]dz
        disk_name = values[0].lower()
        if disk_name == SIZE_ALL:
            # AI doesn't provide with the ability that says initialize
            # all disks discovered on a system.  We therefore have to
            # mark this as unsupported
            self.__unsupported_value(_("<diskname>"), disk_name)
            return

        disk_name = self.__rootdisk_conversion(disk_name,
                                               PREFIX_ROOTDISK)
        if disk_name is None:
            return

        if not self.__is_valid_device_name(disk_name):
            self.__gen_err(LVL_CONVERSION,
                           _("invalid device specified: %(device)s") % \
                             {"device": values[0]})
            return

        # check <type> syntax
        # valid values:         solaris
        #                       dosprimary
        #                       x86boot
        #                       ###
        fdisk_type = values[1]
        if fdisk_type != "solaris":
            self.__unsupported_value(_("<type>"), fdisk_type)
            return

        # check <size> syntax for non-x86boot partitions
        # valid values:         all
        #                       delete
        #                       maxfree
        #                       ###
        size = values[2].lower()
        if size in [SIZE_MAXFREE, SIZE_DELETE, "0"]:
            # maxfree - An fdisk partition is created in the largest
            #           contiguous free space on the specified disk. If an
            #           fdisk partition of the specified type already exists
            #           on the disk, the existing fdisk partition is used.
            #           A new fdisk partition is not created on the disk.
            #           There's no support, mark it as unsupported
            # delete -  All fdisk partitions of the specified type are deleted
            #           on the specified disk.  Although we can technically
            #           support the delete keyword we are going to mark this
            #           as unsupported because the disk handling is so
            #           different in this go around and we are only supporting
            #           the a single root pool creation
            self.__unsupported_value(_("<size>"), size)
            return
        if size != SIZE_ALL:
            size = self.__size_conversion(keyword, size)
            if size is None:
                return

        if self._root_pool is None and \
          self._partitioning == PARTITIONING_DEFAULT and \
          self._rootdisk is None:
            # Only set rootdisk to the value held by fdisk if
            # "partitioning = default".  A value of explicit implies
            # the user is going to specify the layout
            self._rootdisk = disk_name
            if size != SIZE_ALL:
                self._rootdisk_size = size
            self._rootdisk_set_by_keyword = "fdisk"

        # Go ahead and create entry for partition
        disk_node = self.__fetch_disk_node(disk_name)
        if disk_node is None:
            disk_node = self.__create_disk_node(disk_name, size == SIZE_ALL)
        else:
            if size == SIZE_ALL:
                disk_node.set(common.ATTRIBUTE_WHOLE_DISK, "true")
        part_node = self.__add_partition(disk_node)
        size_node = part_node.find(common.ELEMENT_SIZE)
        if size != SIZE_ALL:
            if size_node is None:
                size_node = etree.Element(common.ELEMENT_SIZE)
                part_node.insert(0, size_node)
                size_node.set(common.ATTRIBUTE_VAL, size)
            else:
                spec_size = size_node.get(common.ATTRIBUTE_VAL)
                if size != spec_size:
                    self.__gen_err(LVL_CONVERSION,
                                   _("conflicting fdisk <size> specified: "
                                     "size was previously defined as "
                                     "%(size)s. Ignoring entry") % \
                                     {"size": spec_size})

    def __is_valid_filesys_mount(self, mount):
        """Check whether this is a valid supported mount point.  Return True
           if supported. Otherwise return False

        """
        if mount not in ["/", "swap"]:
            # We reject everything except for '/' and swap
            self.__gen_err(LVL_UNSUPPORTED,
                           _("unsupported mount point of '%(mount)s' "
                             "specified, mount points other than '/' and "
                             "'swap' are not supported") % {"mount": mount})
            return False
        return True

    def __swap_force_root_pool_creation(self):
        """Force the creation of root pool since it doesn't exist.  Return
           True if pool can be created.  False otherwise.

        """
        if self._root_pool is None:
            # We've got a filesys swap entry but we don't have a root
            # pool created yet.  Do we have enough data to create one?
            if self._rootdisk is None:
                # No we don't have enough information to create a root pool
                self.__gen_err(LVL_UNSUPPORTED,
                               _("swap mount is only supported when preceded "
                                 "by a entry that causes the root pool "
                                 "to be created. For example root_device, "
                                 "boot_device, pool, or filesys with a mount "
                                 "point of '/'"))
                return False
            if self._rootdisk == DEVICE_ANY:
                self._rootdisk = self.__pop_usedisk_entry(DEVICE_ANY)
            zpool = self.__create_root_pool(self._rootdisk_set_by_keyword)
            self.__create_vdev(zpool)
            self.__add_device(self._rootdisk,
                              self._rootdisk_size, self._root_pool_name)
            return True

    def __device_conversion(self, device, allow_any_value,
                            use_rootdisk):
        """Perform conversion on the device specified if necessary.  Return
           None if an error condition occurs (device invalid, device could
           not be converted)

           Arguments:
           device - the device being converted
           allow_any_value - boolean to indicate whether to allow of "any"
                    to be returned
           use_rootdisk - indicates whether to translate "any" using rootdisk
                    if rootdisk is set

        """
        if device == DEVICE_ANY:
            if use_rootdisk:
                device = self.__fetch_rootdisk_slice()
                if device is None:
                    device = DEVICE_ANY
            else:
                device = self.__pop_usedisk__slice_entry(DEVICE_ANY)
            if device == DEVICE_ANY:
                if allow_any_value:
                    return device
                else:
                    self.__gen_err(LVL_CONVERSION,
                                   _("unable to convert 'any' device to "
                                     "physical device. Replace 'any' with "
                                     "actual device name"))
                    return None
        else:
            device = self.__rootdisk_conversion(device,
                                                PREFIX_ROOTDISK_DOT)
            if device is None:
                return None

        if not self.__is_valid_slice(device):
            self.__gen_err(LVL_CONVERSION,
                           _("invalid slice specified: %(device)s ") % \
                             {"device": device})
            return None
        return device

    def __size_conversion(self, keyword, size):
        """Perform the necessary conversion for fileys size.  Returns None
           and generates error if not a valid numeric size.

        """
        match_pattern = SIZE_PATTERN.match(size)
        if match_pattern:
            if match_pattern.group(2) == "":
                # No size specified.  Default size is assumed to be in MB
                size += "mb"
            else:
                # Jumpstart uses m and g not mb and gb like installer wants
                size += "b"
        else:
            self.__gen_err(LVL_CONVERSION,
                           _("invalid size '%(size)s' specified for "
                             "%(key)s") % {"key": keyword, "size": size})
            size = None
        return size

    def __convert_filesys_mirror_entry(self, keyword, values):
        """Perform conversion of filesys mirror entry"""
        length = len(values)
        if length < 4:
            self.__invalid_syntax(keyword)
            return

        mirror_name = values[0]
        if length > 4:
            mount = values[4].lower()
        else:
            #
            # From the Solaris 10 jumpstart documentation
            # If file_system is not specified, unnamed is set by default
            # use this as our default value
            mount = FILESYS_DEFAULT_MOUNT_POINT
        pool_name = DEFAULT_POOL_NAME
        if mount == "swap":
            pool_name += "_swap"
        if mirror_name != "mirror":
            try:
                _mirror, pool_name = values[0].split(":")
            except ValueError:
                self.__invalid_syntax(keyword)
                return
        device1 = values[1]
        device2 = values[2]
        size = values[3].lower()

        if length >= 6:
            self.__gen_err(LVL_CONVERSION,
                           _("ignoring optional filesys parameters: "
                             "%(params)s") % {"params": values[5:]})

        if not self.__is_valid_filesys_mount(mount):
            return

        device1 = self.__device_conversion(device=device1,
                                           allow_any_value=False,
                                           use_rootdisk=False)
        if device1 is None:
            return

        device2 = self.__device_conversion(device=device2,
                                           allow_any_value=False,
                                           use_rootdisk=False)
        if device2 is None:
            return

        if size in [SIZE_FREE, SIZE_EXISTING, SIZE_ALL, SIZE_AUTO]:
            self.__unsupported_syntax(keyword,
                                      _("sizes other than a number are not "
                                        "supported for filesys mirror swap"))
            return None

        size = self.__size_conversion(keyword, size)
        if size is None:
            return

        if mount == "/" and self.__root_pool_exists(keyword):
            return

        if not self.__is_valid_mirror(keyword,
                                      [device1, device2], size, mount == "/"):
            return

        if mount == "swap":
            self.__create_filesys_mirrored_swap(pool_name,
                                                device1, device2, size)
            return

        if self._rootdisk is not None:
            # Unset roodisk value since we can't translate a mirror pool
            # rootdisk into the a single device for rootdisk.s0 subsitutions
            self._rootdisk = None
            self._rootdisk = None
            self._rootdisk_set_by_keyword = None

        vdev_name = pool_name + VDEV_SUFFIX
        self.__add_device(device=device1, size=size,
                          in_pool=pool_name,
                          in_vdev=vdev_name, is_swap=None)
        self.__add_device(device=device2, size=size,
                          in_pool=pool_name,
                          in_vdev=vdev_name, is_swap=None)
        zpool = self.__create_root_pool(keyword, pool_name)
        self.__create_vdev(zpool, REDUNDANCY_MIRROR, vdev_name)

    def __convert_filesys_entry(self, keyword, values):
        """Converts the filesys keyword/values from the profile into
        the new xml format

        """
        if self._partitioning == "existing":
            self.__unsupported_syntax(keyword,
                                      _("filesys keyword not supported "
                                        "when partition_type is set to "
                                        "'existing'"))
            return

        #
        # Use the regex pattern '..*:/..*' to determine the # of expressions
        # we have.   If the count greater than 0 then this is a remote
        # file system, otherwise we have a local file system
        if FILESYS_ARG_PATTERN.match(values[0]):
            # Mounting of remote file sys
            #
            # filesys server:path server_address mount_pt_name mount_options
            #
            # Currently not support remote file system so reject entire entry
            self.__unsupported_syntax(keyword,
                                      _("remote file systems are not "
                                        "supported"))
            return

        # We've got a local file system or a mirror (RAID-1) setup
        #
        # filesys slice size file_system optional_parameters
        # filesys mirror[:name]slice [slice] size file_system opt_parameters

        # Invalidate anything with too many or too little args
        length = len(values)
        if length < 2:
            self.__invalid_syntax(keyword)
            return

        if values[0].startswith("mirror"):
            self.__convert_filesys_mirror_entry(keyword, values)
            return

        device = values[0]
        size = values[1].lower()
        if length >= 3:
            mount = values[2].lower()
        else:
            # From the Solaris 10 jumpstart documentation
            # If file_system is not specified, unnamed is set by default
            # use this as our default value
            mount = FILESYS_DEFAULT_MOUNT_POINT
        if length >= 4:
            self.__gen_err(LVL_CONVERSION,
                           _("ignoring optional filesys parameters: "
                             "%(params)s") % {"params": values[3:]})
        if not self.__is_valid_filesys_mount(mount):
            return

        device = self.__device_conversion(device=device,
                                          allow_any_value=(size == SIZE_ALL),
                                          use_rootdisk=(mount == "/"))
        if device is None:
            return

        if size in [SIZE_FREE, SIZE_EXISTING, SIZE_AUTO]:
            self.__unsupported_syntax(keyword,
                                      _("sizes other than a number or all are "
                                        "not supported"))
            return None
        if size != SIZE_ALL:
            size = self.__size_conversion(keyword, size)
            if size is None:
                return

        if mount == "swap":
            self.__create_filesys_swap(device, size)
            return

        if self.__root_pool_exists(keyword):
            return

        if self._rootdisk is not None:
            # We got a condition like
            #
            # root_device cxtxdxsx
            # filesys cxtxdxsx 20 /
            #
            # or
            #
            # boot_device cxtxdx
            # filesys cxtxdxsx 20 /
            #
            # Check for conflicts
            #
            if not self.__rootdisk_slice_conflict_check(keyword, device):
                # Update the device with the rootdisk setting
                # If the user used root_device this will refine the ZFS
                # root pool so it agrees with that setting too
                # This would be equivalent to us processing the root_device
                # line and adding the slice entry to the pool
                self._rootdisk = None

        if self._rootdisk is None:
            # How JumpStart Determines a System's Root Disk
            # 3. If rootdisk is not set and a filesys cwtxdysz size / entry
            #    is specified in the profile, the JumpStart program sets
            #    rootdisk to the disk that is specified in the entry.
            self._root_device = device
            self._rootdisk = self._root_device
            self._rootdisk_set_by_keyword = keyword

        zpool = self.__create_root_pool(keyword, DEFAULT_POOL_NAME)
        self.__create_vdev(zpool)
        self.__add_device(device, size, self._root_pool_name)

    def __create_filesys_swap(self, device, size):
        """Create a non ZFS SWAP device"""
        if self._rootdisk == DEVICE_ANY:
            self.__gen_err(LVL_CONVERSION,
                           _("unable to to use specified swap for device "
                             "since the device 'any' was used to create "
                             "the root pool via keyword %(rd_kw)s. Replace "
                             "'any' with actual device name") % \
                             {"rd_kw": self._rootdisk_set_by_keyword})
            return None

        # <disk>
        #   <disk_name name="c6t1d0" name_type="ctd"/>
        #   <slice action="create" name="0" is_swap="true" >
        #       <size value="35000mb" />
        #   </slice>
        # <disk>
        self.__add_device(device=device, size=size,
                          in_pool=None, in_vdev=None, is_swap="true")

    def __create_filesys_mirrored_swap(self, pool_name,
                                       device1, device2, size):
        """Create a mirror swap for devices specified by user"""

        # Check device2 up front.  Since add_device will do this same
        # check for device1 and we want to fail if either device is not
        # valid
        if not self.__is_valid_to_add_slice(device2, size):
            return
        if size == SIZE_ALL:
            self.__gen_err(LVL_CONVERSION,
                           _("filesys mirror with a mount of swap and all is "
                             "not supported. Change size to actual swap size "
                             "desired"))
            return
        vdev_name = pool_name + VDEV_SUFFIX
        self.__add_device(device=device1, size=size,
                          in_pool=pool_name,
                          in_vdev=vdev_name, is_swap=None)
        self.__add_device(device=device2, size=size,
                          in_pool=pool_name,
                          in_vdev=vdev_name, is_swap=None)
        zpool = self.__create_zfs_pool(pool_name)
        self.__create_vdev(zpool, REDUNDANCY_MIRROR, vdev_name)
        if size not in [SIZE_ALL, SIZE_AUTO]:
            self.__create_zvol(parent=zpool, name="swap", use="swap",
                               zvol_size=size)

    def __convert_install_type_entry(self, keyword, values):
        """Converts the install_type keyword/values from the profile into
        the new xml format

        """
        # We've check the install type up front so there's nothing to do here
        pass

    def __convert_locale_entry(self, keyword, values):
        """Converts the install_type keyword/values from the profile into
        the new xml format

        """
        # Convert
        #   locale locale_name
        #
        # to
        #
        # <software name="ips" type="IPS">
        #  <destination>
        #   <image>
        #     <facet set="false">facet.locale.*</facet>
        #     <facet set="true">facet.locale.${locale_name}</facet>
        #     <facet set="true">facet.locale.de_DE</facet>
        #     <facet set="true">facet.locale.en</facet>
        #     <facet set="true">facet.locale.en_US</facet>
        #     <facet set="true">facet.locale.es</facet>
        #   </image>
        #  </destination>
        #  ...
        # </software

        if len(values) != 1:
            self.__invalid_syntax(keyword)
            return
        if self._image_node is None:
            # We haven't done a local setting operation yet.
            #
            # Find the local settings and delete all the child nodes if
            # they exist, otherwise create the initial structure needed
            #
            software = self.__fetch_solaris_software_node()
            dest = software.find(common.ELEMENT_DESTINATION)
            if dest is None:
                dest = etree.Element(common.ELEMENT_DESTINATION)
                software.insert(0, dest)
            else:
                image = dest.find(common.ELEMENT_IMAGE)
                if image is not None:
                    dest.remove(image)
            self._image_node = etree.SubElement(dest, common.ELEMENT_IMAGE)

            # <facet set="false">facet.locale.*</facet>
            facet = etree.SubElement(self._image_node, common.ELEMENT_FACET)
            facet.set(common.ATTRIBUTE_SET, "false")
            facet.text = FACET_LOCALE_FORM % "*"

        # <facet set="true">facet.locale.${locale}</facet>
        facet = etree.SubElement(self._image_node, common.ELEMENT_FACET)
        facet.set(common.ATTRIBUTE_SET, "true")
        facet.text = FACET_LOCALE_FORM % values[0]

    def __convert_package_entry(self, keyword, values):
        """Converts the package keyword/values from the profile into
        the new xml format

        """
        # Input:        package <package_name> <add|delete> <arg1> <arg2> ...

        package = values[0]
        if len(values) == 2:
            action = values[1].lower()
        elif len(values) < 2:
            action = "add"
        else:
            # If the remote (nfs or http) or local (local_device or local_file)
            # directory option is used we can't support this entry. Log it
            # and return.
            self.__unsupported_syntax(keyword,
                _("package install from specified locations is not supported "
                "for SVR4 packages"))
            return
        if (action != "add" and action != "delete"):
            self.__invalid_syntax(keyword)
            return
        if action == "add":
            self.__add_software_data(package,
                                     SOFTWARE_INSTALL)
        elif action == "delete":
            self.__add_software_data(package,
                                     SOFTWARE_UNINSTALL)

    def __convert_pool_entry(self, keyword, values):
        """Converts the pool keyword/values from the profile into
        the new xml format

        """
        # pool <pool name> <pool size> <swap size> <dump size>
        #                                       <slice> | mirror [<slice>]+
        #
        # Input:        ${1}    - pool name
        #               ${2}    - pool size (num, existing, auto)
        #               ${3}    - swap size (num, auto)
        #               ${4}    - dump size (num, auto)
        #               ${5}    - (mirror, <slice>, rootdisk.s??, any)
        #                .      - (<slice>, rootdisk.s??, any)
        length = len(values)
        if length < 5:
            self.__invalid_syntax(keyword)
            return

        pool_name = values[0]
        # Pool name must be between 0-30 characters
        name_length = len(pool_name)
        if name_length == 0 or name_length > 30:
            self.__gen_err(LVL_CONVERSION,
                           _("invalid pool name of '%(pool)s': must be at "
                             "least 1 character and no more than 30 "
                             "characters in length") % {"pool": pool_name})
            return
        # Update the name that we are using for the root pool
        self._root_pool_name = pool_name

        pool_size = values[1].lower()
        if pool_size == SIZE_EXISTING:
            self.__unsupported_syntax(keyword,
                                      _("pool sizes other than a number or "
                                        "auto are not supported"))
            return None
        elif pool_size != SIZE_AUTO:
            pool_size = self.__size_conversion("<pool_size>", pool_size)
            if pool_size is None:
                return

        swap_size = values[2].lower()
        noswap = "false"
        if swap_size == "0":
            swap_size = None
            noswap = "true"
        elif swap_size != SIZE_AUTO:
            swap_size = self.__size_conversion("<swap_size>", swap_size)
            if swap_size is None:
                return

        dump_size = values[3].lower()
        nodump = "false"
        if dump_size == "0":
            dump_size = None
            nodump = "true"
        elif dump_size != SIZE_AUTO:
            dump_size = self.__size_conversion("<dump_size>", dump_size)
            if dump_size is None:
                return

        if values[4] == "mirror":
            # Mirror must have at least 2 slices
            mirror = True
            if length < 6:
                self.__invalid_syntax(keyword)
                return
            redundancy = "mirror"
            devices = values[5:]
        else:
            mirror = False

            # Non mirror can only have 1 slice specified
            if length > 5:
                self.__invalid_syntax(keyword)
                return
            redundancy = "none"
            devices = values[4:]

        updated_list = list()
        for device in devices:
            allow_any = mirror == False and pool_size == SIZE_AUTO
            update_device = \
                self.__device_conversion(device=device,
                                         allow_any_value=allow_any,
                                         use_rootdisk=(mirror == False))
            if update_device is None:
                return
            updated_list.append(update_device)
        devices = updated_list

        # Check for any conflicts with root_device and boot_device
        # pool will always override these settings
        if mirror:
            if not self.__is_valid_mirror(keyword, devices, pool_size, True):
                return
        else:
            # Check for conditions like
            #
            # root_device cxtxdxsx
            # pool p_abc auto auto auto cxtxdxsx
            #
            # or
            #
            # boot_device cxtxdx
            # pool p_abc auto auto auto cxtxdxsx
            #
            # Warning message has been outputed
            self.__rootdisk_slice_conflict_check(keyword, devices[0])

        zpool = self.__create_root_pool(keyword, self._root_pool_name,
                                        noswap, nodump)
        self.__create_vdev(zpool, redundancy)
        for device in devices:
            self.__add_device(device, pool_size, self._root_pool_name)

        if swap_size is not None and swap_size != SIZE_AUTO:
            self.__create_zvol(parent=zpool, name="swap", use="swap",
                                zvol_size=swap_size)
        if dump_size is not None and dump_size != SIZE_AUTO:
            self.__create_zvol(parent=zpool, name="dump", use="dump",
                                zvol_size=dump_size)
        if not mirror and self._rootdisk is None:
            # Based on how we process things the pool keyword is where
            # we don't follow the basic steps of "How JumpStart Determines"
            # " a System's Root Disk (Initial Installation)"
            #
            # Since rootdisk has not been set via root_disk or boot_device
            # we now want to use the device specified by pool as our
            # root_device.
            self._root_device = devices[0]
            self._rootdisk = self._root_device
            self._rootdisk_set_by_keyword = keyword

    def __convert_system_type_entry(self, keyword, values):
        """Processes the system_type entry in profile and flags any
        value other than standalone as unsupported value

        """
        length = len(values)
        if length > 1 or length == 0:
            self.__invalid_syntax(keyword)
            return
        if values[0] != "standalone":
            self.__unsupported_value(keyword, values[0])

    def __fetch_disk_node(self, disk_name):
        """Returns the <disk> node for the disk 'disk_name'"""
        diskname_node = self.__fetch_diskname_node(disk_name)
        if diskname_node is None:
            return None
        return diskname_node.getparent()

    def __fetch_diskname_node(self, disk_name):
        """Returns the diskname node with the disk_name of 'disk_name'"""
        xpath = "./disk/disk_name[@name='%s']"
        return fetch_xpath_node(self._target, xpath % disk_name)

    def __fetch_pool(self, pool_name):
        """Fetch the ZFS pool with the specified name if it exists"""
        xpath = "./logical/zpool[@name='%s']" % pool_name
        return fetch_xpath_node(self._target, xpath)

    def __fetch_root_pool(self):
        """Fetch the ZFS root pool"""
        xpath = "./logical/zpool[@is_root='true']"
        return fetch_xpath_node(self._target, xpath)

    def __fetch_rootdisk(self):
        """Fetch the rootdisk device value"""
        if self._rootdisk is not None:
            if self._rootdisk == DEVICE_ANY:
                self._rootdisk = self.__pop_usedisk_entry(DEVICE_ANY)
        return self._rootdisk

    def __fetch_rootdisk_slice(self):
        """Fetch the rootdisk device value.  Convert it to slice form if not
           currently in the slice form.

        """
        slice_name = self.__fetch_rootdisk()
        if slice_name not in [None, DEVICE_ANY]:
            # rootdisk may or may not be in the slice form already
            if not self.__is_valid_slice(slice_name):
                return slice_name + "s0"
        return slice_name

    def __fetch_slice_insertion_point(self, disk_name):
        """Return the insertion point for adding slices to the specified
           disk.  On sparc this is <disk> on x86 this is <partition>
           Returns None is disk doesn't exist.

        """
        disk_node = self.__fetch_disk_node(disk_name)
        if disk_node is None:
            return None
        if self._arch in [None, common.ARCH_X86]:
            # We return the partition
            xpath = "./partition[@action='create'][@type='191']"
            return fetch_xpath_node(disk_node, xpath)
        return disk_node

    def __pop_usedisk_entry(self, default_value=None):
        """Return the 1st device off the usedisk stack if one exists.
           Otherwise return default_value

        """
        if len(self._usedisk):
            return self._usedisk.pop(0)
        return default_value

    def __pop_usedisk__slice_entry(self, default_value=None):
        """Return the 1st device off the usedisk stack in slice form
           if one exists. Otherwise return default_value

        """
        if len(self._usedisk):
            return self._usedisk.pop(0) + "s0"
        return default_value

    def __store_boot_device_entry(self, keyword, values):
        """Converts the boot device keyword/values from the profile into the
        new xml format

        """

        # The supported syntax in Solaris 10 was
        #
        # boot_device <device> <eeprom>
        # valid values:
        #       <device>:
        #                       c#[t#]d#s# - SPARC
        #                       c#[t#]d#   - X86
        #                       existing
        #                       any
        #       <eeprom>:
        #                       preserve
        #                       update
        #
        if self._boot_device is not None:
            self.__duplicate_keyword(keyword)
            return
        length = len(values)
        if length > 2 or length == 0:
            self.__invalid_syntax(keyword)
            return

        device = values[0].lower()
        if device == "existing":
            self.__unsupported_value(_("<device>"), device)
            return

        if device != DEVICE_ANY:
            if not self.__is_valid_device_name(device) and \
               not self.__is_valid_slice(device):
                self.__gen_err(LVL_CONVERSION,
                               _("invalid device specified: %(device)s") % \
                                 {"device": device})
                return

            # The device specified is valid.  Set the architecure for the
            # device based on the device value specified. If a valid slice is
            # specified for boot device the architecuture is SPARC.
            # Otherwise it's x86
            if self.__is_valid_slice(device):
                if not self.__change_arch(common.ARCH_SPARC):
                    return
            else:
                if not self.__change_arch(common.ARCH_X86):
                    return

        if length == 2:
            eeprom = values[1].lower()
            if eeprom == "preserve":
                # No action since this says keep it as is
                pass
            elif eeprom == "update":
                # Technically only the preserve value is supported
                # on x86 but since we are ignoring the update value
                # this message is sufficient
                self.__gen_err(LVL_UNSUPPORTED,
                               _("ignoring eeprom 'update' setting, as "
                                 "this action is not supported"))
            else:
                self.__invalid_syntax(keyword)
                return
        if self._root_device is not None:
            # If we follow how jumpstart determines which disk to use for
            # the root disk the root_device keyword has a higher priority
            # than the boot_device keyword.  If the devices specified
            # are different then flag it as a conflict, but use root_device
            # defintion.
            #
            # root_device c1t0d0s1
            # boot_device c0t0d0 update
            cmp_device = self.__device_name_conversion(self._root_device)
            if cmp_device != device:
                self.__gen_err(LVL_CONVERSION,
                               _("conflicting definition: rootdisk previously "
                                 "defined as '%(root_device)s' via keyword "
                                 "'%(rd_kw)s', ignoring entry") % \
                                 {"root_device": self._root_device,
                                  "rd_kw": self._rootdisk_set_by_keyword})
                return
        else:
            self._rootdisk = device
        self._rootdisk_set_by_keyword = keyword
        self._boot_device = device

    def  __store_root_device_entry(self, keyword, values):
        """Set the profile root device value that we'll use if root device
        is specified by the user

        """
        # root_device <slice>
        if self._root_device is not None:
            self.__duplicate_keyword(keyword)
            return
        if len(values) != 1:
            self.__invalid_syntax(keyword)
            return
        if not self.__is_valid_slice(values[0]):
            self.__gen_err(LVL_CONVERSION,
                           _("invalid device specified: %(device)s") % \
                             {"device": values[0]})
            return

        self._root_device = values[0]
        if self._boot_device is not None:
            # Do we have a conflict?
            cmp_device = self.__device_name_conversion(self._boot_device)
            if self._root_device != cmp_device:
                self.__gen_err(LVL_CONVERSION,
                               _("translation conflict between devices "
                                 "specified for boot_device and root_device, "
                                 "using root_device define of "
                                 "'%(root_device)s', ignoring define of "
                                 "boot_device of '%(boot_device)s'") % \
                                 {"root_device": self._root_device,
                                  "boot_device": self._boot_device})
        self._rootdisk = self._root_device
        self._rootdisk_set_by_keyword = keyword

    def  __store_partitioning_entry(self, keyword, values):
        """Set the profile partitioning value that we'll use if fdisk all
        or usedisk is specified by the user later

        """
        # partitioning <type> where type is default, existing or explicit
        if self._partitioning is not None:
            self.__duplicate_keyword(keyword)
            return
        if len(values) != 1:
            self.__invalid_syntax(keyword)
            return
        self._partitioning = values[0].lower()
        if self._partitioning not in [PARTITIONING_DEFAULT,
                                      PARTITIONING_EXPLICIT]:
            self.__gen_err(LVL_UNSUPPORTED,
                           _("unsupported profile, partitioning must be "
                             "'default' or 'explicit'"))
            self._report.conversion_errors = None
            self._tree = None
            raise ValueError

    def __store_usedisk_entry(self, keyword, values):
        """Store the usedisk devices specified by the user.  We'll use these
        to create the root pool if paritioning default is specified.

        """
        #
        # usedisk <device> [<device]
        if len(values) == 0 or len(values) > 2:
            self.__invalid_syntax(keyword)
            return
        for value in values:
            if self.__is_valid_device_name(value):
                self._usedisk.append(value)
            else:
                self.__gen_err(LVL_CONVERSION,
                               _("invalid device specified: %(device)s") % \
                                 {"device": value})
                return

    def __is_valid_install_type(self, keyword, values):
        """The only profiles that are supported are install profiles
        The jumpstart scripts require it as the first keyword in the
        file.  If the install_type is not initial_install reject
        the entire profile

        """
        if keyword != "install_type":
            self.__gen_err(LVL_PROCESS,
                           _("invalid profile, first specified keyword must "
                             "be install_type, got '%(keyword)s'") % \
                             {"keyword": keyword})
            self._report.conversion_errors = None
            self._report.unsupported_items = None
            self._tree = None
            return False
        install_type = values[0].lower()
        if install_type in ["upgrade",
                            "flash_install", "flash_upgrade"]:
            self.__unsupported_value(keyword, values[0])
            self._report.conversion_errors = None
            self._tree = None
            return False
        if install_type != "initial_install":
            self.__invalid_syntax(keyword)
            self._report.conversion_errors = None
            self._report.unsupported_items = None
            self._tree = None
            return False
        return True

    @property
    def tree(self):
        """Returns the xml tree associated with this object"""
        return self._tree

    def fetch_tree(self, arch):
        """Convert the current tree to the specified architecute

        Supported architecutres are:
            common.ARCH_GENERIC
            common.ARCH_SPARC
            common.ARCH_X86

        Conversion not support:
            SPARC to X86

        """
        if arch not in [common.ARCH_GENERIC, common.ARCH_X86,
                         common.ARCH_SPARC]:
            # Programming error
            raise ValueError(-("unsupported architecture specified"))
        if arch == self._arch or self._arch == common.ARCH_GENERIC:
            # Tree is in the proper format for the architecture requested
            return self._tree
        if arch == common.ARCH_X86:
            if self._arch in [None, common.ARCH_X86]:
                # Tree is in the proper format for the architecture requested
                return self._tree
        if arch == common.ARCH_SPARC:
            if self._arch in [None, common.ARCH_X86]:
                # Tree is not in the proper format.  A conversion is necessary
                return self.__fetch_sparc_from_x86_tree(self._tree)

        # Programming error
        raise ValueError(_("Conversion from architecute %(req_arch) to "
                           "%(cur_arch)s is not supported") %
                               {"req_arch": arch,
                                "cur_arch": self._arch})

    def __fetch_sparc_from_x86_tree(self, tree):
        """Converts a x86 manifest xml tree to a sparc manifest xml tree"""
        clone = common.tree_copy(tree)
        # The only difference between an x86 based xml tree and a sparc
        # tree is currently the <partition> node.  Look for the
        # <partition> node and remove it, if it exists.
        xpath = "/auto_install/ai_instance/target"
        target = fetch_xpath_node(clone, xpath)
        for disk in target.findall(common.ELEMENT_DISK):
            # To convert a x86 manifest profile to a sparc we're simply
            # going to take the children slices of the partition and move
            # them up as a child of <disk> and then delete the <partition>
            # node
            partition = disk.find(common.ELEMENT_PARTITION)
            if partition is not None:
                for slice_node in partition.findall(common.ELEMENT_SLICE):
                    partition.remove(slice_node)
                    disk.append(slice_node)
                disk.remove(partition)
        return clone

    def __fetch_keys(self):
        """Fetch the keys that we need to process from the profile dictionary

        """
        if self.prof_dict is None:
            keys = {}
        else:
            # Sort the keys based on the line #
            keys = sorted(self.prof_dict.keys())
        if len(keys) == 0:
            # There's nothing to convert.  This is a valid condition if
            # the file couldn't of been read for example
            self._report.conversion_errors = None
            self._report.unsupported_items = None
            return None
        return keys

    def __find_xml_entry_points(self):
        """Find and set the global xml entry points"""
        self._target = self._ai_instance.find(common.ELEMENT_TARGET)
        if self._target is not None:
            # Delete the target entry from the default manifest
            self._ai_instance.remove(self._target)

        # Create <target> and insert immediately after
        # <ai_instance> node
        self._target = etree.Element(common.ELEMENT_TARGET)
        self._ai_instance.insert(0, self._target)

    profile_conversion_dict = {
        "boot_device": None,
        "bootenv": __unsupported_keyword,
        "client_arch": __unsupported_keyword,
        "client_swap": __unsupported_keyword,
        "cluster": __unsupported_keyword,
        "dontuse": __unsupported_keyword,
        "fdisk": __convert_fdisk_entry,
        "filesys": __convert_filesys_entry,
        "geo": __unsupported_keyword,
        "install_type": __convert_install_type_entry,
        "locale": __convert_locale_entry,
        "num_clients": __unsupported_keyword,
        "package": __convert_package_entry,
        "partitioning": __store_partitioning_entry,
        "pool": __convert_pool_entry,
        "root_device": None,
        "system_type": __convert_system_type_entry,
        "usedisk": None
        }

    def __process_profile(self):
        """Process the profile by taking all keyword/values pairs and
        generating the associated xml for the key value pairs

        """

        keys = self.__fetch_keys()
        if keys is None:
            self._tree = None
            return

        check_for_install_type = True
        pool_obj = None
        line_num = 0
        #
        # The keywords for the profile are processed in 3 different phases.
        #
        # Phase  Actions Performed
        # -----  -------------------------------------------------------------
        #   1    o Check to ensure that "install_type" is the first keyword
        #          in the profile.
        #        o Check the entire profile for "root_device" and "boot_device"
        #          keywords for the generation of the "rootdisk".  This
        #          duplicates the first 2 steps that Jumpstart does when it
        #          determines what disk to use for the System's Root Disk
        #        o Check for the keyword "partitioning" and store for later use
        #        o Check for keyword "usedisk" and store for later use
        #   2    o Process the pool keyword.  If used this is the closest
        #          parallel to how the new installer uses so we give this
        #          the highest priority in what we use to generate the ZFS
        #          root pool
        #   3    o If partition value is "default" create ZFS root pool
        #   4    o Process the remaining keywords in the profile
        #
        for key in keys:
            key_value_obj = self.prof_dict[key]
            if key_value_obj is None:
                raise KeyError

            keyword = key_value_obj.key
            values = key_value_obj.values
            line_num = key_value_obj.line_num

            if line_num is None or values is None or keyword is None:
                raise KeyError(_("Got None value, line_num=%(lineno)s "
                               "values=%(values)s keyword=%(keywords)s") %
                               {"lineno": str(line_num),
                                "values": str(values),
                                "keyword": str(keyword)})

            self._extra_log_params[LOG_KEY_LINE_NUM] = line_num
            if check_for_install_type:
                # The 1st keyword in the profile must be install_type,
                # if it's not we reject the profile
                if not self.__is_valid_install_type(keyword, values):
                    self._tree = None
                    return
                del self.prof_dict[key]
                check_for_install_type = False
            #
            # Scan all the keyword for root_device and boot_device
            # These keywords are special since they allow us to emulate
            # the initial 2 stages in the process of
            #       How JumpStart Determines a System's Root Disk
            #
            # 1. If the root_device keyword is specified in the profile, the
            #    JumpStart program sets rootdisk to the root device.
            # 2. If rootdisk is not set and the boot_device keyword is
            #    specified in the profile, the JumpStart program sets rootdisk
            #    to the boot device.
            if keyword == "root_device":
                self.__store_root_device_entry(keyword, values)
                del self.prof_dict[key]
            elif keyword == "boot_device":
                self.__store_boot_device_entry(keyword, values)
                del self.prof_dict[key]
            elif keyword == "pool":
                del self.prof_dict[key]
                if pool_obj is not None:
                    self.__duplicate_keyword(keyword)
                else:
                    pool_obj = key_value_obj
            elif keyword == "partitioning":
                try:
                    self.__store_partitioning_entry(keyword, values)
                    del self.prof_dict[key]
                except ValueError:
                    return
            elif keyword == "usedisk":
                self.__store_usedisk_entry(keyword, values)
                del self.prof_dict[key]
            elif keyword == "fdisk":
                if not self.__change_arch(common.ARCH_X86):
                    return

        self.__find_xml_entry_points()

        # Next create the zfs pool if the pool keyword was encountered
        # With the new installer we only support creating a single zfs
        # root pool.  The pool keyword takes precidence over any other
        # settings the user may have made
        if pool_obj is not None:
            self._extra_log_params[LOG_KEY_LINE_NUM] = pool_obj.line_num
            self.__convert_pool_entry(pool_obj.key, pool_obj.values)

        # Now process the remaining keys
        keys = sorted(self.prof_dict.keys())
        for key in keys:
            key_value_obj = self.prof_dict[key]
            keyword = key_value_obj.key.lower()
            values = key_value_obj.values
            self._extra_log_params[LOG_KEY_LINE_NUM] = key_value_obj.line_num
            try:
                function_to_call = self.profile_conversion_dict[keyword]
            except KeyError:
                self.__unsupported_keyword(keyword, values)
            else:
                if function_to_call is not None:
                    function_to_call(self, keyword, values)

        # If the root pool is not created attempt to create it now
        if self._root_pool is None:
            if self._rootdisk is not None:
                # rootdisk is set create the pool using the specified rootdisk
                zpool = self.__create_root_pool(self._rootdisk_set_by_keyword)
                self.__create_vdev(zpool)
                self.__add_device(self._rootdisk,
                                  self._rootdisk_size, self._root_pool_name)

            elif self._partitioning is not None and \
                 self._partitioning == PARTITIONING_DEFAULT:
                # Root pool doesn't exist.  User specified partitioning
                # default.  Go ahead and create it now
                zpool = self.__create_root_pool("partitioning")
                self.__create_vdev(zpool)
                if len(self._usedisk) == 0:
                    # No usedisk entries where specified.  Add device
                    # based on "any"
                    self.__add_device(DEVICE_ANY)

        if self._root_pool is not None:
            # Add any additional disks that the user may of told use to
            # use to the root pool as long as it's not a mirrored pool
            xpath = "./vdev[@redundancy='mirror']"
            vdev_mirror = fetch_xpath_node(self._root_pool, xpath)
            if vdev_mirror is None:
                for device in self._usedisk:
                    self.__add_device(device, None, self._root_pool_name)

        # Since we auto create the <target> node by default we want
        # to make sure the node has children before we finish up.
        # If there are no children then we want to delete the node
        if len(self._target) == 0:
            # No children.  Delete target node
            self._ai_instance.remove(self._target)

        # Check to determine if we have any children nodes
        # If we don't and we have errors then clear the xml
        # tree so we don't have a empty tree hierachy that
        # results in a <auto_install/>
        # Conversely though if all that's in the file is
        # initial_install and we have no errors then we should
        # create a file even if it's just <auto_install/>
        # That technically won't have any meaning to the new jumpstart
        # engine though
        if len(self._ai_instance) == 0 and self._report.has_errors():
            self._tree = None
        elif self._root_pool is None:
            if line_num is None:
                line_num = 1
            else:
                line_num += 1
            self._extra_log_params[LOG_KEY_LINE_NUM] = line_num
            # Create the root pool, but tell AI to choose the disk
            zpool = self.__create_root_pool("default")