#!/usr/bin/perl -w

=head1 NAME

    glusterfs-conf-compiler - конфигурирование glusterfs из директории конфигов

=head1 DESCRIPTION

    Раз в 5 минут берёт YAML файлы из /etc/glusterfs/server.d/*.yaml
    парсит, отправляет в шаблон /etc/glusterfs/glusterfsd.tmpl
    Если результат отличается от glusterfsd.vol - перезапускает glusterfsd

    Так же, раз в 5 минут берёт файлики из /etc/glusterfs/client.d/*.vol
    и монтирует всё в /mnt/%HOST/%VOLUME.
    Так же отмонтирует всё, что не нашлось в client.d

    Можно запускать руками.
    
=cut

use strict;
use warnings;

use Getopt::Long;
use File::Slurp;
use File::Path;
use YAML;
use Template;
use Pid::File::Flock qw/:auto/;

my $SERVER_DIR = '/etc/glusterfs/server.d';
my $SERVER_CONFIG_TMPL = '/etc/glusterfs/glusterfsd.tmpl';
my $SERVER_CONFIG = '/etc/glusterfs/glusterfsd.vol';
my $SERVER_INIT = '/etc/init.d/glusterfs-server';
my $CLIENT_DIR = '/etc/glusterfs/client.d';
my $MNT_DIR = '/mnt';

GetOptions(
    "help" => sub {system("podselect -section NAME -section DESCRIPTION $0 | pod2text-utf8 >&2"); exit 0;},
    "server-dir=s" => \$SERVER_DIR,
    "server-config-tmpl=s" => \$SERVER_CONFIG_TMPL,
    "server-config=s" => \$SERVER_CONFIG,
    "server-init=s" => \$SERVER_INIT,
    "client-dir=s" => \$CLIENT_DIR,
    "mnt-dir=s" => \$MNT_DIR,
    ) || die "Error occured";

process_server_files();
process_client_files();

# собираем из $SERVER_CONFIG/* единый файл $SERVER_CONFIG
# если он изменился - перезапускаем $SERVER_INIT
sub process_server_files {
    my @volumes = map {YAML::LoadFile($_)} _get_files($SERVER_DIR, qr/\.yaml$/);
    return if !@volumes;
    my $t = Template->new({ABSOLUTE => 1});
    my $new_cont;
    $t->process($SERVER_CONFIG_TMPL, {volumes => \@volumes}, \$new_cont) || die "Template process error: ".$t->error();
    if (-f $SERVER_CONFIG && read_file($SERVER_CONFIG) eq $new_cont) {
        return;
    }
    write_file($SERVER_CONFIG, {atomic => 1}, $new_cont);
    if (-x $SERVER_INIT) {
        system($SERVER_INIT => 'restart') && die "Can't $SERVER_INIT restart: $!";
    }
}

# находим отличия между файлами $CLIENT_DIR и реально примаунченными
# директориями и домаунтим/анмаунтим всё что нужно
sub process_client_files {
    my %mtab = map {$_->{dir} => $_}
        grep {$_->{type} eq 'fuse.glusterfs' && $_->{dev} =~ /^\Q$CLIENT_DIR\E/}
        _read_mtab();
    for my $file (_get_files($CLIENT_DIR, qr/\.vol$/)) {
        my $cont = read_file($file);
        my ($volume, $host) = $cont =~ /(?:\n|^)\s*volume\s+(\S+).*?\n\s*option\s+remote-host\s+(\S+)/s;
        die "Can't get volumn/host from $file" if !defined $volume;
        my %opts = map {/^\s*#!\s*([\w-]+)\s*:\s*(\S+)*?\s*$/ ? ($1 => $2) : ()} split /\n/, $cont;
        my $dir = $opts{'mount-dir'} || "$MNT_DIR/$host/$volume";
        if (exists $mtab{$dir}) {
            delete $mtab{$dir};
        } else {
            mkpath($dir) if !-d $dir;
            system("mount", -t => "glusterfs", -o => "defaults,volume-name=$volume", $file, $dir) && die "Can't mount: $!";
        }
    }
    while(my ($dir, $info) = each %mtab) {
        system("umount", $dir) && die "Can't umount $dir: $!";
    }
}

# получить из указанной директории все файлы, несодержащие точки в названии
sub _get_files {
    my ($dir, $re) = @_;
    opendir(my $dh, $dir) || die "Can't open dir $dir: $!";
    my @files = sort grep {-f} map {"$dir/$_"} grep {/$re/} readdir($dh);
    closedir($dh) || die "Can't close dir $dir: $!";
    return @files;
}

# прочитать и распарсить /etc/mtab
# на выходе массих хэшей {dev => '/dev/sda1', dir => '/', type => 'ext3', opts => 'defaults'}
sub _read_mtab {
    return 
        map {my @F = split /\s+/; {dev => $F[0], dir => $F[1], type => $F[2], opts => $F[3]}}
        read_file('/etc/mtab');
}
