#! /usr/bin/env python
'''A tool that performs a CTO node conversion'''

# Copyright C 2008-2011 by Isilon Systems, Inc. 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.

#
# A tool that performs a CTO node conversion
#
import os
import re
import sys
import time
import getopt
import syslog

import isi.error
import isi.app.lib.procs as procs
import isi.hw.hal as hal
import isi.misc as misc

import isi_cto_lib as cto

#----------------------------------------------------------------------
def doConversion(packageLocation, noPrompt=False):
    '''Perform the node conversion using the CTO files in the directory
        specified by 'packageLocation' and return error status (error).
        if 'noPrompt' is True, all user prompting will be skipped. This
        is intended solely for development and QA testing and should 
        NEVER be used in the field.'''

    # Sanity check to make sure the node is not already CTO capable
    error = cto.verifyCtoCapable()
    if not error:
        print 'Node is already CTO capable - no need for conversion'
        return error

    # Start by verifying the chassis VPD
    (error, beforeSku) = cto.verifyVPD(cto.VPDTYPE_PRODUCT_SKU)
    if error:
        return error
    (error, beforeSerialNo) = cto.verifyVPD(cto.VPDTYPE_SERIALNO)
    if error:
        return error
    
    print 'Chassis VPD verified (pre-transformation)'

    # Transform the chassis SKU from 851 to CTO base chassis (400)
    error = cto.transformChassisSKU(beforeSku)
    if error:
        return error

    print 'Node SKU transformed'

    # Re-Verify the chassis VPD post transformation
    (error, afterSku) = cto.verifyVPD(cto.VPDTYPE_BASE_SKU)
    if error:
        return error
    (error, afterSerialNo) = cto.verifyVPD(cto.VPDTYPE_SERIALNO)
    if error:
        return error

    print 'Chassis VPD verified (post-transformation)'

    # Import the CTO files
    skuParts = beforeSku.split('-')
    receiptName = 'BTS861%s%s.xml' % (skuParts[1], skuParts[2])
    error = cto.importCtoInformation(packageLocation, receiptName)
    if error:
        return error

    print 'CTO files imported'

    # Verify the node is now CTO capable
    if hasattr(hal, 'reinitSupportsCto'):
        hal.reinitSupportsCto()
        error = cto.verifyCtoCapable()
        if error:
            return error
        print 'Node CTO capability verified'
    else:
        print 'No CTO reinit - skip capability verification'


    # Ask the user if the node should be rebooted
    print 'Node conversion successful'
    if not noPrompt:
        answer = cto.ask('Are you ready to reboot the node? (y/n) ')
        if answer.lower().startswith('y'):
            print 'Rebooting node...'
            error = cto.rebootNode(cto.METHOD_REBOOT)
            if error:
                return error
        else:
            print 'Reboot the node when ready'
    else:
        print 'Skipping user prompting due to noprompt option'

    return error

#----------------------------------------------------------------------
def doVerification(noPrompt=False, noSendABR=False):
    '''Verify the node conversion was successful by re-verifying the
        chassis VPD data is consistent between disk and EEPROM, 
        the node is still CTO capable and running a node configuration
        check. If the configuration check fails, ask the user if they
        still wish to generate a new ABR and send it to Isilon.
        Returns error status (error). if 'noPrompt' is True, all 
        user prompting will be skipped. This is intended solely for 
        development and QA testing and should NEVER be used in the field.
        If 'noSendABR' is True, rather than sending the new ABR to Isilon,
        a copy of the new ABR will be left in the user's current directory.'''

    error = 0

    # Verify the chassis VPD
    (error, sku) = cto.verifyVPD(cto.VPDTYPE_BASE_SKU)
    if error:
        return error
    (error, serialNo) = cto.verifyVPD(cto.VPDTYPE_SERIALNO)
    if error:
        return error

    print 'Chassis VPD verified'

    # Make sure the node is still CTO capable
    error = cto.verifyCtoCapable()
    if error:
        return error

    print 'Node CTO capability verified'

    # Run a configuration check on the node. If the configuration 
    # check fails, ask user if they want to ignore the failure and 
    # generate an ABR anyway
    error = cto.verifyConfigCheck()
    if error:
        if not noPrompt:
            print ''
            answer = cto.ask('Generate a new ABR anyway? (y/n) ')
            if answer.lower().startswith('y'):
                print 'Generating new ABR and sending to Isilon'
            else:
                print 'Fix the failures and re-run verify'
                error = 1
                return error
        else:
            print 'Skipping user prompting due to noprompt option'
    else:
        print 'Node CTO configuration verified'

    # Generate and send a new ABR to Isilon
    error = cto.generateAndSendABR(serialNo, noSendABR)
    if error:
        return error

    if not noSendABR:
        print 'ABR generated and sent to Isilon'

    return error

