The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package RackMan::Format::Cacti;

use strict;
use Carp;
use IPC::Run;


use constant {
    CONFIG_SECTION  => "format:cacti",
    DEFAULT_PATH    => "/var/lib/cacti/cli",
    DEFAULT_PHP     => "/usr/bin/php",
};

my @SUDO;
my @PHP   = ( DEFAULT_PHP, "-q" );
my $CACTI = DEFAULT_PATH;


#
# write()
# -----
sub write {
    my ($class, $args) = @_;

    my $rackdev = $args->{rackdev};
    my $rackman = $args->{rackman};
    my $name    = $rackdev->object_name;
    my $fqdn    = $rackdev->attributes->{FQDN} || $name;

    cacti_configure($rackman);

    my %is = ( lc($rackdev->object_type) => 1 );
    $is{cisco_switch} = 1 if $is{switch} and $rackdev->class =~ /Cisco/;

    # determine the template ID from the object type
    my $tmpl_id = $is{server} ? 3 : $is{cisco_switch} ? 5 : 1;

    # add the device in Cacti
    print "  x adding device $name\n" if $args->{verbose};
    my $host_id = cacti_add_device({
        description => $name,  ip => $fqdn,
        version => 2,  template => $tmpl_id,
    }) or return;

    if ($tmpl_id > 1) {
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        # add data queries
        print "  x adding data queries\n" if $args->{verbose};

        # common options
        my %opts = ( "host-id" => $host_id, "reindex-method" => "fields" );

        # data query: SNMP - Interface Statistics
        cacti_add_data_query({ %opts, "data-query-id" => 1 });

        # data query: SNMP - Get Processor Information
        cacti_add_data_query({ %opts, "data-query-id" => 9 });

        if ($is{server}) {
            # data query: ucd/net - Get Monitored Partitions
            cacti_add_data_query({ %opts, "data-query-id" => 2 });
        }

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        # add graph templates
        print "  x adding graph templates\n" if $args->{verbose};

        # common options
        %opts = ( "host-id" => $host_id );

        # graph template: Interface - Traffic (bits/sec)
        cacti_add_graph_template({ %opts, "graph-template-id" => 2 });

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        # add non-indexed graphs
        print "  x adding non-indexed graphs\n" if $args->{verbose};

        # common options
        %opts = ( "host-id" => $host_id, "graph-type" => "cg" );

        if ($is{cisco_switch}) {
            # graph: Cisco - CPU Usage
            cacti_add_graphs({ %opts, "graph-template-id" => 18 });
        }

        if ($is{server}) {
            # graph: ucd/net - Available Disk Space
            cacti_add_graphs({ %opts, "graph-template-id" => 3 });

            # graph: ucd/net - CPU Usage
            cacti_add_graphs({ %opts, "graph-template-id" => 4 });

            # graph: ucd/net - Load Average
            cacti_add_graphs({ %opts, "graph-template-id" => 11 });

            # graph: ucd/net - Memory Usage
            cacti_add_graphs({ %opts, "graph-template-id" => 13 });
        }
    }
}


#
# cacti_configure()
# ---------------
sub cacti_configure {
    my $rackman = shift;

    $CACTI = $rackman->config->val(CONFIG_SECTION, "path", DEFAULT_PATH);

    @PHP = split / +/,
        $rackman->config->val(CONFIG_SECTION, "php", DEFAULT_PHP." -q");

    if (my $user = $rackman->config->val(CONFIG_SECTION, "sudo_as")) {
        @SUDO = ( "/usr/bin/sudo", "-u", $user );
    }
}


#
# cacti_exec()
# ----------
sub cacti_exec {
    my ($cacti_cmd, $opts, $out) = @_;

    my @cacti_args = map "--$_=$opts->{$_}", keys %$opts;
    my @cmd = ( @SUDO, @PHP, "$CACTI/$cacti_cmd", @cacti_args );
    my ($in, $err);  # note: Cacti commands do not use stderr
    return IPC::Run::run(\@cmd, \$in, $out, \$err);
}


#
# cacti_add_device()
# ----------------
sub cacti_add_device {
    my $opts = shift;

    my $r = cacti_exec("add_device.php", $opts, \my $out);
    my ($host_id) = $out =~ /device-id: +\((\d+)\)/m;
    RackMan->warning("can't obtain ID from Cacti for host ",
        "$opts->{description}: $out") unless $host_id;

    return $host_id
}


#
# cacti_add_data_query()
# --------------------
sub cacti_add_data_query {
    my $opts = shift;

    my $r = cacti_exec("add_data_query.php", $opts, \my $out);
}


#
# cacti_add_graph_template()
# ------------------------
sub cacti_add_graph_template {
    my $opts = shift;

    my $r = cacti_exec("add_graph_template.php", $opts, \my $out);
}


#
# cacti_add_graphs()
# ----------------
sub cacti_add_graphs {
    my $opts = shift;

    my $r = cacti_exec("add_graphs.php", $opts, \my $out);
}


__PACKAGE__

__END__

=pod

=head1 NAME

RackMan::Format::Cacti - Create Cacti graphs for the given RackObject

=head1 SYNOPSIS

    use RackMan::Format::Cacti;

    RackMan::Format::Cacti->write({
        rackdev => $rackdev,  # a RackMan::Device instance
        rackman => $rackman,  # a RackMan instance
    });


=head1 DESCRIPTION

This module declares the given RackObject within Cacti and creates a few
associated graphs or graph templates.


=head1 METHODS

=head2 write

Do the work.

B<Arguments>

Arguments are expected as a hashref with the following keys:

=over

=item *

C<rackdev> - I<(mandatory)> a RackMan::Device instance

=item *

C<rackman> - I<(mandatory)> a RackMan instance

=item *

C<verbose> - I<(optional)> boolean, set to true to be verbose

=back


=head1 CONFIGURATION

This module gets its configuration from the C<[format:cacti]> section
of the main F<rack.conf>, with the following parameters:

=head2 path

Path of the directory where the Cacti command line programs are located.


=head2 php

Path of the PHP interpreter. Default to F</usr/bin/php>


=head2 sudo_as

Specify an optional user account to execute the Cacti programs under,
using sudo(8).


=head1 AUTHOR

Sebastien Aperghis-Tramoni

=cut