X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fpart_export%2Fcacti.pm;h=740be25f0c324ad37cbaafe25d2420d740735adf;hb=600e1ad4ae42a8800527660038b74eaedc13a958;hp=6877c8f5f9ad0e20f69eac163898fc29d1b52938;hpb=e05e4f1f0d6a704db4d3c8b3d9a851216569580c;p=freeside.git diff --git a/FS/FS/part_export/cacti.pm b/FS/FS/part_export/cacti.pm index 6877c8f5f..740be25f0 100644 --- a/FS/FS/part_export/cacti.pm +++ b/FS/FS/part_export/cacti.pm @@ -1,9 +1,35 @@ package FS::part_export::cacti; +=pod + +=head1 NAME + +FS::part_export::cacti + +=head1 SYNOPSIS + +Cacti integration for Freeside + +=head1 DESCRIPTION + +This module in particular handles FS::part_export object creation for Cacti integration; +consult any existing L documentation for details on how that works. + +=cut + use strict; + use base qw( FS::part_export ); -use FS::Record qw( qsearchs ); +use FS::Record qw( qsearchs qsearch ); use FS::UID qw( dbh ); +use FS::cacti_page; + +use File::Rsync; +use File::Slurp qw( slurp ); +use File::stat; +use MIME::Base64 qw( decode_base64 encode_base64 ); +use Storable qw(thaw); + use vars qw( %info ); @@ -14,14 +40,18 @@ tie my %options, 'Tie::IxHash', default => 'freeside' }, 'script_path' => { label => 'Script Path', default => '/usr/share/cacti/cli/' }, - 'base_url' => { label => 'Base Cacti URL', - default => '' }, 'template_id' => { label => 'Host Template ID', default => '' }, - 'tree_id' => { label => 'Graph Tree ID', + 'tree_id' => { label => 'Graph Tree ID (optional)', default => '' }, - 'description' => { label => 'Description (can use $ip_addr and $description tokens)', - default => 'Freeside $description $ip_addr' }, + 'description' => { label => 'Description (can use tokens $contact, $ip_addr and $description)', + default => 'Freeside $contact $description $ip_addr' }, + 'graphs_path' => { label => 'Graph Export Directory (user@host:/path/to/graphs/)', + default => '' }, + 'import_freq' => { label => 'Minimum minutes between graph imports', + default => '5' }, + 'max_graph_size' => { label => 'Maximum size per graph (MB)', + default => '5' }, # 'delete_graphs' => { label => 'Delete associated graphs and data sources when unprovisioning', # type => 'checkbox', # }, @@ -33,7 +63,7 @@ tie my %options, 'Tie::IxHash', 'options' => \%options, 'notes' => <<'END', Add service to cacti upon provisioning, for broadband services.
-See FS::part_export::cacti documentation for details. +See documentation for details. END ); @@ -103,6 +133,7 @@ sub _insert_queue { 'tree_id' => $self->option('tree_id'), 'description' => $self->option('description'), 'svc_desc' => $svc_broadband->description, + 'contact' => $svc_broadband->cust_main->contact, 'svcnum' => $svc_broadband->svcnum, ); return ($queue,$error); @@ -133,12 +164,13 @@ sub ssh_insert { die "Non-numerical Host Template ID, check export configuration\n" unless $opt{'template_id'} =~ /^\d+$/; die "Non-numerical Graph Tree ID, check export configuration\n" - unless $opt{'tree_id'} =~ /^\d+$/; + unless $opt{'tree_id'} =~ /^\d*$/; # Add host to cacti my $desc = $opt{'description'}; $desc =~ s/\$ip_addr/$opt{'hostname'}/g; $desc =~ s/\$description/$opt{'svc_desc'}/g; + $desc =~ s/\$contact/$opt{'contact'}/g; $desc =~ s/'/'\\''/g; my $cmd = $php . $opt{'script_path'} @@ -155,27 +187,18 @@ sub ssh_insert { my $id = $1; # Add host to tree - $cmd = $php - . $opt{'script_path'} - . q(add_tree.php --type=node --node-type=host --tree-id=) - . $opt{'tree_id'} - . q( --host-id=) - . $id; - $response = ssh_cmd(%opt, 'command' => $cmd); - unless ( $response =~ /Added Node node-id: \((\d+)\)/ ) { + if ($opt{'tree_id'}) { + $cmd = $php + . $opt{'script_path'} + . q(add_tree.php --type=node --node-type=host --tree-id=) + . $opt{'tree_id'} + . q( --host-id=) + . $id; + $response = ssh_cmd(%opt, 'command' => $cmd); + unless ( $response =~ /Added Node node-id: \((\d+)\)/ ) { die "Error adding host to tree: $response"; + } } - my $leaf_id = $1; - - # Store id for generating graph urls - my $svc_broadband = qsearchs({ - 'table' => 'svc_broadband', - 'hashref' => { 'svcnum' => $opt{'svcnum'} }, - }); - die "Could not reload broadband service" unless $svc_broadband; - $svc_broadband->set('cacti_leaf_id',$leaf_id); - my $error = $svc_broadband->replace; - return $error if $error; # # Get list of graph templates for new id # $cmd = $php @@ -237,6 +260,194 @@ sub ssh_delete { return ''; } +=head1 SUBROUTINES + +=over 4 + +=item process_graphs JOB PARAM + +Intended to be run as an FS::queue job. + +Copies graphs for a single service from Cacti export directory to FS cache, +generates basic html pages for this service with base64-encoded graphs embedded, +and stores the generated pages in the database. + +=back + +=cut + +sub process_graphs { + my $job = shift; + my $param = thaw(decode_base64(shift)); + + $job->update_statustext(10); + my $cachedir = $FS::UID::cache_dir . '/cacti-graphs/'; + + # load the service + my $svcnum = $param->{'svcnum'} || die "No svcnum specified"; + my $svc = qsearchs({ + 'table' => 'svc_broadband', + 'hashref' => { 'svcnum' => $svcnum }, + }) || die "Could not load svcnum $svcnum"; + + # load relevant FS::part_export::cacti object + my ($self) = $svc->cust_svc->part_svc->part_export('cacti'); + + $job->update_statustext(20); + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + # check for existing pages + my $now = time; + my @oldpages = qsearch({ + 'table' => 'cacti_page', + 'hashref' => { 'svcnum' => $svcnum, 'exportnum' => $self->exportnum }, + 'select' => 'cacti_pagenum, exportnum, svcnum, graphnum, imported', #no need to load old content + 'order_by' => 'ORDER BY graphnum', + }); + if (@oldpages) { + #if pages are recent enough, do nothing and return + if ($oldpages[0]->imported > $self->exptime($now)) { + $job->update_statustext(100); + return ''; + } + #delete old pages + foreach my $oldpage (@oldpages) { + my $error = $oldpage->delete; + if ($error) { + $dbh->rollback if $oldAutoCommit; + die $error; + } + } + } + + $job->update_statustext(30); + + # get list of graphs for this svc from cacti server + my $cmd = $php + . $self->option('script_path') + . q(freeside_cacti.php --get-graphs --ip=') + . $svc->ip_addr + . q('); + my @graphs = map { [ split(/\t/,$_) ] } + split(/\n/, ssh_cmd( + 'host' => $self->machine, + 'user' => $self->option('user'), + 'command' => $cmd + )); + + $job->update_statustext(40); + + # copy graphs from cacti server to cache + # requires version 2.6.4 of rsync, released March 2005 + my $rsync = File::Rsync->new({ + 'rsh' => 'ssh', + 'verbose' => 1, + 'recursive' => 1, + 'source' => $self->option('graphs_path'), + 'dest' => $cachedir, + 'include' => [ + (map { q('**graph_).${$_}[0].q(*.png') } @graphs), + (map { q('**thumb_).${$_}[0].q(.png') } @graphs), + q('*/'), + q('- *'), + ], + }); + #don't know why a regular $rsync->exec isn't doing includes right, but this does + my $error = system(join(' ',@{$rsync->getcmd()})); + die "rsync failed with exit status $error" if $error; + + $job->update_statustext(50); + + # create html file contents + my $svchead = q() + . '

Service #' . $svcnum . '

' + . q(

Last updated ) . scalar(localtime($now)) . q(

); + my $svchtml = $svchead; + my $maxgraph = 1024 * 1024 * ($self->options('max_graph_size') || 5); + my $nographs = 1; + for (my $i = 0; $i <= $#graphs; $i++) { + my $graph = $graphs[$i]; + my $thumbfile = $cachedir . 'graphs/thumb_' . $$graph[0] . '.png'; + if ( + (-e $thumbfile) && + ( stat($thumbfile)->size() < $maxgraph ) + ) { + $nographs = 0; + # add graph to main file + my $graphhead = q(

) . $$graph[1] . q(

); + $svchtml .= $graphhead; + $svchtml .= anchor_tag( $svcnum, $$graph[0], img_tag($thumbfile) ); + # create graph details file + my $graphhtml = $svchead . $graphhead; + my $nodetail = 1; + my $j = 1; + while (-e (my $graphfile = $cachedir.'graphs/graph_'.$$graph[0].'_'.$j.'.png')) { + if ( stat($graphfile)->size() < $maxgraph ) { + $nodetail = 0; + $graphhtml .= img_tag($graphfile); + } + $j++; + } + $graphhtml .= '

