#!/usr/local/bin/python
       
# Copyright C 2001-2011 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.
#
#####
#
#   Script to change HEAD system configuration and/or serial numbers
#   and change the DON owner serial number.
#

import os
import sys
import re
import getopt

import isi.app.lib.procs as procs
import isi.hw.check.lib.sysconfig as sysconfig
import isi.hw.hal as hal
import isi.hw.version as hwver

verbose = False

isilon_system_config = '/etc/isilon_system_config'
isilon_system_serial = '/etc/isilon_serial_number'
isi_dongle_sync      = '/usr/bin/isi_dongle_sync'
isi_vitutil          = '/usr/bin/isi_hwtools/isi_vitutil'

def usage(code):
    print >> sys.stderr, 'usage: isi_update_serialno [-h] [-v]'
    print >> sys.stderr, "\t-h:  help"
    print >> sys.stderr, "\t-v:  verbose"
    sys.exit(code)

#
#    Prompt user for the new serial number.
#
#    Loop until the user specifies a valid serial number
#    or enters <return> to keep the current serial number.
#
#    Legacy serial numbers have the following format:
#
#                                 [ADdKkGgMmRr]YYWWXXXX[SsLlJjPp]         
#
#                                  N/A   = Rainier node (no prefix)
#                                  A     = Accelerator node
#                                  Dd    = Disk-only (DON) node
#                                  Gg    = Graham node
#                                  Mm    = Mauna Loa node
#                                  Rr    = Racer-X node
#                                  YY    = year
#                                  WW    = week
#                                  XXXX  = 4-digit per-platform unique id
#                                  XXXXX = 5-digit per-platform unique id
#                                  Ss    = Sanmina
#                                  Ll    = Flextronix
#                                  Jj    = Jabil
#                                  Pp    = Prototype (Built in Engineering)
#
# Examples of node legacy serial number formats:
#
# Rainier node     :  NA      YYWWXXXXM
# Accelerator node : 'A'     AYYWWXXXXM
# DON node         : 'D'     DYYWWXXXXM
# Graham node      : 'G'     GYYWWXXXXXM
# MaunaLoa node    : 'M'     MYYWWXXXXXM
# Racer-X node     : 'R'     RYYWWXXXXXM
#
# Node SN trailing CM indicator 'M': S=Sanmina, L=Solectron (Flextronics)
#                                  : J=Jabil, P=Prototype (Engineering)
# 
#    New serial numbers have the following format:
#
#       SFCGG-MMYYWW-XXXX
#       S  = "Serial number", always 'S'
#       F  = Family: 
#            X: X-series   (incl. Wingfoot)
#            S: S-series   (incl. Blueflame)
#            N: NL-series
#       C  = Class:
#            2: 2U
#            4: 4U
#       GG = Generation: 
#            00: 1st Gen (X or S)
#       MM = Manufacturer Code: Numeric: (NOT "Month"!!)
#            These are the same the as PCA component codes:
#            10: Sanmina 
#            20: Flextronics (Austin)
#            21: Flextronics (Shenzhen)
#            22: Flextronics (Services)
#            30: Jabil       (Memphis)
#            31: Jabil       (Wuxi)
#            40: Isilon      (Plymouth) (Internal, Prototypes)
#            41: Isilon      (Seattle)  (Internal, Prototypes)
#       YY = Year
#       WW = Week
#     XXXX = 4-digit serno; Resets every week
#
# Examples of node new serial number formats:
#
# Wingfoot node    :         SX200-MMYYWW-XXXX
# Blue Flame node  :         SS200-MMYYWW-XXXX
#                                           
def prompt_for_serialno(chassis, cur_serialno):
    global verbose
    reg_serial_1 = sysconfig._NODE_SERIAL_REGEX('','',4)
    reg_serial_2 = sysconfig._NODE_SERIAL_REGEX('[ADd]','',4)
    reg_serial_3 = sysconfig._NODE_SERIAL_REGEX('[AGgMmRr]','',5)
    reg_serial_4 = sysconfig._BONNE_NODE_SERIAL_REGEX('[NnSsXx]', 
        '[24]', '\d{2}')
    regx_serial_1 = re.compile(reg_serial_1)
    regx_serial_2 = re.compile(reg_serial_2)
    regx_serial_3 = re.compile(reg_serial_3)
    regx_serial_4 = re.compile(reg_serial_4)
    prompt = 'Chassis %d: Enter new serial number [current %s] > ' \
              % (chassis, cur_serialno)

    while True:
        new_serialno = raw_input(prompt)

        if regx_serial_1.match(new_serialno) or \
           regx_serial_2.match(new_serialno) or \
           regx_serial_3.match(new_serialno) or \
           regx_serial_4.match(new_serialno) or \
           not new_serialno:
            break
        else:
            print '\nError:  Invalid serial number "%s"' % new_serialno
            print 'Valid legacy serial numbers follow the following formats:'
            print ''
            print '    Node Type             Prefix   Serial Number Format'
            print '    ---------             ------   --------------------'
            print '    Rainier                N/A         YYWWXXXXM'
            print '    Ullr/Fuego Accelerator "A"        AYYWWXXXXM'
            print '    EX Chassis (GP DON)    "D"        DYYWWXXXXM'
            print '    Annapurna Accelerator  "A"       AYYWWXXXXXM'
            print '    Graham                 "G"       GYYWWXXXXXM'
            print '    Mauna Loa              "M"       MYYWWXXXXXM'
            print '    Racer-X                "R"       RYYWWXXXXXM'
            print ''
            print '                           Where:'
            print '                                 YY    = Year'
            print '                                 WW    = Week'
            print '                                 XXXXX = Unique ID'
            print '                                 M     = Manufacturer Code'
            print ''
            print '                           Manufacturer Codes are:'
            for (key, val) in sysconfig._CM_NODE_CODES.iteritems():
                print '                                 %s: %s' % (key, val)
            print ''
            print '    Modern Serial Number Format'
            print '    ---------------------------'
            print '    SFCGG-MMYYWW-XXXX'
            print ''
            print '    Where:'
            print '        S  = "Serial number", always \'S\''
            print '        F  = Family:' 
            for (key, val) in sysconfig._FAM_NODE_CODES.iteritems():
                print '             %s: %s' % (key, val)
            print '        C  = Class:'
            for (key, val) in sysconfig._CLS_NODE_CODES.iteritems():
                print '             %s: %s' % (key, val)
            print '        GG = Generation:' 
            for (key, val) in sysconfig._GEN_NODE_CODES.iteritems():
                print '             %s: %s' % (key, val)
            print '        MM = Manufacturer Code:'
            print '              - Numeric: (NOT "Month"!!)'
            print '              - These are the same as the'
            print '                PCA component codes:'
            for (key, val) in sysconfig._CM_LOCATION_CODES.iteritems():
                print '                  %s: %s' % (key, val)
            print '        YY = Year'
            print '        WW = Week'
            print '      XXXX = 4-digit serno; Resets every'
            print '             week.'

    if verbose:
        print 'current serial number %s  new serial number %s' \
               % (cur_serialno, new_serialno)

    return new_serialno


