From f715c23517292a11330ab241fb13221fd89ffc37 Mon Sep 17 00:00:00 2001 From: Jonathan Prykop Date: Thu, 30 Apr 2015 17:28:36 -0500 Subject: [PATCH] RT#18834: Cacti integration [added graph generation] --- FS/FS/part_export/cacti.pm | 266 ++++++++++++++++-------- bin/freeside_cacti.php | 13 +- httemplate/browse/part_export.cgi | 2 +- httemplate/edit/elements/part_export/cacti.html | 42 ++++ httemplate/edit/part_export.cgi | 15 ++ 5 files changed, 239 insertions(+), 99 deletions(-) create mode 100644 httemplate/edit/elements/part_export/cacti.html diff --git a/FS/FS/part_export/cacti.pm b/FS/FS/part_export/cacti.pm index abeb5e4d7..eff6c5220 100644 --- a/FS/FS/part_export/cacti.pm +++ b/FS/FS/part_export/cacti.pm @@ -50,16 +50,42 @@ 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', + }, + '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 +101,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 +174,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 +190,7 @@ 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'), ); return ($queue,$error); } @@ -157,6 +199,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,7 +212,10 @@ 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'} . q(add_device.php --description=') @@ -194,51 +240,94 @@ sub ssh_insert { . $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 + . $opt{'script_path'} + . q(freeside_cacti.php --get-graph-templates --host-template=) + . $opt{'template_id'}; + 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 + . $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 ''; } @@ -250,8 +339,8 @@ sub ssh_delete { . 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'}; my $response = ssh_cmd(%opt, 'command' => $cmd); die "Error removing from cacti: " . $response if $response; @@ -368,42 +457,43 @@ 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(

) . $$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); + if (-e $thumbfile) { + if ( 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); + } + unlink($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; } - $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; } + unlink($thumbfile); } - $job->update_statustext(49 + int($i / $#graphs) * 50); + $job->update_statustext(49 + int($i / @graphs) * 50); } $svchtml .= '

No graphs to display for this service

' if $nographs; @@ -452,7 +542,7 @@ 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; } diff --git a/bin/freeside_cacti.php b/bin/freeside_cacti.php index 0a9ee9c1c..9f8e4dde9 100755 --- a/bin/freeside_cacti.php +++ b/bin/freeside_cacti.php @@ -39,18 +39,15 @@ but keeping commented out code for potential future development. include(dirname(__FILE__)."/../site/include/global.php"); include_once($config["base_path"]."/lib/api_device.php"); include_once($config["base_path"]."/lib/api_automation_tools.php"); - -/* include_once($config["base_path"]."/lib/api_data_source.php"); include_once($config["base_path"]."/lib/api_graph.php"); include_once($config["base_path"]."/lib/functions.php"); -*/ /* process calling arguments */ $action = ''; $ip = ''; $host_template = ''; -// $delete_graphs = FALSE; +$delete_graphs = FALSE; $parms = $_SERVER["argv"]; array_shift($parms); if (sizeof($parms)) { @@ -67,21 +64,19 @@ if (sizeof($parms)) { case "--get-device": $action = 'get-device'; break; +*/ case "--get-graph-templates": $action = 'get-graph-templates'; break; -*/ case "--ip": $ip = trim($value); break; case "--host-template": $host_template = trim($value); break; -/* case "--delete-graphs": $delete_graphs = TRUE; break; -*/ case "--version": case "-V": case "-H": @@ -102,7 +97,6 @@ case "get-graphs": break; case "drop-device": $host_id = host_id($ip); -/* if ($delete_graphs) { // code copied & pasted from version 0.8.8a // cacti/site/lib/host.php and cacti/site/graphs.php @@ -126,7 +120,6 @@ case "drop-device": } } } -*/ api_device_remove($host_id); if (host_id($ip,1)) { die("Failed to remove hostname $ip"); @@ -136,6 +129,7 @@ case "drop-device": case "get-device": echo host_id($ip); exit(0); +*/ case "get-graph-templates": if (!$host_template) { die("No host template specified"); @@ -148,7 +142,6 @@ case "get-graph-templates": exit(0); } die("No graph templates associated with this host template"); -*/ default: die("Specified action not found, contact a developer"); } diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi index 1f835d729..af988d37b 100755 --- a/httemplate/browse/part_export.cgi +++ b/httemplate/browse/part_export.cgi @@ -66,7 +66,7 @@ function part_export_areyousure(href) { % if ( $group ) { % my @values = split("\n", $opt{$optname}); % $multiples{$group} ||= []; -% push @{ $multiples{$group} }, [ $optname, @values ] if @values; +% push @{ $multiples{$group} }, [ $def->{label} || $optname, @values ] if @values; % delete $opt{$optname}; % } elsif (length($opt{$optname})) { # the normal case % my $value = $opt{$optname}; diff --git a/httemplate/edit/elements/part_export/cacti.html b/httemplate/edit/elements/part_export/cacti.html new file mode 100644 index 000000000..9e4a8ecd7 --- /dev/null +++ b/httemplate/edit/elements/part_export/cacti.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + +<& /elements/auto-table.html, + template_row => 'mytemplate', + fieldorder => ['graph_template_id','snmp_query_id','snmp_query_type_id','snmp_field','snmp_value'], + data => \@data, + table => 'cacti', +&> +
Graph Template IDSNMP Query IDSNMP Query Type IDSNMP FieldSNMP Value
+ +<%init> +my %opt = @_; +my $part_export = $opt{part_export} || die "No part_export specified"; + +my @fields = ('cacti_graph_template_id','cacti_snmp_query_id','cacti_snmp_query_type_id','cacti_snmp_field','cacti_snmp_value'); +my $multiopts = join(',',@fields); +my @byfield = map { + [ split("\n", $part_export->option($_)) ] +} @fields; +my @data; +for (my $i = 0; $i < @{$byfield[0]}; $i++) { + my @thisrow; + for (my $j = 0; $j < @byfield; $j++) { + push(@thisrow,$byfield[$j][$i]); + } + push(@data,\@thisrow); +} + + diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi index 0e53e29d0..382093116 100644 --- a/httemplate/edit/part_export.cgi +++ b/httemplate/edit/part_export.cgi @@ -183,6 +183,10 @@ my $widget = new HTML::Widgets::SelectLayers( ? $optinfo->{default} : '' ); + + #handle these with post_config_element + next if $type eq 'custom'; + if ( $type eq 'title' ) { $html .= qq!! . $label . @@ -283,6 +287,17 @@ my $widget = new HTML::Widgets::SelectLayers( $html .= ''; + # false laziness with config_element above + # create 'post_config_element' to generate the whole layer with a Mason component + if ( my $include = $exports->{$layer}{post_config_element} ) { + # might need to adjust the scope of this at some point + $html .= $m->scomp($include, + part_export => $part_export, + layer => $layer, + export_info => $exports->{$layer} + ); + } + $html .= ''; -- 2.11.0