#!/usr/bin/python
#
# Copyright C 2002-2012 by Isilon Systems, LLC. All rights not expressly
# granted herein are reserved.
# Created in the United States of America.
# 
# This digital media is protected by U.S. and international copyright and
# intellectual property laws. Unless otherwise specified, all information
# and screens appearing as part of this digital medium, including
# software, services, documents, text, images, icons, and logos design;
# the selection, assembly, arrangement, and design thereof; and the code
# that enables its presentation, are the sole property of Isilon Systems,
# LLC.
# 
# Except as otherwise required by applicable law, no part of any
# copyrighted material may be reproduced, modified, translated,
# distributed, published, or transmitted by any means, electronic or
# mechanical, for any purpose, without the prior written consent of Isilon
# Systems, LLC. 
#  
# Isilon and Isilon Systems are registered trademarks of Isilon Systems,
# LLC.  Legion is a trademark of Isilon Systems, LLC.  All other
# trademarks and registered trademarks are the property of their
# respective holders.

# PROVIDE: isi_create_lni_xml
# REQUIRE: isi_pre_network isi_create_hardware_xml isi_hal_init

"""
Generates the lni.xml configuration file, which contains the physical
interface and logical name mapping information for the network hardware of
the local node. This script runs on every boot and writes out a new,
temporary XML file with the current network information. If the temporary
file differs from the primary lni.xml, the script replaces the primary
lni.xml with the temporary one.

The XML consists of:

    <isilon>
      <nics>
        <nic>
          ...
        </nic>
        ...
      </nics>
      <interfaces>
        <lni>
        ...
        </lni>
        ...
      </interfaces>
    </isilon>

"""

import errno
import filecmp
import os
import sys

import xml.dom.minidom as xmldom

import isi.config.rc as rc
import isi.error as ierr
import isi.net.flexnet as flx
import isi.hw.check.lib.sysconfig as syscfg
import isi.hw.hal as hal

from isi.ui.lib.format import fmtUserMsg

# setting used for specially defined nics above
NONE_HWADDR = '00:00:00:00:00:00'

# hardware xml file, generated by /etc/rc.d/isi_create_hardware_xml
HARDWARE_XML = '/etc/ifs/hardware.xml'

# XXX: Assumes we never exceed 16 interfaces of a given type
ORDINAL_DESCS = ["First", "Second", "Third", "Fourth", "Fifth", "Sixth",
                 "Seventh", "Eighth", "Ninth", "Tenth", "Eleventh",
                 "Twelfth", "Thirteenth", "Fourteenth", "Fifteenth",
                 "Sixteenth"]
DISP_DESCS = {
    flx.LNI_INTERNAL_OK: "internal",
    flx.LNI_EXTERNAL_OK: "external",
    flx.LNI_BONDING: "aggregated",
    flx.LNI_BONDED: "aggregable",
}
NIC_TYPE_DESCS = {
    flx.NIC_TYPE_IB: "Infiniband",
    flx.NIC_TYPE_GIGE: "GigE",
    flx.NIC_TYPE_10_GIGE: "10 GigE",
    flx.NIC_TYPE_LOOPBACK: "Loopback",
}

# XXX: Assumes we never exceed 4 internal interfaces
INTERNAL_LNI_NAMES = ["int-a", "int-b", "int-c", "int-d"]
LOOPBACK_LNI_NAME = "lpbk"
GIG_LNI_PREFIX = "ext-"
TENGIG_LNI_PREFIX = "10gige-"
GIG_AGG_LNI_PREFIX = "ext-agg"
TENGIG_AGG_LNI_PREFIX = "10gige-agg-"

