#!/usr/bin/env python

# Copyright C 2008-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,
# Inc.
# 
# 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. 
#  
# Isilon and Isilon Systems are registered trademarks of Isilon Systems,
# Inc.  Legion is a trademark of Isilon Systems, Inc.  All other
# trademarks and registered trademarks are the property of their
# respective holders.

# This script determines if a "isi_fcb_vpd_tool pull" call is necessary
# and calls the pull command if needed.  Also, for Resume EEPROMs,
# this script determines if the Resume EEPROM Isilon VPD (serno AKA
# serial number, and/or partno AKA system config number AKA SysConfig)
# are 'NULL', indicating that the chassis is a FRU replacement chassis
# from a disk tango swap, which is grounds for a "isi_fcb_vpd_tool push"
# of the node's serno/partno data to the Resume.  NOTE that we will use
# use "isi_fcb_vpd_tool write" to update individual Resume values.

import getopt
import os
import sys
import re

import isi.hw.hal as hal
import isi.hw.version as hwver
import isi.hw.dongle as hwdongle
import isi.app.lib.procs as procs

def get_sku(system_config):
    SKU = 0
    SKU_cases = [812, 851, 400, 900, 610]
    if system_config: # handles non-string None case
        for sku in SKU_cases:
            if system_config.startswith("%03d-" % sku):
                SKU = sku
                break
    return SKU

SERIAL_FILE = "/etc/isilon_serial_number"
CONFIG_FILE = "/etc/isilon_system_config"

def get_legacy_filename(filename):
    return "$LEGACY_%s" % filename

#------------------------------------------------------------------------------

def supports_resume():
    if hal.supportsResumeEeprom():
        return True
    return False

def supports_eeprom():
    if hal.supportsChassisVpdEeprom():
        return True
    if hal.supportsResumeEeprom():
        return True
    return False

def supports_bmc():
    if hal.supportsBMC():
        return True
    return False

def supports_bmc_vpd():
    if hal.supportsBMCVpd():
        return True
    return False

#------------------------------------------------------------------------------

def print_value(value, name, filename=None, update=False):
    width = len('System Config Number in EEPROM is now') # Worst case
    if value is None:
        print("Could not get %s from %s" %
              (name, filename and 'disk' or 'EEPROM'))
        return
    if filename:
        desc = '%s contains' % filename
    elif update:
        desc = '%s in EEPROM is now' % name
    else:
        desc = '%s in EEPROM is' % name
    print("%-*s: %s" % (width, desc, value))

#------------------------------------------------------------------------------
def bmc_pull():
    if hwdongle.bmc_pull():
        print "BMC Pull Failed"

