#!/usr/bin/perl

##
#
# $Id: //depot/RELEASE/Traverse_8.0/src/perl/discovery/probeSnmpTests.pl#1 $
#
# COPYRIGHT:
#   Copyright 2014 Kaseya. All Rights Reserved. 
#   This software is the proprietary information 
#   Kaseya. Use is subject to license terms.
#
# DESCRIPTION:
#  script to perform snmp auto-discovery by traversing certain snmp MIB
#  tree on target device and return collected result in a specific format
#  understood by the web application
#
# OUTPUT: 
#  returns one oid for each monitored object on target device on
#  separate line. each element of the output line are separated by a 
#  '|' character. the elements are always on the same order and are:
#
#  test sub-type      self explanatory
#  test name          descriptive text for the test
#  test units         unit to use when displaying the results
#  display max        maximum value that is displayed on screen
#  snmp oid           oid to use to perform the test
#  max value          optional integer for doing percent calculations
#  unit multiplier    integer such as 1/1024
#  post processing    NONE, PERCENT, DELTA, RATE, RATEPERCENT, DELTAPERCENT
#  warning threshold  optional threshold
#  critical threshold optional threshold
#  element id         optional element identifier for grouping
#
# REQUIRES:
#   Getopt::Long (http://search.cpan.org/search?dist=Getopt-Long)
#   XML::Simple (http://search.cpan.org/search?dist=XML-Simple)
#   Text::Template (http://search.cpan.org/search?dist=Text-Template)
#
##
  
use strict;
use vars qw($opt_device $opt_cid $opt_ver $opt_v3auth $opt_v3priv 
            $opt_port $opt_devtype $opt_snmp_dir $opt_field_sep 
            $opt_conf_dir $opt_input_file $opt_testtype $opt_xml 
            $opt_cache $opt_scope $opt_precompile $opt_remote $opt_username
            $opt_password $opt_endpoint $opt_ver $opt_debug $opt_help $opt_timeout
            $opt_checkpoint $cmd_snmp_walk $cmd_snmp_get
            %NAME_CACHE %TYPE_REGEX %GLOBAL_VAR %TEST_COUNT %EXCLUDE_LIST 
            $SNMP @conf_list $conf_file $stor_file);
use File::Basename;
use Cwd 'abs_path';
use lib (abs_path(dirname($0)) . "/../lib/perl",
         abs_path(dirname($0)) . "/../modules",
         abs_path(dirname($0)) . "/../../../fcots/perl/");
use Getopt::Long;
use XML::Simple;
use JSON;
use HTTP::Tiny;
use Text::Template;
use Data::Dumper;
use Zyrion::Debug;
use Zyrion::Config;
use Zyrion::Discover::SNMP;

## variables -------------------------------------------------------------
##