def cto_get_lni_name(nic_type, nic_idx, agg_lni_idx, disps):
    if flx.LNI_INTERNAL_OK in disps:
        lni_name = INTERNAL_LNI_NAMES[nic_idx]
    else:
        if nic_type == flx.NIC_TYPE_GIGE:
            if flx.LNI_BONDING in disps:
                lni_name = GIG_AGG_LNI_PREFIX
                if agg_lni_idx > 0:
                    lni_name += '-' + str(agg_lni_idx + 1)
            else:
                lni_name = GIG_LNI_PREFIX + str(nic_idx + 1)
        elif nic_type == flx.NIC_TYPE_LOOPBACK:
            lni_name = LOOPBACK_LNI_NAME
        else:
            if flx.LNI_BONDING in disps:
                lni_name = TENGIG_AGG_LNI_PREFIX + str(agg_lni_idx + 1)
            else:
                lni_name = TENGIG_LNI_PREFIX + str(nic_idx + 1)

    return lni_name

def cto_get_lni_description(nic_type, nic_idx, agg_lni_idx, disps):
    if flx.LNI_BONDING in disps:
        ordstr = ORDINAL_DESCS[agg_lni_idx]
    else:
        ordstr = ORDINAL_DESCS[nic_idx]

    return "%s %s %s interface" % (ordstr,
            " ".join([DISP_DESCS[d] for d in disps]),
            NIC_TYPE_DESCS[nic_type])

# NETINFOs are lists of 7-tuples consisting of the following:
# - NIC name
# - LNI name
# - LNI description
# - List of slave LNI names (if bonded)
# - Interface attributes (space-delimited string)
# - LNI index (order in set of LNIs of the same type)
# - Interface type
def cto_build_netinfo_tuple(nic_name, nic_type, nic_idx, agg_lni_idx,
        disps, peers=None):
    netinfo = []
    netinfo.insert(syscfg.NETINFO_NIC_IDX, nic_name)
    netinfo.insert(syscfg.NETINFO_LNI_IDX,
            cto_get_lni_name(nic_type, nic_idx, agg_lni_idx, disps))
    netinfo.insert(syscfg.NETINFO_DESC_IDX,
            cto_get_lni_description(nic_type, nic_idx, agg_lni_idx, disps))
    if peers is None:
        netinfo.insert(syscfg.NETINFO_SLAVES_IDX, [])
    else:
        netinfo.insert(syscfg.NETINFO_SLAVES_IDX, peers)
    netinfo.insert(syscfg.NETINFO_ATTR_IDX, " ".join(disps))
    netinfo.insert(syscfg.NETINFO_INDEX_IDX, nic_idx)
    if flx.LNI_BONDING in disps:
        netinfo.insert(syscfg.NETINFO_TYPE_IDX, flx.NIC_TYPE_AGGREGATION)
    else:
        netinfo.insert(syscfg.NETINFO_TYPE_IDX, nic_type)
    return tuple(netinfo)

def cto_build_lni_agg_list(netinfo_list, nic_set):
    lni_agg_list = []
    for nic_name in nic_set:
        for ni in netinfo_list:
            if nic_name == ni[syscfg.NETINFO_NIC_IDX]:
                lni_agg_list.append(ni[syscfg.NETINFO_LNI_IDX])
                break

    lni_agg_list.sort()
    return lni_agg_list