def eeprom_pull():
    eepromSerialNumber = diskSerialNumber = None
    eepromSystemConfig = diskSystemConfig = None

    # Get system config from /etc
    try:
        with open(CONFIG_FILE, 'r') as f:
            diskSystemConfig = f.readline().rstrip('\n')
    except EnvironmentError as e:
        diskSystemConfig = None
        print "Could not get VPD from disk: %s" % e
    try:
        with open(SERIAL_FILE, 'r') as f:
            diskSerialNumber = f.readline().rstrip('\n')
    except EnvironmentError as e:
        diskSerialNumber = None
        print "Could not get VPD from disk: %s" % e

    # Determine EEPROM system config and serial number
    regexSystemConfig = re.compile(".*%s\s*:\s*\[?\s*([^]]*)\s*\]?.*" % CONFIG_FILE)
    regexSerialNumber = re.compile(".*%s\s*:\s*\[?\s*([^]]*)\s*\]?.*" % SERIAL_FILE)

    # Get system config from EEPROM
    # Remember any errorDump failure reading from EEPROM - if unable to read,
    # do not write below (updating Resume for NULL SKU or serial number).
    cmd = '/usr/bin/isi_hwtools/isi_fcb_vpd_tool dump'
    errorDump, output = procs.get_cmd_output(cmd)
    if errorDump:
        print("Error: isi_fcb_vpd_tool dump failed, could not get VPD from EEPROM")
        for line in output:
            print "Error: %s" % line

    for line in output:
        match = regexSerialNumber.match(line)
        if match:
            eepromSerialNumber = match.group(1).split(',')[0].strip(" '")
        match = regexSystemConfig.match(line)
        if match:
            eepromSystemConfig = match.group(1).split(',')[0].strip(" '")

    print_value(eepromSystemConfig, 'System Config Number')
    print_value(diskSystemConfig, 'System Config Number', filename=CONFIG_FILE)
    print_value(eepromSerialNumber, 'Serial Number')
    print_value(diskSerialNumber, 'Serial Number', filename=SERIAL_FILE)

    diskSKU = get_sku(diskSystemConfig)
    eepromSKU = get_sku(eepromSystemConfig)

    # Logic for executing VPD write to Resume EEPROM if necessary
    # If either eepromSKU or eepromSerialNumber are NULL, where we
    # define NULL to be an empty string '' (at least first byte in
    # Resume data field is 0x00=='\0'), and we have a valid node
    # SKU/SERNO, attempt write to Resume EEPROM before pull.
    # Never write an 812 SKU to EEPROM.  In fact -- ONLY write
    # expected Resume SKU part numbers, namely 900 or 610
    # Never write if previous failure reading EEPROM above, in dump.

    if supports_resume() and (not eepromSKU or not eepromSerialNumber):
        validResumeSKUs = [900, 610]
        eepromWriteData = {}
        if not eepromSKU and diskSKU in validResumeSKUs:
            eepromWriteData[CONFIG_FILE] = diskSystemConfig
        if not eepromSerialNumber and diskSerialNumber:
            # Strip dashes, just in case of legacy style SERNO.
            # Resume EEPROM only accepts 16 char serial numbers.
            eepromWriteData[SERIAL_FILE] = diskSerialNumber.replace('-', '')
        if eepromWriteData and errorDump:
            # Do not write if previous failure reading EEPROM
            print("Error: EEPROM may have invalid %s values, "
                  "but isi_fcb_vpd_tool dump failed; "
                  "Not writing VPD to Resume EEPROM from disk" %
                  " and ".join(eepromWriteData.keys()))
        elif eepromWriteData:
            # On success of 'writen' command, this will invalidate our
            # (cached) variables, namely eepromSKU. Update as needed.
            # One approach is to re-do ALL of the above queries specific
            # to the EEPROM. A simpler approach is to merely update our
            # variables before continuing, on successful writes.
            values = ["'%s' '%s'" % (get_legacy_filename(filename), value)
                      for filename, value in eepromWriteData.iteritems()]
            cmd = ('/usr/bin/isi_hwtools/isi_fcb_vpd_tool writen %d %s' %
                   (len(eepromWriteData), ' '.join(values)))
            error, output = procs.get_cmd_output(cmd)
            if error:
                print("Error: isi_fcb_vpd_tool write failed, could not "
                      "write VPD to Resume EEPROM from disk")
                for line in output:
                    print "Error: %s" % line
            else:
                print "Wrote VPD to Resume EEPROM (FRU replacement chassis?):"
                if CONFIG_FILE in eepromWriteData:
                    # Determine updated EEPROM SKU
                    eepromSystemConfig = eepromWriteData[CONFIG_FILE]
                    eepromSKU = get_sku(eepromSystemConfig)
                    print_value(eepromSystemConfig, 'System Config Number',
                                update=True)
                if SERIAL_FILE in eepromWriteData:
                    # Determine updated EEPROM Serial Number (unused...)
                    eepromSerialNumber = eepromWriteData[SERIAL_FILE]
                    print_value(eepromSerialNumber, 'Serial Number',
                                update=True)

    # Logic for executing VPD pull if necessary
    pullCmd = '/usr/bin/isi_hwtools/isi_fcb_vpd_tool pull'

    if (eepromSKU in [400, 851, 900, 610] and diskSKU == 0):
        (errorPull, outputPull) = procs.get_cmd_output(pullCmd)
        if errorPull:
            print("Error: isi_fcb_vpd_tool pull failed, could not pull VPD "
                  "from EEPROM to disk")
            for line in outputPull:
                print "Error: %s" % line
        else:
            print "Pulling VPD from EEPROM to create disk copy"

    elif (eepromSKU in [400, 851, 900, 610] and diskSKU == eepromSKU):
        (errorPull, outputPull) = procs.get_cmd_output(pullCmd)
        if errorPull:
            print("Error: isi_fcb_vpd_tool pull failed, could not pull VPD "
                  "from EEPROM to disk")
            for line in outputPull:
                print "Error: %s" % line
        elif diskSKU == 851:
            print "Node SKUs appear correct, pulling VPD from EEPROM to disk"
        elif diskSKU in [400, 900, 610]:
            print("Node System Config Number appears correct (CTO node), "
                  "pulling VPD from EEPROM to disk")

    elif (diskSKU == 851 and eepromSKU == 812):
        print "Pending disk tango, not pulling VPD from EEPROM to disk"

    elif (diskSKU == 812 and eepromSKU == 812):
        print("Warning: 812 SKU on disk and in EEPROM, not pulling VPD from "
              "EEPROM to disk")

    else:
        print("Error: Not pulling VPD from EEPROM to disk due to invalid "
              "System Config Number")
        if diskSKU == 0:
            print "Error: Invalid System Config Number on disk"
        if eepromSKU == 0:
            print "Error: Invalid System Config Number in EEPROM"