#----------------------------------------------------------------------
def usage():
    '''Print the usage message'''

    usage = (
'''Usage: %s [OPTION] [ARGS]

OPTIONS:
    -h, --help  Prints this text
    --convert   Perform the conversion
    --filepath <package location>  Designate a package location
    --verify    Verify the conversion was successsful
''')

    print usage % sys.argv[0]

#----------------------------------------------------------------------
def main(argv):
    '''The main 851 to CTO conversion script'''

    productSku = None
    serialNo = None
    convert = False
    verify = False
    noPrompt = False
    noSendABR = False
    filepath = None

    try:
        # NOTE: The 'noprompt' and 'nosendabr' options are for testing
        #       only and hidden from the usage message on purpose.
        opt, args = getopt.getopt(argv, 'h', ['convert', 'verify',
            'noprompt', 'nosendabr', 'filepath=']) 

    except getopt.GetoptError, err:
        print str(err)
        usage()
        sys.exit(1)

    for o, a in opt:
        if o in ('-h', '--help'):
            usage()
            sys.exit(0)
        elif o in ('--convert'):
            convert = True
        elif o in ('--verify'):
            verify = True
        elif o in ('--noprompt'):
            noPrompt = True
        elif o in ('--nosendabr'):
            noSendABR = True
        elif o in ('--filepath'):
            filepath = a

    if not convert and not verify:
        print 'Missing required option'
        usage()
        sys.exit(1)

    if convert and verify:
        print 'Must be run separately'
        usage()
        sys.exit(1)

    # Filepath option must be provided in compliance mode
    if misc.sysctlbyname('isi.compliance') == 1 and not filepath:
        print '--filepath argument must be used in compliance mode'
        usage()
        sys.exit(1)
    
    syslog.openlog('isi_cto_convert', 0, syslog.LOG_USER)
    
    # Get package location
    (packageLocation, scriptName) = cto.getPackageInfo(sys.argv[0])
    # If filepath option is provided, override packageLocation
    if filepath:
        packageLocation = filepath

    # Verify package integrity
    msg = 'Verifying package integrity'
    syslog.syslog(syslog.LOG_NOTICE, msg)
    print msg
    error = cto.verifyMd5(packageLocation)
    if error:
        msg = 'Package integrity is BAD'
        syslog.syslog(syslog.LOG_CRIT, msg)
        print msg
        sys.exit(1)
    msg = 'Package integrity is good'
    syslog.syslog(syslog.LOG_NOTICE, msg)
    print msg

    if convert:
        msg = 'Beginning conversion'
        # Perform conversion
        syslog.syslog(syslog.LOG_NOTICE, msg)
        print msg
        error = doConversion(packageLocation, noPrompt)
        if error:
            msg = 'Conversion failed'
            syslog.syslog(syslog.LOG_CRIT, msg)
            print msg
            sys.exit(1)
        msg = 'Conversion successful'
        syslog.syslog(syslog.LOG_NOTICE, msg)
        print msg

    elif verify:
        # Verify conversion
        msg = 'Beginning verification'
        syslog.syslog(syslog.LOG_NOTICE, msg)
        print msg
        error = doVerification(noPrompt, noSendABR)
        if error:
            msg = 'Verification failed'
            syslog.syslog(syslog.LOG_CRIT, msg)
            print msg
            sys.exit(1)
        msg = 'Verification successful'
        syslog.syslog(syslog.LOG_NOTICE, msg)
        print msg

    print 'Done'
    sys.exit(0)

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