#!/usr/bin/env python
'''
USAGE:   gpcheck_hostdump [--appliance] [--linux] [--solaris]
         where --appliance will do a dump of appliance server
         where --linux will do a dump of a generic linux server 
         where --solaris will do a dump of a generic solaris server 
'''

import os, sys, re, tempfile, subprocess, pickle, glob, stat, time

try:
    from optparse import Option, OptionParser 
    from gppylib.gpparseopts import OptParser, OptChecker
    from gppylib.gpcheckutil import HostType, ApplianceOutputData, GenericLinuxOutputData, GenericSolarisOutputData
    from gppylib.gpcheckutil import chkconfig, omreport, grubconf, mounts, GpMount, GpMount, inittab, ntp
    from gppylib.gpcheckutil import securetty, ioschedulers, blockdev, bcu, rclocal, sysctl, limitsconf, limitsconf_entry, uname, connectemc
    from gppylib.gpcheckutil import solaris_etc_system, solaris_etc_project, solaris_etc_user_attr
except ImportError, e:    
    sys.exit('Cannot import modules.  Please check that you have sourced greenplum_path.sh.  Detail: ' + str(e))

output = None
options = None
hosttype = HostType.GPCHECK_HOSTTYPE_UNDEFINED

def removeComments(line):
    words = line.split("#")
    if len(words) < 2:
        return line
    return words[0]