#
#    Prompt user for the new configuration number.
#
#    Loop until the user specifies a valid configuration number
#    or enters <return> to keep the current configuration number.
#
#    System configuration numbers have the following format:
#
#            XXX-XXXX-XX
#
def prompt_for_configno(chassis, cur_configno):
    global verbose
    reg_config = '\d{3}-\d{4}-\d{2}$'
    regx_config = re.compile(reg_config)
    prompt = 'Chassis %d: Enter new configuration number (XXX-XXXX-XX) ' \
             '[current %s] > ' % (chassis, cur_configno)

    while True:
        new_configno = raw_input(prompt)

        if regx_config.match(new_configno) or not new_configno:
            break
        else:
            print '\nError:  Invalid configuration number "%s"' % new_configno
            print 'Valid configuration numbers follow the following format:'
            print ''
            print ' XXX-XXXX-XX'
            print ''

    if verbose:
        print 'current configuration number %s  new configuration number %s' \
                % (cur_configno, new_configno)

    return new_configno


def main(args):
    global verbose

    try:
        opts, args = getopt.getopt(sys.argv[1:], 'hv')
    except:
        usage(1)

    #
    #    Parse the command line arguments.
    #
    for opt, arg in opts:
        if opt == '-h':
            usage(0)
        if opt == '-v':
            verbose = True

    #
    #   Get the chassis counts for this node.
    #
    chassis_count = hal.getChassisCount()
    chassis_total_up = hal.getActiveChassisCount()
    if chassis_count != chassis_total_up:
        print "Warning:  not all chassis' are up (up %d total %d)" % \
              (chassis_total_up, chassis_count)
    if verbose:
        print "Chassis count %d  up %d" % (chassis_count, chassis_total_up)

    #
    #   Prompt the user for possible changes to the HEAD chassis config number.
    #
    sync_the_dongle   = False
    head_old_configno = hal.getChassisConfigNumber(0)
    head_new_configno = prompt_for_configno(0, head_old_configno)
    if verbose:
        print "HEAD System Config Number:  old %s new %s" % \
              (head_old_configno, head_new_configno)
    if head_new_configno:
        print 'Changing HEAD system configuration number from "%s" to "%s"' % \
              (head_old_configno, head_new_configno)
        cmd = "echo %s > %s" % (head_new_configno, isilon_system_config)
        (error, output) = procs.get_cmd_output(cmd)
        if error:
             print "Writing system configuration number to %s failed" % \
                   isilon_system_config
             sys.exit(1)
        else:
             sync_the_dongle = True

    #
    #   Prompt the user for possible changes to the HEAD chassis serial number.
    #
    head_old_serialno = hal.getChassisSerialNumber(0)
    head_serialno     = head_old_serialno
    head_new_serialno = prompt_for_serialno(0, head_old_serialno)
    if verbose:
        print "HEAD Serial Number:  current %s new %s" % \
              (head_old_serialno, head_new_serialno)
    if head_new_serialno:
        print 'Changing HEAD serial number from "%s" to "%s"' % \
              (head_old_serialno, head_new_serialno)
        cmd = "echo %s > %s" % (head_new_serialno, isilon_system_serial)
        (error, output) = procs.get_cmd_output(cmd)
        if error:
             print "Writing serial number to %s failed" % \
                   isilon_system_serial
             sys.exit(1)
        else:
             sync_the_dongle = True
             head_serialno = head_new_serialno

    #
    #   Loop through each DON chassis; change the owner serial number.
    #
    for chassis in range(1, chassis_count):
        chassis_is_up = hal.chassisIsActive(chassis)
        if not chassis_is_up:
            print "Chassis %d is not up, skipping..." % chassis
            continue

        don_owner_serialno = hwver.chasnOwnerSerialno(chassis)

        if verbose:
            print "Chassis %d serial number:  head %s owner %s" % \
                  (chassis, head_serialno, don_owner_serialno)

        #
        #     Possible HEAD serialno and DON owner serialno scenarios.
        #
        #  +================================================================+
        #  | OLD HEAD |  NEW HEAD |   CURRENT DON  ||   OK to change DON    |
        #  | serialno |  serialno | owner serialno ||   owner serialno?     |
        #  +==========+===========+================++=======================+
        #  |     X    |     Y     |     missing    || OK-change to Y        |
        #  |     X    |     Y     |        X       || OK-change to Y        |
        #  |     X    |     Y     |        Y       || WARN-from other node? |
        #  |     X    |     Y     |        Z       || WARN-from other node? |
        #  +----------+-----------+----------------++-----------------------+
        #  |     X    | unchanged |     missing    || OK-change to X        |
        #  |     X    | unchanged |        X       || No change needed      |
        #  |     X    | unchanged |        Y       || WARN-from other node? |
        #  |     X    | unchanged |        Z       || WARN-from other node? |
        #  +----------+-----------+----------------++-----------------------+
        #  |  missing |     Y     |     missing    || OK-change to Y        |
        #  |  missing |     Y     |        X       || OK-change to Y        |
        #  |  missing |     Y     |        Y       || OK-change to Y        |
        #  |  missing |     Y     |        Z       || OK-change to Y        |
        #  +----------+-----------+----------------++-----------------------+
        #  |  missing | unchanged |   don't care   || ERROR                 |
        #  +================================================================+
        #
	if not head_old_serialno and not head_new_serialno:
            print "Error:  the HEAD chassis doesn't have a valid serial number"
            print "Can't change chassis %d owner serial number" % chassis
            continue

        if head_serialno == don_owner_serialno:
            print "Chassis %d owner serial number (%s) is correct" \
                   % (chassis, don_owner_serialno)
            continue

        if (don_owner_serialno and head_old_serialno
            and (don_owner_serialno != head_old_serialno)):
	    print "WARN:  chassis %d could have originated from another node" \
                  % chassis
            print "Proceed with extreme caution.  If you change the owner" \
                  " serial number"
            print "on this chassis and it does not belong to this node," \
                  " you will lose"
            print "*all* the user data from this chassis"

        prompt = "Change chassis %d owner serial number from" \
                 " %s to %s? (y/n):n: " \
                 % (chassis, don_owner_serialno, head_serialno)

        response = raw_input(prompt).lower()
        if response in ['y', 'yes']:
            cmd = '%s -so "%s" chas%d' % (isi_vitutil, head_serialno, chassis)
            (error, output) = procs.get_cmd_output(cmd)
            if error:
                print "%s, failed to change owner serial number" % cmd
            else:
                print "Changed chassis %d owner serial number from" \
                      " %s to %s" % \
                      (chassis, don_owner_serialno, head_serialno)
        else:
            print 'Chassis %d owner serial number (%s) is unchanged' \
                  % (chassis, don_owner_serialno)

    if sync_the_dongle == True:
        print "*****************************************************" \
            "***********************"
        print "WARNING: Changing the system configuration and/or " \
            "serial number requires"
        print "         a reboot before the change(s) will take effect. " \
            "Please reboot as "
        print "         soon as possible!"
        print "*****************************************************" \
            "***********************"
        if verbose == True:
            print "Syncing the dongle....."
        cmd = "%s -p" % isi_dongle_sync
        (error, output) = procs.get_cmd_output(cmd)
        if error:
            print "Error:  writing changes to the dongle failed"
            sys.exit(1)

    return 0


if __name__ == '__main__':

    status = main(sys.argv)

    sys.exit(status)

