summaryrefslogtreecommitdiff
path: root/src/pmdas/snmp
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmdas/snmp')
-rw-r--r--src/pmdas/snmp/GNUmakefile53
-rwxr-xr-xsrc/pmdas/snmp/Install28
-rw-r--r--src/pmdas/snmp/README52
-rwxr-xr-xsrc/pmdas/snmp/Remove29
-rwxr-xr-xsrc/pmdas/snmp/pmdasnmp.pl413
-rw-r--r--src/pmdas/snmp/snmp.conf44
6 files changed, 619 insertions, 0 deletions
diff --git a/src/pmdas/snmp/GNUmakefile b/src/pmdas/snmp/GNUmakefile
new file mode 100644
index 0000000..8c50814
--- /dev/null
+++ b/src/pmdas/snmp/GNUmakefile
@@ -0,0 +1,53 @@
+#!gmake
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = snmp
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl snmp.conf
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ $(INSTALL) -m 644 snmp.conf $(PMDADIR)/snmp.conf
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/snmp/Install b/src/pmdas/snmp/Install
new file mode 100755
index 0000000..999f0a6
--- /dev/null
+++ b/src/pmdas/snmp/Install
@@ -0,0 +1,28 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the SNMP gateway PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=snmp
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/snmp/README b/src/pmdas/snmp/README
new file mode 100644
index 0000000..15fe1d3
--- /dev/null
+++ b/src/pmdas/snmp/README
@@ -0,0 +1,52 @@
+The PCP SNMP gateway PMDA is a plugin that allows PCP clients to query
+for data from SNMP agents.
+
+Depends on perl modules:
+PCP::PMDA libpcp-pmda-perl
+Net::SNMP libnet-snmp-perl
+
+The PCP names that are exported are (currently)
+
+ snmp.host.$hostname.$oid
+ snmp.host.$hostname.$oid[$rownr]
+
+In the future I hope to propose an extension to the PCP agent protocol
+to allow the use of virtual hostnames, which would change the names
+exported.
+
+I also hope in the future to load in full MIB names, which would also
+change the exported names to make them more readable.
+
+Also as a future target, as OIDs are queried for by the PCP client,
+this gateway will dynamically create mappings between the PCP internal
+ID and the SNMP OID. This creates limitations on the total number of
+mappings available.
+
+SNMP uses an open ended hierachical namespace with a potentially unlimited
+number of values. MIB files on the SNMP client are used to map between
+dotted names and dotted numbers. These MIB files also provide type
+information and help text for each OID
+
+PCP uses a 64bit value to uniquely identify each available value. This is
+divided into:
+ 9 bits - "domain" number (./src/pmns/Make.stdpmid gives 56-58 to SNMP)
+ 12 bits - "cluster" number
+ 10 bits - "item" number (cluster+item identify the metric)
+ 32 bits - "instance" of the metric
+PCP uses an open ended dotted text name to translate to this fixed 64bit value.
+The PCP agent sends the text to number mapping to the PCP client as needed.
+
+This gateway is configured with a config file:
+* define each SNMP hostname and credentials
+* define a list of MIB namespaces to load (future)
+* define a list of static OID -> PCP mappings (including room for tables..)
+* a static mapping of a tree (and the max static size of that tree?) (future)
+* a static "table" mapping for special use of the "instance" value
+* define a "high water mark" for static mappings (future)
+
+If a query comes in for a static mapping, then the PCP value from the
+config is used every time. If a query for a new mapping is received
+then the gateway dynamically allocates the next free ID (starting from
+MAXINT working downwards). This dynamic mapping might change on gateway
+restarts or on idle timeouts.
+
diff --git a/src/pmdas/snmp/Remove b/src/pmdas/snmp/Remove
new file mode 100755
index 0000000..7a412d9
--- /dev/null
+++ b/src/pmdas/snmp/Remove
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Remove the SNMP PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=snmp
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/snmp/pmdasnmp.pl b/src/pmdas/snmp/pmdasnmp.pl
new file mode 100755
index 0000000..058659d
--- /dev/null
+++ b/src/pmdas/snmp/pmdasnmp.pl
@@ -0,0 +1,413 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2011-2012 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use FileHandle;
+use PCP::PMDA;
+use Net::SNMP qw(:asn1);
+
+use Data::Dumper;
+$Data::Dumper::Indent = 1;
+$Data::Dumper::Sortkeys = 1;
+$Data::Dumper::Quotekeys = 0;
+$Data::Dumper::Useqq = 1; # PMDA log doesnt like binary :-(
+
+our $VERSION='0.3';
+my $db = {};
+my $option = {
+ max_row => 100, # default maximum number of rows for a table
+ pmid_per_host => 100, # default number of pmid's for each host
+};
+
+# SNMP string type name to numeric type number
+#
+my $snmptype2val = {
+ INTEGER => INTEGER32,
+ INTEGER32 => INTEGER32,
+ OCTET_STRING => OCTET_STRING,
+ STRING => OCTET_STRING,
+ OBJECT_IDENTIFIER => OBJECT_IDENTIFIER,
+ IPADDRESS => IPADDRESS,
+ COUNTER => COUNTER32,
+ COUNTER32 => COUNTER32,
+ GAUGE => GAUGE32,
+ GAUGE32 => GAUGE32,
+ UNSIGNED32 => UNSIGNED32,
+ TIMETICKS => TIMETICKS,
+ OPAQUE => OPAQUE,
+ COUNTER64 => COUNTER64,
+};
+
+# SNMP numeric type number to PCP type number
+#
+my $snmptype2pcp = {
+ 0x02 => { type=> PM_TYPE_32, sem=> PM_SEM_INSTANT }, # INTEGER32
+ 0x04 => { type=> PM_TYPE_STRING, sem=> PM_SEM_DISCRETE }, # OCTET_STRING
+ 0x06 => { type=> PM_TYPE_STRING, sem=> PM_SEM_DISCRETE }, # OBJECT_IDENTIFIER
+ 0x40 => { type=> PM_TYPE_STRING, sem=> PM_SEM_DISCRETE }, # IPADDRESS
+ 0x41 => { type=> PM_TYPE_U32, sem=> PM_SEM_COUNTER }, # COUNTER32
+ 0x42 => { type=> PM_TYPE_32, sem=> PM_SEM_INSTANT }, # GAUGE32
+ 0x42 => { type=> PM_TYPE_U32, sem=> PM_SEM_INSTANT }, # UNSIGNED32
+ 0x43 => { type=> PM_TYPE_64, sem=> PM_SEM_COUNTER }, # TIMETICKS
+ 0x44 => { type=> PM_TYPE_STRING, sem=> PM_SEM_DISCRETE }, # OPAQUE
+ 0x46 => { type=> PM_TYPE_64, sem=> PM_SEM_COUNTER }, # COUNTER64
+};
+
+my $dom_rows = 0; # this indom nr used for generic row numbers
+
+my $pmda = PCP::PMDA->new('snmp', 56);
+
+# Read in the config file(s)
+#
+sub load_config {
+ my $db = shift;
+
+ if (!defined $db->{hosts}) {
+ $db->{hosts} = {};
+ }
+ if (!defined $db->{map}) {
+ $db->{map} = {};
+ $db->{map}{hosts} = [];
+ $db->{map}{oids} = [];
+ }
+
+ for my $filename (@_) {
+ my $fh = FileHandle->new($filename);
+ if (!defined $fh) {
+ warn "opening $filename $!";
+ next;
+ }
+
+ while(<$fh>) {
+ chomp; s/\r//g;
+
+ # strip whitespace at the beginning and end of the line
+ s/^\s+//;
+ s/\s+$//;
+
+ # strip comments
+ s/^#.*//; # line starts with a comment char
+ s/[^\\]#.*//; # non quoted comment char
+
+ if (m/^$/) {
+ # empty lines, or lines that were all comment
+ next;
+ }
+
+ if (m/^set\s+(\w+)\s+(.*)$/) {
+ # set an option
+ my $key = $1;
+ my $val = $2;
+
+ $option->{$key}=$val;
+ } elsif (m/^host\s+(\S+)\s+(.*)$/) {
+ my $e = {};
+ $e->{hostname}=$1;
+ $e->{community}=$2;
+
+ # The reversed dotted hostname is used in the metric name
+ $e->{revname} = join('.',reverse(split('\.',$1)));
+
+ # TODO - lazy create snmp sessions on first use
+ my ($session,$error) = Net::SNMP->session(
+ -hostname =>$e->{hostname},
+ -community=>$e->{community},
+ );
+ if (!$session) {
+ warn("SNMP session to $e->{hostname}: $error");
+ $e->{error}=$error;
+ } else {
+ $e->{snmp}=$session;
+ $e->{snmp}->translate([-timeticks=>0]);
+ }
+ $db->{hosts}{$1} = $e;
+ my $id = scalar @{$db->{map}{hosts}};
+ # TODO - allow this pmid 'index base' to be set
+ $e->{id} = $id;
+ @{$db->{map}{hosts}}[$id]=$e;
+ } elsif (m/^map\s+(single|column)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/) {
+ my $snmptype = $snmptype2val->{$3};
+ if (!defined $snmptype) {
+ warn("Invalid SNMP type '$3' on oid '$2'\n");
+ next;
+ }
+ my $e = {};
+ my $id = $4;
+ if ($id eq '+') {
+ # select the next available number
+ $id = scalar @{$db->{map}{oids}};
+ }
+ if ($id > $option->{pmid_per_host}) {
+ warn("More metrics than allowed by pmid_per_host");
+ next;
+ }
+ $e->{type}=$1;
+ $e->{oid}=$2;
+ $e->{snmptype}=$snmptype;
+ $e->{id}=$id;
+ $e->{text}=$5;
+ @{$db->{map}{oids}}[$id]=$e;
+ } else {
+ warn("Unrecognised config line: $_\n");
+ }
+ # TODO - add map tree, mib load
+ }
+ }
+
+ $db->{max}{hosts} = scalar keys %{$db->{hosts}};
+ $db->{max}{oids} = scalar @{$db->{map}{oids}};
+ $db->{max}{static} = $db->{max}{hosts} * $option->{pmid_per_host};
+ # any PMID above max static is available for dynamicly created mappings
+
+ return $db;
+}
+
+# Create the fake generic rows indom
+# TODO - demand create the rows indoms
+sub db_create_indom {
+ my ($db) = @_;
+
+ my @dom;
+ for my $row (0..$option->{max_row}) {
+ # first is id, second is string description
+ # for now, both are the same
+ # TODO - populate the indom with rational names from an SNMP column
+ push @dom,$row,$row;
+ }
+ $pmda->add_indom($dom_rows,\@dom,'SNMP rows','');
+}
+
+# Using the mappings, define all the metrics
+#
+sub db_add_metrics {
+ my ($db) = @_;
+
+ # TODO - nuke the PMDA.xs current list of metrics here
+ # (there is a clear_metrics() in the xs that might be adapted to work)
+
+ # add our version
+ $pmda->add_metric(pmda_pmid(0,0), PM_TYPE_STRING,
+ PM_INDOM_NULL, PM_SEM_DISCRETE,
+ pmda_units(0,0,0,0,0,0), "snmp.version", '', '');
+
+ for my $host (@{$db->{map}{hosts}}) {
+ # calculate the pmid for the first metric for this host
+ my $hostbase = $host->{id} * $option->{pmid_per_host};
+
+ # skip hosts that did not setup their snmp session
+ next if (!$host->{snmp});
+
+ for my $e (@{$db->{map}{oids}}) {
+ # for each predefined static mapping, register a metric
+
+ if (!defined $e) {
+ next;
+ }
+ my $id = $hostbase + $e->{id};
+
+ # hack around the too transparent opaque datatype
+ my $cluster = int($id /1024);
+ my $item = $id %1024;
+
+ my $type = $snmptype2pcp->{$e->{snmptype}};
+ if (!defined $type) {
+ warn("Unknown type=$type for id=$e->{id}\n");
+ next;
+ }
+
+ my $indom;
+ if ($e->{type} eq 'single') {
+ $indom = PM_INDOM_NULL;
+ } elsif ($e->{type} eq 'column') {
+ # TODO - use metric specific indom, for now, just use generic
+ $indom = $dom_rows;
+ $e->{indom} = $indom;
+ } else {
+ warn("Unknown map type = $e->{type}\n");
+ next;
+ }
+ $pmda->add_metric(pmda_pmid($cluster,$item),
+ $type->{type},
+ $indom, $type->{sem},
+ pmda_units(0,0,0,0,0,0),
+ 'snmp.host.'.$host->{revname}.'.'.$e->{oid}, $e->{text}, ''
+ );
+ }
+ }
+}
+
+# debug when fetch is called
+# fetch_func is called with no params during a "fetch", after refreshing the
+# PMNS before calling refresh_func
+#
+sub fetch {
+ if ($option->{debug}) {
+ $pmda->log("fetch");
+ }
+}
+
+# debug when instance is called
+# instance_func is called with "indom" param during a "instance", after
+# refreshing the PMNS before calling pmdaInstance
+#
+sub instance {
+ my ($indom) = @_;
+ if ($option->{debug}) {
+ $pmda->log("instance $indom");
+ }
+}
+
+sub fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ my $id = $cluster*1024 + $item;
+
+ if ($option->{debug}) {
+ my $metric_name = pmda_pmid_name($cluster, $item);
+ $pmda->log("fetch_callback $metric_name $cluster:$item ($inst)");
+ }
+
+ if ($id == 0) {
+ return ($VERSION,1);
+ }
+
+ my $hostnr = int($id / $option->{pmid_per_host});
+ my $host = @{$db->{map}{hosts}}[$hostnr];
+ if (!defined $host) {
+ return (PM_ERR_NOTHOST, 0);
+ }
+
+ my $map = @{$db->{map}{oids}}[$id % $option->{pmid_per_host}];
+ if (!defined $map) {
+ return (PM_ERR_PMID, 0);
+ }
+ my $oid = $map->{oid};
+
+ if (defined $map->{indom}) {
+ # only metrics with rows have an indom
+ $oid.='.'.$inst;
+ }
+
+ # TODO - maybe check if a map single has been called with an inst other
+ # than PM_INDOM_NULL
+
+ if ($option->{debug}) {
+ $pmda->log("fetch_callback hostnr=$hostnr rownr=$inst");
+ }
+
+ if (!defined $host->{snmp}) {
+ # We have no snmp object for this host
+ # FIXME - a better errno?
+ return (PM_ERR_EOF, 0);
+ }
+ my $snmp = $host->{snmp};
+
+ my $result = $snmp->get_request(
+ -varbindlist=>[
+ $oid,
+ ]
+ );
+
+ if (!$result) {
+ # We didnt get a valid snmp response
+ return (PM_ERR_PMID, 0);
+ }
+
+ my $types = $snmp->var_bind_types();
+ if ($map->{snmptype} != $types->{$oid}) {
+ return (PM_ERR_CONV, 0);
+ }
+ return ($result->{$oid},1);
+}
+
+load_config($db,
+ pmda_config('PCP_PMDAS_DIR').'/snmp/snmp.conf',
+# 'snmp.conf'
+);
+
+db_create_indom($db);
+
+db_add_metrics($db);
+
+$pmda->set_fetch(\&fetch);
+$pmda->set_instance(\&instance);
+$pmda->set_fetch_callback(\&fetch_callback);
+
+if ($option->{debug}) {
+ $pmda->log("db=".Dumper($db)."\n");
+ $pmda->log("option=".Dumper($option)."\n");
+}
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdasnmp - Gateway from SNMP to PCP (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdasnmp> is a Performance Metrics Domain Agent (PMDA) which
+provides a generic gateway from PCP queries from a PCP client to SNMP queries
+to one or more SNMP agents.
+
+=head1 INSTALLATION
+
+If you want access to the SNMP gateway performance
+metrics, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/snmp
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/snmp
+ # ./Remove
+
+B<pmdasnmp> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 CONFIGURATION
+
+TODO: define config file format here - map/set/host/... etc
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/snmp/snmp.conf
+
+optional configuration file for B<pmdasnmp>
+
+=item $PCP_PMDAS_DIR/snmp/Install
+
+installation script for the B<pmdasnmp> agent
+
+=item $PCP_PMDAS_DIR/snmp/Remove
+
+undo installation script for the B<pmdasnmp> agent
+
+=item $PCP_LOG_DIR/pmcd/snmp.log
+
+default log file for error and warn() messages from B<pmdasnmp>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1) and SNMP
diff --git a/src/pmdas/snmp/snmp.conf b/src/pmdas/snmp/snmp.conf
new file mode 100644
index 0000000..ed7b88b
--- /dev/null
+++ b/src/pmdas/snmp/snmp.conf
@@ -0,0 +1,44 @@
+# Simple, first pass at a config file. I expect to need a more expressive
+# file format in the future
+
+# hashes begin comments
+# comments can begin in the # middle of a line
+
+# define a host
+#host $hostname $community
+# TODO - allow the pmid 'index base' to be set for each host
+# currently only community based auth is supported
+host localhost public
+
+# load a mib (future) - gives name to number oid mappings
+#mib $mibfilename
+
+# static mapping
+# single maps a single oid
+# column maps a simple table column where the last octet in the oid is the row
+# id's start at 1 and redefininitions result in the last define winning
+# the snmptype is used to calculate the pcp metric type to use
+# the text is used as the metric short help text
+#map (single|column) $oid $snmptype $id $text
+
+map single 1.3.6.1.2.1.1.3.0 TIMETICKS 1 sysUpTime
+map single 1.3.6.1.2.1.1.5.0 STRING 2 sysName
+
+map column 1.3.6.1.2.1.2.2.1.2 STRING 10 ifDescr
+map column 1.3.6.1.2.1.2.2.1.5 GAUGE32 + ifSpeed
+map column 1.3.6.1.2.1.2.2.1.10 COUNTER32 + ifInOctets
+
+# TODO - work out some kind of static walk define
+#map tree $oid $id_start $id_max $text_prefix
+
+# For the moment, table oids are limited to this max number
+set max_row 50
+
+# Set the maximum number of metrics per host. This is used to create the
+# metric ID and thus if changed will affect merging data with older archives.
+set pmid_per_host 1000
+
+# set an option
+#set key val
+set debug 1
+