#! /usr/bin/python -u
#
# Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# This software product is a proprietary product of Nvidia Corporation
# and its affiliates (the "Company") and all right, title, and interest
# in and to the software product, including all associated intellectual
# property rights, are and shall remain exclusively with the Company.
#
# This software product is governed by the End User License Agreement
# provided with the software product.
# All Rights reserved.

# This program is a Net-SNMP pass persist script that services the
# Resource Query MIB that's been defined by Cumulus Networks.

import subprocess
import syslog
import getopt
import sys
import os.path
import re
try:
    from collections import OrderedDict
except ImportError:
    import ordereddict

import snmp_passpersist as snmp

oid_base = '.1.3.6.1.4.1.40310.1'
header_regexp = 'Timestamp:\s*([\w,:, ]+)\s*Poll Interval: ([0-9]+) msec Measurement Interval: ([0-9])+'
# To run from the commandline for debugging,
# simply set debug to non-zero and run to see
# STDOUT output.
debug = 0

def pp_add_int(oid, val):
    if debug:
        print '%s : %s' %(oid, val)
    else:
        pp.add_int(oid, val)

def pp_add_str(oid, val):
    if debug:
        print '%s : %s' %(oid, val)
    else:
        pp.add_str(oid, val)

def update_buf_utilizn():
    """
    Extract the buffer utilization stats from the file and stuff it into SNMP
    The file /tmp/hal-buf-util.txt will get created only if user does:
        echo "1" > /cumulus/switchd/config/buf_util/measure_interval
        echo "1" > /cumulus/switchd/config/buf_util/poll_interval
    """
    try:
        with open('/tmp/hal-buf-util.txt') as x: buf_lines = x.readlines()
    except Exception as e:
        # this is not important as they may not have turned these stats on
        return


    # This is based on the assumption that the format looks like this:
    # Timestamp: Wed Jul 25 07:22:10 2012 Poll Interval: 10 msec Measurement
    # Interval: 1 min(s) Next 4 lines are for each service pool: poolID, min,
    # max, avg, variance, SD
    # 0, 0, 0, 0.0000    , 0.0000    , 0.0000
    # 1, 0, 0, 0.0000    , 0.0000    , 0.0000
    # 2, 0, 0, 0.0000    , 0.0000    , 0.0000
    # 3, 0, 0, 0.0000    , 0.0000    , 0.0000

    # Extract the non-table entries from the first line
    m = re.match(header_regexp, buf_lines[0])
    if (m != None):
        tstamp = m.group(1).strip()
        poll_val = m.group(2)
        meas_val = m.group(3)
    else:
        tstamp = poll_val = meas_val = -1

    # Convert timestamp (ctime format) into what SNMP TimeStamp format demands.
    # Can't really support the TimeStamp format via the pass_persist mechanism.
    # So, we'll just convert it into the format of TimeStamp and let the client
    # deal with it.
    # For crying out loud, the time module in Cumulus doesn't support strptime()
    # So do the work here. Luckily, SNMP only needs the month converted
    MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
              "Sep", "Oct", "Nov", "Dec"]

    ts = tstamp.split()
    if ts[1] in MONTHS:
        mnth = MONTHS.index(ts[1])+1

    tstamp = "%s-%d-%s,%s.0"%(ts[4], mnth, ts[2], ts[3])

    pp_add_str("3.1", tstamp)
    pp_add_int("3.2", poll_val)
    pp_add_int("3.3", meas_val)

    # Lets parse the rest of the lines now for the actual buffer data
    baseoid = "3.4.1"
    try:
        s = OrderedDict()
    except Exception as e:
        syslog.syslog('resq_pp:  EXCEPTION=%s' % e)
        s = ordereddict.OrderedDict()

    for line in buf_lines[2:]:
        if line.strip():
            l = ([ i.strip() for i in line.split(",") ])
            s.update(dict({l[0]:l[1:]}))

    for idx in s.iterkeys():
        pp_add_int("%s.%d.%s"%(baseoid, 1, idx), int(idx))

    for i in range(0,2):
        for idx, vals in s.iteritems():
            pp_add_int("%s.%d.%s"%(baseoid, i+2, idx), vals[i])

    for i in range(2,5):
        for idx, vals in s.iteritems():
            pp_add_str("%s.%d.%s"%(baseoid, i+2, idx), vals[i])


