6877c8f5f9ad0e20f69eac163898fc29d1b52938
[freeside.git] / FS / FS / part_export / cacti.pm
1 package FS::part_export::cacti;
2
3 use strict;
4 use base qw( FS::part_export );
5 use FS::Record qw( qsearchs );
6 use FS::UID qw( dbh );
7
8 use vars qw( %info );
9
10 my $php = 'php -q ';
11
12 tie my %options, 'Tie::IxHash',
13   'user'              => { label   => 'User Name',
14                            default => 'freeside' },
15   'script_path'       => { label   => 'Script Path',
16                            default => '/usr/share/cacti/cli/' },
17   'base_url'          => { label   => 'Base Cacti URL',
18                            default => '' },
19   'template_id'       => { label   => 'Host Template ID',
20                            default => '' },
21   'tree_id'           => { label   => 'Graph Tree ID',
22                            default => '' },
23   'description'       => { label   => 'Description (can use $ip_addr and $description tokens)',
24                            default => 'Freeside $description $ip_addr' },
25 #  'delete_graphs'     => { label   => 'Delete associated graphs and data sources when unprovisioning', 
26 #                           type    => 'checkbox',
27 #                         },
28 ;
29
30 %info = (
31   'svc'             => 'svc_broadband',
32   'desc'            => 'Export service to cacti server, for svc_broadband services',
33   'options'         => \%options,
34   'notes'           => <<'END',
35 Add service to cacti upon provisioning, for broadband services.<BR>
36 See FS::part_export::cacti documentation for details.
37 END
38 );
39
40 # standard hooks for provisioning/unprovisioning service
41
42 sub _export_insert {
43   my ($self, $svc_broadband) = @_;
44   my ($q,$error) = _insert_queue($self, $svc_broadband);
45   return $error;
46 }
47
48 sub _export_delete {
49   my ($self, $svc_broadband) = @_;
50   my ($q,$error) = _delete_queue($self, $svc_broadband);
51   return $error;
52 }
53
54 sub _export_replace {
55   my($self, $new, $old) = @_;
56   return '' if $new->ip_addr eq $old->ip_addr; #important part didn't change
57   #delete old then insert new, with second job dependant on the first
58   my $oldAutoCommit = $FS::UID::AutoCommit;
59   local $FS::UID::AutoCommit = 0;
60   my $dbh = dbh;
61   my ($dq, $iq, $error);
62   ($dq,$error) = _delete_queue($self,$old);
63   if ($error) {
64     $dbh->rollback if $oldAutoCommit;
65     return $error;
66   }
67   ($iq,$error) = _insert_queue($self,$new);
68   if ($error) {
69     $dbh->rollback if $oldAutoCommit;
70     return $error;
71   }
72   $error = $iq->depend_insert($dq->jobnum);
73   if ($error) {
74     $dbh->rollback if $oldAutoCommit;
75     return $error;
76   }
77   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
78   return '';
79 }
80
81 sub _export_suspend {
82   return '';
83 }
84
85 sub _export_unsuspend {
86   return '';
87 }
88
89 # create queued jobs
90
91 sub _insert_queue {
92   my ($self, $svc_broadband) = @_;
93   my $queue = new FS::queue {
94     'svcnum' => $svc_broadband->svcnum,
95     'job'    => "FS::part_export::cacti::ssh_insert",
96   };
97   my $error = $queue->insert(
98     'host'        => $self->machine,
99     'user'        => $self->option('user'),
100     'hostname'    => $svc_broadband->ip_addr,
101     'script_path' => $self->option('script_path'),
102     'template_id' => $self->option('template_id'),
103     'tree_id'     => $self->option('tree_id'),
104     'description' => $self->option('description'),
105         'svc_desc'    => $svc_broadband->description,
106     'svcnum'      => $svc_broadband->svcnum,
107   );
108   return ($queue,$error);
109 }
110
111 sub _delete_queue {
112   my ($self, $svc_broadband) = @_;
113   my $queue = new FS::queue {
114     'svcnum' => $svc_broadband->svcnum,
115     'job'    => "FS::part_export::cacti::ssh_delete",
116   };
117   my $error = $queue->insert(
118     'host'          => $self->machine,
119     'user'          => $self->option('user'),
120     'hostname'      => $svc_broadband->ip_addr,
121     'script_path'   => $self->option('script_path'),
122 #    'delete_graphs' => $self->option('delete_graphs'),
123   );
124   return ($queue,$error);
125 }
126
127 # routines run by queued jobs
128
129 sub ssh_insert {
130   my %opt = @_;
131
132   # Option validation
133   die "Non-numerical Host Template ID, check export configuration\n"
134     unless $opt{'template_id'} =~ /^\d+$/;
135   die "Non-numerical Graph Tree ID, check export configuration\n"
136     unless $opt{'tree_id'} =~ /^\d+$/;
137
138   # Add host to cacti
139   my $desc = $opt{'description'};
140   $desc =~ s/\$ip_addr/$opt{'hostname'}/g;
141   $desc =~ s/\$description/$opt{'svc_desc'}/g;
142   $desc =~ s/'/'\\''/g;
143   my $cmd = $php
144           . $opt{'script_path'} 
145           . q(add_device.php --description=')
146           . $desc
147           . q(' --ip=')
148           . $opt{'hostname'}
149           . q(' --template=)
150           . $opt{'template_id'};
151   my $response = ssh_cmd(%opt, 'command' => $cmd);
152   unless ( $response =~ /Success - new device-id: \((\d+)\)/ ) {
153     die "Error adding device: $response";
154   }
155   my $id = $1;
156
157   # Add host to tree
158   $cmd = $php
159        . $opt{'script_path'}
160        . q(add_tree.php --type=node --node-type=host --tree-id=)
161        . $opt{'tree_id'}
162        . q( --host-id=)
163        . $id;
164   $response = ssh_cmd(%opt, 'command' => $cmd);
165   unless ( $response =~ /Added Node node-id: \((\d+)\)/ ) {
166       die "Error adding host to tree: $response";
167   }
168   my $leaf_id = $1;
169
170   # Store id for generating graph urls
171   my $svc_broadband = qsearchs({
172     'table'   => 'svc_broadband',
173     'hashref' => { 'svcnum' => $opt{'svcnum'} },
174   });
175   die "Could not reload broadband service" unless $svc_broadband;
176   $svc_broadband->set('cacti_leaf_id',$leaf_id);
177   my $error = $svc_broadband->replace;
178   return $error if $error;
179
180 #  # Get list of graph templates for new id
181 #  $cmd = $php
182 #       . $opt{'script_path'} 
183 #       . q(freeside_cacti.php --get-graph-templates --host-template=)
184 #       . $opt{'template_id'};
185 #  my @gtids = split(/\n/,ssh_cmd(%opt, 'command' => $cmd));
186 #  die "No graphs configured for host template"
187 #    unless @gtids;
188 #
189 #  # Create graphs
190 #  foreach my $gtid (@gtids) {
191 #
192 #    # sanity checks, should never happen
193 #    next unless $gtid;
194 #    die "Bad graph template: $gtid"
195 #      unless $gtid =~ /^\d+$/;
196 #
197 #    # create the graph
198 #    $cmd = $php
199 #         . $opt{'script_path'}
200 #         . q(add_graphs.php --graph-type=cg --graph-template-id=)
201 #         . $gtid
202 #         . q( --host-id=)
203 #         . $id;
204 #    $response = ssh_cmd(%opt, 'command' => $cmd);
205 #    die "Error creating graph $gtid: $response"
206 #      unless $response =~ /Graph Added - graph-id: \((\d+)\)/;
207 #    my $gid = $1;
208 #
209 #    # add the graph to the tree
210 #    $cmd = $php
211 #         . $opt{'script_path'}
212 #         . q(add_tree.php --type=node --node-type=graph --tree-id=)
213 #         . $opt{'tree_id'}
214 #         . q( --graph-id=)
215 #         . $gid;
216 #    $response = ssh_cmd(%opt, 'command' => $cmd);
217 #    die "Error adding graph $gid to tree: $response"
218 #      unless $response =~ /Added Node/;
219 #
220 #  } #foreach $gtid
221
222   return '';
223 }
224
225 sub ssh_delete {
226   my %opt = @_;
227   my $cmd = $php
228           . $opt{'script_path'} 
229           . q(freeside_cacti.php --drop-device --ip=')
230           . $opt{'hostname'}
231           . q(');
232 #  $cmd .= q( --delete-graphs)
233 #    if $opt{'delete_graphs'};
234   my $response = ssh_cmd(%opt, 'command' => $cmd);
235   die "Error removing from cacti: " . $response
236     if $response;
237   return '';
238 }
239
240 #fake false laziness, other ssh_cmds handle error/output differently
241 sub ssh_cmd {
242   use Net::OpenSSH;
243   my $opt = { @_ };
244   my $ssh = Net::OpenSSH->new($opt->{'user'}.'@'.$opt->{'host'});
245   die "Couldn't establish SSH connection: ". $ssh->error if $ssh->error;
246   my ($output, $errput) = $ssh->capture2($opt->{'command'});
247   die "Error running SSH command: ". $ssh->error if $ssh->error;
248   die $errput if $errput;
249   return $output;
250 }
251
252 =pod
253
254 =head1 NAME
255
256 FS::part_export::cacti
257
258 =head1 SYNOPSIS
259
260 Cacti integration for Freeside
261
262 =head1 DESCRIPTION
263
264 This module in particular handles FS::part_export object creation for Cacti integration;
265 consult any existing L<FS::part_export> documentation for details on how that works.
266 What follows is more general instructions for connecting your Cacti installation
267 to your Freeside installation.
268
269 =head2 Connecting Cacti To Freeside
270
271 Copy the freeside_cacti.php script from the bin directory of your Freeside
272 installation to the cli directory of your Cacti installation.  Give this file 
273 the same permissions as the other files in that directory, and create 
274 (or choose an existing) user with sufficient permission to read these scripts.
275
276 In the regular Cacti interface, create a Host Template to be used by 
277 devices exported by Freeside, and note the template's id number.
278
279 In Freeside, go to Configuration->Services->Provisioning exports to
280 add a new export.  From the Add Export page, select cacti for Export then enter...
281
282 * the User Name with permission to run scripts in the cli directory
283
284 * enter the full Script Path to that directory (eg /usr/share/cacti/cli/)
285
286 * enter the Base Cacti URL for your cacti server (eg https://example.com/cacti/)
287
288 * the Host Template ID for adding new devices
289
290 * the Graph Tree ID for adding new devices
291
292 * the Description for new devices;  you can use the tokens
293   $ip_addr and $description to include the equivalent fields
294   from the broadband service definition
295
296 After adding the export, go to Configuration->Services->Service definitions.
297 The export you just created will be available for selection when adding or
298 editing broadband service definitions.
299
300 When properly configured broadband services are provisioned, they should now
301 be added to Cacti using the Host Template you specified, and the created device
302 will also be added to the specified Graph Tree.
303
304 Once added, a link to the graphs for this host will be available when viewing 
305 the details of the provisioned service in Freeside (you will need to authenticate 
306 into Cacti to view them.)
307
308 Devices will be deleted from Cacti when the service is unprovisioned in Freeside, 
309 and they will be deleted and re-added if the ip address changes.
310
311 Currently, graphs themselves must still be added in cacti by hand or some
312 other form of automation tailored to your specific graph inputs and data sources.
313
314 =head1 AUTHOR
315
316 Jonathan Prykop 
317 jonathan@freeside.biz
318
319 =head1 LICENSE AND COPYRIGHT
320
321 Copyright 2015 Freeside Internet Services      
322
323 This program is free software; you can redistribute it and/or           |
324 modify it under the terms of the GNU General Public License             |
325 as published by the Free Software Foundation.
326
327 =cut
328
329 1;
330
331