#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) Greenplum Inc 2010. All Rights Reserved. 
#

import os

# import GPDB modules
try:
    from gppylib.gpparseopts import *
    from gppylib.gplog import *
    from gppylib.gparray import GpArray
    from gppylib.db import dbconn
    from gppylib.commands.base import WorkerPool, Command, LOCAL, REMOTE
except ImportError, e:
    sys.exit('ERROR: Cannot import modules.  Please check that you '
             'have sourced greenplum_path.sh.  Detail: ' + str(e))

EXECNAME = os.path.split(__file__)[-1]

FIX_SUBNET_DESC="Updated address for dbid %s to fix primary/mirror subnet conflict."

#-------------------------------------------------------------------------
# SQL Queries
#-------------------------------------------------------------------------

#Segment count per hostname
SEG_COUNT_PER_HOST_SQL="""
    SELECT hostname, count(*) 
        FROM gp_segment_configuration 
        WHERE content > -1 GROUP BY 1 ORDER BY 2
"""

UNIQUE_HOSTNAMES_SQL="""
    SELECT distinct(hostname) from gp_segment_configuration
"""

# Primary segments on same subnet as it's mirror segment
PRIMARY_MIRROR_SAME_SUBNET_SQL="""
    SELECT  p.dbid AS primary_dbid, m.dbid AS mirror_dbid, 
            p.hostname AS primary_hostname, m.hostname AS mirror_hostname,
            p.address AS primary_addr, m.address AS mirror_addr 
        FROM gp_segment_configuration p JOIN gp_segment_configuration m 
        ON p.content = m.content WHERE p.role = 'p' AND m.role = 'm' AND p.content > -1 
            AND substr(p.address, strpos(p.address, '-') + 1) = substr(m.address, strpos(m.address, '-') + 1)
        ORDER BY 1
"""

IN_FAULT_OR_RECOVERY_SQL="""
    SELECT * 
        FROM gp_segment_configuration
        WHERE role <> preferred_role OR status = 'd' OR mode <> 's'
"""


class GetApplianceVersion(Command):
    
    DCA_VERSION_FILE='/etc/gpdb-appliance-version'
    
    def __init__(self, name,ctxt=LOCAL, remoteHost=None):
        cmdStr = 'cat %s' % GetApplianceVersion.DCA_VERSION_FILE
        Command.__init__(self,name,cmdStr,ctxt,remoteHost)

    def get_version(self):
        try:
            res = self.get_results()
            (major, minor, subminor, subsubminor) = res.stdout.strip().split('.')
        except Exception, ex:
            major=0
            minor=0
            subminor=0
            subsubminor=0
        return (int(major), int(minor), int(subminor), int(subsubminor))

def parseargs():
    """parses and validates command line args."""
    
    parser = OptParser(option_class=OptChecker,
                       version='%prog version $Revision: #1 $')
    parser.setHelp([])
    parser.remove_option('-h')
    
    # General options section
    optgrp = OptionGroup(parser, 'General options')
    optgrp.add_option('-?', '--help', dest='help', action='store_true',
                      help='display this help message and exit')
    optgrp.add_option('-v', dest='version', action='store_true',
                      help='display version information and exit')
    parser.add_option_group(optgrp)

    # Logging options section
    optgrp = OptionGroup(parser, 'Logging options')
    optgrp.add_option('-q', '--quiet', action='store_true',
                      help='quiet mode, do not log progress to screen')
    optgrp.add_option('-D', '--debug', action='store_true', default=False,
                      help='enable debug logging')
    parser.add_option_group(optgrp)

    # SQL generation options
    optgrp = OptionGroup(parser, 'Output options')
    optgrp.add_option('-o', '--out-file', type='string', dest='out_file',
                      help='Filename to output fix SQL')
    optgrp.add_option('-c', '--check-only', action='store_true', dest='check_only',
                      help='Only validate configuration. Do not generate SQL.')
    parser.add_option_group(optgrp)
    
    # Parse the command line arguments
    (options, args) = parser.parse_args()

    if options.help:
        parser.print_help()
        parser.exit(0, None)

    if options.version:
        parser.print_version()
        parser.exit(0, None)

    if options.out_file and options.check_only:
        logger.error("-c and -o cannot be specified together.")
        parser.exit(1, None)
        
    if not options.out_file and not options.check_only:
        logger.error("Option -c or -o must be specified.")
        parser.exit(1, None)
        
    return (options, args)


#-------------------------------------------------------------------------
def db_to_str(db):
    return "%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s" % (db.content,
                                                db.dbid,
                                                db.role,
                                                db.preferred_role,
                                                db.mode,
                                                db.status,
                                                db.hostname,
                                                db.address,
                                                db.port,
                                                db.datadir,
                                                db.replicationPort)


#-------------------------------------------------------------------------
def seg_count_per_host_is_valid(conn):
    curr = dbconn.execSQL(conn, SEG_COUNT_PER_HOST_SQL)
    res = curr.fetchall()
    curr.close()
    
    return res[0][1] == res[-1][1]


#-------------------------------------------------------------------------
def get_wrong_subnet_segments(conn):
    res = []
    curr = dbconn.execSQL(conn, PRIMARY_MIRROR_SAME_SUBNET_SQL)
    for r in curr:
        res.append(r)
    curr.close()
    
    return res