def eeprom_sysconfig_read():
    sysconfig = None
    regex = re.compile("\s*\S*%s\s*=\s*\[\s*'(\S+)'\s*.*" % CONFIG_FILE)
    filename = get_legacy_filename(CONFIG_FILE)
    cmd = ("/usr/bin/isi_hwtools/isi_fcb_vpd_tool read '%s'" % filename)
    error, output = procs.get_cmd_output(cmd)
    if not error and len(output):
        for line in output:
            match = regex.match(line)
            if match:
                sysconfig = match.group(1)
    elif error:
        print "Error: isi_fcb_vpd_tool read failed:"
        for line in output:
            print "Error: %s" % line
    return sysconfig

def bmc_sysconfig_read():
    sysconfig = None
    cmd = ('/usr/local/sbin/dmidecode -s "chassis-version"')
    error, output = procs.get_cmd_output(cmd)
    if not error and len(output):
        sysconfig = output[0]
    return sysconfig

def reimage_pull():
    if supports_eeprom():
        print 'Pulling System Config Number from EEPROM'
        sysconfig = eeprom_sysconfig_read()
    elif supports_bmc():
        print 'Pulling System Config Number from BMC'
        sysconfig = bmc_sysconfig_read()
    else:
        sysconfig = None
    if sysconfig:
        print 'System Config Number: %s' % sysconfig
        with open(CONFIG_FILE, 'w') as f:
            f.write('%s\n' % sysconfig)
            f.flush()
    else:
        print 'Unable to get System Config Number'

def main(argv):
    reimage = False
    try:
        opts, args = getopt.getopt(argv, "r", ['reImage'])
    except getopt.GetoptError, err:
        print str(err)
        sys.exit(1)
        
    for opt, arg in opts:
        if opt in ['-r', '--reImage']:
            reimage = True

    # As part of 7.1.0, USB reimages now run isi_inventory_tool.
    # For Bug 109508, a way to determine whether a S/X200
    # is CTO or SKU was needed. To accomplish this, the reImage
    # flag was added to isi_vpd_pull to only pull the system config
    # number and write it to disk.
    if reimage:
        reimage_pull()
        return

    if supports_eeprom():
        eeprom_pull()
    elif supports_bmc_vpd():
        bmc_pull()
    else:
        return

#------------------------------------------------------------------------------
if __name__ == "__main__":
    main(sys.argv[1:])