def collectChkconfigData():

    data = chkconfig()

    p = subprocess.Popen("/sbin/chkconfig --list", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    result = p.communicate()

    data.errormsg = result[1].strip()

    if p.returncode:
        return data

    startxInet = False
    for line in result[0].splitlines():
        if not startxInet:
            if re.search("xinetd based services", line):
                startxInet = True
            else:
                words = line.split()
                if len(words) != 8:
                    continue
                levels = dict()
                for i in range(1,8):
                    state = words[i].split(":")
                    if state[1] == 'on':
                        levels[state[0]] = True
                    else:
                        levels[state[0]] = False
                data.services[words[0]] = levels
        else:
            words = line.split()
            if len(words) != 2:
                continue
            name = words[0].split(":")
            if words[1] == 'on':
                data.xinetd[name[0]] = True
            else:
                data.xinetd[name[0]] = False
        
    
    return data

# argument is a reference to instantiated omreport object
def omReportOmVersion(data):

    # Obtain Dell OpenManage software version

    p = subprocess.Popen("omreport system version", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    result = p.communicate()

    data.omversion_errormsg = result[1].strip()

    if p.returncode:
        return

    nextLineisOurs = False
    for line in result[0].splitlines():

        words = line.split(":")
        if len(words) != 2:
            continue

        if nextLineisOurs:
            data.omversion = words[1].strip()
            break

        label = words[1].strip()
        if label == 'Dell Server Administrator':
            nextLineisOurs = True



# argument is a reference to instantiated omreport object
def omReportStorageController(data):

    p = subprocess.Popen("omreport storage controller", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    result = p.communicate()

    data.controller_errormsg = result[1].strip()

    if p.returncode:
        return

    for line in result[0].splitlines():

        words = line.split(":")
        if len(words) != 2:
            continue

        data.controller[words[0].strip()] = words[1].strip()


# argument is a reference to instantiated omreport object
def omReportStorageVdisk(data):

    p = subprocess.Popen("omreport storage vdisk controller=0", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    result = p.communicate()

    data.vdisks_errormsg = result[1].strip()

    if p.returncode:
        return
    
    counter = -1
    for line in result[0].splitlines():
        if re.search("ID\s*:", line):
            counter += 1
            data.vdisks.append(dict())
        words = line.split(":")
        if len(words) < 2:
            continue
        data.vdisks[counter][words[0].strip()] =  ":".join(words[1:]).strip()


# argument is a reference to instantiated omreport object
def omReportChassisRemoteaccess(data):

    p = subprocess.Popen("omreport chassis remoteaccess", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
    result = p.communicate()

    if p.returncode:
        # the remote access command on omreport is flaky... retry it once
        p = subprocess.Popen("omreport chassis remoteaccess", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
        result = p.communicate()
        data.remoteaccess_errormsg = result[1].strip()

    if p.returncode:
        data.remoteaccess_errormsg = "%s\nreturn code from omreport %d" % (data.remoteaccess_errormsg, p.returncode)
        return

    attribute = None
    for line in result[0].splitlines():

        if not attribute:
            if re.search("Attribute :", line):
                words = line.strip().split("Attribute :")
                attribute = words[1].strip()
        else:
            if re.search("Value     :", line):
                words = line.strip().split("Value     :")
                data.remoteaccess[attribute] = words[1].strip()

            attribute = None

# argument is a reference to instantiated omreport object
def omReportChassisBios(data):

    p = subprocess.Popen("omreport chassis bios", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    result = p.communicate()

    data.bios_errormsg = result[1].strip()

    if p.returncode:
        return

    for line in result[0].splitlines():

        words = line.split(":")

        if len(words) != 2:
            continue

        data.bios[words[0].strip()] = words[1].strip()

# argument is a reference to instantiated omreport object
def omReportChassisBiossetup(data):

    p = subprocess.Popen("omreport chassis biossetup", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    result = p.communicate()

    data.biossetup_errormsg = result[1].strip()

    if p.returncode:
        return

    attribute = None
    for line in result[0].splitlines():

        if not attribute:
            if re.search("Attribute :", line):
                words = line.strip().split("Attribute :")
                attribute = words[1].strip()
        else:
            if re.search("Setting   :", line):
                words = line.strip().split("Setting   :")
                data.biossetup[attribute] = words[1].strip()

            attribute = None

# argument is a reference to instantiated omreport object
def omReportSystemAlertlog(data):

    p = subprocess.Popen("omreport system alertlog", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    result = p.communicate()

    data.alerts_errormsg = result[1].strip()

    if p.returncode:
        return

    openAlert = False

    for line in result[0].splitlines():

        words = line.split(":")
        if len(words) < 2:
            openAlert = False
            continue

        if len(words) != 2 and len(words) != 4:
            continue

        key = words[0].strip()

        if len(words) == 4:
            if words[0].strip() != 'Date and Time':
                continue

            value = ":".join(words[1:])
        else:
            value = words[1].strip()

        if key == 'Severity':

            openAlert = False
            
            # this is not an OK alert ... add it to the list and start adding attributes
            if value != 'Ok':
                workingDictionary = dict()
                data.alerts.append(workingDictionary)
                openAlert = True

        if openAlert:
            # add kvp to last element in list of alerts
            data.alerts[-1][key] = value


# argument is a reference to instantiated omreport object
def omReportChassisBiossetupBootorder(data):

    p = subprocess.Popen("omreport chassis biossetup attribute=bootorder", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    result = p.communicate()

    data.bootorder_errormsg = result[1].strip()

    if p.returncode:
        return

    for line in result[0].splitlines():
        if re.search("BIOS Hard Disk Sequence", line):
            break

        if re.search("Device Name :", line):
            words = line.strip().split("Device Name :")
            data.bootorder.append(words[1].strip())
 
# returns populated omreport object
def collectOmReports():

    data = omreport()

    omReportOmVersion(data)
    omReportChassisBios(data)
    omReportChassisBiossetup(data)
    omReportChassisBiossetupBootorder(data)
    omReportChassisRemoteaccess(data)
    omReportStorageVdisk(data)
    omReportStorageController(data)
    omReportSystemAlertlog(data)

    return data

def collectBcu():

    data = bcu()
    p = subprocess.Popen("bcu adapter --query 1", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    result = p.communicate()

    data.errormsg = result[1].strip()

    if p.returncode:
        return data

    for line in result[0].splitlines():
        if re.search("bios version:", line):
            words = line.strip().split("bios version:")
            if len(words) == 2:
                data.biosversion = words[1].strip()
        if re.search("fw version:", line):
            words = line.strip().split("fw version:")
            if len(words) == 2:
                data.firmware = words[1].strip()

    return data


# returns populated mounts object
def collectMounts():

    data = mounts()

    p = subprocess.Popen("mount", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    result = p.communicate()

    data.errormsg = result[1].strip()

    if p.returncode:
        return data

    for line in result[0].splitlines():

        mdata = GpMount()
        words = line.strip().split()
        mdata.partition = words[0]
        mdata.dir = words[2]
        mdata.type = words[4]

        # get the options string
        tmpa = words[5]
        tmpb = tmpa.strip().strip("()")
        tmpc = tmpb.split(",")
        for op in tmpc:
            mdata.options.add(op)

        data.entries[mdata.partition] = mdata

    return data

# returns populated blockdev object
def collectBlockdev():
    
    data = blockdev()
    devices = list()

    try:
        devices = glob.glob("/dev/sd*")

    except Exception, e:
        data.errormsg = e.__str__()

    for d in devices:

        p = subprocess.Popen("/sbin/blockdev --getra %s" % d, shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

        result = p.communicate()

        data.errormsg += result[1].strip()

        if p.returncode:
            continue

        data.ra[d] = result[0].strip()
    
    return data


# returns populated ioschedulers object
def collectIOschedulers():
    
    data = ioschedulers()
    devices = set()

    try:
        files = glob.glob("/sys/block/*/queue/scheduler")

        for f in files:
            words = f.split("/")
            if len(words) != 6:
                continue
            devices.add(words[3].strip())

    except Exception, e:
        data.errormsg = e.__str__()

    for d in devices:

        try:
            fd = open("/sys/block/%s/queue/scheduler" % d, 'r')
            scheduler = fd.read()
            words = scheduler.split("[")
            if len(words) != 2:
                continue
            words = words[1].split("]")
            if len(words) != 2:
                continue

            data.devices[d] = words[0].strip()

        except Exception, e:
            data.errormsg += e.__str__()
    
    return data


# returns populated grubconf object
def collectGrubConf():
    data = grubconf()

    try:
        for line in open("/boot/grub/grub.conf", "r"):
            line = removeComments(line)
            if re.search("^serial ", line):
                data.serial_declaration = True
            if re.search("^terminal ", line):
                data.terminal_declaration = True
            if re.search("kernel", line):
                if re.search(" console=ttyS1,", line):
                    data.ttyS1_declaration = True
    
    except Exception, e:
        data.errormsg = e.__str__()
    
    return data


# returns populated limitsconf object
def collectLimits():

    data = limitsconf()

    try:
        for line in open("/etc/security/limits.conf", "r"):

            line = removeComments(line)

            words = line.split()
            if len(words) != 4:
                continue

            domain = words[0].strip()
            type = words[1].strip()
            item = words[2].strip()
            value = words[3].strip()
            data.lines.append(limitsconf_entry(domain, type, item, value))

    except Exception, e:
        data.errormsg = e.__str__()

    return data




# returns populated sysctl object
def collectSysctl():

    data = sysctl()

    try:
        for line in open("/etc/sysctl.conf", "r"):

            line = removeComments(line)
            words = line.split("=")
            if len(words) != 2:
                continue

            key = words[0].strip()
            value = words[1].strip()
            data.variables[key] = value

    except Exception, e:
        data.errormsg = e.__str__()

    return data

# returns populated rclocal object
def collectRclocal():

    data = rclocal()
    data.isexecutable = False

    try:
        bits = os.stat('/etc/rc.d/rc.local')[stat.ST_MODE]
        if bits & 0100:
            data.isexecutable = True

    except Exception, e:
        pass

    return data

# returns populated inittab object
def collectInittab():
    data = inittab()

    try:
        for line in open("/etc/inittab", "r"):
            if re.search("^id:", line):
                words = line.split(":")
                if len(words) > 2:
                    data.defaultRunLevel = words[1]

            if re.search("^S1", line):
                data.s1 = True
    
    except Exception, e:
        data.errormsg = e.__str__()
    
    return data

# returns populated ojbect of type: ntp
def collectNtpd():

    data = ntp()

    p = subprocess.Popen("/usr/sbin/ntpq -p", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    result = p.communicate()

    data.errormsg = result[1].strip()

    if not p.returncode:

        startHosts = False
        for line in result[0].splitlines():

            if startHosts:
                words = line.split()
                if len(words) < 2: # there are actually 10 fields but we only care about the first field
                    continue
                host = words[0].strip()
                if host.startswith("*"):
                    host = host.lstrip("*")
                data.hosts.add(host)
            else:
                if re.search("======", line):
                    startHosts = True

    p = subprocess.Popen("pgrep ntpd", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    result = p.communicate()

    if data.errormsg:
        data.errormsg = "%s\n%s" % (data.errormsg, result[1].strip())
    else:
        data.errormsg = result[1].strip()

    if not p.returncode:
        for line in result[0].splitlines():
            try:
                pid = int(line.strip())
                data.running = True
            except:
                pass

    data.currenttime = time.time()

    return data

 # returns populated uname object
def collectUname():

    data = uname()

    p = subprocess.Popen("uname -r", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    result = p.communicate()

    data.errormsg = result[1].strip()

    if p.returncode:
        return data

    data.output = result[0].strip()
 
    return data

 # returns populated connectemc object
def collectConnectemc():

    data = connectemc()

    p = subprocess.Popen("/etc/init.d/connectemc status", shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)

    result = p.communicate()

    data.errormsg = result[1].strip()

    if p.returncode:
        return data

    data.output = result[0].strip()
 
    return data


 # returns populated securetty object
def collectSecuretty():
    data = securetty()

    try:
        for line in open("/etc/securetty", "r"):

            val = line.strip()
            data.data.add(val)

    except Exception, e:
        data.errormsg = e.__str__()
    
    return data
        
# returns poulated object of type: solaris_etc_system
def collectSolarisEtcSystem():

    # create dictionary
    data = solaris_etc_system()

    # comments begin with either a * or #
    # lines we care about look like: set rlim_fd_max=262144

    try:
        for line in open("/etc/system", "r"):

            line = line.strip()
            if line.startswith("#"):
                continue
            if line.startswith("*"):
                continue
            words = line.split("set ")
            if len(words) != 2:
                continue
            words = words[1].split("=")
            if len(words) != 2:
                continue
            key = words[0].strip()
            value = words[1].strip()
            data.parameters[key] = value

    except Exception, e:
        data.errormsg = e.__str__()

    return data

# returns poulated object of type: solaris_etc_project
def collectSolarisEtcProject():
    
    # store list of lines
    # ignore lines that start with # or do not contain :
    data = solaris_etc_project()

    try:
        for line in open("/etc/project", "r"):

            val = line.strip()
            if val.startswith("#"):
                continue
            if not re.search(":", val):
                continue
            data.lines.append(val)

    except Exception, e:
        data.errormsg = e.__str__()

    return data

# returns poulated object of type: solaris_etc_user_attr
def collectSolarisEtcUserAttr():

    # store list of lines
    # ignore lines that start with # or do not contain :
    data = solaris_etc_user_attr()

    try:
        for line in open("/etc/user_attr", "r"):

            val = line.strip()
            if val.startswith("#"):
                continue
            if not re.search(":", val):
                continue
            data.lines.append(val)

    except Exception, e:
        data.errormsg = e.__str__()
 
    return data

def processGenericSolarisServer():
    global output
    output = GenericSolarisOutputData()
    output.etc_system = collectSolarisEtcSystem()
    output.etc_project = collectSolarisEtcProject()
    output.etc_user_attr = collectSolarisEtcUserAttr()
    output.uname = collectUname()

def processGenericLinuxServer():

    global output
    output = GenericLinuxOutputData()
    output.mounts = collectMounts()
    output.uname = collectUname()
    output.ioschedulers = collectIOschedulers()
    output.blockdev = collectBlockdev()
    output.sysctl = collectSysctl()
    output.limitsconf = collectLimits()
    output.ntp = collectNtpd()


def processApplianceServer():

    global output
    output = ApplianceOutputData()
    output.chkconfig = collectChkconfigData()
    output.omreport = collectOmReports()
    output.grubconf = collectGrubConf()
    output.mounts = collectMounts()
    output.inittab = collectInittab()
    output.uname = collectUname()
    output.securetty = collectSecuretty()
    output.ioschedulers = collectIOschedulers()
    output.blockdev = collectBlockdev()
    output.bcu = collectBcu()
    output.rclocal = collectRclocal()
    output.sysctl = collectSysctl()
    output.limitsconf = collectLimits()
    output.connectemc = collectConnectemc()
    output.ntp = collectNtpd()

# True is an error
def parseargs():

    global options, hosttype

    parser = OptParser(option_class=OptChecker)
    parser.remove_option('-h')
    parser.add_option('-h', '-?', '--help', action='store_true')
    parser.add_option('--appliance',  action='store_true')
    parser.add_option('--linux',  action='store_true')
    parser.add_option('--solaris',  action='store_true')
    (options, args) = parser.parse_args()

    global gphome
    gphome = os.environ.get('GPHOME')
    if not gphome:
        logger.error("GPHOME not set")
        return True

    if options.help:
        print __doc__
        return True

    if options.solaris:
        hosttype = HostType.GPCHECK_HOSTTYPE_GENERIC_SOLARIS

    if options.linux:
        hosttype = HostType.GPCHECK_HOSTTYPE_GENERIC_LINUX

    if options.appliance:
        hosttype = HostType.GPCHECK_HOSTTYPE_APPLIANCE

    if hosttype == HostType.GPCHECK_HOSTTYPE_UNDEFINED:
        print "host type not specified on command line"
        return True

    return False


if (parseargs()):
    sys.exit(1)

if hosttype == HostType.GPCHECK_HOSTTYPE_APPLIANCE:
    processApplianceServer()
elif hosttype == HostType.GPCHECK_HOSTTYPE_GENERIC_LINUX:
    processGenericLinuxServer()
elif hosttype == HostType.GPCHECK_HOSTTYPE_GENERIC_SOLARIS:
    processGenericSolarisServer()
   
(fd, filename) = tempfile.mkstemp(dir='/tmp', prefix='gpcheck_dump', text=True)
fd = open(filename, "wb")
pickle.dump(output, fd)
fd.close()
print filename
