munin-mergedb 5.98 KB
#!/usr/bin/perl
#
# Merge munin db (datafile{,.storable} / limits) for multi-update masters
# environment
#
# (c) GPL - Adrien "ze" Urban
# Copyright (c) 2017 Mathieu Roy <yeupou--gnu.org>
#                   http://yeupou.wordpress.com
#
#   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

use strict;
use Fcntl qw(:flock);
use Storable;
use Munin::Master::Utils;

# Example of config (munin-merge.conf):
#   # what to merge ?
#   merge_datafile yes
#   merge_limits yes
#   # destination is the directory having this file
#   
#   # source directories to merge from (option should be used multiple times)
#   merge_source_dbdir /nfs/munin/db/updatehost1
#   merge_source_dbdir /nfs/munin/db/updatehost2
#   merge_source_dbdir /nfs/munin/db/updatehost3
#   merge_source_dbdir /nfs/munin/db/updatehost4


# 2017
# silently forbid concurrent runs
# (http://perl.plover.com/yak/flock/samples/slide006.html)
open(LOCK, "< $0") or die "Failed to ask lock. Exit";
flock(LOCK, LOCK_EX | LOCK_NB) or exit;


my $config_dir = '/etc/munin';
my $config_file = 'munin-merge.conf';
my $config_type = {
        'dbdir' =>'STRING', 
	'merge_source_dbdir' => 'ARRAY',
	'merge_datafile' => 'BOOL',
	'merge_limits' => 'BOOL',
};
my $config = {
        'dbdir' => undef,
#	'merge_dbdir' => undef,
	'merge_source_dbdir' => [],
	'merge_datafile' => 0,
	'merge_limits' => 0,
};

sub usage()
{
	print STDERR <<EOF;
Usage:
	$0 

	    Unlike the original version, this assumes 
	    $config_dir/$config_file
	    exists and contains something like:

	    merge_datafile yes
	    merge_limits yes

	    # destination is the directory having this file
	    dbdir /var/lib/munin

	    # source directories to merge from 
	    # (option should be used multiple times)
	    merge_source_dbdir /var/lib/munin
	    merge_source_dbdir /var/lib/munin-mergedb/<someother>
	    
Usually, you will want merge data to use them with your local instance.
Obviously that will work properly only if this script run just after
files to merge were generated by -update and -limit scripts (otherwise it
would duplicates entries).
EOF
	exit 1;
}

sub load_config()
{
	# 2017 yeupou: use proper /etc conf
	my $file = $config_dir."/".$config_file;

	open FILE, "<", $file or die "open: $!\n";
	while (<FILE>) {
		chomp;
		next if (/^[[:space:]]*#/); # comment
		next if (/^[[:space:]]*$/); # empty line
		unless (/^[[:space:]]*([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]*$/) {
			die "$.: Unrecogized line format\n";
		}
		my ($key, $value) = ($1, $2);
		if (not defined $config_type->{$key}) {
			die "$.: $key: unrecognized option\n";
		}
		if ('ARRAY' eq $config_type->{$key}) {
			push @{$config->{$key}}, $value;
		} elsif ('BOOL' eq $config_type->{$key}) {
			if ($value =~ /(yes|y|1|true)/i) {
				$config->{$key} = 1;
			} elsif ($value =~ /(no?|0|false)/i) {
				$config->{$key} = 0;
			} else {
				die "$.: unrecognized boolean: $value\n";
			}
		} elsif ('STRING' eq $config_type->{$key}) {
		    # 2017 yeupou: allow string value 
		    $config->{$key} = $value;
		} else {
			die "INTERNAL ERROR: $config_type->{$key}: " .
				"type not implemented\n";
		}
	}
	close FILE;
}
sub check_sources()
{
    # 2017 yeupou: check if (destination) dbdir is set
    die "No destination dbdir set.\n" unless $config->{'dbdir'};
    
	if (0 == scalar(@{$config->{'merge_source_dbdir'}})) {
		die "No source dbdir. " . 
			"Should I produce a result from thin air?\n";
	}
	# no datafile, means it's not really a munin dbdir
	for my $srcdir (@{$config->{'merge_source_dbdir'}}) {
		unless (-f "$srcdir/datafile") {
			die "$srcdir: datafile not found";
		}
	}
}
sub merge_plaintext($)
{
	my $name = shift;
	my $data = [];
	my $version = undef;
	for my $srcdir (@{$config->{'merge_source_dbdir'}}) {
		my $srcfile = "$srcdir/$name";
		unless (-f $srcfile) {
			die "$srcdir: $name not found";
		}
		open FILE, "<", $srcfile or
			die "open: $srcfile: $!\n";
		my $ver = <FILE>;
		if (defined $version) {
			die "$srcfile: versions differs: $version vs $ver\n"
				if ($ver ne $version);
		} else {
			$version = $ver;
		}
		push @$data, <FILE>;
		close FILE;
		#print $name, " ", scalar(@$data), " ", $srcdir, "\n";
	}
	# 2017 yeupou: s/merge_dbdir/dbdir/
	my $dstfile = $config->{'dbdir'} . "/" . $name;
	my $dsttmp = $dstfile . ".tmp.$$";
	open FILE, ">", $dsttmp or
		die "open: $dsttmp: $!\n";
	print FILE $version, @$data;
	close FILE;
	rename $dsttmp, $dstfile or
		die "mv $dsttmp $dstfile: $!\n";
}
sub merge_datafile_storable()
{
	my $name = 'datafile.storable';
	
	my $data = undef;
	for my $srcdir (@{$config->{'merge_source_dbdir'}}) {
		my $srcfile = "$srcdir/$name";
		unless (-f $srcfile) {
			die "$srcdir: $name not found";
		}
		my $info = retrieve($srcfile);
		if (defined $data) {
	        	$data = munin_overwrite($data, $info);
		} else {
			$data = $info;
		}
	}
	# 2017 yeupou: s/merge_dbdir/dbdir/  
	my $dstfile = $config->{'dbdir'} . "/" . $name;
	my $dsttmp = $dstfile . ".tmp.$$";
	Storable::nstore($data, $dsttmp);
	rename $dsttmp, $dstfile or
		die "mv $dsttmp $dstfile: $!\n";
}
sub merge_datafile()
{
	merge_plaintext('datafile');
	merge_datafile_storable();
}
sub merge_limits()
{
	merge_plaintext('limits');
}


# 2017 yeupou: print help if any arg is passed
$config->{'help'} = shift @ARGV;
usage if $config->{'help'};
    
load_config;
check_sources;

merge_datafile if ($config->{'merge_datafile'});
merge_limits if ($config->{'merge_limits'});

exit 0;