Your IP : 18.220.194.29


Current Path : /lib64/nagios/plugins/nccustom/
Upload File :
Current File : //lib64/nagios/plugins/nccustom/check_ntp_client

#!/usr/libexec/platform-python

import argparse
import csv
import io
import os
import platform
import socket
import subprocess
import sys

def main(args):
#    os_family           = platform.linux_distribution()[0]

    if args.daemon == 'chronyc':
        if args.resource == 'leap_status':
            chronyc_monitoring     = chronycTracking()

            resultToIcinga(chronyc_monitoring)

        elif args.resource == 'sources':
            sources_monitoring = chronycSources('sources')

            resultToIcinga(sources_monitoring)

        elif args.resource == 'falseticker':
            sources_monitoring = chronycSources('falseticker')

            resultToIcinga(sources_monitoring)

    elif args.daemon == 'systemd-timesyncd':
        if args.resource == 'leap_status':
            timesyncd_monitoring = timedatectlStatus()

            resultToIcinga(timesyncd_monitoring)

        else:
            print('Unsupported systemd-timesyncd resource. Supported resources: leap_status')
            sys.exit(3)


def chronycMonitor(chronyc_command):
    chronyc_stdout  = subprocess.Popen([ 'chronyc', '-c', chronyc_command ], stdout=subprocess.PIPE)
    chronyc_o       = io.TextIOWrapper(chronyc_stdout.stdout, newline=os.linesep)
    chronyc_o_csv   = csv.reader((line for line in chronyc_o), delimiter=',')

    return list(chronyc_o_csv)


def chronycSources(chk_resource):
    # TODO: Support thresholds from the cmd line

    chronyc_status              = chronycMonitor('sources')
    chronyc_sources             = len(chronyc_status)
    sources_out                 = {}
    sources_out['service']      = {}
    sources_out['perfd']        = {}

    if chk_resource == 'falseticker':
        falseticker_msg             = 'All of the configured sources are falseticker'
        falseticker_status          = 2
        falseticker_cnt             = 0
    elif chk_resource == 'sources':
        c_sources_msg               = 'No synced source.'
        c_sources_status            = 2
        c_sources_available         = 0
        c_sources_unavailable       = 0
        c_sources_synchronized      = 0
        c_sources_unreliable        = 0

    sources_configured = 0

    for s in chronyc_status:
        s_status    = s[1]
        s_host      = s[2]

        sources_configured += 1

        if chk_resource == 'falseticker':
            if s_status in ('x'):
                falseticker_cnt += 1
        elif chk_resource == 'sources':
            if s_status in ('+', '-', '*'):
                c_sources_available += 1
                if s_status == '*':
                    c_sources_synchronized += 1
            elif s_status in ('?'):
                c_sources_unavailable += 1
            elif s_status in ('~'):
                c_sources_unreliable += 1

    if chk_resource == 'falseticker':

        if falseticker_cnt == 0:
            falseticker_status  = 0
            falseticker_msg     = 'No falseticker found'
        elif falseticker_cnt < sources_configured:
            falseticker_status  = 1
            falseticker_msg     = '{} falseticker found'.format(falseticker_cnt)

        r_name      = 'NTP Falseticker Status'
        r_status    = falseticker_status
        r_msg       = falseticker_msg

    elif chk_resource == 'sources':

        r_name = 'NTP Sources Status'

        if c_sources_unavailable <= sources_configured // 2:

            if c_sources_synchronized > 0:
                c_sources_status  = 0
                c_sources_msg     = '{} out of {} sources are available; {} synchronized source'.format(c_sources_available, sources_configured, c_sources_synchronized)
        elif c_sources_unavailable <= sources_configured - 1:

            if c_sources_synchronized > 0:
                c_sources_status  = 1
                c_sources_msg     = '{} out of {} sources are available; {} synchronized source'.format(c_sources_available, sources_configured, c_sources_synchronized)

        r_name      = 'NTP Sources Status'
        r_status    = c_sources_status
        r_msg       = c_sources_msg

    sources_out['service']['name']                              = r_name
    sources_out['service']['state']                             = r_status
    sources_out['service']['msg']                               = r_msg

    if chk_resource == 'falseticker':
        sources_out['perfd']['falseticker_sources']             = {}
        sources_out['perfd']['falseticker_sources']['value']    = falseticker_cnt
        sources_out['perfd']['falseticker_sources']['max']      = sources_configured
        sources_out['perfd']['falseticker_sources']['crit']     = sources_configured
        sources_out['perfd']['configured_sources']              = {}
        sources_out['perfd']['configured_sources']['value']     = sources_configured
        sources_out['perfd']['configured_sources']['max']       = sources_configured
    elif chk_resource == 'sources':
        sources_out['perfd']['available_sources']               = {}
        sources_out['perfd']['available_sources']['value']      = c_sources_available
        sources_out['perfd']['available_sources']['max']        = sources_configured
        sources_out['perfd']['available_sources']['warn']       = sources_configured // 2
        sources_out['perfd']['available_sources']['crit']       = 0
        sources_out['perfd']['synchronized_sources']            = {}
        sources_out['perfd']['synchronized_sources']['value']   = c_sources_synchronized
        sources_out['perfd']['synchronized_sources']['max']     = sources_configured
        sources_out['perfd']['unavailable_sources']             = {}
        sources_out['perfd']['unavailable_sources']['value']    = c_sources_unavailable
        sources_out['perfd']['unavailable_sources']['max']      = sources_configured
        sources_out['perfd']['unavailable_sources']['warn']     = sources_configured // 2
        sources_out['perfd']['unavailable_sources']['crit']     = sources_configured - 1
        sources_out['perfd']['unreliable_sources']              = {}
        sources_out['perfd']['unreliable_sources']['value']     = c_sources_unreliable
        sources_out['perfd']['unreliable_sources']['max']       = sources_configured
        sources_out['perfd']['configured_sources']              = {}
        sources_out['perfd']['configured_sources']['value']     = sources_configured
        sources_out['perfd']['configured_sources']['max']       = sources_configured

    return sources_out