#-------------------------------------------------------------------------
def generate_fix_sql(filename, gparray, wrong_subnet_segs):
    fp = None
    
    us_dict = {}
    for us in wrong_subnet_segs:
        us_dict[us[1]] = True
    
    try:
        fp = open(filename, 'w')
        logger.info("Writing update SQL to %s" % filename)
        
        fp.write("-- content, dbid, role, preferred_role, mode, status, hostname, address, port, datadir, replicationPort\n")
        fp.write("-- ----------------------------------------------------------------------------------------------------\n")
        for db in gparray.getDbList():
            if us_dict.has_key(db.dbid):
                fp.write("-- *** %s\n" % db_to_str(db))
            else:
                fp.write("-- %s\n" % db_to_str(db))
            
        fp.write("\n")    
        for us in wrong_subnet_segs:
            (host,interface) = us[5].split('-')
            if interface == '1':
                new_address = '%s-%s' % (host, '2')
            else:
                new_address = '%s-%s' % (host, '1')
            
            fp.write("UPDATE gp_segment_configuration SET address = '%s' WHERE dbid = %s;\n" % (new_address, us[1]))
            fp.write("INSERT INTO gp_configuration_history VALUES (now(), %s, '%s');\n" % (us[1], FIX_SUBNET_DESC % us[1]))
            fp.write("\n")
    except:
        raise
    finally:
        if fp:
            fp.close()


#-------------------------------------------------------------------------
def validate_appliance_version(conn):
    res = True
    curr = dbconn.execSQL(conn, UNIQUE_HOSTNAMES_SQL)
    hosts = curr.fetchall()
    curr.close()
    
    try:
        pool = WorkerPool()
        
        for h in hosts:
            cmd = GetApplianceVersion('Appliance ver on %s' % h[0], ctxt=REMOTE, remoteHost=h[0])
            pool.addCommand(cmd)
             
        pool.wait_and_printdots(len(hosts))
        
        for cmd in pool.getCompletedItems():
            (major, minor, subminor, subsubminor) = cmd.get_version()
            if major != 1 or minor > 0:
                res = False
    except Exception, ex:
        res = False
        logger.exception(ex)
    finally:
        pool.haltWork()
        pool.joinWorkers()
    
    return res
    



#-------------------------------------------------------------------------
def is_in_fault(conn):
    curr = dbconn.execSQL(conn, IN_FAULT_OR_RECOVERY_SQL)
    segs_in_fault = curr.rowcount
    curr.close()
    return segs_in_fault != 0



#-------------------------------------------------------------------------
# Main
#-------------------------------------------------------------------------

if __name__ == '__main__':
    url = None
    conn = None
    gparray = None
    incorrect_subnet_cfg = False
    logger = get_default_logger()
    
    try:
        (options, args) = parseargs()
        
        # Turn on debug logging if needed
        if options.debug:
            enable_verbose_logging()
        elif options.quiet:
            quiet_stdout_logging()
    
        url = dbconn.DbURL()
        logger.info("Connecting to %s@%s:%s..." % (url.pguser, url.pghost, url.pgport))
        conn = dbconn.connect(url, verbose=options.debug)

        logger.info("Checking appliance version...")
        if not validate_appliance_version(conn):
            logger.error('One or more hosts in the array are running an appliance version')
            logger.error('later than 1.0.2.0.  This utility is not designed for hardware')
            logger.error('used by this version.')
            sys.exit(1)

        logger.info("Checking segment state...")
        if is_in_fault(conn):
            logger.error("Currently there are segments that are not in their preferred role")
            logger.error("or are in the process of resyncronizing.  This utility cannot validate")
            logger.error("the segment configuration while the database is in this state.")
            logger.error("Please rerun this utility after segment recovery has completed.")
            sys.exit(1)
            
        logger.info("Checking segment configuration...")
        if not seg_count_per_host_is_valid(conn):
            logger.error("The check of segment configuration found that some hosts")
            logger.error("have more segments than others.  This utility cannot handle")
            logger.error("this configuration.  Please contact EMC/Greenplum support for")
            logger.error("assistance with your configuration.")
            sys.exit(3)

        logger.info("Retrieving array configuration...")
        gparray = GpArray.initFromCatalog(url)

        logger.info("Checking for mirror segments on incorrect subnet...")
        wrong_subnet_segs = get_wrong_subnet_segments(conn)
        if len(wrong_subnet_segs) > 0:
            incorrect_subnet_cfg = True
            if options.out_file:
                generate_fix_sql(options.out_file, gparray, wrong_subnet_segs)
                logger.info("You should verify the generated SQL file with EMC/Greenplum")
                logger.info("support prior to executing it.")
            else:
                logger.info("%s mirror segments were found to be on the same subnet" % len(wrong_subnet_segs))
                logger.info("as their associated primary segment.")
                logger.info("You can run %s with the -o option to generate a SQL" % EXECNAME)
                logger.info("file that will correct this configuration issue.")
        else:
            logger.info("Primary and mirror segments appear to be on the")
            logger.info("correct subnets already.")
            
        logger.info("Done.")
        
        if incorrect_subnet_cfg:
            sys.exit(2)
    
    except KeyboardInterrupt:
        logger.info("User canceled")
        sys.exit(4)    
    except Exception, ex:
        logger.exception(ex)
        sys.exit(5)
    finally:
        if conn:
            conn.close()