def cto_build_netinfo_list():
    netinfo_list = []
    agg_peer_sets = {}

    # Internal interfaces
    for idx, nic in enumerate(hal.getInternalNicNames()):
        netinfo_list.append(cto_build_netinfo_tuple(nic, flx.NIC_TYPE_IB,
                idx, 0, [flx.LNI_INTERNAL_OK]))

    # External GigE interfaces
    for idx, nic in enumerate(hal.getExternalGigeNicNames()):
        netinfo_list.append(cto_build_netinfo_tuple(nic, flx.NIC_TYPE_GIGE,
                idx, 0, [flx.LNI_EXTERNAL_OK, flx.LNI_BONDED]))
        agg_peer_sets[frozenset(hal.getNicAggregationPeers(nic))] = \
                flx.NIC_TYPE_GIGE

    # External 10 GigE interfaces
    for idx, nic in enumerate(hal.getExternal10GigeNicNames()):
        netinfo_list.append(cto_build_netinfo_tuple(nic,
                flx.NIC_TYPE_10_GIGE, idx, 0, [flx.LNI_EXTERNAL_OK,
                flx.LNI_BONDED]))
        agg_peer_sets[frozenset(hal.getNicAggregationPeers(nic))] = \
                flx.NIC_TYPE_10_GIGE

    # Aggregate interfaces

    # Aggregate LNIs are ordered and indexed per NIC_TYPE, so we have to
    # keep track of the current index for each.
    agg_lni_idx_dict = { flx.NIC_TYPE_GIGE: 0, flx.NIC_TYPE_10_GIGE: 0 }

    # Aggregate NICs are ordered and indexed across all NIC_TYPEs. This is
    # because the virtual NICs created by the aggregate driver must all
    # have unique numbers, regardless of NIC_TYPE. So, we have a separate
    # index to track all aggregate NICs.
    agg_nic_idx = 0

    for nic_set, nic_type in agg_peer_sets.items():
        lni_peer_list = cto_build_lni_agg_list(netinfo_list, nic_set)
        if len(nic_set) == 2:
            # Two NIC/LNI peers yields one aggregate combination:
            #    (A, B)
            netinfo_list.append(cto_build_netinfo_tuple(
                    "fec%d" % agg_nic_idx,
                    nic_type, agg_nic_idx, agg_lni_idx_dict[nic_type],
                    [flx.LNI_EXTERNAL_OK, flx.LNI_BONDING],
                    lni_peer_list))
            agg_lni_idx_dict[nic_type] += 1
            agg_nic_idx += 1
        elif len(nic_set) == 4:
            # Four NIC/LNI peers yields 3 aggregate combinations:
            #    (A, B), (C, D), (A, B, C, D)

            # First 2 in peer list
            netinfo_list.append(cto_build_netinfo_tuple(
                    "fec%d" % agg_nic_idx,
                    nic_type, agg_nic_idx, agg_lni_idx_dict[nic_type],
                    [flx.LNI_EXTERNAL_OK, flx.LNI_BONDING],
                    lni_peer_list[:2]))
            agg_lni_idx_dict[nic_type] += 1
            agg_nic_idx += 1

            # Last 2 in peer list
            netinfo_list.append(cto_build_netinfo_tuple(
                    "fec%d" % agg_nic_idx,
                    nic_type, agg_nic_idx, agg_lni_idx_dict[nic_type],
                    [flx.LNI_EXTERNAL_OK, flx.LNI_BONDING],
                    lni_peer_list[2:]))
            agg_lni_idx_dict[nic_type] += 1
            agg_nic_idx += 1

            # All 4 in peer list
            netinfo_list.append(cto_build_netinfo_tuple(
                    "fec%d" % agg_nic_idx,
                    nic_type, agg_nic_idx, agg_lni_idx_dict[nic_type],
                    [flx.LNI_EXTERNAL_OK, flx.LNI_BONDING],
                    lni_peer_list))
            agg_lni_idx_dict[nic_type] += 1
            agg_nic_idx += 1

    return netinfo_list

# create a nic element
def create_nic(doc, name, hwaddr, nic_type):
    d_nic = doc.createElement(flx.LNI_NIC)
    d_nic.setAttribute(flx.LNI_NAME, name)
    d_nic.setAttribute(flx.LNI_NIC_TYPE, str(nic_type))
    d_mac = doc.createElement(flx.LNI_MAC)
    d_mac.appendChild(doc.createTextNode(hwaddr))
    d_nic.appendChild(d_mac)

    return d_nic

def try_load_mac_addr(nic_name):
    mac_addr = NONE_HWADDR
    try:
        mac_addr = flx.nic_load(nic_name).mac_addr
    except ierr.IsiSystemError, e:
        if e.error != errno.EINVAL:
            raise e

    return mac_addr

