38413: Cacti integration
[freeside.git] / FS / FS / part_export / cacti.pm
index b41fe9b..31c0dc5 100644 (file)
@@ -53,6 +53,8 @@ tie my %options, 'Tie::IxHash',
   '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',
@@ -191,6 +193,7 @@ sub _delete_queue {
     'hostname'      => $svc_broadband->ip_addr,
     'script_path'   => $self->option('script_path'),
     'delete_graphs' => $self->option('delete_graphs'),
+    'include_path'  => $self->option('include_path'),
   );
   return ($queue,$error);
 }
@@ -217,7 +220,7 @@ sub ssh_insert {
 #  $desc =~ s/'/'\\''/g;
   $desc =~ s/'//g;
   my $cmd = $php
-          . $opt{'script_path'} 
+          . trailslash($opt{'script_path'})
           . q(add_device.php --description=')
           . $desc
           . q(' --ip=')
@@ -233,7 +236,7 @@ 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=)
@@ -246,9 +249,11 @@ sub ssh_insert {
 
   # Get list of graph templates for new id
   $cmd = $php
-       . $opt{'script_path'} 
+       . 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
@@ -301,7 +306,7 @@ sub ssh_insert {
 
     # create the graph
     $cmd = $php
-         . $opt{'script_path'}
+         . trailslash($opt{'script_path'})
          . q(add_graphs.php --graph-type=)
          . ($isds ? 'ds' : 'cg')
          . q( --graph-template-id=)
@@ -335,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( --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;
@@ -367,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";
@@ -387,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,
@@ -432,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),
@@ -442,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);
 
@@ -460,14 +474,18 @@ sub process_graphs {
     if (-e $thumbfile) {
       if ( stat($thumbfile)->size() < $maxgraph ) {
         $nographs = 0;
+        my $thumbnail = img_tag($thumbfile);
         # add graph to main file
         my $graphhead = q(<H3>) . $$graph[1] . q(</H3>);
         $svchtml .= $graphhead;
-        $svchtml .= anchor_tag( $svcnum, $$graph[0], img_tag($thumbfile) );
+        $svchtml .= anchor_tag( $svcnum, $$graph[0], $thumbnail );
         # create graph details file
         my $graphhtml = $svchead . $graphhead;
         my $nodetail = 1;
         my $j = 1;
+        # no easy way to tell what detail graphs should exist,
+        # and don't want detail graphs that are out of sync with thumbnail,
+        # so just use what we can find
         while (-e (my $graphfile = $cachedir.'graphs/graph_'.$$graph[0].'_'.$j.'.png')) {
           if ( stat($graphfile)->size() < $maxgraph ) {
             $nodetail = 0;
@@ -478,12 +496,22 @@ sub process_graphs {
         }
         $graphhtml .= '<P>No detail graphs to display for this graph</P>'
           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) {
@@ -491,22 +519,45 @@ sub process_graphs {
           die $error;
         }
       } else {
-        warn "File $thumbfile is too large, skipping";
+        $svchtml .= qq(<P STYLE="color: #FF0000">File $thumbfile is too large, skipping</P>);
       }
       unlink($thumbfile);
     } else {
-      warn "File $thumbfile does not exist, skipping";
+      # 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(<H3>) . $$graph[1] . q(</H3>);
+        $svchtml .= $graphhead;
+        $svchtml .= qq(<P STYLE="color: #FF0000">Current graphs unavailable; using previously imported data.</P>);
+        $svchtml .= anchor_tag( $svcnum, $$graph[0], $oldpages{$$graph[0]}->thumbnail );
+      } else {
+        $svchtml .= qq(<P STYLE="color: #FF0000">Error loading graph: $$graph[0]</P>);
+      }
     }
+    # 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 .= '<P>No graphs to display for this service</P>'
     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) {
@@ -551,6 +602,19 @@ sub ssh_cmd {
   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
@@ -602,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;