X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fpart_export%2Fcacti.pm;h=31c0dc53707000c32ddd3cc6d085d912eb46397f;hb=f4daf7e8c831fe126dc3fd1d6d0db5ee3b46d602;hp=abeb5e4d774a95f4069aac1e9ea0c0daa9568047;hpb=9cfdddf49df7d5f47691ca467d9fbae51bfd71a0;p=freeside.git
diff --git a/FS/FS/part_export/cacti.pm b/FS/FS/part_export/cacti.pm
index abeb5e4d7..31c0dc537 100644
--- a/FS/FS/part_export/cacti.pm
+++ b/FS/FS/part_export/cacti.pm
@@ -50,16 +50,44 @@ tie my %options, 'Tie::IxHash',
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',
-# },
+ 'delete_graphs' => { label => 'Delete associated graphs and data sources when unprovisioning',
+ type => 'checkbox',
+ },
+ 'include_path' => { label => 'Path to cacti include dir (relative to script_path)',
+ default => '../site/include/' },
+ 'cacti_graph_template_id' => {
+ 'label' => 'Graph Template',
+ 'type' => 'custom',
+ 'multiple' => 1,
+ },
+ 'cacti_snmp_query_id' => {
+ 'label' => 'SNMP Query ID',
+ 'type' => 'custom',
+ 'multiple' => 1,
+ },
+ 'cacti_snmp_query_type_id' => {
+ 'label' => 'SNMP Query Type ID',
+ 'type' => 'custom',
+ 'multiple' => 1,
+ },
+ 'cacti_snmp_field' => {
+ 'label' => 'SNMP Field',
+ 'type' => 'custom',
+ 'multiple' => 1,
+ },
+ 'cacti_snmp_value' => {
+ 'label' => 'SNMP Value',
+ 'type' => 'custom',
+ 'multiple' => 1,
+ },
;
%info = (
- 'svc' => 'svc_broadband',
- 'desc' => 'Export service to cacti server, for svc_broadband services',
- 'options' => \%options,
- 'notes' => <<'END',
+ 'svc' => 'svc_broadband',
+ 'desc' => 'Export service to cacti server, for svc_broadband services',
+ 'post_config_element' => '/edit/elements/part_export/cacti.html',
+ 'options' => \%options,
+ 'notes' => <<'END',
Add service to cacti upon provisioning, for broadband services.
See documentation for details.
END
@@ -75,8 +103,23 @@ sub _export_insert {
sub _export_delete {
my ($self, $svc_broadband) = @_;
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+ foreach my $page (qsearch('cacti_page',{ svcnum => $svc_broadband->svcnum })) {
+ my $error = $page->delete;
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
my ($q,$error) = _delete_queue($self, $svc_broadband);
- return $error;
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ return '';
}
sub _export_replace {
@@ -133,6 +176,7 @@ sub _insert_queue {
'svc_desc' => $svc_broadband->description,
'contact' => $svc_broadband->cust_main->contact,
'svcnum' => $svc_broadband->svcnum,
+ 'self' => $self
);
return ($queue,$error);
}
@@ -148,7 +192,8 @@ sub _delete_queue {
'user' => $self->option('user'),
'hostname' => $svc_broadband->ip_addr,
'script_path' => $self->option('script_path'),
-# 'delete_graphs' => $self->option('delete_graphs'),
+ 'delete_graphs' => $self->option('delete_graphs'),
+ 'include_path' => $self->option('include_path'),
);
return ($queue,$error);
}
@@ -157,6 +202,7 @@ sub _delete_queue {
sub ssh_insert {
my %opt = @_;
+ my $self = $opt{'self'};
# Option validation
die "Non-numerical Host Template ID, check export configuration\n"
@@ -169,9 +215,12 @@ sub ssh_insert {
$desc =~ s/\$ip_addr/$opt{'hostname'}/g;
$desc =~ s/\$description/$opt{'svc_desc'}/g;
$desc =~ s/\$contact/$opt{'contact'}/g;
- $desc =~ s/'/'\\''/g;
+#for some reason, device names with apostrophes fail to export graphs in Cacti
+#just removing them for now, someday maybe dig to figure out why
+# $desc =~ s/'/'\\''/g;
+ $desc =~ s/'//g;
my $cmd = $php
- . $opt{'script_path'}
+ . trailslash($opt{'script_path'})
. q(add_device.php --description=')
. $desc
. q(' --ip=')
@@ -187,58 +236,103 @@ sub ssh_insert {
# Add host to tree
if ($opt{'tree_id'}) {
$cmd = $php
- . $opt{'script_path'}
+ . trailslash($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";
+ die "Host added, but error adding host to tree: $response";
}
}
-# # Get list of graph templates for new id
-# $cmd = $php
-# . $opt{'script_path'}
-# . q(freeside_cacti.php --get-graph-templates --host-template=)
-# . $opt{'template_id'};
-# my @gtids = split(/\n/,ssh_cmd(%opt, 'command' => $cmd));
-# die "No graphs configured for host template"
-# unless @gtids;
-#
-# # Create graphs
-# foreach my $gtid (@gtids) {
-#
-# # sanity checks, should never happen
-# next unless $gtid;
-# die "Bad graph template: $gtid"
-# unless $gtid =~ /^\d+$/;
-#
-# # create the graph
-# $cmd = $php
-# . $opt{'script_path'}
-# . q(add_graphs.php --graph-type=cg --graph-template-id=)
-# . $gtid
-# . q( --host-id=)
-# . $id;
-# $response = ssh_cmd(%opt, 'command' => $cmd);
-# die "Error creating graph $gtid: $response"
-# unless $response =~ /Graph Added - graph-id: \((\d+)\)/;
-# my $gid = $1;
-#
-# # add the graph to the tree
-# $cmd = $php
-# . $opt{'script_path'}
-# . q(add_tree.php --type=node --node-type=graph --tree-id=)
-# . $opt{'tree_id'}
-# . q( --graph-id=)
-# . $gid;
-# $response = ssh_cmd(%opt, 'command' => $cmd);
-# die "Error adding graph $gid to tree: $response"
-# unless $response =~ /Added Node/;
-#
-# } #foreach $gtid
+ # Get list of graph templates for new id
+ $cmd = $php
+ . trailslash($opt{'script_path'})
+ . q(freeside_cacti.php --get-graph-templates --host-template=)
+ . $opt{'template_id'};
+ $cmd .= q( --include-path=') . $self->option('include_path') . q(')
+ if $self->option('include_path');
+ my $ginfo = { map { $_ ? ($_ => undef) : () } split(/\n/,ssh_cmd(%opt, 'command' => $cmd)) };
+
+ # Add extra config info
+ my @xtragid = split("\n", $self->option('cacti_graph_template_id'));
+ my @query_id = split("\n", $self->option('cacti_snmp_query_id'));
+ my @query_type_id = split("\n", $self->option('cacti_snmp_query_type_id'));
+ my @snmp_field = split("\n", $self->option('cacti_snmp_field'));
+ my @snmp_value = split("\n", $self->option('cacti_snmp_value'));
+ for (my $i = 0; $i < @xtragid; $i++) {
+ my $gtid = $xtragid[$i];
+ $ginfo->{$gtid} ||= [];
+ push(@{$ginfo->{$gtid}},{
+ 'gtid' => $gtid,
+ 'query_id' => $query_id[$i],
+ 'query_type_id' => $query_type_id[$i],
+ 'snmp_field' => $snmp_field[$i],
+ 'snmp_value' => $snmp_value[$i],
+ });
+ }
+
+ my @gdefs = map {
+ ref($ginfo->{$_}) ? @{$ginfo->{$_}} : {'gtid' => $_}
+ } keys %$ginfo;
+ warn "Host ".$opt{'hostname'}." exported to cacti, but no graphs configured"
+ unless @gdefs;
+
+ # Create graphs
+ my $gerror = '';
+ foreach my $gdef (@gdefs) {
+ # validate graph info
+ my $gtid = $gdef->{'gtid'};
+ next unless $gtid;
+ $gerror .= " Bad graph template: $gtid"
+ unless $gtid =~ /^\d+$/;
+ my $isds = $gdef->{'query_id'}
+ || $gdef->{'query_type_id'}
+ || $gdef->{'snmp_field'}
+ || $gdef->{'snmp_value'};
+ if ($isds) {
+ $gerror .= " Bad SNMP Query Id: " . $gdef->{'query_id'}
+ unless $gdef->{'query_id'} =~ /^\d+$/;
+ $gerror .= " Bad SNMP Query Type Id: " . $gdef->{'query_type_id'}
+ unless $gdef->{'query_type_id'} =~ /^\d+$/;
+ $gerror .= " SNMP Field cannot contain apostrophe"
+ if $gdef->{'snmp_field'} =~ /'/;
+ $gerror .= " SNMP Value cannot contain apostrophe"
+ if $gdef->{'snmp_value'} =~ /'/;
+ }
+ next if $gerror;
+
+ # create the graph
+ $cmd = $php
+ . trailslash($opt{'script_path'})
+ . q(add_graphs.php --graph-type=)
+ . ($isds ? 'ds' : 'cg')
+ . q( --graph-template-id=)
+ . $gtid
+ . q( --host-id=)
+ . $id;
+ if ($isds) {
+ $cmd .= q( --snmp-query-id=)
+ . $gdef->{'query_id'}
+ . q( --snmp-query-type-id=)
+ . $gdef->{'query_type_id'}
+ . q( --snmp-field=')
+ . $gdef->{'snmp_field'}
+ . q(' --snmp-value=')
+ . $gdef->{'snmp_value'}
+ . q(');
+ }
+ $response = ssh_cmd(%opt, 'command' => $cmd);
+ #might be more than one graph added, just testing success
+ $gerror .= "Error creating graph $gtid: $response"
+ unless $response =~ /Graph Added - graph-id: \((\d+)\)/;
+
+ } #foreach $gtid
+
+ # job fails, but partial export may have occurred
+ die $gerror . " Partial export occurred\n" if $gerror;
return '';
}
@@ -246,12 +340,14 @@ sub ssh_insert {
sub ssh_delete {
my %opt = @_;
my $cmd = $php
- . $opt{'script_path'}
+ . trailslash($opt{'script_path'})
. q(freeside_cacti.php --drop-device --ip=')
. $opt{'hostname'}
. q(');
-# $cmd .= q( --delete-graphs)
-# if $opt{'delete_graphs'};
+ $cmd .= q( --delete-graphs)
+ if $opt{'delete_graphs'};
+ $cmd .= q( --include-path=') . $opt{'include_path'} . q(')
+ if $opt{'include_path'};
my $response = ssh_cmd(%opt, 'command' => $cmd);
die "Error removing from cacti: " . $response
if $response;
@@ -278,7 +374,7 @@ sub process_graphs {
my ($job,$param) = @_;
$job->update_statustext(10);
- my $cachedir = $FS::UID::cache_dir . '/cacti-graphs/';
+ my $cachedir = trailslash($FS::UID::cache_dir,'cache.'.$FS::UID::datasrc,'cacti-graphs');
# load the service
my $svcnum = $param->{'svcnum'} || die "No svcnum specified";
@@ -298,36 +394,40 @@ sub process_graphs {
# check for existing pages
my $now = time;
- my @oldpages = qsearch({
+ my %oldpages = map { ($_->graphnum || 'MAIN') => $_ } qsearch({
'table' => 'cacti_page',
'hashref' => { 'svcnum' => $svcnum, 'exportnum' => $self->exportnum },
- 'select' => 'cacti_pagenum, exportnum, svcnum, graphnum, imported', #no need to load old content
+ 'select' => 'cacti_pagenum, exportnum, svcnum, graphnum, imported, thumbnail', #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;
+
+ # if all existing pages are recent enough, do nothing and return
+ # (won't detect newly introduced graphs, but they can wait for next run)
+ my $uptodate = 0;
+ if (keys %oldpages) {
+ $uptodate = 1;
+ foreach my $oldpage (keys %oldpages) {
+ if ($oldpages{$oldpage}->imported <= $self->exptime($now)) {
+ $uptodate = 0;
+ last;
}
}
}
+ if ($uptodate) {
+ $job->update_statustext(100);
+ return '';
+ }
$job->update_statustext(30);
# get list of graphs for this svc from cacti server
my $cmd = $php
- . $self->option('script_path')
+ . trailslash($self->option('script_path'))
. q(freeside_cacti.php --get-graphs --ip=')
. $svc->ip_addr
. q(');
+ $cmd .= q( --include-path=') . $self->option('include_path') . q(')
+ if $self->option('include_path');
my @graphs = map { [ split(/\t/,$_) ] }
split(/\n/, ssh_cmd(
'host' => $self->machine,
@@ -343,7 +443,9 @@ sub process_graphs {
'rsh' => 'ssh',
'verbose' => 1,
'recursive' => 1,
- 'source' => $self->option('graphs_path'),
+ 'quote-src' => 1,
+ 'quote-dst' => 1,
+ 'source' => trailslash($self->option('graphs_path')),
'dest' => $cachedir,
'include' => [
(map { q('**graph_).${$_}[0].q(*.png') } @graphs),
@@ -353,8 +455,9 @@ sub process_graphs {
],
});
#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;
+ my $rscmd = join(' ',@{$rsync->getcmd()});
+ my $error = system($rscmd);
+ die "rsync ($rscmd) failed with exit status $error" if $error;
$job->update_statustext(50);
@@ -368,51 +471,93 @@ sub process_graphs {
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(
No detail graphs to display for this graph
' + if $nodetail; + #delete old detail page + if ($oldpages{$$graph[0]}) { + $error = $oldpages{$$graph[0]}->delete; + if ($error) { + $dbh->rollback if $oldAutoCommit; + die $error; + } + } + #insert new detail page + my $newobj = new FS::cacti_page { + 'exportnum' => $self->exportnum, + 'svcnum' => $svcnum, + 'graphnum' => $$graph[0], + 'imported' => $now, + 'content' => $graphhtml, + 'thumbnail' => $thumbnail, + }; + $error = $newobj->insert; + if ($error) { + $dbh->rollback if $oldAutoCommit; + die $error; } - $j++; + } else { + $svchtml .= qq(File $thumbfile is too large, skipping
); } - $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; + unlink($thumbfile); + } else { + # try to use old page for this graph + if ($oldpages{$$graph[0]} && $oldpages{$$graph[0]}->thumbnail) { + $nographs = 0; + # add old graph to main file + my $graphhead = q(Current graphs unavailable; using previously imported data.
); + $svchtml .= anchor_tag( $svcnum, $$graph[0], $oldpages{$$graph[0]}->thumbnail ); + } else { + $svchtml .= qq(Error loading graph: $$graph[0]
); } } - $job->update_statustext(49 + int($i / $#graphs) * 50); + # remove old page from hash even if it is being reused, + # remaining entries in hash will be deleted from database below + delete $oldpages{$$graph[0]} if $oldpages{$$graph[0]}; + $job->update_statustext(49 + int($i / @graphs) * 50); } $svchtml .= 'No graphs to display for this service
' if $nographs; + # delete remaining old pages, including svc index + foreach my $oldpage (keys %oldpages) { + $error = $oldpages{$oldpage}->delete; + if ($error) { + $dbh->rollback if $oldAutoCommit; + die $error; + } + } + # insert new index page for svc my $newobj = new FS::cacti_page { 'exportnum' => $self->exportnum, 'svcnum' => $svcnum, 'graphnum' => '', 'imported' => $now, 'content' => $svchtml, + 'thumbnail' => '', }; $error = $newobj->insert; if ($error) { @@ -452,11 +597,24 @@ sub ssh_cmd { my $ssh = Net::OpenSSH->new($opt->{'user'}.'@'.$opt->{'host'}); die "Couldn't establish SSH connection: ". $ssh->error if $ssh->error; my ($output, $errput) = $ssh->capture2($opt->{'command'}); - die "Error running SSH command: ". $ssh->error if $ssh->error; + die "Error running SSH command: ". $opt->{'command'}. ' ERROR: ' . $ssh->error if $ssh->error; die $errput if $errput; return $output; } +#there's probably a better place to put this? +#makes sure there's a trailing slash between/after input +#doesn't add leading slashes +sub trailslash { + my @paths = @_; + my $out = ''; + foreach my $path (@paths) { + $out .= $path; + $out .= '/' unless $out =~ /\/$/; + } + return $out; +} + =head1 METHODS =over 4 @@ -508,14 +666,6 @@ sub exptime { Jonathan Prykop jonathan@freeside.biz -=head1 LICENSE AND COPYRIGHT - -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 -as published by the Free Software Foundation. - =cut 1;