# create a list of nic elements including loopback nic
def create_nics(doc, mynetinfo):
    d_nics = doc.createElement(flx.LNI_NICS)
    # create all the nic elements from the nic names list 
    for interface in mynetinfo:
        if interface[syscfg.NETINFO_ATTR_IDX].find(flx.LNI_BONDING) == -1:
            # create a nic element and load with hw address
            mac_addr = try_load_mac_addr(interface[syscfg.NETINFO_NIC_IDX])
            d_nics.appendChild(create_nic(doc,
                    interface[syscfg.NETINFO_NIC_IDX], mac_addr,
                    interface[syscfg.NETINFO_TYPE_IDX]))
        else:
            lni_name = interface[syscfg.NETINFO_SLAVES_IDX][0]
            for tmpiface in mynetinfo:
                if tmpiface[syscfg.NETINFO_LNI_IDX] == lni_name:
                    mac_addr = try_load_mac_addr(
                            tmpiface[syscfg.NETINFO_NIC_IDX])
                    break

            # create a nic element for bonded nic with placeholders for hw
            # addr
            d_nics.appendChild(create_nic(doc,
                    interface[syscfg.NETINFO_NIC_IDX], mac_addr,
                    flx.NIC_TYPE_AGGREGATION))

    # create the loopback nic element which is not part of nicnames list
    d_nics.appendChild(create_nic(doc,
            syscfg.NETINFO_LOOPBACK[syscfg.NETINFO_NIC_IDX], NONE_HWADDR,
            syscfg.NETINFO_LOOPBACK[syscfg.NETINFO_TYPE_IDX]))

    return d_nics

# create one lni element
def create_lni(doc, interface):
    d_lni = doc.createElement(flx.LNI)
    d_display = doc.createElement(flx.LNI_DISPLAY)
    d_display.appendChild(doc.createTextNode(
            interface[syscfg.NETINFO_LNI_IDX]))
    d_lni.appendChild(d_display)
    d_desc = doc.createElement(flx.LNI_DESCRIPTION)
    d_desc.appendChild(doc.createTextNode(
            interface[syscfg.NETINFO_DESC_IDX]))
    d_lni.appendChild(d_desc)
    d_blnis = doc.createElement(flx.LNI_BONDED_INTERFACES)
    for blni in interface[syscfg.NETINFO_SLAVES_IDX]:
        d_blni = doc.createElement(flx.LNI_BONDED_INTERFACE)
        d_blni.appendChild(doc.createTextNode(blni))
        d_blnis.appendChild(d_blni)
    d_lni.appendChild(d_blnis)
    d_lni.setAttribute(flx.LNI_NIC, interface[syscfg.NETINFO_NIC_IDX])
    d_lni.setAttribute(flx.LNI_FLAGS, interface[syscfg.NETINFO_ATTR_IDX])
    d_lni.setAttribute(flx.LNI_INDEX,
            str(interface[syscfg.NETINFO_INDEX_IDX]))

    return d_lni

# create the list of lni elements including the Loopback interface 
def create_interfaces(doc, mynetinfo):
    """Creates the <interfaces> tag."""
    d_interfaces = doc.createElement(flx.LNI_INTERFACES)

    # create all interface elements from interface table
    for interface in mynetinfo:
        d_interfaces.appendChild(create_lni(doc, interface))

    # create the loopback interface for all interface configuration versions
    d_interfaces.appendChild(create_lni(doc, syscfg.NETINFO_LOOPBACK))

    return d_interfaces

def guess_nic_type(nicname):
    nic_type = hal.getNicTypeFromName(nicname)

    if nic_type is None:
        # Default to GigE
        nic_type = flx.NIC_TYPE_GIGE

    return nic_type

def prompt_user_for_nic(iface_desc):
    nic_name = None
    while nic_name is None:
        print fmtUserMsg("Enter NIC for the %s" % iface_desc)
        nic_name = raw_input(">>> ").strip().lower()

    return (nic_name, guess_nic_type(nic_name))

