1 # Copyright (C) 2002 Stanislav Sinyagin
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
17 # $Id: Validator.pm,v 1.1 2010-12-27 00:03:45 ivan Exp $
18 # Stanislav Sinyagin <ssinyagin@yahoo.com>
21 package Torrus::ConfigTree::Validator;
23 use Torrus::ConfigTree;
26 use Torrus::SiteConfig;
29 Torrus::SiteConfig::loadStyling();
31 %Torrus::ConfigTree::Validator::reportedErrors = ();
35 'leaf-type' => {'rrd-def' => {'rrd-ds' => undef,
36 'rrd-cf' => {'AVERAGE' => undef,
42 'rrd-cdef' => {'rpn-expr' => undef}},
45 my %rrdmulti_params = ( 'ds-names' => undef );
47 # Plugins might need to add a new storage type
48 our %collector_params =
50 'collector-type' => undef,
56 'rrd-def' => {'rrd-ds' => undef,
57 'rrd-cf' => {'AVERAGE' => undef,
61 'rrd-create-dstype' => {'GAUGE' => undef,
64 'ABSOLUTE' => undef },
65 'rrd-create-rra' => undef,
66 'rrd-create-heartbeat' => undef,
69 'rrd-create-hw-rralen' => undef},
76 'COUNTER64' => undef },
77 'ext-service-id' => undef,
78 '+ext-service-units' => {
80 'collector-period' => undef,
81 'collector-timeoffset' => undef,
82 '+collector-scale' => undef,
83 '+collector-dispersed-timeoffset' => {
86 # collector-timeoffset-min, max, step, and hashstring are validated
87 # during post-processing
91 # Plugins might in theory create new datasource types
93 ('ds-type' => {'rrd-file' => \%rrd_params,
94 'rrd-multigraph' => \%rrdmulti_params,
95 'collector' => \%collector_params},
96 'rrgraph-views' => undef,
97 '+rrd-scaling-base' => {'1000' => undef, '1024' => undef},
98 '+graph-logarithmic' => {'yes' => undef, 'no' => undef},
99 '+graph-rigid-boundaries' => {'yes' => undef, 'no' => undef},
100 '+graph-ignore-decorations' => {'yes' => undef, 'no' => undef});
104 ('monitor-type' => {'expression' => {'rpn-expr' => undef},
105 'failures' => undef},
111 ('action-type' => {'tset' => {'tset-name' => undef},
112 'exec' => {'command' => undef} }
117 'view-type' => {'rrgraph' => {'width' => undef,
120 'line-style' => undef,
121 'line-color' => undef,
122 '+ignore-limits' => {
123 'yes'=>undef, 'no'=>undef },
124 '+ignore-lower-limit' => {
125 'yes'=>undef, 'no'=>undef },
126 '+ignore-upper-limit' => {
127 'yes'=>undef, 'no'=>undef }},
128 'rrprint' => {'start' => undef,
129 'print-cf' => undef},
130 'html' => {'html-template' => undef},
135 # Load additional validation, configurable from
136 # torrus-config.pl and torrus-siteconfig.pl
138 foreach my $mod ( @Torrus::Validator::loadLeafValidators )
140 eval( 'require ' . $mod );
142 eval( '&' . $mod . '::initValidatorLeafParams( \%leaf_params )' );
149 my $config_tree = shift;
150 my $token = $config_tree->token('/');
152 if( defined($token) )
154 return validateNode($config_tree, $token);
158 Error("The datasource tree is empty");
165 my $config_tree = shift;
168 &Torrus::DB::checkInterrupted();
172 if( $config_tree->isLeaf($token) )
174 # Verify the default view
175 my $view = $config_tree->getNodeParam( $token, 'default-leaf-view' );
176 if( not defined( $view ) )
178 my $path = $config_tree->path( $token );
179 Error("Default view is not defined for leaf $path");
182 elsif( not $config_tree->{'validator'}{'viewExists'}{$view} and
183 not $config_tree->viewExists( $view ) )
185 my $path = $config_tree->path( $token );
186 Error("Non-existent view is defined as default for leaf $path");
191 # Cache the view name
192 $config_tree->{'validator'}{'viewExists'}{$view} = 1;
196 $ok = validateInstanceParams($config_tree, $token,
197 'node', \%leaf_params);
202 $config_tree->getNodeParam( $token, 'rrgraph-views' );
204 # Check the cache first
205 if( not $config_tree->{'validator'}{'graphviews'}{$rrviewslist} )
207 my @rrviews = split( ',', $rrviewslist );
209 if( scalar(@rrviews) != 5 )
211 my $path = $config_tree->path( $token );
212 Error('rrgraph-views sould refer 5 views in' . $path);
217 foreach my $view ( @rrviews )
219 if( not $config_tree->viewExists( $view ) )
221 my $path = $config_tree->path( $token );
222 Error("Non-existent view ($view) is defined in " .
223 "rrgraph-views for $path");
226 elsif( $config_tree->getParam($view, 'view-type') ne
229 my $path = $config_tree->path( $token );
230 Error("View $view is not of type rrgraph in " .
231 "rrgraph-views for $path");
240 $config_tree->{'validator'}{'graphviews'}{$rrviewslist}=1;
245 # Verify monitor references
246 my $mlist = $config_tree->getNodeParam( $token, 'monitor' );
249 foreach my $param ( 'monitor-period', 'monitor-timeoffset' )
251 if( not defined( $config_tree->getNodeParam( $token,
254 my $path = $config_tree->path( $token );
255 Error('Mandatory parameter ' . $param .
256 ' is not defined in ' . $path);
261 foreach my $monitor ( split(',', $mlist) )
263 if( not $config_tree->{'validator'}{'monitorExists'}{$monitor}
265 not $config_tree->monitorExists( $monitor ) )
267 my $path = $config_tree->path( $token );
268 Error("Non-existent monitor: $monitor in $path");
273 $config_tree->{'validator'}{'monitorExists'}{$monitor} = 1;
278 $config_tree->getNodeParam( $token, 'monitor-vars' );
279 if( defined $varstring )
281 foreach my $pair ( split( '\s*;\s*', $varstring ) )
283 if( $pair !~ /^\w+\s*\=\s*[0-9\-+.eU]+$/o )
285 Error("Syntax error in monitor variables: $pair");
292 $config_tree->getNodeParam($token, 'monitor-action-target');
293 if( defined( $action_target ) )
295 my $target = $config_tree->getRelative($token, $action_target);
296 if( not defined( $target ) )
298 my $path = $config_tree->path( $token );
299 Error('monitor-action-target points to an invalid path: ' .
300 $action_target . ' in ' . $path);
303 elsif( not $config_tree->isLeaf( $target ) )
305 my $path = $config_tree->path( $token );
306 Error('monitor-action-target must point to a leaf: ' .
307 $action_target . ' in ' . $path);
313 # Verify if the data-dir exists
314 my $datadir = $config_tree->getNodeParam( $token, 'data-dir' );
315 if( defined $datadir )
317 if( not $config_tree->{'validator'}{'dirExists'}{$datadir} and
318 not ( -d $datadir ) and
319 not $Torrus::ConfigTree::Validator::reportedErrors{$datadir} )
321 my $path = $config_tree->path( $token );
322 Error("Directory does not exist: $datadir in $path");
324 $Torrus::ConfigTree::Validator::reportedErrors{$datadir} = 1;
329 $config_tree->{'validator'}{'dirExists'}{$datadir} = 1;
333 # Verify type-specific parameters
334 my $dsType = $config_tree->getNodeParam( $token, 'ds-type' );
335 if( not defined( $dsType ) )
337 # Writer has already complained
341 if( $dsType eq 'rrd-multigraph' )
344 split(',', $config_tree->getNodeParam( $token, 'ds-names' ) );
346 if( scalar(@dsNames) == 0 )
348 my $path = $config_tree->path( $token );
349 Error("ds-names list is empty in $path");
352 foreach my $dname ( @dsNames )
354 my $param = 'ds-expr-' . $dname;
355 my $expr = $config_tree->getNodeParam( $token, $param );
356 if( not defined( $expr ) )
358 my $path = $config_tree->path( $token );
359 Error("Parameter $param is not defined in $path");
364 $ok = validateRPN( $token, $expr, $config_tree ) ? $ok : 0;
367 foreach my $paramprefix ( 'graph-legend-', 'line-style-',
368 'line-color-', 'line-order-' )
370 my $param = $paramprefix.$dname;
371 my $value = $config_tree->getNodeParam($token, $param);
372 if( not defined( $value ) )
374 my $path = $config_tree->path( $token );
375 Error('Parameter ' . $param .
376 ' is not defined in ' . $path);
379 elsif( $param eq 'line-style-' and
380 not validateLine( $value ) )
382 my $path = $config_tree->path( $token );
383 Error('Parameter ' . $param .
384 ' is defined incorrectly in ' . $path);
387 elsif( $param eq 'line-color-' and
388 not validateColor( $value ) )
390 my $path = $config_tree->path( $token );
391 Error('Parameter ' . $param .
392 ' is defined incorrectly in ' . $path);
398 elsif( $dsType eq 'rrd-file' and
399 $config_tree->getNodeParam( $token, 'leaf-type' ) eq 'rrd-cdef')
401 my $expr = $config_tree->getNodeParam( $token, 'rpn-expr' );
402 if( defined( $expr ) )
404 $ok = validateRPN( $token, $expr, $config_tree ) ? $ok : 0;
406 # Otherwise already reported by validateInstanceParams()
408 elsif($dsType eq 'collector' and
409 $config_tree->getNodeParam( $token, 'collector-type' ) eq 'snmp')
411 # Check the OID syntax
412 my $oid = $config_tree->getNodeParam( $token, 'snmp-object' );
413 if( defined($oid) and $oid =~ /^\./o )
415 my $path = $config_tree->path( $token );
416 Error("Invalid syntax for snmp-object in " .
417 $path . ": OID must not start with dot");
425 my $view = $config_tree->getNodeParam( $token,
426 'default-subtree-view' );
428 if( not defined( $view ) )
430 my $path = $config_tree->path( $token );
431 Error("Default view is not defined for subtree $path");
434 elsif( not $config_tree->{'validator'}{'viewExists'}{$view} and
435 not $config_tree->viewExists( $view ) )
437 my $path = $config_tree->path( $token );
438 Error("Non-existent view is defined as default for subtree $path");
444 $config_tree->{'validator'}{'viewExists'}{$view} = 1;
447 foreach my $ctoken ( $config_tree->getChildren($token) )
449 if( not $config_tree->isAlias($ctoken) )
451 $ok = validateNode($config_tree, $ctoken)
459 my %validFuntcionNames =
471 my $config_tree = shift;
472 my $timeoffset_supported = shift;
474 &Torrus::DB::checkInterrupted();
478 # There must be at least one DS reference
481 my $rpn = new Torrus::RPN;
483 # The callback for RPN translation
486 my ($noderef, $timeoffset) = @_;
489 if( $noderef =~ s/^(.+)\@//o )
494 if( defined( $function ) and not $validFuntcionNames{$function} )
496 my $path = $config_tree->path($token);
497 Error('Invalid function name ' . $function .
498 ' in node reference at ' . $path);
503 my $leaf = length($noderef) > 0 ?
504 $config_tree->getRelative($token, $noderef) : $token;
506 if( not defined $leaf )
508 my $path = $config_tree->path($token);
509 Error("Cannot find relative reference $noderef at $path");
513 if( not $config_tree->isLeaf( $leaf ) )
515 my $path = $config_tree->path($token);
516 Error("Relative reference $noderef at $path is not a leaf");
520 if( $config_tree->getNodeParam($leaf, 'leaf-type') ne 'rrd-def' )
522 my $path = $config_tree->path($token);
523 Error("Relative reference $noderef at $path must point to a ".
524 "leaf of type rrd-def");
528 if( defined( $timeoffset ) and not $timeoffset_supported )
530 my $path = $config_tree->path($token);
531 Error("Time offsets are not supported at $path");
540 $rpn->translate( $expr, $callback );
541 if( $ok and $ds_couter == 0 )
543 my $path = $config_tree->path($token);
544 Error("RPN must contain at least one DS reference at $path");
554 my $config_tree = shift;
557 foreach my $view ($config_tree->getViewNames())
559 &Torrus::DB::checkInterrupted();
561 $ok = validateInstanceParams($config_tree, $view,
562 'view', \%view_params) ? $ok:0;
563 if( $ok and $config_tree->getParam($view, 'view-type') eq 'rrgraph' )
565 my $hrulesList = $config_tree->getParam($view, 'hrules');
566 if( defined( $hrulesList ) )
568 foreach my $hrule ( split(',', $hrulesList ) )
571 $config_tree->getParam($view, 'hrule-value-' . $hrule);
572 if( not defined( $valueParam ) or $valueParam !~ /^\S+$/o )
574 Error('Mandatory parameter hrule-value-' . $hrule .
575 ' is not defined or incorrect for view ' .
580 $config_tree->getParam($view, 'hrule-color-'.$hrule);
581 if( not defined( $color ) )
583 Error('Mandatory parameter hrule-color-' . $hrule .
584 ' is not defined for view ' . $view);
589 $ok = validateColor( $color ) ? $ok:0;
594 my $decorList = $config_tree->getParam($view, 'decorations');
595 if( defined( $decorList ) )
597 foreach my $decorName ( split(',', $decorList ) )
599 foreach my $paramName ( qw(order style color expr) )
601 my $param = 'dec-' . $paramName . '-' . $decorName;
602 if( not defined( $config_tree->
603 getParam($view, $param) ) )
605 Error('Missing parameter: ' . $param .
606 ' in view ' . $view);
611 $ok = validateLine( $config_tree->
613 'dec-style-' . $decorName) )
615 $ok = validateColor( $config_tree->
617 'dec-color-' . $decorName) )
622 $ok = validateColor( $config_tree->getParam($view, 'line-color') )
624 $ok = validateLine( $config_tree->getParam($view, 'line-style') )
627 my $gprintValues = $config_tree->getParam($view, 'gprint-values');
628 if( defined( $gprintValues ) and length( $gprintValues ) > 0 )
630 foreach my $gprintVal ( split(',', $gprintValues ) )
633 $config_tree->getParam($view,
634 'gprint-format-' . $gprintVal);
635 if( not defined( $format ) or length( $format ) == 0 )
637 Error('GPRINT format for ' . $gprintVal .
638 ' is not defined for view ' . $view);
654 if( $color !~ /^\#[0-9a-fA-F]{6}$/o )
656 if( $color =~ /^\#\#(\S+)$/o )
658 if( not $Torrus::Renderer::graphStyles{$1}{'color'} )
660 Error('Incorrect color reference: ' . $color);
666 Error('Incorrect color syntax: ' . $color);
680 if( $line =~ /^\#\#(\S+)$/o )
682 if( not $Torrus::Renderer::graphStyles{$1}{'line'} )
684 Error('Incorrect line style reference: ' . $line);
688 elsif( not $Torrus::SiteConfig::validLineStyles{$line} )
690 Error('Incorrect line syntax: ' . $line);
700 my $config_tree = shift;
703 foreach my $action ($config_tree->getActionNames())
705 $ok = validateInstanceParams($config_tree, $action,
706 'action', \%action_params) ? $ok:0;
707 my $atype = $config_tree->getParam($action, 'action-type');
708 if( $atype eq 'tset' )
710 my $tset = $config_tree->getParam($action, 'tset-name');
714 if( not $config_tree->tsetExists( $tset ) )
716 Error("Token-set does not exist: $tset in action $action");
720 # Otherwise the error is already reported by validateInstanceParams
722 elsif( $atype eq 'exec' )
724 my $launch_when = $config_tree->getParam($action, 'launch-when');
725 if( defined $launch_when )
727 foreach my $when ( split(',', $launch_when) )
730 foreach my $event ('set', 'repeat', 'clear', 'forget')
732 if( $when eq $event )
739 if( $when eq 'throw' )
741 Error('Event type "throw" is no longer ' .
742 'supported. Replace with "set".');
746 Error("Invalid value in parameter launch-when " .
747 "in action $action: $when");
754 my $setenv_dataexpr =
755 $config_tree->getParam( $action, 'setenv-dataexpr' );
757 if( defined( $setenv_dataexpr ) )
759 # <param name="setenv_dataexpr"
760 # value="ENV1=expr1, ENV2=expr2"/>
762 foreach my $pair ( split( ',', $setenv_dataexpr ) )
764 my ($env, $param) = split( '=', $pair );
767 Error("Syntax error in setenv-dataexpr in action " .
768 $action . ": \"" . $pair . "\"");
771 elsif( $env =~ /\W/o )
773 Error("Illegal characters in environment variable ".
774 "name in setenv-dataexpr in action " . $action .
775 ": \"" . $env . "\"");
778 elsif( not defined ($config_tree->getParam( $action,
781 Error("Parameter referenced in setenv-dataexpr is " .
782 "not defined in action " .
783 $action . ": " . $param);
791 foreach my $monitor ($config_tree->getMonitorNames())
793 $ok = validateInstanceParams($config_tree, $monitor,
794 'monitor', \%monitor_params) ? $ok:0;
795 my $alist = $config_tree->getParam( $monitor, 'action' );
796 foreach my $action ( split(',', $alist ) )
798 if( not $config_tree->actionExists( $action ) )
800 Error("Non-existent action: $action in monitor $monitor");
809 sub validateTokensets
811 my $config_tree = shift;
814 my $view = $config_tree->getParam( 'SS', 'default-tsetlist-view' );
815 if( not defined( $view ) )
817 Error("View is not defined for tokensets list");
820 elsif( not $config_tree->viewExists( $view ) )
822 Error("Non-existent view is defined for tokensets list");
826 foreach my $tset ($config_tree->getTsets())
828 &Torrus::DB::checkInterrupted();
830 $view = $config_tree->getParam($tset, 'default-tset-view');
831 if( not defined( $view ) )
833 $view = $config_tree->getParam('SS', 'default-tset-view');
836 if( not defined( $view ) )
838 Error("Default view is not defined for tokenset $tset");
841 elsif( not $config_tree->viewExists( $view ) )
843 Error("Non-existent view is defined for tokenset $tset");
853 sub validateInstanceParams
855 my $config_tree = shift;
856 my $inst_name = shift;
857 my $inst_type = shift;
860 &Torrus::DB::checkInterrupted();
862 # Debug("Validating $inst_type $inst_name");
865 my @namemaps = ($mapref);
867 while( $ok and scalar(@namemaps) > 0 )
869 my @next_namemaps = ();
871 foreach my $namemap (@namemaps)
873 foreach my $paramkey (keys %{$namemap})
875 # Debug("Checking param: $pname");
877 my $pname = $paramkey;
879 if( $pname =~ s/^\+//o )
885 if( $pname =~ s/^\@//o )
891 $config_tree->getInstanceParam($inst_type,
897 @pvalues = split(',', $pvalue);
901 @pvalues = ( $pvalue );
904 if( not defined( $pvalue ) )
909 if( $inst_type eq 'node' )
911 $msg = $config_tree->path( $inst_name );
915 $msg = "$inst_type $inst_name";
917 Error("Mandatory parameter $pname is not ".
924 if( ref( $namemap->{$paramkey} ) )
926 foreach my $pval ( @pvalues )
928 if( exists $namemap->{$paramkey}->{$pval} )
930 if( defined $namemap->{$paramkey}->{$pval} )
932 push( @next_namemaps,
933 $namemap->{$paramkey}->{$pval} );
939 if( $inst_type eq 'node' )
941 $msg = $config_tree->path( $inst_name );
945 $msg = "$inst_type $inst_name";
947 Error("Parameter $pname has ".
948 "unknown value: $pval for $msg");
956 @namemaps = @next_namemaps;
967 # indent-tabs-mode: nil
968 # perl-indent-level: 4