def get_resourceinfo():
    try:
        reslines = subprocess.check_output((['/usr/cumulus/bin/cl-resource-query','-k']),
                                           stderr=subprocess.STDOUT,
                                           shell=False).splitlines()
    except Exception as e:
        # we need to log the error if we can't get resources
        syslog.syslog("resq_pp:  Exception=%s" % e)

    if debug:
        print reslines
    if not reslines:
        return

    """parses output from cl-resource-query -k
            host_entry_count=2
            host_entry_max=8192
            host_v4_entry_count=2
            host_v6_entry_count=0
            route_0_entry_count=5
            route_0_entry_max=32668
            route_1_entry_count=13
            route_1_entry_max=16384
            route_v4_entry_count=5
            route_v6_entry_count=13
            route_total_entry_count=18
            route_total_entry_max=32768
            ecmp_nh_entry_count=0
            ecmp_nh_entry_max=16327
            mac_entry_count=0
            mac_entry_max=32768
            in_acl_entry_count=60
            in_acl_entry_max=1280
            in_acl_counter_count=81
            in_acl_counter_max=1280
            in_acl_meter_count=21
            in_acl_meter_max=4096
            in_acl_slice_count=2
            in_acl_slice_max=4
            eg_acl_entry_count=24
            eg_acl_entry_max=256
            eg_acl_counter_count=48
            eg_acl_counter_max=1024
            eg_acl_meter_count=24
            eg_acl_meter_max=256
            eg_acl_slice_count=1
            eg_acl_slice_max=1


    And returns dictionary in the following format:
            {'eg_acl_slice_count': 1, 'host_entry_count': 2, 'eg_acl_counter_max': 1024, 'in_acl_slice_count': 2, 'host_entry_max': 8192, 'in_acl_meter_count': 21, 'route_total_entry_count': 18, 'mac_entry_count': 0, 'eg_acl_meter_max': 256, 'route_v4_entry_count': 5, 'host_v6_entry_count': 0, 'ecmp_nh_entry_count': 0, 'in_acl_slice_max': 4, 'eg_acl_counter_count': 48, 'ecmp_nh_entry_max': 16327, 'route_1_entry_count': 13, 'in_acl_counter_count': 81, 'route_total_entry_max': 32768, 'in_acl_counter_max': 1280, 'host_v4_entry_count': 2, 'in_acl_meter_max': 4096, 'eg_acl_entry_max': 256, 'route_0_entry_max': 32668, 'in_acl_entry_count': 60, 'in_acl_entry_max': 1280, 'route_v6_entry_count': 13, 'route_1_entry_max': 16384, 'eg_acl_entry_count': 24, 'route_0_entry_count': 5, 'eg_acl_slice_max': 1, 'mac_entry_max': 32768, 'eg_acl_meter_count': 24}
    """

    resdict = {}
    for line in reslines:
        if '=' in line:
            (rname, rvalstr) = line.split('=')
        else:
            continue
        try:
            resdict[rname.strip()] = int(rvalstr.strip())
        except Exception as e:
            syslog.syslog('resq_pp:  EXCEPTION=%s' % e)

    return resdict

def update_resourceinfo():
    resdict = get_resourceinfo()
    if debug:
        print('resdict=%s'%resdict)
    # L3 entries
    try:
        pp_add_int('1.1', resdict.get('host_0_entry_count',0))
        pp_add_int('1.2', resdict.get('host_0_entry_max',0))

        pp_add_int('1.3', resdict.get('route_total_entry_count',0))
        pp_add_int('1.4', resdict.get('route_total_entry_max',0))

        pp_add_int('1.5', resdict.get('route_total_entry_count',0))
        pp_add_int('1.6', resdict.get('route_total_entry_max',0))

        pp_add_int('1.9', resdict.get('ecmp_nh_entry_count',0))
        pp_add_int('1.10', resdict.get('ecmp_nh_entry_max',0))

        pp_add_int('1.11', resdict.get('in_acl_entry_count',0))
        pp_add_int('1.12', resdict.get('in_acl_entry_max',0))
        pp_add_int('1.13', resdict.get('in_acl_counter_count',0))
        pp_add_int('1.14', resdict.get('in_acl_counter_max',0))
        pp_add_int('1.15', resdict.get('in_acl_meter_count',0))
        pp_add_int('1.16', resdict.get('in_acl_meter_max',0))
        pp_add_int('1.17', resdict.get('in_acl_slice_count',0))
        pp_add_int('1.18', resdict.get('in_acl_slice_max',0))
        pp_add_int('1.19', resdict.get('eg_acl_entry_count',0))
        pp_add_int('1.20', resdict.get('eg_acl_entry_max',0))
        pp_add_int('1.21', resdict.get('eg_acl_counter_count',0))
        pp_add_int('1.22', resdict.get('eg_acl_counter_max',0))
        pp_add_int('1.23', resdict.get('eg_acl_meter_count',0))
        pp_add_int('1.24', resdict.get('eg_acl_meter_max',0))
        pp_add_int('1.25', resdict.get('eg_acl_slice_count',0))
        pp_add_int('1.26', resdict.get('eg_acl_slice_max',0))

        # L2 entries
        pp_add_int('2.1', resdict.get('mac_entry_count',0))
        pp_add_int('2.2', resdict.get('mac_entry_max',0))
    except Exception as e:
        syslog.syslog('resq_pp: ethtool EXCEPTION=%s' % e)

def update():
    update_resourceinfo()
    update_buf_utilizn()

if debug:
    update()
else:
    pp = snmp.PassPersist(oid_base)
    pp.start(update, 37)           # So we won't ever conflict with the writer