def prompt_user_for_custom_netinfo():
    custom_netinfo = []
    nic_num = 0
    while nic_num != "1" and nic_num != "2" and nic_num != "4":
        print fmtUserMsg("Would you like to configure 1, 2, or 4 "
                "NICs?")
        nic_num = raw_input(">>> ").strip().lower()

    # GENERIC nodes currently assume a 2 NIC setup is similar to
    # VMWARE NETINFO_EN2, need shared INTERNAL + EXTERNAL interface
    use_combo_primary_int_ext = (nic_num == "1")
    if hal.isGenericNode() and nic_num == "2":
        use_combo_primary_int_ext = True

    iface_desc = "Primary Internal%s Interface" % \
            (use_combo_primary_int_ext and "/External" or "")
    nic_name, nic_type = prompt_user_for_nic(iface_desc)
    custom_netinfo.append((nic_name, 'int-a', iface_desc, [],
            use_combo_primary_int_ext and "%s %s" % (flx.LNI_INTERNAL_OK,
            flx.LNI_EXTERNAL_OK) or flx.LNI_INTERNAL_OK, 0, nic_type))

    if nic_num == "2":
        iface_desc = "Primary External Interface"
        nic_name, nic_type = prompt_user_for_nic(iface_desc)
        custom_netinfo.append((nic_name, 'ext-1', iface_desc, [],
                flx.LNI_EXTERNAL_OK, 0, nic_type))

    if nic_num == "4":
        iface_desc = "Secondary Internal Interface"
        nic_name, nic_type = prompt_user_for_nic(iface_desc)
        custom_netinfo.append((nic_name, 'int-b', iface_desc, [],
                flx.LNI_INTERNAL_OK, 1, nic_type))

        iface_desc = "Primary External Interface"
        nic_name, nic_type = prompt_user_for_nic(iface_desc)
        custom_netinfo.append((nic_name, 'ext-1', iface_desc, [],
                flx.LNI_EXTERNAL_OK, 0, nic_type))

        iface_desc = "Secondary External Interface"
        nic_name, nic_type = prompt_user_for_nic(iface_desc)
        custom_netinfo.append((nic_name, 'ext-2', iface_desc, [],
                flx.LNI_EXTERNAL_OK, 1, nic_type))

    return custom_netinfo

def nic_name_list(netinfo_list):
    dlist = [ ni[syscfg.NETINFO_NIC_IDX] for ni in netinfo_list
            if ni[syscfg.NETINFO_TYPE_IDX] != flx.NIC_TYPE_AGGREGATION ]
    return dlist

def nic_name_list_str(netinfo_list):
    return str(nic_name_list(netinfo_list))

def prompt_user_for_netinfo():
    strnetinfo = ""
    mynetinfo = None

    # This list must be updated if new HW interface versions are added to
    # isi_hw library.
    while not strnetinfo:
        print fmtUserMsg("Select the list of NICs that matches the "
                "physical NICs present on this node:", prefix="")
        print "1 - %s" % nic_name_list_str(syscfg.NETINFO_EN2_BC2)
        print "2 - %s" % nic_name_list_str(syscfg.NETINFO_EN2_IB2)
        print "3 - %s" % nic_name_list_str(syscfg.NETINFO_EN2)
        print "4 - %s" % nic_name_list_str(syscfg.NETINFO_EN3)
        print "5 - %s" % nic_name_list_str(syscfg.NETINFO_CH1_EN1_IB2)
        print "6 - %s (1U)" % nic_name_list_str(syscfg.NETINFO_CH2_EN2_IB2)
        print "7 - %s (2U, 4U)" % \
            nic_name_list_str(syscfg.NETINFO_LRISER_CH2_EN2_IB2)
        print "8 - %s" % nic_name_list_str(syscfg.NETINFO_CH4_EN2_IB2)
        print "9 - %s" % nic_name_list_str(syscfg.NETINFO_EN4_IB2)
        print "0 - Custom"

        strnetinfo = raw_input(">>> ").strip()
        if strnetinfo not in map(str, range(0,11)):
            strnetinfo = ""
            continue

        if strnetinfo == "0":
            mynetinfo = None
        if strnetinfo == "1":
            mynetinfo = syscfg.NETINFO_EN2_BC2
        if strnetinfo == "2":
            mynetinfo = syscfg.NETINFO_EN2_IB2
        if strnetinfo == "3":
            mynetinfo = syscfg.NETINFO_EN2
        if strnetinfo == "4":
            mynetinfo = syscfg.NETINFO_EN3
        if strnetinfo == "5":
            mynetinfo = syscfg.NETINFO_CH1_EN1_IB2
        if strnetinfo == "6":
            mynetinfo = syscfg.NETINFO_CH2_EN2_IB2
        if strnetinfo == "7":
            mynetinfo = syscfg.NETINFO_LRISER_CH2_EN2_IB2
        if strnetinfo == "8":
            mynetinfo = syscfg.NETINFO_CH4_EN2_IB2
        if strnetinfo == "9":
            mynetinfo = syscfg.NETINFO_EN4_IB2

        if mynetinfo is None:
            mynetinfo = prompt_user_for_custom_netinfo()

    return mynetinfo

