updates
[torrus-plus.git] / plugins / siam / siam.in
blob4d66d5bd07af0a38051299b0feca46e7caef7568
1 #!@PERL@
2 #  Copyright (C) 2011  Stanislav Sinyagin
4 #  This program is free software; you can redistribute it and/or modify
5 #  it under the terms of the GNU General Public License as published by
6 #  the Free Software Foundation; either version 2 of the License, or
7 #  (at your option) any later version.
9 #  This program is distributed in the hope that it will be useful,
10 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 #  GNU General Public License for more details.
14 #  You should have received a copy of the GNU General Public License
15 #  along with this program; if not, write to the Free Software
16 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
18 # Stanislav Sinyagin <ssinyagin@yahoo.com>
20 use strict;
21 use warnings;
23 BEGIN { require '@siam_config_pl@'; }
26 use Getopt::Long;
27 use Sys::Hostname;
28 use XML::LibXML;
30 use Torrus::SIAM;
31 use Torrus::ConfigBuilder;
32 use Torrus::Log;
34 my $server = hostname();
36 my $ddx_path = $Torrus::SIAM::ddx_path;
37 my $bundles_path = $Torrus::SIAM::bundles_path;
38 my $aggr_path = $Torrus::SIAM::aggr_xml_path;
39 my $nodes_path = $Torrus::SIAM::nodes_path;
40 my $rrd_path = $Torrus::SIAM::rrd_path;
43 my $debug = 0;
44 my $verbose = 0;
48 my $ok = GetOptions( 'server=s'    => \$server,
49                      'pddx=s'      => \$ddx_path,
50                      'pbundles=s'  => \$bundles_path,
51                      'paggr=s'     => \$aggr_path,
52                      'pnodes=s'    => \$nodes_path,
53                      'prrd=s'      => \$rrd_path,
54                      'verbose'     => \$verbose,
55                      'debug'       => \$debug );
57 if( not $ok or scalar( @ARGV ) > 0 )
59     print STDERR
60         ("Usage: $0 [options...]\n",
61          "Options:\n",
62          " --server=HOSTNAME       Torrus server to match in SIAM [" .
63          $server . "]\n",
64          " --pddx=PATH             Path to store DDX files\n",
65          "    [" . $ddx_path . "]\n",
66          " --pbundles=PATH         Path to store bundles XML\n",
67          "    [" . $bundles_path . "]\n",
68          " --paggr=PATH            Path to store aggregates XML\n",
69          "    [" . $aggr_path . "]\n",
70          " --pnodes=PATH           Path to store per-node XML\n",
71          "    [" . $nodes_path . "]\n",
72          " --prrd=PATH             RRD data storage path\n",
73          "    [" . $rrd_path . "]\n",
74          " --verbose               print extra information\n",
75          " --debug                 print debugging information\n",
76          "\n");
77     exit 1;
80 if( $debug )
82     Torrus::Log::setLevel('debug');
84 elsif( $verbose )
86     Torrus::Log::setLevel('verbose');
89 if( not -d $bundles_path )
91     Error('No such directory: ' . $bundles_path);
94 if( not -d $nodes_path )
96     Error('No such directory: ' . $nodes_path);
99 my @tree_names;
100 my %tree_for_deviceid;
101 my $success = 1;
103 my $siam = Torrus::SIAM->open();
104 exit(1) unless defined($siam);
106 Verbose('Connected to SIAM');
108 # Generate discovery files
110     my $devices = $siam->get_contained_objects
111         ('SIAM::Device', {'match_attribute' => ['torrus.server', [$server]]});
113     Verbose('Retrieved ' . scalar(@{$devices}) . ' devices from SIAM');
116     # Distribute devices into bundles by torrus.tree attribute
117     my %bundles;
118     for my $device (@{$devices})
119     {
120         next unless $device->is_complete();
121         next unless $device->attr('snmp.managed');
123         my $tree = $device->attr('torrus.tree');
124         if( not defined($tree) )
125         {
126             Error('SIAM::Device "' . $device->id .
127                   '" does not define "torrus.tree" attribute. Aborting.');
128             exit(1);
129         }
131         $tree_for_deviceid{$device->id} = $tree;
133         if( not defined($bundles{$tree}) )
134         {
135             $bundles{$tree} = [];
136         }
138         push(@{$bundles{$tree}}, $device);
139     }
141     @tree_names = sort keys %bundles;
143     Verbose('Preparing to generate ' . scalar(keys %bundles) . ' bundles');
145     for my $tree ( @tree_names )
146     {
147         Verbose('Bundle ' . $tree . ': ' . scalar(@{$bundles{$tree}}) .
148                 ' devices');
150         my $outfile = $ddx_path . 'siam-' . $tree . '.ddx';
151         Verbose('Preparing to write ' . $outfile);
153         my $doc = XML::LibXML->createDocument( "1.0", "UTF-8" );
154         my $root = $doc->createElement('snmp-discovery');
155         $doc->setDocumentElement( $root );
157         {
158             my $fileInfoNode = $doc->createElement('file-info');
159             $root->appendChild( $fileInfoNode );
161             my $formatNode = $doc->createElement('format-version');
162             $formatNode->appendText('1.0');
163             $fileInfoNode->appendChild( $formatNode );
164         }
166         {
167             my $creatorNode = $doc->createElement('creator-info');
168             $creatorNode->appendText
169                 ("This file is generated from SIAM\n" .
170                  'for Torrus tree ' . $tree . ' on ' .
171                  scalar(localtime(time())));
172             $root->appendChild( $creatorNode );
173         }
175         my $globalParams = {
176             'output-bundle'  => $bundles_path . '/siam-' . $tree . '.xml',
177             'data-dir'       => $rrd_path,
178             'SIAM::managed'   => 'yes',
179         };
181         for my $p (keys %{$Torrus::SIAM::ddx_defaults})
182         {
183             $globalParams->{$p} = $Torrus::SIAM::ddx_defaults->{$p};
184         }
186         createParamsDom( $globalParams, $doc, $root );
188         for my $device (sort {$a->attr('siam.device.name') cmp
189                                       $b->attr('siam.device.name')}
190                             @{$bundles{$tree}})
191         {
192             my $name = $device->attr('siam.device.name');
194             my $params = {
195                 'output-file' => $nodes_path . '/' . $name . '.xml',
196                 'system-id' => $name,
197             };
199             $params->{'SIAM::device-inventory-id'} =
200                 $device->attr('siam.device.inventory_id');
202             my $host = $device->attr('snmp.host');
203             if( not defined($host) )
204             {
205                 $host = $name;
206             }
208             $params->{'snmp-host'} = $host;
210             for my $p ('version', 'community', 'timeout', 'retries')
211             {
212                 my $v = $device->attr('snmp.' . $p);
213                 if( defined($v) )
214                 {
215                     $params->{'snmp-' . $p} = $v;
216                 }
217             }
219             my $subtree = $device->attr('torrus.subtree-path');
220             if( defined($subtree) )
221             {
222                 $params->{'host-subtree'} = $subtree;
223             }
225             my $hostNode = $doc->createElement('host');
226             $root->appendChild( $hostNode );
228             createParamsDom( $params, $doc, $hostNode );
229         }
231         if( $doc->toFile( $outfile, 2 ) )
232         {
233             Info("Wrote $outfile");
234         }
235         else
236         {
237             Error("Cannot write $outfile: $!");
238             $success = 0;
239         }
240     }
244 # Generate traffic aggregate graphs
246     my %aggr_elements;
247     my %aggr_tree;
248     my %aggr_name;
249     my %aggr_description;
250     my %aggr_unit_type;
251     my %aggr_nodeid;
252     my %unit_name;
254     for my $svctype ( @Torrus::SIAM::aggr_svc_types )
255     {
256         my $services =
257             $siam->get_objects_by_attribute
258             ('SIAM::Service', 'siam.svc.type', $svctype );
260         for my $service ( @{$services} )
261         {
262             next unless $service->is_complete();
264             my $svc_server = $service->attr('torrus.server');
265             if( not defined($svc_server) )
266             {
267                 Error('Attribute "torrus.server" is undefined for the ' .
268                       'traffic aggregate SIAM::Service ' . $service->id);
269                 $success = 0;
270                 next;
271             }
273             next unless $svc_server eq $server;
275             my $aggr_id = $service->id();
277             $aggr_name{$aggr_id} =
278                 $service->attr($Torrus::SIAM::aggr_subtree_name_attr);
279             if( not defined($aggr_name{$aggr_id}) )
280             {
281                 Error('Cannot determine the subtree path for aggregate ' .
282                       'SIAM::Service ' . $service->id);
283                 $success = 0;
284                 next;
285             }
287             $aggr_nodeid{$aggr_id} =
288                 $service->attr('torrus.aggregate.nodeid');
290             my $units = $service->get_service_units();
291             for my $unit (@{$units})
292             {
293                 next unless $unit->is_complete();
295                 my $device_id = $unit->attr('siam.svcunit.device_id');
296                 my $tree = $tree_for_deviceid{$device_id};
298                 if( not defined($tree) )
299                 {
300                     Error('SIAM::Device "' . $device_id .
301                           '" does not define "torrus.tree" attribute. ' .
302                           'It will not be a part of ' . $aggr_id .
303                           ' aggregate');
304                     $success = 0;
305                     next;
306                 }
308                 # Make sure that the aggregate is built from elements
309                 # of the same tree
310                 if( not defined($aggr_tree{$aggr_id}) )
311                 {
312                     $aggr_tree{$aggr_id} = $tree;
313                 }
314                 elsif( $aggr_tree{$aggr_id} ne $tree )
315                 {
316                     Error('Aggregate ' . $aggr_id . ' is defined for ' .
317                           'devices from different trees: ' . $tree .
318                           ', ' . $aggr_tree{$aggr_id});
319                     $success = 0;
320                     next;
321                 }
323                 my $unit_type = $unit->attr('siam.svcunit.type');
324                 if( not defined($aggr_unit_type{$aggr_id}) )
325                 {
326                     $aggr_unit_type{$aggr_id} = $unit_type;
327                 }
328                 elsif( $aggr_unit_type{$aggr_id} ne $unit_type )
329                 {
330                     Error('Aggregate ' . $aggr_id . ' is defined for ' .
331                           'service units of different types: ' . $unit_type .
332                           ', ' . $aggr_unit_type{$aggr_id});
333                     $success = 0;
334                     next;
335                 }
337                 my $nodeid = $unit->attr('torrus.port.nodeid');
338                 if( not defined($nodeid) )
339                 {
340                     Error('SIAM::ServiceUnit, id="' . $unit->id .
341                           '" does not define torrus.port.nodeid');
342                     $success = 0;
343                     next;
344                 }
346                 $aggr_elements{$tree}{$aggr_id}{$nodeid} = 1;
348                 my $descr =
349                     $service->attr($Torrus::SIAM::aggr_descr_attr);
350                 if( defined($descr) )
351                 {
352                     $aggr_description{$aggr_id} = $descr;
353                 }
355                 $unit_name{$nodeid} = $unit->attr('siam.svcunit.name');
356             }
357         }
358     }
360     if( not $success )
361     {
362         Error('Skipping generation of aggregate XML ' .
363               'because of previous errors');
364         $siam->disconnect();
365         exit(1);
366     }
368     for my $tree ( @tree_names )
369     {
370         my $outfile = $aggr_path . '/siam-aggr-' . $tree . '.xml';
371         Verbose('Preparing to write ' . $outfile);
373         # we write the XML in any case, even if it's empty
374         my $cb = new Torrus::ConfigBuilder;
375         $cb->addCreatorInfo( "This file was generated by:\n" . $0 . "\n");
377         my $aggregates = $aggr_elements{$tree};
378         if( defined($aggregates) )
379         {
380             # Build the subtree containing all our aggregates
381             my $path = $Torrus::SIAM::aggr_tree_location;
383             # Chop the first and last slashes
384             $path =~ s/^\///;
385             $path =~ s/\/$//;
387             # generate subtree path XML
388             my $aggrTopNode = undef;
389             for my $subtreeName ( split( '/', $path ) )
390             {
391                 $aggrTopNode = $cb->addSubtree( $aggrTopNode, $subtreeName );
392             }
394             $cb->addParams($aggrTopNode, {
395                 'has-overview-shortcuts' => 'yes',
396                 'overview-shortcuts' => 'traffic',
397                 'overview-subleave-name-traffic' => 'InOut_bps',
398                 'overview-shortcut-text-traffic' => 'All traffic',
399                 'overview-shortcut-title-traffic' =>
400                     'Traffic for all aggregates',
401                     'overview-page-title-traffic' =>
402                     'Aggregate Input/Output Graphs',
403             });
405             for my $aggr_id (sort keys %{$aggregates})
406             {
407                 my $unit_type = $aggr_unit_type{$aggr_id};
408                 if( $unit_type eq 'IFMIB.Port' )
409                 {
410                     # remove all invalid characters
411                     my $name_translated = $aggr_name{$aggr_id};
412                     $name_translated =~ s/\W/_/go;
414                     Debug('Building aggregate: ' . $path . '/' .
415                           $name_translated);
417                     my $aggrParam = {
418                         'node-display-name' => $aggr_name{$aggr_id},
419                         'nodeid' => $aggr_nodeid{$aggr_id},
420                     };
422                     if( defined($aggr_description{$aggr_id}) )
423                     {
424                         $aggrParam->{'comment'} =
425                             $aggr_description{$aggr_id};
426                     }
428                     # build the legend from aggregate members
429                     {
430                         my $legend = '';
431                         for my $port_nodeid
432                             ( sort keys %{$aggr_elements{$tree}{$aggr_id}} )
433                         {
434                             my $name = $unit_name{$port_nodeid};
435                             $name =~ s/:/{COLON}/og;
436                             $name =~ s/;/{SEMICOL}/og;
437                             $name =~ s/%/{PERCENT}/og;
439                             $legend .= 'member: ' . $name . ';';
440                         }
441                         $aggrParam->{'legend'} = $legend;
442                     }
444                     my $aggrSubtreeNode = $cb->addSubtree
445                         ( $aggrTopNode, $name_translated, $aggrParam );
447                     my %rpnexpr;
449                     # Add the Bytes_In and Bytes_Out leaves. They are needed
450                     # for the monitor mostly.
452                     for my $dir ('In', 'Out')
453                     {
454                         my $dirsmall = lc($dir);
456                         my $rpn = '';
457                         for my $port_nodeid
458                             ( sort keys %{$aggr_elements{$tree}{$aggr_id}} )
459                         {
460                             my $nodeid =
461                                 $port_nodeid . '//' . $dirsmall . 'bytes';
463                             # if value is unknown, it is treated as zero
464                             my $memRef =
465                                 '{[[' . $nodeid . ']]}';
467                             if( $rpn eq '' )
468                             {
469                                 $rpn = $memRef;
470                             }
471                             else
472                             {
473                                 $rpn .= ',' . $memRef . ',ADDNAN';
474                             }
475                         }
477                         my $param = {
478                             'comment' => $dir . 'put bytes',
479                             'graph-legend' => $dir . 'put bytes',
480                             'vertical-label' => 'Bps',
481                             'ds-type' => 'rrd-file',
482                             'leaf-type' => 'rrd-cdef',
483                             'rpn-expr' => $rpn,
484                             'graph-lower-limit' => 0,
485                         };
487                         if( defined($aggr_nodeid{$aggr_id}) )
488                         {
489                             $param->{'nodeid'} =
490                                 $aggr_nodeid{$aggr_id} . '//' .
491                                 $dirsmall . 'bytes';
492                         }
494                         $cb->addLeaf( $aggrSubtreeNode,
495                                       'Bytes_' . $dir, $param );
497                         # store the RPN for the multigraph
498                         $rpnexpr{$dir} = $rpn;
499                     }
501                     # Add the InOut_bps multigraph
502                     {
503                         my $param = {
504                             'comment' => 'In/Out bits per second graphs',
505                             'vertical-label' => 'bps',
506                             'graph-lower-limit' => 0,
507                             'ds-type' => 'rrd-multigraph',
508                             'ds-names' => 'in,out',
509                         };
511                         if( defined($aggr_nodeid{$aggr_id}) )
512                         {
513                             $param->{'nodeid'} =
514                                 $aggr_nodeid{$aggr_id} . '//inoutbit';
515                         }
517                         for my $dir ('In', 'Out')
518                         {
519                             my $ds = lc($dir);
520                             $param->{'ds-expr-' . $ds} =
521                                 $rpnexpr{$dir} . ',8,*';
522                             $param->{'graph-legend-' . $ds} =
523                                 'Bits per second ' . $ds;
524                             $param->{'line-style-' . $ds} = '##Bps' . $dir;
525                             $param->{'line-color-' . $ds} = '##Bps' . $dir;
526                             $param->{'maxline-style-' . $ds} =
527                                 '##Bps' . $dir . 'Max';
528                             $param->{'maxline-color-' . $ds} =
529                                 '##Bps' . $dir . 'Max';
530                         }
532                         $param->{'line-order-in'} = 1;
533                         $param->{'line-order-out'} = 2;
535                         $cb->addLeaf( $aggrSubtreeNode, 'InOut_bps', $param );
536                     }
538                 }
539                 else
540                 {
541                     Error('Service unit type ' . $unit_type .
542                           ' is not supported for aggregates');
543                     $success = 0;
544                 }
545             }
546         }
548         if( $cb->toFile( $outfile ) )
549         {
550             Info('Wrote ' . $outfile);
551         }
552         else
553         {
554             Error('Cannot write aggregates to ' . $outfile . ': ' . $!);
555             $success = 0;
556         }
558     }
561 $siam->disconnect();
563 exit($success ? 0:1);
565 sub createParamsDom
567     my $params = shift;
568     my $doc = shift;
569     my $parentNode = shift;
571     for my $param ( sort keys %{$params} )
572     {
573         my $paramNode = $doc->createElement('param');
574         $paramNode->setAttribute( 'name', $param );
575         $paramNode->setAttribute( 'value', $params->{$param} );
576         $parentNode->appendChild( $paramNode );
577     }
579     return;
582 # Local Variables:
583 # mode: perl
584 # indent-tabs-mode: nil
585 # perl-indent-level: 4
586 # End: