RT#18834: Cacti integration [added graph generation]
authorJonathan Prykop <jonathan@freeside.biz>
Thu, 30 Apr 2015 22:28:36 +0000 (17:28 -0500)
committerJonathan Prykop <jonathan@freeside.biz>
Mon, 11 May 2015 23:00:14 +0000 (18:00 -0500)
FS/FS/part_export/cacti.pm
bin/freeside_cacti.php
httemplate/browse/part_export.cgi
httemplate/edit/elements/part_export/cacti.html [new file with mode: 0644]
httemplate/edit/part_export.cgi

index 740be25..cbb50af 100644 (file)
@@ -52,16 +52,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.<BR>
 See <A HREF="http://www.freeside.biz/mediawiki/index.php/Freeside:4:Documentation:Cacti#Connecting_Cacti_To_Freeside">documentation</A> for details.
 END
@@ -77,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 {
@@ -135,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);
 }
@@ -150,7 +192,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);
 }
@@ -159,6 +201,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"
@@ -171,7 +214,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=')
@@ -196,51 +242,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 '';
 }
@@ -252,8 +341,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;
@@ -371,42 +460,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(<H3>) . $$graph[1] . q(</H3>);
-      $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(<H3>) . $$graph[1] . q(</H3>);
+        $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 .= '<P>No detail graphs to display for this graph</P>'
+          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 .= '<P>No detail graphs to display for this graph</P>'
-        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 .= '<P>No graphs to display for this service</P>'
     if $nographs;
@@ -455,7 +545,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;
 }
index 0a9ee9c..9f8e4dd 100755 (executable)
@@ -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");
 }
index 1f835d7..af988d3 100755 (executable)
@@ -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 (file)
index 0000000..9e4a8ec
--- /dev/null
@@ -0,0 +1,42 @@
+<table bgcolor="#cccccc" border=0 cellspacing=3>
+<TR>
+  <TH>Graph Template ID</TH>
+  <TH>SNMP Query ID</TH>
+  <TH>SNMP Query Type ID</TH>
+  <TH>SNMP Field</TH>
+  <TH>SNMP Value</TH>
+</TR>
+<TR id="mytemplate">
+  <TD><INPUT TYPE="text" NAME="graph_template_id" ID="graph_template_id"></TD>
+  <TD><INPUT TYPE="text" NAME="snmp_query_id" ID="snmp_query_id"></TD>
+  <TD><INPUT TYPE="text" NAME="snmp_query_type_id" ID="snmp_query_type_id"></TD>
+  <TD><INPUT TYPE="text" NAME="snmp_field" ID="snmp_field"></TD>
+  <TD><INPUT TYPE="text" NAME="snmp_value" ID="snmp_value"></TD>
+</TR>
+<& /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',
+&>
+</table>
+<INPUT TYPE="hidden" name="multi_options" value="<% $multiopts %>">
+<%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);
+}
+
+</%init>
index 0e53e29..3820931 100644 (file)
@@ -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!<TR><TH COLSPAN=1 ALIGN="right"><FONT SIZE="+1">! .
                  $label .
@@ -283,6 +287,17 @@ my $widget = new HTML::Widgets::SelectLayers(
 
     $html .= '</TABLE>';
 
+    # 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 .= '<INPUT TYPE="hidden" NAME="options" VALUE="'.
              join(',', keys %{$exports->{$layer}{options}} ). '">';