No detail graphs to display for this graph

' + if $nodetail; + my $newobj = new FS::cacti_page { + 'exportnum' => $self->exportnum, + 'svcnum' => $svcnum, + 'graphnum' => $$graph[0], + 'imported' => $now, + 'content' => $graphhtml, + }; + $error = $newobj->insert; + if ($error) { + $dbh->rollback if $oldAutoCommit; + die $error; + } + } + $job->update_statustext(49 + int($i / $#graphs) * 50); + } + $svchtml .= '

No graphs to display for this service

' + if $nographs; + my $newobj = new FS::cacti_page { + 'exportnum' => $self->exportnum, + 'svcnum' => $svcnum, + 'graphnum' => '', + 'imported' => $now, + 'content' => $svchtml, + }; + $error = $newobj->insert; + if ($error) { + $dbh->rollback if $oldAutoCommit; + die $error; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + $job->update_statustext(100); + return ''; +} + +sub img_tag { + my $somefile = shift; + return q(
); +} + +sub anchor_tag { + my ($svcnum, $graphnum, $contents) = @_; + return q() + . $contents + . q(); +} + +#this gets used by everything else #fake false laziness, other ssh_cmds handle error/output differently sub ssh_cmd { use Net::OpenSSH; @@ -249,67 +460,51 @@ sub ssh_cmd { return $output; } -=pod - -=head1 NAME - -FS::part_export::cacti - -=head1 SYNOPSIS - -Cacti integration for Freeside - -=head1 DESCRIPTION - -This module in particular handles FS::part_export object creation for Cacti integration; -consult any existing L documentation for details on how that works. -What follows is more general instructions for connecting your Cacti installation -to your Freeside installation. - -=head2 Connecting Cacti To Freeside +=head1 METHODS -Copy the freeside_cacti.php script from the bin directory of your Freeside -installation to the cli directory of your Cacti installation. Give this file -the same permissions as the other files in that directory, and create -(or choose an existing) user with sufficient permission to read these scripts. +=over 4 -In the regular Cacti interface, create a Host Template to be used by -devices exported by Freeside, and note the template's id number. +=item cleanup -In Freeside, go to Configuration->Services->Provisioning exports to -add a new export. From the Add Export page, select cacti for Export then enter... +Removes all expired graphs for this export from the database. -* the User Name with permission to run scripts in the cli directory - -* enter the full Script Path to that directory (eg /usr/share/cacti/cli/) - -* enter the Base Cacti URL for your cacti server (eg https://example.com/cacti/) - -* the Host Template ID for adding new devices +=cut -* the Graph Tree ID for adding new devices +sub cleanup { + my $self = shift; + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + my $sth = $dbh->prepare('DELETE FROM cacti_page WHERE exportnum = ? and imported <= ?') + or do { + $dbh->rollback if $oldAutoCommit; + return $dbh->errstr; + }; + $sth->execute($self->exportnum,$self->exptime) + or do { + $dbh->rollback if $oldAutoCommit; + return $dbh->errstr; + }; + $dbh->commit or return $dbh->errstr if $oldAutoCommit; + return ''; +} -* the Description for new devices; you can use the tokens - $ip_addr and $description to include the equivalent fields - from the broadband service definition +=item exptime [ TIME ] -After adding the export, go to Configuration->Services->Service definitions. -The export you just created will be available for selection when adding or -editing broadband service definitions. +Accepts optional current time, defaults to actual current time. -When properly configured broadband services are provisioned, they should now -be added to Cacti using the Host Template you specified, and the created device -will also be added to the specified Graph Tree. +Returns timestamp for the oldest possible non-expired graph import, +based on the import_freq option. -Once added, a link to the graphs for this host will be available when viewing -the details of the provisioned service in Freeside (you will need to authenticate -into Cacti to view them.) +=cut -Devices will be deleted from Cacti when the service is unprovisioned in Freeside, -and they will be deleted and re-added if the ip address changes. +sub exptime { + my $self = shift; + my $now = shift || time; + return $now - 60 * ($self->option('import_freq') || 5); +} -Currently, graphs themselves must still be added in cacti by hand or some -other form of automation tailored to your specific graph inputs and data sources. +=back =head1 AUTHOR @@ -320,8 +515,8 @@ jonathan@freeside.biz Copyright 2015 Freeside Internet Services -This program is free software; you can redistribute it and/or | -modify it under the terms of the GNU General Public License | +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. =cut