my $my_user       = getlogin || (getpwuid($<))[0];
my $my_name       = basename($0);
my $my_path       = abs_path(dirname($0));
my $my_start_time = time;
my $my_version;
   { no warnings 'qw';
     $my_version  = (qw$Revision: #1 $)[1];
   }

$opt_conf_dir     = "${my_path}/../etc/typedef";
$opt_conf_dir     = "${opt_conf_dir}|${my_path}/../plugin/monitors|${my_path}/../lib/ext";
$opt_snmp_dir     = dirname(${my_path}) . "/bin";   # set this
$opt_cache        = 1;
$opt_debug        = 0;

$ENV{"MIBS"}            = "";
$ENV{"MIBFILE"}         = "";
$ENV{"MIBFILE_v2"}      = "";
$Data::Dumper::Indent   = 1;

%TYPE_REGEX       = (
  'unknown'   => 'unknown',
  'generic'   => 'unknown',
  'other'     => 'unknown',
  '^nt'       => 'nt',
  'window'    => 'nt',
  'win2k'     => 'nt',
  'winnt'     => 'nt',
  'unix'      => 'unix',
  'linux'     => 'unix',
  'router'    => 'router',
  'switch'    => 'switch',
  'firewall'  => 'firewall',
  '^fw$'      => 'firewall',
  'balancer'  => 'slb',
  '^load\s+'  => 'slb',
  'proxy'     => 'proxy',
  '^vpn\s*'   => 'vpnc',
  '^print'    => 'printer',
  'wireless'  => 'wireless',
  'disk'      => 'storage',
  'storage'   => 'storage',
  'vmware'    => 'vmware',
  'xen'       => 'vmware',
  'hyper'     => 'vmware',
  'ucs'       => 'vmware',
  'cloud'     => 'vmware'
);

  
## preliminary checks ----------------------------------------------------
##

# defaut values
#
$opt_port     = "161";                              # default snmpd port
$opt_ver      = "99";                               # discover supported version
$opt_cid      = "public";                           # default community
$opt_timeout  = "10";                               # default query timeout
$opt_v3auth   = "MD5";                              # v3 auth protocol
$opt_v3priv   = "DES";                              # v3 priv protocol
$opt_devtype  = "UNKNOWN";                          # assume discover
$opt_scope    = "device";                           # not called from network discovery
$opt_endpoint = "127.0.0.1";                        # assume execusion on bve server

GetOptions ('type|y=s'        => \$opt_devtype, 
              'only=s'        => \$opt_testtype,
              'xml=s'         => \$opt_xml,
              'host|h=s'      => \$opt_device,
              'version|v=s'   => \$opt_ver,
              'comm|c=s'      => \$opt_cid,
              'port|p=i'      => \$opt_port,
              'timeout|t=i'   => \$opt_timeout,
              'authproto|a=s' => \$opt_v3auth,
              'privproto|x=s' => \$opt_v3priv,
              'input|f=s'     => \$opt_input_file,
              'dir|i=s'       => \$opt_conf_dir,
              'checkpoint'    => \$opt_checkpoint,
              'cache=i'       => \$opt_cache,
              'scope=s'       => \$opt_scope,
              'compile'       => \$opt_precompile,
              'remote|r=s'    => \$opt_remote,
              'username=s'    => \$opt_username,
              'password=s'    => \$opt_password,
              'endpoint|e=s'  => \$opt_endpoint,
              'help|?'        => \$opt_help,
              'debug|d+'      => \$opt_debug);

if ($opt_help) { &show_usage; }                                                                        
$DEBUG = $opt_debug;

# we need either a real device or input file w/static data 
# collected via snmpwalk
#
if ((! $opt_device) && (! $opt_input_file) && (! $opt_precompile)) {
  &error("no device name or input file name has been provided");
  &show_usage;
}

# we need login credentials when sending request to remote host
#
if ($opt_remote && ((! $opt_username) || (! $opt_password))) {
  &error("login credentials required when using --remote option");
  &show_usage;
}

# if using an input file, make sure it exists
#
if ($opt_input_file) {
  my @file_list = split(/,/, $opt_input_file);
  foreach my $file_name (@file_list) {
    if (! -e "${file_name}") {
      &error("unable to locate input file \'${file_name}\'");
      &exit_now(1);
    }
    # only one data file in json format is expected for remote execution mode
    #
    if ($opt_remote) {
      $opt_input_file = $file_name;
      last;
    }
  }
}

if (! $opt_precompile) {
  $opt_ver      = "2c" if ($opt_ver eq "2");        # ucd has only v2c
  $opt_xml      = basename($opt_xml) if ($opt_xml);
  
  # validate snmpv3 parameters (values must match those use by jni)
  #
  if ($opt_v3auth =~ /sha/i) {
    $opt_v3auth = "SHA";
  } else {
    $opt_v3auth = "MD5";
  }
    
  if ($opt_v3priv =~ /aes/i) {
    $opt_v3priv = "AES";
  } else {
    $opt_v3priv = "DES";
  }

  foreach (keys %TYPE_REGEX) {
    ## &debug("comparing $opt_devtype =~ /${_}/i ...");
    if ($opt_devtype =~ /${_}/i) {
      &debug("converted device type \'${opt_devtype}\' into \'$TYPE_REGEX{$_}\'");
      $opt_devtype = $TYPE_REGEX{$_};
      last;
    }
  }
  $opt_devtype = uc($opt_devtype);                # convert to uppercase
  $GLOBAL_VAR{'vendor'} = $GLOBAL_VAR{'model'} = "unknown";

  # do we support this device type?
  #
  if (! grep(/^$opt_devtype$/i, @NV_DEVICE_TYPES)) { 
    &error("\'${opt_devtype}\' is not one of the supported device types:", 
        join(",", @NV_DEVICE_TYPES));
    &exit_now(1);
  }

  my %module_params             = ();
  if ($opt_input_file) {
    $module_params{input}       = $opt_input_file;
    $module_params{version}     = $opt_ver;
  } else {
    $module_params{host}        = $opt_device;
    $module_params{version}     = $opt_ver;
    $module_params{port}        = $opt_port;
    $module_params{community}   = $opt_cid;
    $module_params{timeout}     = $opt_timeout;
    $module_params{separator}   = '|';
    if (($opt_cid =~ /\:/) || (($opt_ver eq "3") || ($opt_ver eq "99"))) {
      $module_params{authProto} = $opt_v3auth;  
      $module_params{privProto} = $opt_v3priv;
    }
  }
  $module_params{separator}   = '|';

  $SNMP = eval { new Zyrion::Discover::SNMP(%module_params) };
  if (ref($SNMP) ne "Zyrion::Discover::SNMP") {
    &error("failed to initialize snmp object", $@);
    &exit_now(1);
  } 
  if ($SNMP->error_message) {
    &error($SNMP->error_message);
    &exit_now(1);
  }
  
} # if not precompile

# do we have the dso for 'Storable' module?
#
eval "use Storable;";
if ($@) {
  if ($opt_precompile) {
    &error("unable to load storable module ... cannot pre-compile xml configurations");
    &exit_now(1);
  } else {
    &debug("[init] failed to load storable module", $@);
    $opt_cache = 0;
  }
}


## remote execution mode -----------------------------------------------
##

if ($opt_remote) {
  &remote_discovery();
  &exit_now(0);
}


## beginning of program ------------------------------------------------
##

if ($opt_scope eq "network") {
  &info("Discovering SNMP tests on ${opt_device} ...");
}

&get_file_list($opt_conf_dir, \@conf_list)  || &exit_now(1);

if ((! $opt_precompile) && (! $opt_input_file)) {
  if ($opt_port == 161) {
    &get_agent_version() || &exit_now(1);
    if ($opt_scope eq "network") {
      &info("Retrieving device vendor/model information ...");
   } else {
      &debug("==== starting vendor/device model discovery");
    }
    &vendor_model_probe() || &exit_now(1);
  }
}

if ($opt_scope eq "network") {
  &info("Scanning for available metrics (tests)...");
} else {
  &debug("==== starting processing of probe configurations");
}

foreach $conf_file (@conf_list) {

  # use this entry or pass?
  #
  if ($opt_xml) {
    if ($conf_file !~ /$opt_xml/i) {
      ## &debug("skipping this file as it does not match user requested file");
      next;
    }
  }

  &debug("---- current config file: \'" . basename($conf_file) . "\' ...");

  # is the cache obsolete? if it is older than the xml file, or this
  # script, remove it so that it is regenerated
  #
  $stor_file = $conf_file;
  $stor_file =~ s/\.xml$/.stor/;
  if (-f $stor_file) {
    if (((stat($conf_file))[9] > (stat($stor_file))[9]) ||
        ((stat($0))[9] > (stat($stor_file))[9])) {
      &debug("removing stale cache for this configuration ...");
      unlink($stor_file);
    }
  }
  
  if ($opt_checkpoint) {
    $my_start_time = time;
    print "===| checkpoint: loading config file " . basename($conf_file) . "\n";
  }
        
  # create new xml objects
  #
  my $XML_OBJ = new XML::Simple();
  my $XML_REF = eval { 
    $XML_OBJ->XMLin($conf_file, 
                    keeproot   => 1, 
                    keyattr    => [ 'name' ], 
                    forcearray => [ 'verify', 'setvar', 'setopt', 'probeConfig', 'subType', 'poll', 'filter' ], 
                    cache      => ($opt_cache ? [ 'storable' ] : undef));
  };
  if ($@) {
    &error("error(s) encountered while parsing config file \'" . basename($conf_file) . "\'", $@);
    next;
  }
  
  # print Dumper($XML_REF); ## &exit_now(1);

  # no need to proceed any further if we're building (storable) cache for this xml configuration
  #
  if ($opt_precompile) { next; }
  
  # keep track of how many tests were discovered from this config file
  #
  my $discovered_tests = 0;  
  my $error_status = 0;

  # perform pre-discovery checks
  #
  unless (defined($XML_REF->{NetVigil}->{monitor}->{probeConfig}->[0]->{subType})) {
    &debug("file does not contain any known test discovery configuration ... skipping");
    next;
  }

  if ($XML_REF->{NetVigil}->{monitor}->{type} ne "snmp") {
    &debug("file does not contain any snmp test discovery configuration ... skipping");
    next;
  }

  my %OLD_OPTIONS = ();
  if (defined($XML_REF->{NetVigil}->{monitor}->{onLoad}->{override})) {
    my %NEW_OPTIONS = ();
    foreach (keys %{ $XML_REF->{NetVigil}->{monitor}->{onLoad}->{override} }) {
      my $new_param = uc($_);
      my $new_value = $XML_REF->{NetVigil}->{monitor}->{onLoad}->{override}->{$_};
      &debug("onload override option ${new_param}=${new_value}");
      $NEW_OPTIONS{$new_param} = $new_value;
    }
    %OLD_OPTIONS = $SNMP->set_query_options(%NEW_OPTIONS);
  } 

  foreach (@{ $XML_REF->{NetVigil}->{monitor}->{onLoad}->{verify} }) {
    my $verify_type = $_->{type} || "";
    my $match_pattern = $_->{pattern} || "";
    my $match_value = $_->{object} || "";
    my $verify_action = $_->{action} || "accept";
    next if (($match_pattern =~ /^\s*$/) && ($match_value =~ /^\s*$/));
    &debug("onload verify ${verify_type}:${match_value}=${match_pattern}");

    if ($verify_type eq "oid") {
      if ($match_value =~ /^\.?[\d\.]+\d+$/) {
        $match_value = "." . $match_value unless ($match_value =~ /^\./);
        $match_value = length($SNMP->get($match_value)) ? $SNMP->get($match_value) : "";
      }
    }
    
    elsif ($verify_type eq "count") {
      $match_value = scalar($SNMP->walk($match_value));
      $match_value = 0 unless ($match_value > 0);
    }
    
    elsif ($verify_type eq "table") {
      if ($match_value =~ /^\.?[\d\.]+\d+$/) {
        $match_value = "." . $match_value unless ($match_value =~ /^\./);
        my @walk_result = $SNMP->walk($match_value);
        $match_value = "";
        foreach my $walk_data (@walk_result) {
          (undef, my $walk_value) = split(/\s+/, $walk_data, 2);
          if ($walk_value =~ /$match_pattern/i) {
            $match_value = $walk_value;
            chop $match_value;
            last;
          }
        }
      }
    }
    
    elsif ($verify_type eq "variable") {
      if (defined($GLOBAL_VAR{"set_${match_value}"})) {
        $match_value = $GLOBAL_VAR{"set_${match_value}"} || "";
      } else {
        $match_value = "";
      }
    }
    
    elsif ($verify_type eq "module") {
      $match_pattern = '^1$';
      eval "use $match_value";
      if ($@) {
        &error("failed to load module ${match_value}", $@);
        $match_value = '0';
      } else {
        $match_value = '1';
      }
    }
    
    if ($verify_action =~ /reject/i) {
      # skip if matches
      #
      &debug("making sure \'${match_value}\' does not match /${match_pattern}/");
      if ($match_value =~ /$match_pattern/i) {
        &debug("pattern match found .. skipping");
        $error_status = 1;
        last;
      }
    } else {
      # skip unless matches
      #
      &debug("making sure ${match_value} matches /${match_pattern}/");
      if ($match_value !~ /$match_pattern/i) {
        &debug("pattern match not found .. skipping");
        $error_status = 1;
        last;
      }
    }
  }

  if (! $error_status) {
    foreach my $probe_counter (0 .. $#{ $XML_REF->{NetVigil}->{monitor}->{probeConfig} }) {  
      foreach my $probe_type (keys %{ $XML_REF->{NetVigil}->{monitor}->{probeConfig}->[$probe_counter]->{subType} }) {
  
        # get a (shorter) reference to the section we need
        #
        my $TEST_REF = $XML_REF->{NetVigil}->{monitor}->{probeConfig}->[$probe_counter]->{subType}->{$probe_type};
        $TEST_REF->{name} = $probe_type;
        $TEST_REF->{name} =~ tr/A-Z/a-z/;

        &debug("---- current entry : \'$TEST_REF->{name}\'");

        # use this entry or pass?
        #
        if (($opt_testtype) && ($TEST_REF->{name} !~ /$opt_testtype/i)) {
          &debug("skipping this entry as it does not match user requested type");
          next;
        }

        if ($EXCLUDE_LIST{$TEST_REF->{name}}) {
          &debug("skipping this entry as this category is excluded via external configuration file");
          next;
        }

        $TEST_REF->{enabled} = "false" unless ($TEST_REF->{enabled});
        if ($TEST_REF->{enabled} !~ /true|yes/i) {
          &debug("skipping this entry as it is not enabled");
          next;
        }

        &check_condition($TEST_REF) || next;

        # we can proceed ... initialize storage container
        #
        my %SNMP_OBJECTS = ();

        # is this test subtype handled via an add-on module?
        #
        if (defined($TEST_REF->{moduleConfig})) {
          if (! defined($TEST_REF->{moduleConfig}->{target})) {
            &debug("[error] do not know which module to use for this configuration");
            next;
          }
          my $MOD_OBJ = $TEST_REF->{moduleConfig}->{target};
          my $MOD_REF = eval { new $MOD_OBJ(snmp=>$SNMP, debug=>$opt_debug) };
          if ($@) {
            &debug("[error] failed to load module $MOD_OBJ", $@);
            next;
          }
          %SNMP_OBJECTS = $MOD_REF->discover($TEST_REF);
        } else {
          # no, use the default discovery method
          #
          %SNMP_OBJECTS = $SNMP->discover($TEST_REF);
        }

        if ($SNMP_OBJECTS{'error'}) {
          &debug("[error] $SNMP_OBJECTS{'error'}");
          next;    
        }

        # data has been collected. perform post-processing tasks on data
        #
        ## &debug("--- discovered values ..."); print Dumper(\%SNMP_OBJECTS);
        $SNMP->post_process($TEST_REF, \%SNMP_OBJECTS);
    
        my $monitor_inout = 0;
        my $param_threshold = "";
        $monitor_inout = 1 if (defined($SNMP_OBJECTS{'results'}{'oid_in'}) &&
          defined($SNMP_OBJECTS{'results'}{'oid_out'}));
              
        &debug("--- printing monitor: entries for this subtype ...");

        foreach my $object_index (sort keys %{ $SNMP_OBJECTS{'mib'}{'objectIndex'} }) {
          next unless ($SNMP_OBJECTS{'results'}{'test_name'}{$object_index});
          next if ($SNMP_OBJECTS{'results'}{'test_name'}{$object_index} =~ /^\s*$/);
          &debug("current entry is for $SNMP_OBJECTS{'results'}{'test_name'}{$object_index}");
    
          # print test definition for in/default object
          #
          if (defined($SNMP_OBJECTS{'results'}{'oid_in'})) {
            if ($SNMP->print($TEST_REF->{name}, 
                  $SNMP_OBJECTS{'results'}{'test_name'}{$object_index} . 
                    ($monitor_inout ? " In" : ""), 
                  $SNMP_OBJECTS{'results'}{'display_unit'}{$object_index}, 
                  $SNMP_OBJECTS{'results'}{'display_max'}{$object_index},
                  $SNMP_OBJECTS{'results'}{'oid_in'}{$object_index},
                  $SNMP_OBJECTS{'results'}{'test_max'}{$object_index},
                  $SNMP_OBJECTS{'results'}{'result_mult'}{$object_index}, 
                  uc($TEST_REF->{postProcess}->{directive}),
                  $SNMP_OBJECTS{'results'}{'warn_threshold'}{$object_index},
                  $SNMP_OBJECTS{'results'}{'crit_threshold'}{$object_index},
                  $SNMP_OBJECTS{'results'}{'element'}{$object_index})) {
              $TEST_COUNT{lc($TEST_REF->{name})}++;
            }
            $discovered_tests++;
          }

          # print test definition for out object
          #
          if (defined($SNMP_OBJECTS{'results'}{'oid_out'})) {
            if ($SNMP->print($TEST_REF->{name}, 
                  $SNMP_OBJECTS{'results'}{'test_name'}{$object_index} . 
                    ($monitor_inout ? " Out" : ""), 
                  $SNMP_OBJECTS{'results'}{'display_unit'}{$object_index}, 
                  $SNMP_OBJECTS{'results'}{'display_max'}{$object_index},
                  $SNMP_OBJECTS{'results'}{'oid_out'}{$object_index},
                  $SNMP_OBJECTS{'results'}{'test_max'}{$object_index},
                  $SNMP_OBJECTS{'results'}{'result_mult'}{$object_index},
                  uc($TEST_REF->{postProcess}->{directive}),
                  $SNMP_OBJECTS{'results'}{'warn_threshold'}{$object_index},
                  $SNMP_OBJECTS{'results'}{'crit_threshold'}{$object_index},
                  $SNMP_OBJECTS{'results'}{'element'}{$object_index})) {
              $TEST_COUNT{lc($TEST_REF->{name})}++;
            }
            $discovered_tests++;
          }
        } # foreach %SNMP_OBJECTS

      } # subType()
    } # probeConfig()
  }

  if (defined($XML_REF->{NetVigil}->{monitor}->{onLoad}->{override}) && scalar((keys %OLD_OPTIONS))) {
    &debug("onexit restore query options");
    $SNMP->set_query_options(%OLD_OPTIONS);
  } 

  if ($opt_checkpoint) {
    print "===| checkpoint: completed config file processing in " . &pretty_uptime(time - $my_start_time) . "\n";
  }

  if ($discovered_tests) {
    foreach (@{ $XML_REF->{NetVigil}->{monitor}->{onExit}->{setvar} }) {
      my $match_pattern = $_->{variable} || "";
      my $match_value = $_->{value} || "";
      next if ($match_pattern =~ /^\s*$/);
      $GLOBAL_VAR{"set_${match_pattern}"} = $match_value;
      &debug("onexit set variable ${match_pattern}=${match_value}");
    }
    
    foreach (@{ $XML_REF->{NetVigil}->{monitor}->{onExit}->{setopt} }) {
      my $match_pattern = $_->{parameter} || "";
      my $match_value = $_->{value};
      $match_value = "" unless ($match_value =~ /\S+/);
      next if ($match_pattern =~ /^\s*$/);
      &debug("onexit set config ${match_pattern}=${match_value}");
      print "CONFIG: ${match_pattern}=${match_value}\n";
    }
    
  }

}

# all done!
#
&exit_now(0);


# sub routines ---------------------------------------------------------
#

sub exit_now {

  my ($return_value) = @_;
  exit $return_value;

}

sub show_usage {

  print <<eofUsage
  
  ${my_name} (${my_version})
  usage: ${my_name} --host=<fqdn|ip_address>
         [ --community=<community_string> ]      [ --version=<1|2|3> ]
         [ --authproto=<none|md5|sha> ] [ --privproto=<none|des|aes> ]
         [ --type=<windows|unix|router|switch|firewall|slb|unknown>  ]
         [ --help ] [ remote_execution_options ]
                
  --host      = host name or ip address of device to probe
  --community = snmp community string (user:password:secret for version=3)
  --version   = snmp version supported by device
  --authproto = snmp agent authentication protocol        (only version=3)
  --privproto = snmp agent privary protocol               (only version=3)
  --type      = type of device being probed
  --help      = print this help message

  for remote execution via internal communication bus:
  
  --remote    = perform discovery on specified remote dge/dge extension
  --username  = login id with superuser privileges    (required parameter)
  --password  = password for specified login user     (required parameter)
  --endpoint  = host name or ip address of bve         (default=localhost) 

eofUsage
;

  &exit_now(0);
  
}


##
# get a list of xml files in the plugin monitor directories
##

sub get_file_list {

  &debug("==== generating list of xml files in config and plugin directories");
  my ($plugin_dirs, $array_ref) = @_;
  my %FILE_LIST = ();
  
  @{ $array_ref } = ();
  if (! $plugin_dirs) { return 0; }

  foreach my $plugin_dir_entry (split('\|', $plugin_dirs)) {
    # make sure plugin monitor directory exists
    #
    if (! -d $plugin_dir_entry) {
      &debug("skipping non-existant plugin monitor directory ...",
        "directory: ${plugin_dir_entry}");
      next;
    } else {
      # get a list of xml files in the directory
      #
      if (! opendir(DH_PLUGIN, $plugin_dir_entry)) {
        &error("failed to read contents of plugin monitor directory",
          "directory ${plugin_dir_entry}", "reason: $!");
        return 0;
      }

      &debug("plugin directory: ${plugin_dir_entry} ...");
      foreach (sort grep { /\.xml$/i && -f "${plugin_dir_entry}/${_}" } readdir(DH_PLUGIN)) {
        next if (/^ctty_/i);
        next if ((/_wmi_/i) && (! $opt_precompile));
        ## &debug("\tconfig file: ${_}");
        $FILE_LIST{$_} = $plugin_dir_entry;
      }
      closedir(DH_PLUGIN);
    }

    # look for ttd exclude configuration file
    #
    if (-f "${plugin_dir_entry}/${opt_scope}.exclude") {
      if (open(FILTER_EXC, "< ${plugin_dir_entry}/${opt_scope}.exclude")) {
        &debug("reading contents of ${plugin_dir_entry}/${opt_scope}.exclude ...");
        while (<FILTER_EXC>) {
          next unless (/^\s*([\w\d_]+)\/(\S+)/);
          my $filter_test_type    = $1;
          my $filter_test_subtype = $2;
          next unless (lc($filter_test_type) eq "snmp");
          $EXCLUDE_LIST{$filter_test_subtype} = 1;
          &debug("\twill exclude $filter_test_type/$filter_test_subtype tests");
        }
      }
      close (FILTER_EXC);
    }

    # look for ttd include (override) configuration file
    #
    if (-f "${plugin_dir_entry}/${opt_scope}.include") {
      if (open(FILTER_INC, "< ${plugin_dir_entry}/${opt_scope}.include")) {
        &debug("reading contents of ${plugin_dir_entry}/${opt_scope}.include ...");
        while (<FILTER_INC>) {
          next unless (/^\s*([\w\d_]+)\/(\S+)/);
          my $filter_test_type    = $1;
          my $filter_test_subtype = $2;
          next unless (lc($filter_test_type) eq "snmp");
          delete($EXCLUDE_LIST{$filter_test_subtype});
          &debug("\twill include $filter_test_type/$filter_test_subtype tests");
        }
      }
      close (FILTER_INC);
    }
  }

  # sort config files by name, plugin will override defaults
  #
  foreach (sort keys %FILE_LIST) {
    push (@{ $array_ref }, "$FILE_LIST{$_}/${_}");
  }
  
  # do we have any config files to work with?
  #
  if ($#{ $array_ref } < 0) {
    &debug("unable to locate any valid configuration files");
    return 0;
  }
  
  &debug("==== done generating list of xml files");
  return 1;

}


##
# discover which snmp version is supported by remote agent
##

sub get_agent_version {

  &debug("==== discovering snmp version supported by remote agent");
  my $discovered_version = $opt_ver;

  # prefer version 2 over 1 as with version 2, we have access to 64-bit 
  # countes, useful with busy network interfaces
  #
  my $curr_ver = 0;
  foreach my $current_version ("3", "2c", "1") {
    next unless (($discovered_version eq "99") || ($discovered_version eq $current_version));
    &debug("attempting snmp query with version ${current_version}");
    $opt_ver = $SNMP->{AGENT_VERSION} = $current_version;

    # Check 'standard' oids for existence. These should be mandatory as
    # specified in RFC's, but not all device adhere. Existence of any of
    # these oids should allow us to continue.
    foreach my $curr_oid ( ".1.3.6.1.2.1.1.1.0", ".1.3.6.1.2.1.1.2.0", ".1.3.6.1.2.1.1.3.0",
                           ".1.3.6.1.2.1.1.4.0", ".1.3.6.1.2.1.1.5.0", ".1.3.6.1.2.1.1.6.0",
                           ".1.3.6.1.2.1.1.7.0", ".1.3.6.1.2.1.1.8.0" ) {
      delete($SNMP->{OID_CACHE}->{$curr_oid});
      if ($SNMP->get($curr_oid)) {
        $curr_ver = $opt_ver;
        last;
      } 
    }
    if($curr_ver == $opt_ver) {
      last;
    } else {
      &debug("snmp query with version ${current_version} failed");
    }
  }

  if ($curr_ver) {
    $opt_ver = $curr_ver;
    &debug("remote host supports snmp version ${opt_ver}");
    print "VERSION: ${opt_ver}\n";
  } else {
    &error("snmp agent not running on remote host or incorrect community string");
    return 0;
  }

  &debug("==== done discovering snmp version");
  return 1;
    
}


##
# verify that current probe config is application to the device
##

sub check_condition {

  &debug("making sure this config entry is applicable to this device ...");

  my ($TC_REF) = @_;
  my $condition_failed = 0;
  my ($condition_name, $condition_required, $condition_found) = "";
  
  foreach $condition_name (keys %{ $TC_REF->{appliesTo} }) {
    $condition_required = $TC_REF->{appliesTo}->{$condition_name};
    next if ($condition_required =~ /^$/);
    next if ($condition_required eq '*');
    
    if ($condition_name =~ /devicetype/i) {
      &debug("[cond_c] (${condition_name}) ${condition_required} =~ ${opt_devtype}");
      if ((uc($opt_devtype) ne "UNKNOWN") && (! grep(/^$opt_devtype$/i, (split(/\s*,\s*/, $condition_required))))) {
        $condition_failed = 1;
        $condition_found = $opt_devtype;
        last;
      }
    } elsif ($condition_name =~ /agentversion/i) {
      $condition_required = "(2c|3)" if ($condition_required =~ /2/);
      &debug("[cond_c] (${condition_name}) ${opt_ver} =~ ${condition_required}");
      if ($opt_ver !~ /^$condition_required$/) {
        $condition_failed = 1;
        $condition_found = $opt_ver;
        last;
      }
    } elsif ($condition_name =~ /vendorname/i) {
      &debug("[cond_c] (${condition_name}) ${condition_required} =~ $GLOBAL_VAR{'vendor'}");
      if ($GLOBAL_VAR{'vendor'} !~ /$condition_required/i) {
        $condition_failed = 1;
        $condition_found = $GLOBAL_VAR{'vendor'};
        last;
      }
    } elsif ($condition_name =~ /modelname/i) {
      &debug("[cond_c] (${condition_name}) ${condition_required} =~ $GLOBAL_VAR{'model'}");
      if ($GLOBAL_VAR{'model'} !~ /$condition_required/i) {
        $condition_failed = 1;
        $condition_found = $GLOBAL_VAR{'model'};
        last;
      }
    } elsif ($condition_name =~ /configcount/i) {
      my $existing_test_count = $TEST_COUNT{lc($TC_REF->{name})} || 0;
      &debug("[cond_c] (${condition_name}) ${condition_required} < $existing_test_count");
      if ($condition_required < $existing_test_count) {
        $condition_failed = 1;
        $condition_found = $existing_test_count;
        last;
      }
    } else {
      &debug("[cond_e] ignoring unhandled condition \'$condition_name\'");
      next;
    }
  }
  
  if ($condition_failed) { 
    &debug("[cond_r] ${condition_name} does not match ${condition_found}");
    return 0;
  }
  
  &debug("[cond_r] allowing this configuration entry");
  return 1;

}


sub vendor_model_probe {

  if ($GLOBAL_VAR{'vendor'} ne "unknown") {
    &debug("vendor information already collected/displayed");
    return;
  }
  
  (my $oid_base, my $oid_vars, my $test_proc, my @match_patterns) = @_;

  &debug("==> entering vendor_model_probe()");

  my %SNMP_OBJECTS = ();
  my $param_name = my $param_value = "";
  
#  &debug("proc=${test_proc}");
#  foreach $param_name (split(/\s*,\s*/, $test_proc)) {
#    next if ($param_name =~ /^\s*$/);
#    ($param_name, $param_value) = split(/\s*=\s*/, $param_name);
#    next unless ($param_value =~ /yes/);
#    $param_name =~ tr/A-Z/a-z/;
#    &debug("initializing global var \'${param_name}\'");
#    $GLOBAL_VAR{$param_name} = "";
#  }
  
  # use the sysObjectID value to look at the enterprise number
  # and if a known value, set vendor information
  #
  $param_value = $SNMP->get(".1.3.6.1.2.1.1.2.0") || "";
  &debug("sysObjectID.0 = ${param_value}");
  
  if ($param_value) {
    if ($param_value =~ /\.1\.3\.6\.1\.4\.1\.(\d+)\./) {
      &debug("enterprise number = $1");
      $GLOBAL_VAR{'vendor'} = $NV_ENTERPRISE_ID{$1} || "";
      if ($GLOBAL_VAR{'vendor'}) {
        &debug("matched this number to \'$GLOBAL_VAR{'vendor'}\'");
      }
    }
  }    

  # sysDescr.0 may provide additional information, specially
  # model/version specific information
  #
  $param_value = $SNMP->get(".1.3.6.1.2.1.1.1.0") || "";
  &debug("sysDescr.0 = ${param_value}");

  # for unix systems, general format: os node rel ver mach
  # solaris: SunOS freefall 5.6 Generic_105181-19 sun4u
  # linux: Linux kepler.fidelia.com 2.2.16-22 #1 Tue Aug 22 16:49:06 EDT 2000 i686
  #
  # first, check for some major vendors / release information 
  if ($param_value =~ /red\s*hat.*\s+release\s+(.*)/i) {
    &debug("this looks like a Red Hat Linux host", "release is $1");
    $GLOBAL_VAR{'vendor'} = "Red Hat";
    $GLOBAL_VAR{'model'}  = $1;
  }

  elsif ($param_value =~ /suse\s+.*server\s+(.*)/i) {
    &debug("this looks like a SUSE Linux host", "release is $1");
    $GLOBAL_VAR{'vendor'} = "Novell SUSE";
    $GLOBAL_VAR{'model'}  = "Enterprise Server $1";
  }

  elsif ($param_value =~ /ubuntu\s+(\d+.*)/i) {
    &debug("this looks like an Ubuntu Linux host", "release is $1");
    $GLOBAL_VAR{'vendor'} = "Canonical Ubuntu";
    $GLOBAL_VAR{'model'}  = $1;
  }

  # fall back on default format (matching "uname -a" output)
  elsif ($param_value =~ /(linux|sunos|.*bsd|\w+ix)\s+\S+\s+(\S+)/i) {
    &debug("this looks like a unix host", "os is $1 $2");
    $GLOBAL_VAR{'vendor'} = $1;
    $GLOBAL_VAR{'model'}  = $2;
    
    # Solaris special case
    if ($GLOBAL_VAR{'vendor'} =~ /SunOS/i) {
      $GLOBAL_VAR{'vendor'} = "Sun Microsystems";
      $GLOBAL_VAR{'model'} = "Solaris/SunOS " . $GLOBAL_VAR{'model'};
    }
  }

  # "Sun SNMP Agent, UltraAX-i2"
  elsif ($param_value =~ /Sun\s+SNMP\s+Agent,\s+(.*)/i) {
    $GLOBAL_VAR{'vendor'} = "Sun Microsystems";
    $GLOBAL_VAR{'model'} = $1;
  }
  
  # windows 2000/nt
  # Hardware: x86 Family 6 Model 5 Stepping 2 AT/AT COMPATIBLE -
  # Software: Windows 2000 Version 5.0 (Build 2195 Uniprocessor Free)
  #
  elsif ($param_value =~ /hardware:.*software:\s+(.*)\s+version.*\s+\((.*)\)/i) {
    $GLOBAL_VAR{'vendor'} = "Microsoft Corporation";
    $GLOBAL_VAR{'model'} = $1;
  }

  elsif ($param_value =~ /wireless/i) {
    $opt_devtype = "WIRELESS";
  }

  # cisco routers running ios
  #
  elsif ($GLOBAL_VAR{'vendor'} =~ /\bcisco\b/i) {
    if ($param_value =~ /c6sup|catalyst|c5rsm|c6rsm/i) {
      $GLOBAL_VAR{'model'} = "Catalyst";
    } elsif ($param_value =~ /vpn/i) {
      $GLOBAL_VAR{'model'} = "VPN Concentrator";
    } elsif ($param_value =~ /(.*)\s+chassis,/i) {
      $GLOBAL_VAR{'model'} = $1;
    } elsif ($param_value =~ /NX\-OS\S+\s+(n|m)(\S+),/i) {
      my $device_prefix = $1;
      my $device_model  = $2;
      $GLOBAL_VAR{'model'} = $SNMP->get(".1.3.6.1.2.1.47.1.1.1.1.2.149") || "Nexus ${device_model}";
      if ($device_prefix eq "m") {
        $opt_devtype = "STORAGE";
      }
    } elsif ($param_value =~ /SAN\-OS\S+\s+m(\S+),/i) {
      $GLOBAL_VAR{'model'} = "MDS $1";
      $opt_devtype = "STORAGE";
    } elsif ($param_value =~ /\bap(\d+)\b/i) {
      $GLOBAL_VAR{'model'}  = $SNMP->get(".1.2.840.10036.2.1.1.9.2") || "";
    } else {
      $GLOBAL_VAR{'model'} = $SNMP->get(".1.3.6.1.2.1.47.1.1.1.1.2.1");
      if ($GLOBAL_VAR{'model'}) {
        $GLOBAL_VAR{'model'} =~ s/^(\S+)\s+chassis.*/$1/i;
        $GLOBAL_VAR{'model'} =~ s/\"//g;
        $GLOBAL_VAR{'model'} =~ s/^Cisco Systems\s*,?\s*(Inc)?\.?\s+//i;
        $GLOBAL_VAR{'model'} =~ s/^Cisco\s+//i;
      } else {
        $GLOBAL_VAR{'model'} = "unknown ";
      }
      if ($GLOBAL_VAR{'model'} =~ /^air\-/i) {
        $opt_devtype = "WIRELESS";
      }  
    }
  }

  # access point supporting 802dot11 mib
  #
  elsif ($opt_devtype eq "WIRELESS") {
    unless ($GLOBAL_VAR{'vendor'}) {
      $GLOBAL_VAR{'vendor'} = ($SNMP->get(".1.2.840.10036.3.1.2.1.2"))[0] || "";
      $GLOBAL_VAR{'vendor'} = (split(/\s+/, $GLOBAL_VAR{'vendor'}, 2))[1] || "";
    }
    unless ($GLOBAL_VAR{'model'}) {
      $GLOBAL_VAR{'model'}  = $SNMP->get(".1.2.840.10036.2.1.1.9.2") || "";
    }
  }
  
  elsif ($param_value =~ /\bapc\b/i) {
    $GLOBAL_VAR{'vendor'} = "APC";
    $GLOBAL_VAR{'model'} = $SNMP->get(".1.3.6.1.4.1.318.1.1.1.1.1.1.0") || (split(/\s+/, $param_value, 2))[1];
  }

  elsif ($GLOBAL_VAR{'vendor'} =~ /\bbmc\b/i) {
    if ($param_value =~ /Release\s+.*on\s+(.*)/i) {
      $GLOBAL_VAR{'model'} = $1;  
      if ($param_value =~ /patrol/i) {
        $GLOBAL_VAR{'model'} = "PATROL Agent for " . $GLOBAL_VAR{'model'};
      }
    }
  }
  
  elsif ($GLOBAL_VAR{'vendor'} =~ /foundry/i) {
    if ($param_value =~ /labeled\s+as\s+(\S+)/i) {
      $GLOBAL_VAR{'model'} = $1;
    } else {
      $param_value =~ s/Foundry\s+Networks(,)?\s?(Inc.)?\s?(.*)/$3/;
      $param_value =~ s/(.*)(,){1}/$2/;
      $GLOBAL_VAR{'model'} = $1;
    }
  }
  
  # default handler
  #
  else {
    if ($GLOBAL_VAR{'vendor'}) {
      $GLOBAL_VAR{'model'} = $param_value;
    } else {
      ($GLOBAL_VAR{'vendor'}, $GLOBAL_VAR{'model'}) = split(/\s+/, $param_value, 2);
    }
  }

  if ($GLOBAL_VAR{'vendor'}) {
    $GLOBAL_VAR{'vendor'} =~ s/[\"\']//g;
    print "VENDOR: $GLOBAL_VAR{'vendor'}\n" if ($GLOBAL_VAR{'vendor'});
  } else {
    $GLOBAL_VAR{'vendor'} = "unknown";
  }

  ## $GLOBAL_VAR{'model'} = "49 42 4D 20 50 6F 77 65 72 50 43 20 43 48 52 50 20 43 6F 6D 70 75 74\n65 72 0A 4D 61 63 68 69 6E 65 20 54 79 70 65 3A 20 6E 20 06 0A 37 30 32 36\n2D 36 48 31 2A 53 4E 2D 6E 20 06 0A 31 30 37 31 30 36 46 20 20 53 65 72 69\n61 6C 20 4E 75 6D 62 65 72 3A 20 6E 20 06 0A 31 30 37 31 30 36 46 20 20 0A\n42 61 73 65 20 4F 70 65 72 61 74 69 6E 67 20 53 79 73 74 65 6D 20 52 75 6E\n74 69 6D 65 20 41 49 58 20 76 65 72 73 69 6F 6E 3A 20 30 34 2E 30 33 2E 30\n30 30 33 2E 30 30 37 35 0A 54 43 50 2F 49 50 20 43 6C 69 65 6E 74 20 53 75\n70 70 6F 72 74 20 20 76 65 72 73 69 6F 6E 3A 20 30 34 2E 30 33 2E 30 30 30\n33 2E 30 30 37 35 ";
  if ($GLOBAL_VAR{'model'}) {
    $GLOBAL_VAR{'model'} =~ s/[\"\']//g;
    print "MODEL: " . $SNMP->_sanitize_text($GLOBAL_VAR{'model'}, 1) . "\n" if ($GLOBAL_VAR{'model'});
  } else {
    $GLOBAL_VAR{'model'} = "unknown";
  }

  # if unknown device type was picked, this is a good place to 
  # make some intelligent guess :/
  #
  if ($opt_devtype eq "UNKNOWN") {
    if ($GLOBAL_VAR{'vendor'} =~ /sunos|linux|\w+ix|bsd/i) {
      $opt_devtype = "UNIX";
    } elsif ($GLOBAL_VAR{'vendor'} =~ /microsoft|windows/i) {
      $opt_devtype = "NT";
    } elsif ($GLOBAL_VAR{'model'} =~ /windows\s+(nt|2000|xp)/i) {
      $opt_devtype = "NT";
    } elsif ($GLOBAL_VAR{'vendor'} =~ /\bsun\s*microsystem/i) {
      $opt_devtype = "UNIX";
    } elsif ($GLOBAL_VAR{'vendor'} =~ /cisco/i) {
      if ($GLOBAL_VAR{'model'} =~ /catalyst|29\d\d/i) {
        $opt_devtype = "SWITCH";
      } elsif ($GLOBAL_VAR{'model'} =~ /\bvpn\b/i) {
        $opt_devtype = "VPNC";
      } elsif ($GLOBAL_VAR{'model'} =~ /\b(firewall|security)\b/i) {
        $opt_devtype = "FIREWALL";
      } else {
        $opt_devtype = "ROUTER";
      }    
    } elsif ($GLOBAL_VAR{'vendor'} =~ /foundry/i) {
      $opt_devtype = "SWITCH";      
    } elsif ($GLOBAL_VAR{'model'} =~ /\bswitch\b/i) {
      $opt_devtype = "SWITCH";
    } elsif ($GLOBAL_VAR{'model'} =~ /router/i) {
      $opt_devtype = "ROUTER";
    } elsif ($GLOBAL_VAR{'model'} =~ /jetdirect|print\s*server/i) {
      $opt_devtype = "PRINTER";
    } elsif ($GLOBAL_VAR{'model'} =~ /firewall/i) {
      $opt_devtype = "FIREWALL";
    } elsif ($GLOBAL_VAR{'model'} =~ /\bvpn\b/i) {
      $opt_devtype = "VPNC";
    } elsif ($GLOBAL_VAR{'vendor'} =~ /\bbmc\b/i) {
      if ($GLOBAL_VAR{'model'} =~ /windows/i) {
        $opt_devtype = "NT";
      } else {
        $opt_devtype = "UNIX";
      }
    } else {
      # try to figure out the type from sysServices.0
      #
      $opt_devtype = $SNMP->get(".1.3.6.1.2.1.1.7.0") || 0;
      if (($opt_devtype >= 2) && ($opt_devtype <= 3)) {
        $opt_devtype = "SWITCH";
      } else {
        $opt_devtype = "UNIX";
      }
    }
    &debug("discovered device type: ${opt_devtype}");  
    print "TYPE: ${opt_devtype}\n";
  }
  
  &debug("<== exiting vendor_model_probe()");
  return 1;
  
}

sub pretty_uptime {

  my ($uptime) = @_;
  my ($seconds, $minutes, $hours, $days, $result);

  # we divide the uptime by hundred since we're not interested in
  # sub-second precision.  however, we'll go to great lengths to
  # avoid the result being negative
  #

  #$uptime >>= 1;
  #$uptime &= 0x7fffffff if $uptime < 0;
  #$uptime /= 50;

  $days = $uptime / (60 * 60 * 24);
  $uptime %= (60 * 60 * 24);
  
  $hours = $uptime / (60 * 60);
  $uptime %= (60 * 60);
  
  $minutes = $uptime / 60;
  $seconds = $uptime % 60;
  
  if ($days == 0){
    $result = sprintf("%d:%02d:%02d", $hours, $minutes, $seconds);
  } elsif ($days == 1) {
    $result = sprintf("%d day, %dh %02dm %02ds", 
      $days, $hours, $minutes, $seconds);
  } else {
    $result = sprintf("%d days, %dh %02dm %02ds", 
      $days, $hours, $minutes, $seconds);
  }
  return $result;
}

##
# call web services api to initiate same discovery on remote dge
##

sub remote_discovery {

  my (%api_param, $input_buffer);
  my ($api_response, $http_response, $json_payload);
  my (%PPD_NAME, $HTTP_ENDPOINT, $HTTP_OBJECT);
  
  $api_response = ""; # initialize

  # if using mock/sample data, load the contents
  #
  if ($opt_input_file) {

    $input_buffer = "";
    if (! open(JSON_IF, "< $opt_input_file")) {
      &error("unable to open input file \'${opt_input_file}\'", "reason: $!");
      &exit_now(1);
    }
    foreach (<JSON_IF>) { $input_buffer .= $_; }
    close (JSON_IF);

    if (length($input_buffer)) {
      $api_response = from_json($input_buffer);
    }
      
  } else {
     
    # request parameters
    #
    %api_param = ();
    $api_param{'username'}        = $opt_username;
    $api_param{'password'}        = $opt_password;
    $api_param{'dgeName'}         = $opt_remote;
    $api_param{'host'}            = $opt_device;
    $api_param{'communityString'} = $opt_cid;
    $api_param{'snmpVersion'}     = "V" . uc($opt_ver);
    $api_param{'snmpPort'}        = $opt_port;
    $api_param{'authProto'}       = $opt_v3auth ? $opt_v3auth : "NONE" ;
    $api_param{'privProto'}       = $opt_v3priv ? $opt_v3priv : "NONE";
    $api_param{'deviceType'}      = $opt_devtype;
    $api_param{'scope'}           = $opt_scope;
      
    $json_payload                 = to_json( \%api_param, { pretty => 1, canonical => 1 } );
    &debug("json payload for api request:", $json_payload);
  
    $HTTP_ENDPOINT                = "http://${opt_endpoint}/api/json/admin/test/discoverSnmpTests";
    $HTTP_OBJECT                  = new HTTP::Tiny(agent => 'Traverse/${my_name}');
  
    &debug("sending remote discovery request via ${HTTP_ENDPOINT} ...");
    $http_response                =  $HTTP_OBJECT->post( 
                                       $HTTP_ENDPOINT  => {
                                         headers   => { "Content-Type" => "application/json" },
                                         content   => $json_payload
                                       }
                                     );
    &debug("api call completed; status=" . $http_response->{status} . "; reason=" . $http_response->{reason});
    unless ($http_response->{success}) {
      &error("failed to send request to remote dge/dge extension via api", 
             "reason: " . $http_response->{status} . " " . $http_response->{reason});
      &exit_now(1);
    }
  
    if (length($http_response->{content})) {
      $api_response = from_json($http_response->{content});
    }
  }

  if (length($api_response)) {
    if ($DEBUG) { print Dumper \$api_response; };
    unless ($api_response->{'success'}) {
      &error("unable to perform test discovery on remote host",
             "reason: " . $api_response->{'errorMessage'});
    }
  }

  # print vendor/model information
  #
  if ($api_response->{'result'}->{'version'}) {
    print "VERSION: "  . $api_response->{'result'}->{'version'}  . "\n";
  }
  if ($api_response->{'result'}->{'vendor'}) {
    print "VENDOR: " . $api_response->{'result'}->{'vendor'} . "\n";
  }
  if ($api_response->{'result'}->{'model'}) {
    print "MODEL: "  . $api_response->{'result'}->{'model'}  . "\n";
  }

  # we need to map numeric value of post-processing directive to text counterpart
  #
  foreach $input_buffer (keys %NV_POST_PROCESSOR) {
    my $ppd_number         = $NV_POST_PROCESSOR{$input_buffer};
    $PPD_NAME{$ppd_number} = $input_buffer;
  }

  # print elements
  #
  # ---------------------------------------------------
  #  'elements' => [
  #    {
  #      'elementId' => 'diskio_17',
  #      'name' => 'Disk IO loop0',
  #      'category' => 'storage'
  #    }
  #  ]
  # ---------------------------------------------------
  #
  foreach $input_buffer ( @{ $api_response->{'result'}->{'elements'} }) {
    print "ELEMENT: ";
    print join('|',
      (
        $input_buffer->{'elementId'}, 
        $input_buffer->{'name'}, 
        $input_buffer->{'category'}, 
        ""
      )
    );
    print "\n";
  }

  # print test configurations
  #
  # ---------------------------------------------------
  #  'tests' => [
  #    {
  #      'actionProfile' => undef,
  #      'resultProcessDirective' => 0,
  #      'timeBasedThresholds' => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ),
  #      'criticalThreshold' => undef,
  #      'testName' => 'System Load Avg',
  #      'timeBasedCriticalThresholds' => undef,
  #      'thresholdType' => 1,
  #      'elementId' => undef,
  #      'warningThreshold' => undef,
  #      'maxValue' => 0,
  #      'maxDisplayValue' => '0',
  #      'monitorConfigId' => -1,
  #      'testSubType' => 'host_load_avg',
  #      'interval' => 0,
  #      'flapPreventionWaitCycles' => -1,
  #      'testType' => 'host_load_avg',
  #      'scheduleId' => 0,
  #      'snmpOid' => '.1.3.6.1.4.1.2021.10.1.5.2',
  #      'resultMultiplier' => '0.01',
  #      'units' => '',
  #      'timeBasedWarningThresholds' => undef
  #    }
  #  ]
  # ---------------------------------------------------
  #
  foreach $input_buffer ( @{ $api_response->{'result'}->{'tests'} }) {
    print "MONITOR: ";
    print join('|',
      (
        $input_buffer->{'testSubType'}, 
        $input_buffer->{'testName'}, 
        $input_buffer->{'units'}, 
        $input_buffer->{'maxDisplayValue'},
        $input_buffer->{'snmpOid'},
        $input_buffer->{'maxValue'},
        $input_buffer->{'resultMultiplier'}, 
        $PPD_NAME{$input_buffer->{'resultProcessDirective'}},
        $input_buffer->{'warningThreshold'},
        $input_buffer->{'criticalThreshold'},
        $input_buffer->{'elementId'}
      )
    );
    print "\n";
  }

}

