From f6e10c1db16267ec433445e74bc9a03f6bb3dd7e Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 11 Jun 2015 10:49:36 +0200 Subject: Use a single LDAP connection per Munin round to collect slapd statistics. Using multigraphs instead. --- roles/common-LDAP/tasks/main.yml | 21 +- .../files/usr/local/share/munin/plugins/slapd2 | 269 ++++++++++++++++ .../files/usr/local/share/munin/plugins/slapd2_ | 348 --------------------- .../etc/munin/plugin-conf.d/munin-node.j2 | 3 +- 4 files changed, 276 insertions(+), 365 deletions(-) create mode 100755 roles/common/files/usr/local/share/munin/plugins/slapd2 delete mode 100755 roles/common/files/usr/local/share/munin/plugins/slapd2_ diff --git a/roles/common-LDAP/tasks/main.yml b/roles/common-LDAP/tasks/main.yml index a8c784d..aff0e58 100644 --- a/roles/common-LDAP/tasks/main.yml +++ b/roles/common-LDAP/tasks/main.yml @@ -8,7 +8,7 @@ - ldapvi - db-util - python-ldap - # for the 'slapd2_' munin plugin + # for the 'slapd2' munin plugin - libnet-ldap-perl - libauthen-sasl-perl @@ -137,22 +137,13 @@ mode=0755 -- name: Install 'slapd2_' Munin wildcard plugin - # we don't install 'slapd_' because it doesn't support SASL binds - file: src=/usr/local/share/munin/plugins/slapd2_ - dest=/etc/munin/plugins/slapd2_{{ item }} +- name: Install 'slapd2' Munin plugin + # we don't install 'slapd_' because it doesn't support SASL binds and + # ours is more parcimonious with LDAP connections + file: src=/usr/local/share/munin/plugins/slapd2 + dest=/etc/munin/plugins/slapd2 owner=root group=root state=link force=yes - with_items: - # sudo /usr/share/munin/plugins/slapd2_ suggest - - connections - - statistics_entries - - operations_diff - - statistics_referrals - - statistics_pdu - - waiters - - statistics_bytes - - operations tags: - munin - munin-node 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..68c482b --- /dev/null +++ b/roles/common/files/usr/local/share/munin/plugins/slapd2 @@ -0,0 +1,269 @@ +#!/usr/bin/perl -w + +# Munin plugin for monitoring slapd. Based on Bjorn Ruberg's slapd_ +# plugin. The main difference is that in our case munin SASL-binds +# against the LDAP directory, and a single connection is used to collect +# all statistics. +# Copyright Bjorn Ruberg +# Copyright © 2015 Guilhem Moulin +# +# 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, version 2 of the License. +# +# 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, see . + +use warnings; +use strict; + +use Net::LDAP (); +use Net::LDAP::Util (); +use Authen::SASL (); + +# 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" + } + ); + + +my $ldap = Net::LDAP::->new( 'ldapi://' ); +my $sasl = Authen::SASL::->new( mechanism => 'EXTERNAL' ); +my $mesg = $ldap->bind( undef, sasl => $sasl ); +die "LDAP error code $mesg->code: $mesg->error\n", + Net::LDAP::Util::ldap_error_text($mesg), "\n" + if $mesg->code; +my $basedn = 'cn=Monitor'; + + +if (@ARGV and $ARGV[0] eq 'config') { + foreach my $action (keys %OPS) { + print "multigraph slapd2_$action\n"; + print "graph_title $OPS{$action}->{title}\n"; + print "graph_info $OPS{$action}->{info}\n"; + print "graph_vlabel $OPS{$action}->{vlabel}\n"; + print "graph_args --base 1000 -l 0\n"; + print "graph_scale no\n"; + print "graph_category OpenLDAP\n"; + + if ($OPS{$action}->{label2}) { + foreach my $key (keys %{$OPS{$action}->{label2}}) { + my $name = $action . "_" . $key; + print "$name.label $OPS{$action}->{label2}->{$key}\n"; + print "$name.type GAUGE\n"; + } + } elsif ($action =~ /^operations(?:_diff)?$/) { + my $mesg = $ldap->search ( base => "$OPS{$action}->{search},$basedn" + , scope => 'one' + , deref => 'never' + , filter => '(objectclass=*)' + , attrs => [ 'monitorOpInitiated' + , 'monitorOpCompleted' + , 'cn' ] + ); + die "LDAP error code $mesg->code: $mesg->error\n", + Net::LDAP::Util::ldap_error_text($mesg), "\n" + if $mesg->code; + + while (my $e = $mesg->pop_entry) { + my $cn = $e->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"; + } + } + } else { + print "$action.label $OPS{$action}->{label}\n"; + print "$action.type DERIVE\n"; + print "$action.min 0\n"; + } + } +} + +else { + foreach my $action (keys %OPS) { + 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 $mesg = $ldap->search ( base => "$OPS{$action}->{search},$basedn" + , scope => $OPS{$action}->{scope} // 'one' + , deref => 'never' + , filter => $OPS{$action}->{filter} // '(objectclass=*)' + , attrs => \@searchattrs + ); + die "LDAP error code $mesg->code: $mesg->error\n", + Net::LDAP::Util::ldap_error_text($mesg), "\n" + if $mesg->code; + + print "multigraph slapd2_$action\n"; + while (my $e = $mesg->pop_entry) { + my $cn = $e->get_value('cn'); + if ($action =~ /operations(_diff)?$/) { + if ($1) { + my $opsInit = $e->get_value('monitorOpInitiated'); + my $opsComp = $e->get_value('monitorOpCompleted'); + printf "operations_diff_%s.value %d\n", lc $cn, ($opsInit - $opsComp); + } else { + printf "operations_%s.value %d\n", lc $cn, $e->get_value('monitorOpCompleted'); + } + } else { + # Hotfix, must do for now + printf "%s.value %d\n", + (($action =~ /_/ or $action eq 'connections') ? lc $action : lc "${action}_${cn}"), + $e->get_value('monitorCounter'); + } + } + } +} + +#$mesg = $ldap->search( base => 'cn=Monitor' +# , scope => 'base' +# , deref => 'never' +# , attrs => + +$ldap->unbind(); + + +#$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/files/usr/local/share/munin/plugins/slapd2_ b/roles/common/files/usr/local/share/munin/plugins/slapd2_ deleted file mode 100755 index 56774ac..0000000 --- a/roles/common/files/usr/local/share/munin/plugins/slapd2_ +++ /dev/null @@ -1,348 +0,0 @@ -#!/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 e5ba9b5..6cfa3f9 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 @@ -136,7 +136,6 @@ env.PGPORT 5432 [fail2ban] user root -[slapd2_*] +[slapd2] user munin group munin -env.server ldapi:// -- cgit v1.2.3