From f24f936c69ee97cca6095923549430cb6d510320 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Wed, 10 Jun 2015 18:16:13 +0200 Subject: slapd monitoring. We don't use the provided 'slapd_' Munin plugin because it doesn't support SASL binds. --- .../files/usr/local/share/munin/plugins/slapd2_ | 348 +++++++++++++++++++++ .../etc/munin/plugin-conf.d/munin-node.j2 | 5 + 2 files changed, 353 insertions(+) create mode 100755 roles/common/files/usr/local/share/munin/plugins/slapd2_ (limited to 'roles/common') diff --git a/roles/common/files/usr/local/share/munin/plugins/slapd2_ b/roles/common/files/usr/local/share/munin/plugins/slapd2_ new file mode 100755 index 0000000..56774ac --- /dev/null +++ b/roles/common/files/usr/local/share/munin/plugins/slapd2_ @@ -0,0 +1,348 @@ +#!/usr/bin/perl -w +# -*- perl -*- +# vim: ft=perl + +# Copyright Bjorn Ruberg +# Licenced under GPL v2 +# +# TODO: +# - Check for OpenLDAP version + +# We use one script for all monitoring. +# This script may be symlinked with several names, all +# performing different functions: +# slapd_statistics_bytes +# slapd_statistics_pdu +# slapd_statistics_referrals +# slapd_statistics_entries +# slapd_connections +# slapd_waiters +# slapd_operations +# slapd_operations_diff + +# Magic markers +#%# family=auto +#%# capabilities=autoconf suggest + +use strict; + +my $ret = ''; + +if (! eval "require Net::LDAP;") { + $ret = "Net::LDAP not found"; +} + +use vars qw ( $config $param $act $scope $descr $cn $vlabel + $info $title $label); + +# Change these to reflect your LDAP ACL. The given DN must have +# read access to the Monitor branch. +my $basedn = "cn=Monitor"; +my $server = ($ENV{'server'} || 'localhost'); +my $userdn = ($ENV{'binddn'} || ''); +my $userpw = ($ENV{'bindpw'} || ''); + +# Remember: connections, bytes, pdu needs scope=base + +# The possible measurements +my %ops = + ('statistics_bytes' + => { + 'search' => "cn=Bytes,cn=Statistics", + 'desc' => "The number of bytes sent by the LDAP server.", + 'vlabel' => 'Bytes per ${graph_period}', + 'label' => 'Bytes', + 'title' => "Number of bytes sent", + 'info' => "The graph shows the number of bytes sent", + 'scope' => "base" + }, + 'statistics_pdu' + => { + 'search' => "cn=PDU,cn=Statistics", + 'desc' => "The number of PDUs sent by the LDAP server.", + 'vlabel' => 'PDUs per ${graph_period}', + 'label' => 'PDUs', + 'title' => "Number of PDUs sent", + 'info' => "The graph shows the number of PDUs sent", + 'scope' => "base" + }, + # Referrals + 'statistics_referrals' + => { + 'search' => "cn=Referrals,cn=Statistics", + 'desc' => "The number of Referrals sent by the LDAP server.", + 'vlabel' => 'Referrals per ${graph_period}', + 'label' => 'Referrals', + 'title' => "Number of LDAP Referrals", + 'info' => "The graph shows the number of referrals sent", + 'scope' => "base" + }, + # Entries + 'statistics_entries' + => { + 'search' => "cn=Entries,cn=Statistics", + 'desc' => "The number of Entries sent by the LDAP server.", + 'vlabel' => 'Entries per ${graph_period}', + 'label' => 'Entries', + 'title' => "Number of LDAP Entries", + 'info' => "The graph shows the number of entries sent", + 'scope' => "base" + }, + # Only read Total + 'connections' + => { + 'search' => 'cn=Total,cn=Connections', + 'desc' => 'The number of connections', + 'label' => 'Connections', + 'vlabel' => 'Connections per ${graph_period}', + 'title' => 'Number of Connections', + 'info' => 'Number of connections to the LDAP server', + 'scope' => "base" + }, + # dn: cn=Write,cn=Waiters,cn=Monitor + # dn: cn=Read,cn=Waiters,cn=Monitor + 'waiters' + => { + 'search' => 'cn=Waiters', + 'filter' => '(|(cn=Write)(cn=Read))', + 'desc' => "The current number of Waiters", + 'label2' => {'write' => 'Write', + 'read' => 'Read'}, + 'vlabel' => "Waiters", + 'title' => "Number of Waiters", + 'info' => "The graph shows the number of Waiters" + }, + 'operations' + => { + 'search' => "cn=Operations", + 'desc' => "Operations", + 'vlabel' => 'Operations per ${graph_period}', + 'label' => 'Operations', + 'title' => "Operations", + 'info' => "Number of completed LDAP operations" + }, + 'operations_diff' + => { + 'search' => "cn=Operations", + 'desc' => "Operations deviance", + 'vlabel' => 'Deviance', + 'label' => 'Deviance', + 'title' => "Operations deviance", + 'info' => "Deviance between Initiated and Completed ops" + } + ); + +# Config subroutine +sub config { + my $action = shift; + print <{'vlabel'} +graph_title $ops{$action}->{'title'} +graph_category OpenLDAP +graph_info $ops{$action}->{'info'} +EOF + + if ($ops{$action}->{'label2'}) { + while (my ($key, $val) = each (%{$ops{$action}->{'label2'}})) { + my $name = $action . "_" . $key; + print "$name.label $val\n"; + print "$name.type GAUGE\n"; + } + } elsif ($action =~ /^operations(?:_diff)?$/) { + my $ldap = Net::LDAP->new ($server) + or die "Failed to connect to server $server: $@"; + my $mesg; + if ($userdn ne '') { + $mesg = $ldap->bind ($userdn, password => $userpw) + or die "Failed to bind with $userdn: $@"; + } else { + require Authen::SASL; + my $sasl = Authen::SASL::->new( mechanism => 'EXTERNAL' ); + $mesg = $ldap->bind( undef, sasl => $sasl ) + or die "Failed to SASL bind: $@"; + } + if ($mesg->code) { + die "Failed to bind: " . $mesg->error; + } + my $searchdn = $ops{$action}->{'search'} . "," . $basedn; + $mesg = + $ldap->search ( + base => $searchdn, + scope => 'one', + filter => '(objectclass=*)', + attrs => ['monitorOpInitiated', + 'monitorOpCompleted', + 'cn'], + ); + $mesg->code && die $mesg->error; + + my $max = $mesg->count; + for (my $i = 0 ; $i < $max ; $i++) { + my $entry = $mesg->entry ($i); + my $cn = $entry->get_value ('cn'); + my $name = $action . "_" . lc ($cn); + print "$name.label $cn\n"; + print "$name.type DERIVE\n"; + print "$name.min 0\n"; + if ($action eq "operations") { + print "$name.info The number of $cn operations\n"; + } else { + print "$name.info The difference between Initiated "; + print "and Completed operations (should be 0)\n"; + print "$name.warning 1\n"; + } + } + + $ldap->unbind; + } else { + print "$action.label $ops{$action}->{'label'}\n"; + print "$action.type DERIVE\n"; + print "$action.min 0\n"; + } +} + +# Determine action based on filename first +(my $action = $0) =~ s/^.*slapd2_([\w\d_]+)$/$1/; + +if ($ARGV[0]) { + if ($ARGV[0] eq 'autoconf') { + # Check for Net::LDAP + if ($ret) { + print "no ($ret)\n"; + exit 0; + } + + # Check for LDAP version 3 + my $ldap = Net::LDAP->new ($server, version => 3) + or do { print "no ($@)\n"; exit 0; }; + + my $mesg; + if ($userdn ne '') { + $mesg = $ldap->bind ($userdn, password => $userpw) + or do { print "no ($@)\n"; exit 0; }; + } else { + require Authen::SASL; + my $sasl = Authen::SASL::->new( mechanism => 'EXTERNAL' ); + $mesg = $ldap->bind( undef, sasl => $sasl ) + or do { print "no ($@)\n"; exit 0; }; + } + if ($mesg->code) { + print "no (" . $mesg->error . ")\n"; + exit 0; + } + + $mesg = + $ldap->search ( + base => $basedn, + scope => 'one', + filter => '(objectClass=monitorServer)', + attrs => [ + 'cn', + ], + ); + if ($mesg->code) { + print "no (" . $mesg->error . ")\n"; + exit 0; + } + print "yes\n"; + exit 0; + } elsif ($ARGV[0] eq "config") { + if(!exists $ops{$action}) { + die "Unknown action specified: $action"; + } + &config ($action); + } elsif ($ARGV[0] eq "suggest") { + print join ("\n", keys (%ops)), "\n"; + } + exit 0; +} + +# If $action isn't in %ops, we quit +if(!exists $ops{$action}) { + die "Unknown action specified: $action"; +} + +# Default scope for LDAP searches. We'll change to other scopes if +# necessary. +$scope = "one"; + +# Net::LDAP variant +my $ldap = Net::LDAP->new ($server, version => 3) + or die "Failed to connect to server $server: $@"; +my $mesg; +if ($userdn ne '') { + $mesg = $ldap->bind ($userdn, password => $userpw) + or die "Failed to bind with $userdn: $@"; +} else { + require Authen::SASL; + my $sasl = Authen::SASL::->new( mechanism => 'EXTERNAL' ); + $mesg = $ldap->bind( undef, sasl => $sasl ) + or die "Failed to bind anonymously: $@"; +} +if ($mesg->code) { + die "Failed to bind: " . $mesg->error; +} + +my $searchdn = $ops{$action}->{'search'} . "," . $basedn; +my $searchattrs; + +if ($action =~ /^operations(_diff)?$/) { + # We look for different parameters in Operations branch + $searchattrs = ['monitorOpInitiated', 'monitorOpCompleted', 'cn']; +} else { + $searchattrs = ['monitorCounter', 'cn']; +} + +my $filter; +if ($ops{$action}->{'filter'}) { + $filter = "(&(objectclass=*)" . $ops{$action}->{'filter'} . ")"; +} else { + $filter = "(objectClass=*)"; +} + +if ($ops{$action}->{'scope'}) { + $scope = $ops{$action}->{'scope'}; +} + +$mesg = + $ldap->search ( + base => $searchdn, + scope => $scope, + filter => $filter, + attrs => $searchattrs, + ); + +$mesg->code && die $mesg->error; + +my $max = $mesg->count; + +for (my $i = 0 ; $i < $max ; $i++) { + my $entry = $mesg->entry ($i); + my $cn = $entry->get_value('cn'); + if ($action =~ /operations(_diff)?$/) { + if ($1) { + my $opsInit = + $entry->get_value('monitorOpInitiated'); + my $opsComp = + $entry->get_value('monitorOpCompleted'); + print lc ("operations_diff_${cn}.value "); + print ($opsInit - $opsComp); + print "\n"; + } else { + print lc ("operations_${cn}.value "); + print $entry->get_value('monitorOpCompleted'), + "\n"; + } + } else { + # Hotfix, must do for now + if ($action =~ /_/ || $action eq 'connections') { + print lc ("${action}.value "); + } else { + print lc ("${action}_${cn}.value "); + } + print $entry->get_value('monitorCounter'), "\n"; + } +} +$ldap->unbind; diff --git a/roles/common/templates/etc/munin/plugin-conf.d/munin-node.j2 b/roles/common/templates/etc/munin/plugin-conf.d/munin-node.j2 index fa05327..e5ba9b5 100644 --- a/roles/common/templates/etc/munin/plugin-conf.d/munin-node.j2 +++ b/roles/common/templates/etc/munin/plugin-conf.d/munin-node.j2 @@ -135,3 +135,8 @@ env.PGPORT 5432 [fail2ban] user root + +[slapd2_*] +user munin +group munin +env.server ldapi:// -- cgit v1.2.3