def chronycTracking():
    chronyc_status          = chronycMonitor('tracking')
    leap_status             = 0 if chronyc_status[0][13] == 'Normal' else 1
    system_time             = chronyc_status[0][4]
    root_delay              = chronyc_status[0][10]
    root_dispersion         = chronyc_status[0][11]
    max_estimated_error     = float(system_time) + float(root_dispersion) / 2 + float(root_dispersion)
    tracking_out            = {}
    tracking_out['service'] = {}
    tracking_out['perfd']   = {}
    tracking_msg            = 'System clock is not in sync'
    tracking_status         = 2

    if leap_status == 0:
        tracking_status = 0
        tracking_msg    = 'System clock is in sync.'

    tracking_out['service']['name']                         = 'Leap Status'
    tracking_out['service']['state']                        = tracking_status
    tracking_out['service']['msg']                          = tracking_msg

    tracking_out['perfd']['leap_status']                    = {}
    tracking_out['perfd']['leap_status']['value']           = leap_status
    tracking_out['perfd']['leap_status']['max']             = 2
    tracking_out['perfd']['leap_status']['crit']            = 2

    tracking_out['perfd']['max_estimated_error']            = {}
    tracking_out['perfd']['max_estimated_error']['value']   = max_estimated_error

    return tracking_out


def timedatectlStatus():
    tdctl_stdout            = subprocess.Popen(['timedatectl', 'status'], stdout=subprocess.PIPE)
    tdctl_out               = {}
    tdctl_out['service']    = {}
    tdctl_out['perfd']      = {}
    tdctl_msg               = 'System clock is not in sync.'
    tdctl_status            = 2

    for line in io.TextIOWrapper(tdctl_stdout.stdout, encoding='UTF-8'):
        if line.lstrip().startswith('NTP synchronized'):
