summaryrefslogtreecommitdiff
path: root/torrus/perllib/Torrus/ConfigTree/Writer.pm
diff options
context:
space:
mode:
Diffstat (limited to 'torrus/perllib/Torrus/ConfigTree/Writer.pm')
-rw-r--r--torrus/perllib/Torrus/ConfigTree/Writer.pm755
1 files changed, 755 insertions, 0 deletions
diff --git a/torrus/perllib/Torrus/ConfigTree/Writer.pm b/torrus/perllib/Torrus/ConfigTree/Writer.pm
new file mode 100644
index 000000000..9c1af8f86
--- /dev/null
+++ b/torrus/perllib/Torrus/ConfigTree/Writer.pm
@@ -0,0 +1,755 @@
+# Copyright (C) 2002-2007 Stanislav Sinyagin
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+
+# $Id: Writer.pm,v 1.1 2010-12-27 00:03:45 ivan Exp $
+# Stanislav Sinyagin <ssinyagin@yahoo.com>
+
+#
+# Write access for ConfigTree
+#
+
+package Torrus::ConfigTree::Writer;
+
+use Torrus::ConfigTree;
+our @ISA=qw(Torrus::ConfigTree);
+
+use Torrus::Log;
+use Torrus::TimeStamp;
+use Torrus::SiteConfig;
+use Torrus::ServiceID;
+
+use strict;
+use Digest::MD5 qw(md5); # needed as hash function
+
+
+our %multigraph_remove_space =
+ ('ds-expr-' => 1,
+ 'graph-legend-' => 0);
+
+
+# instance of Torrus::ServiceID object, if needed
+my $srvIdParams;
+
+# tree names where we initialized service IDs
+my %srvIdInitialized;
+
+
+sub new
+{
+ my $proto = shift;
+ my %options = @_;
+ my $class = ref($proto) || $proto;
+ $options{'-WriteAccess'} = 1;
+ my $self = $class->SUPER::new( %options );
+ if( not defined( $self ) )
+ {
+ return undef;
+ }
+
+ bless $self, $class;
+
+ $self->{'viewparent'} = {};
+ $self->{'mayRunCollector'} =
+ Torrus::SiteConfig::mayRunCollector( $self->treeName() );
+
+ $self->{'collectorInstances'} =
+ Torrus::SiteConfig::collectorInstances( $self->treeName() );
+
+ $self->{'db_collectortokens'} = [];
+ foreach my $instance ( 0 .. ($self->{'collectorInstances'} - 1) )
+ {
+ $self->{'db_collectortokens'}->[$instance] =
+ new Torrus::DB( 'collector_tokens' . '_' .
+ $instance . '_' . $self->{'ds_config_instance'},
+ -Subdir => $self->treeName(),
+ -WriteAccess => 1,
+ -Truncate => 1 );
+ }
+
+ # delay writing of frequently changed values
+ $self->{'db_dsconfig'}->delay();
+ $self->{'db_otherconfig'}->delay();
+ return $self;
+}
+
+
+sub newToken
+{
+ my $self = shift;
+ my $token = $self->{'next_free_token'};
+ $token = 1 unless defined( $token );
+ $self->{'next_free_token'} = $token + 1;
+ return sprintf('T%.4d', $token);
+}
+
+
+sub setParam
+{
+ my $self = shift;
+ my $name = shift;
+ my $param = shift;
+ my $value = shift;
+
+ if( $self->getParamProperty( $param, 'remspace' ) )
+ {
+ $value =~ s/\s+//go;
+ }
+
+ $self->{'paramcache'}{$name}{$param} = $value;
+ $self->{'db_otherconfig'}->put( 'P:'.$name.':'.$param, $value );
+ $self->{'db_otherconfig'}->addToList('Pl:'.$name, $param);
+}
+
+sub setNodeParam
+{
+ my $self = shift;
+ my $name = shift;
+ my $param = shift;
+ my $value = shift;
+
+ if( $self->getParamProperty( $param, 'remspace' ) )
+ {
+ $value =~ s/\s+//go;
+ }
+
+ $self->{'paramcache'}{$name}{$param} = $value;
+ $self->{'db_dsconfig'}->put( 'P:'.$name.':'.$param, $value );
+ $self->{'db_dsconfig'}->addToList('Pl:'.$name, $param);
+}
+
+
+sub setParamProperty
+{
+ my $self = shift;
+ my $param = shift;
+ my $prop = shift;
+ my $value = shift;
+
+ $self->{'paramprop'}{$prop}{$param} = $value;
+ $self->{'db_paramprops'}->put( $param . ':' . $prop, $value );
+}
+
+
+sub initRoot
+{
+ my $self = shift;
+ if( not defined( $self->token('/') ) )
+ {
+ my $token = $self->newToken();
+ $self->{'db_dsconfig'}->put( 'pt:/', $token );
+ $self->{'db_dsconfig'}->put( 'tp:'.$token, '/' );
+ $self->{'db_dsconfig'}->put( 'n:'.$token, 0 );
+ $self->{'nodetype_cache'}{$token} = 0;
+ }
+}
+
+sub addChild
+{
+ my $self = shift;
+ my $token = shift;
+ my $childname = shift;
+ my $isAlias = shift;
+
+ if( not $self->isSubtree( $token ) )
+ {
+ Error('Cannot add a child to a non-subtree node: ' .
+ $self->path($token));
+ return undef;
+ }
+
+ my $path = $self->path($token) . $childname;
+
+ # If the child already exists, do nothing
+
+ my $ctoken = $self->token($path);
+ if( not defined($ctoken) )
+ {
+ $ctoken = $self->newToken();
+
+ $self->{'db_dsconfig'}->put( 'pt:'.$path, $ctoken );
+ $self->{'db_dsconfig'}->put( 'tp:'.$ctoken, $path );
+
+ $self->{'db_dsconfig'}->addToList( 'c:'.$token, $ctoken );
+ $self->{'db_dsconfig'}->put( 'p:'.$ctoken, $token );
+ $self->{'parentcache'}{$ctoken} = $token;
+
+ my $nodeType;
+ if( $isAlias )
+ {
+ $nodeType = 2; # alias
+ }
+ elsif( $childname =~ /\/$/o )
+ {
+ $nodeType = 0; # subtree
+ }
+ else
+ {
+ $nodeType = 1; # leaf
+ }
+ $self->{'db_dsconfig'}->put( 'n:'.$ctoken, $nodeType );
+ $self->{'nodetype_cache'}{$ctoken} = $nodeType;
+ }
+ return $ctoken;
+}
+
+sub setAlias
+{
+ my $self = shift;
+ my $token = shift;
+ my $apath = shift;
+
+ my $ok = 1;
+
+ my $iamLeaf = $self->isLeaf($token);
+
+ # TODO: Add more verification here
+ if( not defined($apath) or $apath !~ /^\//o or
+ ( not $iamLeaf and $apath !~ /\/$/o ) or
+ ( $iamLeaf and $apath =~ /\/$/o ) )
+ {
+ my $path = $self->path($token);
+ Error("Incorrect alias at $path: $apath"); $ok = 0;
+ }
+ elsif( $self->token( $apath ) )
+ {
+ my $path = $self->path($token);
+ Error("Alias already exists: $apath at $path"); $ok = 0;
+ }
+ else
+ {
+ # Go through the alias and create subtrees if neccessary
+
+ my @pathelements = $self->splitPath($apath);
+ my $aliasChildName = pop @pathelements;
+
+ my $nodepath = '';
+ my $parent_token = $self->token('/');
+
+ foreach my $nodename ( @pathelements )
+ {
+ $nodepath .= $nodename;
+ my $child_token = $self->token( $nodepath );
+ if( not defined( $child_token ) )
+ {
+ $child_token = $self->addChild( $parent_token, $nodename );
+ if( not defined( $child_token ) )
+ {
+ return 0;
+ }
+ }
+ $parent_token = $child_token;
+ }
+
+ my $alias_token = $self->addChild( $parent_token, $aliasChildName, 1 );
+ if( not defined( $alias_token ) )
+ {
+ return 0;
+ }
+
+ $self->{'db_dsconfig'}->put( 'a:'.$alias_token, $token );
+ $self->{'db_dsconfig'}->addToList( 'ar:'.$token, $alias_token );
+ $self->{'db_aliases'}->put( $apath, $token );
+ }
+ return $ok;
+}
+
+sub addView
+{
+ my $self = shift;
+ my $vname = shift;
+ my $parent = shift;
+ $self->{'db_otherconfig'}->addToList('V:', $vname);
+ if( defined( $parent ) )
+ {
+ $self->{'viewparent'}{$vname} = $parent;
+ }
+}
+
+
+sub addMonitor
+{
+ my $self = shift;
+ my $mname = shift;
+ $self->{'db_otherconfig'}->addToList('M:', $mname);
+}
+
+
+sub addAction
+{
+ my $self = shift;
+ my $aname = shift;
+ $self->{'db_otherconfig'}->addToList('A:', $aname);
+}
+
+
+sub addDefinition
+{
+ my $self = shift;
+ my $name = shift;
+ my $value = shift;
+ $self->{'db_dsconfig'}->put( 'd:'.$name, $value );
+ $self->{'db_dsconfig'}->addToList('D:', $name);
+}
+
+
+sub setVar
+{
+ my $self = shift;
+ my $token = shift;
+ my $name = shift;
+ my $value = shift;
+
+ $self->{'setvar'}{$token}{$name} = $value;
+}
+
+
+sub isTrueVar
+{
+ my $self = shift;
+ my $token = shift;
+ my $name = shift;
+
+ my $ret = 0;
+
+ while( defined( $token ) and
+ not defined( $self->{'setvar'}{$token}{$name} ) )
+ {
+ $token = $self->getParent( $token );
+ }
+
+ if( defined( $token ) )
+ {
+ my $value = $self->{'setvar'}{$token}{$name};
+ if( defined( $value ) )
+ {
+ if( $value eq 'true' or
+ $value =~ /^\d+$/o and $value )
+ {
+ $ret = 1;
+ }
+ }
+ }
+
+ return $ret;
+}
+
+sub finalize
+{
+ my $self = shift;
+ my $status = shift;
+
+ if( $status )
+ {
+ # write delayed data
+ $self->{'db_dsconfig'}->commit();
+ $self->{'db_otherconfig'}->commit();
+
+ Verbose('Configuration has compiled successfully. Switching over to ' .
+ 'DS config instance ' . $self->{'ds_config_instance'} .
+ ' and Other config instance ' .
+ $self->{'other_config_instance'} );
+
+ $self->setReady(1);
+ if( not $self->{'-NoDSRebuild'} )
+ {
+ $self->{'db_config_instances'}->
+ put( 'ds:' . $self->treeName(),
+ $self->{'ds_config_instance'} );
+ }
+
+ $self->{'db_config_instances'}->
+ put( 'other:' . $self->treeName(),
+ $self->{'other_config_instance'} );
+
+ Torrus::TimeStamp::init();
+ Torrus::TimeStamp::setNow($self->treeName() . ':configuration');
+ Torrus::TimeStamp::release();
+ }
+}
+
+
+sub postProcess
+{
+ my $self = shift;
+
+ my $ok = $self->postProcessNodes();
+
+ # Propagate view inherited parameters
+ $self->{'viewParamsProcessed'} = {};
+ foreach my $vname ( $self->getViewNames() )
+ {
+ &Torrus::DB::checkInterrupted();
+
+ $self->propagateViewParams( $vname );
+ }
+ return $ok;
+}
+
+
+
+sub postProcessNodes
+{
+ my $self = shift;
+ my $token = shift;
+
+ &Torrus::DB::checkInterrupted();
+
+ my $ok = 1;
+
+ if( not defined( $token ) )
+ {
+ $token = $self->token('/');
+ }
+
+ my $nodeid = $self->getNodeParam( $token, 'nodeid', 1 );
+ if( defined( $nodeid ) )
+ {
+ # verify the uniqueness of nodeid
+
+ my $oldToken = $self->{'db_nodeid'}->get($nodeid);
+ if( defined($oldToken) )
+ {
+ Error('Non-unique nodeid ' . $nodeid .
+ ' in ' . $self->path($token) .
+ ' and ' . $self->path($oldToken));
+ $ok = 0;
+ }
+ else
+ {
+ $self->{'db_nodeid'}->put($nodeid, $token);
+ }
+ }
+
+
+ if( $self->isLeaf($token) )
+ {
+ # Process static tokenset members
+
+ my $tsets = $self->getNodeParam( $token, 'tokenset-member' );
+ if( defined( $tsets ) )
+ {
+ foreach my $tset ( split(/,/o, $tsets) )
+ {
+ my $tsetName = 'S'.$tset;
+ if( not $self->tsetExists( $tsetName ) )
+ {
+ my $path = $self->path( $token );
+ Error("Referenced undefined token set $tset in $path");
+ $ok = 0;
+ }
+ else
+ {
+ $self->tsetAddMember( $tsetName, $token, 'static' );
+ }
+ }
+ }
+
+ my $dsType = $self->getNodeParam( $token, 'ds-type' );
+ if( defined( $dsType ) )
+ {
+ if( $dsType eq 'rrd-multigraph' )
+ {
+ # Expand parameter substitutions in multigraph leaves
+
+ my @dsNames =
+ split(/,/o, $self->getNodeParam($token, 'ds-names') );
+
+ foreach my $dname ( @dsNames )
+ {
+ foreach my $param ( 'ds-expr-', 'graph-legend-' )
+ {
+ my $dsParam = $param . $dname;
+ my $value = $self->getNodeParam( $token, $dsParam );
+ if( defined( $value ) )
+ {
+ my $newValue = $value;
+ if( $multigraph_remove_space{$param} )
+ {
+ $newValue =~ s/\s+//go;
+ }
+ $newValue =
+ $self->expandSubstitutions( $token, $dsParam,
+ $newValue );
+ if( $newValue ne $value )
+ {
+ $self->setNodeParam( $token, $dsParam,
+ $newValue );
+ }
+ }
+ }
+ }
+ }
+ elsif( $dsType eq 'collector' and $self->{'mayRunCollector'} )
+ {
+ # Split the collecting job between collector instances
+ my $instance = 0;
+ my $nInstances = $self->{'collectorInstances'};
+
+ my $oldOffset =
+ $self->getNodeParam($token, 'collector-timeoffset');
+ my $newOffset = $oldOffset;
+
+ my $period =
+ $self->getNodeParam($token, 'collector-period');
+
+ if( $nInstances > 1 )
+ {
+ my $hashString =
+ $self->getNodeParam($token,
+ 'collector-instance-hashstring');
+ if( not defined( $hashString ) )
+ {
+ Error('collector-instance-hashstring is not defined ' .
+ 'in ' . $self->path( $token ));
+ $hashString = '';
+ }
+
+ $instance =
+ unpack( 'N', md5( $hashString ) ) % $nInstances;
+ }
+
+ $self->setNodeParam( $token,
+ 'collector-instance',
+ $instance );
+
+ my $dispersed =
+ $self->getNodeParam($token,
+ 'collector-dispersed-timeoffset');
+ if( defined( $dispersed ) and $dispersed eq 'yes' )
+ {
+ # Process dispersed collector offsets
+
+ my %p;
+ foreach my $param ( 'collector-timeoffset-min',
+ 'collector-timeoffset-max',
+ 'collector-timeoffset-step',
+ 'collector-timeoffset-hashstring' )
+ {
+ my $val = $self->getNodeParam( $token, $param );
+ if( not defined( $val ) )
+ {
+ Error('Mandatory parameter ' . $param . ' is not '.
+ ' defined in ' . $self->path( $token ));
+ $ok = 0;
+ }
+ else
+ {
+ $p{$param} = $val;
+ }
+ }
+
+ if( $ok )
+ {
+ my $min = $p{'collector-timeoffset-min'};
+ my $max = $p{'collector-timeoffset-max'};
+ if( $max < $min )
+ {
+ Error('collector-timeoffset-max is less than ' .
+ 'collector-timeoffset-min in ' .
+ $self->path( $token ));
+ $ok = 0;
+ }
+ else
+ {
+ my $step = $p{'collector-timeoffset-step'};
+ my $hashString =
+ $p{'collector-timeoffset-hashstring'};
+
+ my $bucketSize = int( ($max - $min) / $step );
+ $newOffset =
+ $min
+ +
+ $step * ( unpack( 'N', md5( $hashString ) ) %
+ $bucketSize )
+ +
+ $instance * int( $step / $nInstances );
+ }
+ }
+ }
+ else
+ {
+ $newOffset += $instance * int( $period / $nInstances );
+ }
+
+ $newOffset %= $period;
+
+ if( $newOffset != $oldOffset )
+ {
+ $self->setNodeParam( $token,
+ 'collector-timeoffset',
+ $newOffset );
+ }
+
+ $self->{'db_collectortokens'}->[$instance]->put
+ ( $token, sprintf('%d:%d', $period, $newOffset) );
+
+ my $storagetypes =
+ $self->getNodeParam( $token, 'storage-type' );
+ foreach my $stype ( split(/,/o, $storagetypes) )
+ {
+ if( $stype eq 'ext' )
+ {
+ if( not defined( $srvIdParams ) )
+ {
+ $srvIdParams =
+ new Torrus::ServiceID( -WriteAccess => 1 );
+ }
+
+ my $srvTrees =
+ $self->getNodeParam($token, 'ext-service-trees');
+
+ if( not defined( $srvTrees ) or
+ length( $srvTrees ) == 0 )
+ {
+ $srvTrees = $self->treeName();
+ }
+
+ my $serviceid =
+ $self->getNodeParam($token, 'ext-service-id');
+
+ foreach my $srvTree (split(/\s*,\s*/o, $srvTrees))
+ {
+ if( not Torrus::SiteConfig::treeExists($srvTree) )
+ {
+ Error
+ ('Error processing ext-service-trees' .
+ 'for ' . $self->path( $token ) .
+ ': tree ' . $srvTree .
+ ' does not exist');
+ $ok = 0;
+ }
+ else
+ {
+ if( not $srvIdInitialized{$srvTree} )
+ {
+ $srvIdParams->cleanAllForTree
+ ( $srvTree );
+ $srvIdInitialized{$srvTree} = 1;
+ }
+ else
+ {
+ if( $srvIdParams->idExists( $serviceid,
+ $srvTree ) )
+ {
+ Error('Duplicate ServiceID: ' .
+ $serviceid . ' in tree ' .
+ $srvTree);
+ $ok = 0;
+ }
+ }
+ }
+ }
+
+ if( $ok )
+ {
+ # sorry for ackward Emacs auto-indent
+ my $params = {
+ 'trees' => $srvTrees,
+ 'token' => $token,
+ 'dstype' =>
+ $self->getNodeParam($token,
+ 'ext-dstype'),
+ 'units' =>
+ $self->getNodeParam
+ ($token, 'ext-service-units')
+ };
+
+ $srvIdParams->add( $serviceid, $params );
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ my $path = $self->path( $token );
+ Error("Mandatory parameter 'ds-type' is not defined for $path");
+ $ok = 0;
+ }
+ }
+ else
+ {
+ foreach my $ctoken ( $self->getChildren( $token ) )
+ {
+ if( not $self->isAlias( $ctoken ) )
+ {
+ $ok = $self->postProcessNodes( $ctoken ) ? $ok:0;
+ }
+ }
+ }
+ return $ok;
+}
+
+
+sub propagateViewParams
+{
+ my $self = shift;
+ my $vname = shift;
+
+ # Avoid processing the same view twice
+ if( $self->{'viewParamsProcessed'}{$vname} )
+ {
+ return;
+ }
+
+ # First we do the same for parent
+ my $parent = $self->{'viewparent'}{$vname};
+ if( defined( $parent ) )
+ {
+ $self->propagateViewParams( $parent );
+
+ my $parentParams = $self->getParams( $parent );
+ foreach my $param ( keys %{$parentParams} )
+ {
+ if( not defined( $self->getParam( $vname, $param ) ) )
+ {
+ $self->setParam( $vname, $param, $parentParams->{$param} );
+ }
+ }
+ }
+
+ # mark this view as processed
+ $self->{'viewParamsProcessed'}{$vname} = 1;
+}
+
+
+sub validate
+{
+ my $self = shift;
+
+ my $ok = 1;
+
+ $self->{'is_writing'} = undef;
+
+ if( not $self->{'-NoDSRebuild'} )
+ {
+ $ok = Torrus::ConfigTree::Validator::validateNodes($self);
+ }
+ $ok = Torrus::ConfigTree::Validator::validateViews($self) ? $ok:0;
+ $ok = Torrus::ConfigTree::Validator::validateMonitors($self) ? $ok:0;
+ $ok = Torrus::ConfigTree::Validator::validateTokensets($self) ? $ok:0;
+
+ return $ok;
+}
+
+
+1;
+
+# Local Variables:
+# mode: perl
+# indent-tabs-mode: nil
+# perl-indent-level: 4
+# End: