blob: 58b39636f2666b1ac31d28f11f4f2003332f1d9b [file]
#!/usr/bin/env python3
#
# Copyright (c) Greenplum Inc 2010. All Rights Reserved.
#
#
# THIS IMPORT MUST COME FIRST
# import mainUtils FIRST to get python version check
#
from gppylib.mainUtils import *
import os, sys
import json
import re
from optparse import Option, OptionGroup, OptionParser, OptionValueError
from gppylib.gpparseopts import OptParser, OptChecker
from gppylib import gplog, gparray, pgconf
from gppylib.commands import base, gp, pg, unix
from gppylib.db import dbconn
logger = gplog.get_default_logger()
# Return codes for PQping(), exported by libpq and returned from pg_isready.
PQPING_OK = 0
PQPING_REJECT = 1
PQPING_NO_RESPONSE = 2
PQPING_NO_ATTEMPT = 3
PQPING_MIRROR_READY = 64
# When attempting to connect to a mirror, the postmaster will report the
# software version after a short prefix. This is that prefix, which must match
# that in <postmaster/postmaster.h>.
POSTMASTER_MIRROR_VERSION_DETAIL_MSG = "- VERSION:"
def _get_segment_status(segment):
cmd = base.Command('pg_isready for segment',
"PGOPTIONS=\"-c gp_role=utility\" pg_isready -q -h %s -p %d -d %s" % (segment.hostname, segment.port, gp.PGDATABASE_FOR_COMMON_USE))
cmd.run()
rc = cmd.get_return_code()
if rc == PQPING_OK:
if segment.role == gparray.ROLE_PRIMARY:
return 'Up'
elif segment.role == gparray.ROLE_MIRROR:
return 'Acting as Primary'
elif rc == PQPING_REJECT:
return 'Rejecting Connections'
elif rc == PQPING_NO_RESPONSE:
return 'Down'
elif rc == PQPING_MIRROR_READY:
if segment.role == gparray.ROLE_PRIMARY:
return 'Acting as Mirror'
elif segment.role == gparray.ROLE_MIRROR:
return 'Up'
return None
# Used by _get_segment_version() to find the version string for a mirror.
_version_regex = re.compile(r'%s (.*)' % POSTMASTER_MIRROR_VERSION_DETAIL_MSG)
def _get_segment_version(seg):
try:
if seg.role == gparray.ROLE_PRIMARY:
dburl = dbconn.DbURL(hostname=seg.hostname, port=seg.port, dbname="template1")
conn = dbconn.connect(dburl, utility=True)
return dbconn.querySingleton(conn, "select version()")
if seg.role == gparray.ROLE_MIRROR:
cmd = base.Command("Try connecting to mirror",
"psql -h %s -p %s template1 -c 'select 1'"
%(seg.hostname, seg.port))
cmd.run(validateAfter=False)
if cmd.results.rc == 0:
raise RuntimeError("Connection to mirror succeeded unexpectedly")
stderr = cmd.results.stderr.splitlines()
for line in stderr:
match = _version_regex.match(line)
if match:
return match.group(1)
raise RuntimeError("Unexpected error from mirror connection: %s" % cmd.results.stderr)
logger.error("Invalid role '%s' for dbid %d", seg.role, seg.dbid)
return None
except Exception as ex:
logger.error("Could not get segment version for dbid %d", seg.dbid, exc_info=ex)
return None
#
# todo: the file containing this should be renamed since it gets more status than just from transition
#
class GpSegStatusProgram:
"""
Program to fetch status from the a segment(s).
Multiple pieces of status information can be fetched in a single request by
passing in multiple status request options on the command line
"""
def __init__(self, options):
self.__options = options
self.__pool = None
def getPidStatus(self, seg, pidRunningStatus):
"""
returns a dict containing "pid" and "error" fields. Note that
the "error" field may be non-None even when pid is non-zero (pid if zero indicates
unable to determine the pid). This can happen if the pid is there in the
lock file but not active on the port.
The caller can rely on this to try to differentiate between an active pid and an inactive one
"""
lockFileExists = pidRunningStatus['lockFileExists']
ssPortActive = pidRunningStatus['ssPortActive']
pidValue = pidRunningStatus['pidValue']
lockFileName = gp.get_lockfile_name(seg.getSegmentPort())
error = None
if not lockFileExists and not ssPortActive:
error = "No socket connection or lock file (%s) found for port %s" % (lockFileName, seg.getSegmentPort())
elif not lockFileExists and ssPortActive:
error = "No lock file %s but process running on port %s" % (lockFileName, seg.getSegmentPort())
elif lockFileExists and not ssPortActive:
error = "Have lock file %s but no process running on port %s" % (lockFileName, seg.getSegmentPort())
else:
if pidValue == 0:
error = "Have lock file and process is active, but did not get a pid value" # this could be an assert?
res = {}
res['pid'] = pidValue
res['error'] = error
return res
def getPidRunningStatus(self, seg):
"""
Get an object containing various information about the postmaster pid's status
"""
(postmasterPidFileExists, tempFileExists, lockFileExists, ssPortActive, pidValue) = \
gp.chk_local_db_running(seg.getSegmentDataDirectory(), seg.getSegmentPort())
return {
'postmasterPidFileExists' : postmasterPidFileExists,
'tempFileExists' : tempFileExists,
'lockFileExists' : lockFileExists,
'ssPortActive' : ssPortActive,
'pidValue' : pidValue
}
def run(self):
if self.__options.statusQueryRequests is None:
raise ProgramArgumentValidationException("-s argument not specified")
if self.__options.dirList is None:
raise ProgramArgumentValidationException("-D argument not specified")
toFetch = self.__options.statusQueryRequests.split(":")
segments = list(map(gparray.Segment.initFromString, self.__options.dirList))
output = {}
for seg in segments:
pidRunningStatus = self.getPidRunningStatus(seg)
outputThisSeg = output[seg.getSegmentDbId()] = {}
for statusRequest in toFetch:
data = None
if statusRequest == gp.SEGMENT_STATUS__GET_VERSION:
data = _get_segment_version(seg)
elif statusRequest == gp.SEGMENT_STATUS__GET_MIRROR_STATUS:
data = _get_segment_status(seg)
if data is not None:
data = {'databaseStatus': data}
elif statusRequest == gp.SEGMENT_STATUS__GET_PID:
data = self.getPidStatus(seg, pidRunningStatus)
elif statusRequest == gp.SEGMENT_STATUS__HAS_POSTMASTER_PID_FILE:
data = pidRunningStatus['postmasterPidFileExists']
elif statusRequest == gp.SEGMENT_STATUS__HAS_LOCKFILE:
data = pidRunningStatus['lockFileExists']
else:
raise Exception("Invalid status request %s" % statusRequest )
outputThisSeg[statusRequest] = data
status = '\nSTATUS_RESULTS:' + json.dumps(output)
logger.info(status)
def cleanup(self):
if self.__pool:
self.__pool.haltWork()
@staticmethod
def createParser():
parser = OptParser(option_class=OptChecker,
description="Gets status from segments on a single host "
"using a transition message. Internal-use only.",
version='%prog version $Revision: #1 $')
parser.setHelp([])
addStandardLoggingAndHelpOptions(parser, True)
addTo = parser
addTo.add_option("-s", None, type="string",
dest="statusQueryRequests",
metavar="<statusQueryRequests>",
help="Status Query Message")
addTo.add_option("-D", "--dblist", type="string", action="append",
dest="dirList",
metavar="<dirList>",
help="Directory List")
parser.set_defaults()
return parser
@staticmethod
def createProgram(options, args):
if len(args) > 0 :
raise ProgramArgumentValidationException(\
"too many arguments: only options may be specified", True)
return GpSegStatusProgram(options)
#-------------------------------------------------------------------------
if __name__ == '__main__':
mainOptions = { 'setNonuserOnToolLogger':True}
simple_main( GpSegStatusProgram.createParser, GpSegStatusProgram.createProgram, mainOptions)