#             if line.split(': ')[1] == 'yes\n':
             if 'yes' in line.split(': ')[1]:
                 tdctl_status   = 0
        elif line.lstrip().startswith('System clock synchronized'):
#             if line.split(': ')[1] == 'yes\n':
             if 'yes' in line.split(': ')[1]:
                 tdctl_status   = 0

    if tdctl_status == 0:
        tdctl_msg = 'System clock is in sync'

    tdctl_out['service']['name']                = 'Leap Status'
    tdctl_out['service']['state']               = tdctl_status
    tdctl_out['service']['msg']                 = tdctl_msg

    tdctl_out['perfd']['leap_status']           = {}
    tdctl_out['perfd']['leap_status']['value']  = tdctl_status
    tdctl_out['perfd']['leap_status']['max']    = 2
    tdctl_out['perfd']['leap_status']['crit']   = 2

    return tdctl_out


def resultToIcinga(svc_result):
    svc_dict    = svc_result['service']
    chk_name    = svc_dict['name']
    chk_state   = svc_dict['state']
    chk_msg     = svc_dict['msg']

    if chk_state == 0:
        chk_state_str  = 'OK'
    elif chk_state == 1:
        chk_state_str  = 'WARNING'
    elif chk_state == 2:
        chk_state_str  = 'CRITICAL'
    elif chk_state == 3:
        chk_state_str  = 'UNKNOWN'
    else:
        chk_state      = 2
        chk_state_str  = 'Invalid check result'

    if 'perfd' in svc_result:

        pdata_str = None

        for k, v in svc_result['perfd'].items():

            if 'value' not in v:
                # invalid perfdata, missing value; do not process it
                continue

            label       = k

            pd_substr   = '{0}={1}'.format(label, v['value'])

            # set sane defaults for min, max, warn & crit as fallback
            if 'min' not in v:
                pd_substr   = ';'.join([pd_substr, '0'])
            else:
                pd_substr   = ';'.join([pd_substr, str(v['min'])])

            if 'max' not in v:
                pd_substr   = ';'.join([pd_substr, ''])
            else:
                pd_substr   = ';'.join([pd_substr, str(v['max'])])

            if 'warn' not in v:
                pd_substr   = ';'.join([pd_substr, ''])
            else:
                pd_substr   = ';'.join([pd_substr, str(v['warn'])])

            if 'crit' not in v:
                pd_substr   = ';'.join([pd_substr, ''])
            else:
                pd_substr   = ';'.join([pd_substr, str(v['crit'])])

            if not pdata_str:
                pdata_str = pd_substr
            else:
                pdata_str = ' '.join([pdata_str, pd_substr])

    chk_output = '{0} {1} - {2} | {3}'.format(chk_name.upper(), chk_state_str, chk_msg, pdata_str)

    print(chk_output)

    if chk_state == 0:
        sys.exit(0)
    elif chk_state == 1:
        sys.exit(1)
    elif chk_state == 2:
        sys.exit(2)
    else:
        sys.exit(3)

def parseArgs():
    argParser = argparse.ArgumentParser(description='Check the chronyc or systemd-timesyncd sync status.')
    argParser.add_argument('-d', '--daemon', dest='daemon', required=True, type=str, \
                            choices=[ 'chronyc', 'systemd-timesyncd' ], \
                            help='Monitored daemon.')
    argParser.add_argument('-r', '--resource', dest='resource', type=str, default='leap_status', \
                            choices=[ 'leap_status', 'sources', 'falseticker' ], \
                            help='Checked resource. \
                                  Supported options for chronyc: leap_status, sources, falseticker. \
                                  Supported options for systemd-timesyncd: leap_status')

    return argParser.parse_args()


if __name__ == "__main__":
    args = parseArgs()
    main(args)

?>