# create the lni.xml configuration file
def create_lni_xml():
    """Creates the local lni.xml config file for this node."""

    #
    # Case 1: Can not determine if 3/4 chassis (SKU not programmed)
    #         - Since this case is valid and can occur in MFG before
    #           a node has gone through Wakeup, we must trigger the
    #           auto-detect code to determine the network configuration
    #           by setting mynetinfo to None.
    #
    # Case 2: Is not a 3/4 chassis
    #         - Since we know what the network configuration should 
    #           be for a product SKU, we can use the default key to 
    #           perform the lookup.
    #
    # Case 3: Is a 3/4 NL chassis
    #         - Since a 3/4 NL chassis may or may not have a 10GigE
    #           add-on NIC installed for testing but should NEVER
    #           contain a GigE add-on NIC, we choose the key based
    #           on whether or not there is a 10GigE add-on NIC 
    #           installed and stop and inform the user if we find a 
    #           GigE NIC installed.
    #
    # Case 4: Is a 3/4 X chassis
    #         - Since a 3/4 X chassis should always have an add-on
    #           NIC installed, we must choose the key based on 
    #           which type of NIC is installed and stop and inform
    #           the user if we find that neither NIC is installed.
    #
    is_3qtr = hal.is3qtrChassis()
    is_3qtr_nl = hal.is3qtrChassis() and hal.isNlSeries()
    is_3qtr_x = hal.is3qtrChassis() and hal.isXSeries()

    if hal.isGenericNode():
        mynetinfo = None
    elif is_3qtr is None or is_3qtr_nl is None or is_3qtr_x is None:
        mynetinfo = None
    elif not is_3qtr:
        mynetinfo = syscfg.get_sysconfig_data(data_key='netinfo')
    else:
        has_gige_nic = hal.hasAddonGigeNic()
        has_10gige_nic = hal.hasAddon10gigeNic()

        if is_3qtr_nl:
            #
            # NOTE: Prior to the release of the 108NL, there was a 3/4 chassis
            #       used for NL's and a separate 3/4 chassis for X's. However,
            #       when the 108NL was released, Isilon decided to allow GigE
            #       add-on NICs in that product. This completely blurs the line
            #       between NL and X products and for that reason, we are not
            #       able to specifially dis-allow add-on NICs in 3/4 NL chassis.
            #
            if has_10gige_nic:
                netinfo_key = 'w10g_netinfo'
            else:
                netinfo_key = 'netinfo'

        elif is_3qtr_x:
            if has_10gige_nic:
                netinfo_key = 'w10g_netinfo'
            elif has_gige_nic:
                netinfo_key = 'netinfo'
            else:
                print fmtUserMsg("This 3/4 X chassis must have either a "
                                 "10GigE or a GigE NIC add-on card "
                                 "installed.", prefix="")
                print fmtUserMsg("Please remedy this situation and "
                                 "try again.", prefix="")
                return
        else:
            print fmtUserMsg("Unrecognized type of 3/4 chassis. "
                             "Neither an X, nor an NL.", prefix="")
            netinfo_key = None

        mynetinfo = syscfg.get_sysconfig_data(data_key=netinfo_key)

    impl = xmldom.getDOMImplementation()
    hwxml = impl.createDocument(None, "isilon", None)
    root = hwxml.documentElement

    # CTO nodes get netinfo from HAL APIs.
    if hal.supportsCto():
        mynetinfo = cto_build_netinfo_list()

    # If manufacturing or we cannot read the serial number first try to auto
    # detect the netinfo.
    defaults = rc.parse_all_defaults()
    if defaults.is_yes("manufacturing") or mynetinfo is None:
        auto_set = set(hal.getLegacyNetifaceNames(hal.getLegacyNetiface()))
        for ni in syscfg.NETINFO_LIST:
            ni_set = nic_name_list(ni)
            if len(auto_set.symmetric_difference(ni_set)) == 0:
                mynetinfo = ni
                break

    # If that fails, or this node isn't in manufacturing mode prompt user for
    # custom or unknown interface configuration.
    if mynetinfo is None or (len(sys.argv) > 1 and sys.argv[1] == '-d'):
        if hal.isGenericNode():
            print fmtUserMsg("Generic network configuration for this node "
                             "could not be detected.")
        else:
            print fmtUserMsg("System Configuration for this node could "
                             "not be detected.")
        prompt = 1
    else:
        prompt = 0

    while True:
        if prompt:
            mynetinfo = prompt_user_for_netinfo()
            if mynetinfo is None:
                print fmtUserMsg("Error selecting network interface list. "
                        "Try again.")
                continue
            elif len(mynetinfo) == 0:
                print fmtUserMsg("User-specified network interface list is "
                        "empty. Try again.")
                mynetinfo = None
                continue

        # We have a well-formed netinfo list, see if it works for this
        # node.
        try:
            root.appendChild(create_nics(hwxml, mynetinfo))
            root.appendChild(create_interfaces(hwxml, mynetinfo))
        except Exception, e:
            print fmtUserMsg("Error detecting network interfaces. Expected "
                    "%s: %s" % (nic_name_list(mynetinfo), str(e)))

            print fmtUserMsg("Would you like to specify a different network "
                    "interface list [yes]?", prefix="")
            while True:
                answer = raw_input(">>> ").strip().lower()
                if answer not in ["yes", "no", ""]:
                    print fmtUserMsg("Type 'yes', 'no', or press 'Enter' "
                        "to accept the default")
                else:
                    break

            if answer == "yes" or answer == "":
                prompt = 1
                continue
            else:
                print fmtUserMsg("The network interface configuration file "
                        "will not be created which may prevent this node "
                        "from functioning properly. Are you sure you want "
                        "to continue without specifying a valid network "
                        "interface configuration [no]?", prefix="")
                while True:
                    answer = raw_input(">>> ").strip().lower()
                    if answer not in ["yes", "no", ""]:
                        print fmtUserMsg("Type 'yes', 'no', or press "
                                "'Enter' to accept the default")
                    else:
                        break

                if answer == "no" or answer == "":
                    prompt = 1
                    continue
                else:
                    print fmtUserMsg("Network interface configuration file "
                            "not created.", prefix="")
                    return
        break

    # Write the new lni.xml to a temporary file; force flush/sync to disk.
    f = open(flx.LNI_CONFIG_FILE + ".new", "w")
    hwxml.writexml(f, '', ' ', '\n')
    f.flush()
    os.fsync(f.fileno())
    f.close()

    # Rename to the tmp file if real file doesn't exist or if the tmp
    # file is different (interfaces have changed).
    lni_xml_exists = os.path.exists(flx.LNI_CONFIG_FILE)
    if not lni_xml_exists or not filecmp.cmp(flx.LNI_CONFIG_FILE,
            flx.LNI_CONFIG_FILE + ".new", shallow=False):
        print "%s network interface configuration file" % \
                ("Generated" if not lni_xml_exists else "Regenerated")
        os.rename(flx.LNI_CONFIG_FILE + '.new', flx.LNI_CONFIG_FILE)
    else:
        os.unlink(flx.LNI_CONFIG_FILE + ".new")

    os.chmod(flx.LNI_CONFIG_FILE, 0644)

def main(argv):
    # remove any existing override file
    try:
        os.unlink("/etc/mcp/override/lni.xml")
    except:
        pass

    # generate/regenerate lni.xml in case interfaces have changed.
    create_lni_xml()

    return 0

if __name__ == '__main__':
    sys.exit(main(sys.argv))
