#!/usr/bin/perl -w
# nwsip - creates an MDS compliant LDIF out of a NWS subsystem 
# Copyright (C) 2001 by Augusto Ciuffoletti

#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
#
#The author can be contacted by e.mail: augusto@di.unipi.it
######################################
#
#   P A C K A G E    I N T E R V A L
#
######################################
# Measures the delay between the call of the "begin" and "end" method.
# Measures are collected in a array of strings, that is flushed to
# the indicated logfile using the "flush" method
package interval;
use strict;
require 'sys/syscall.ph';

my $x=1234;

sub new {
    shift;
    my ( $logfile ) = @_;
    if ( defined $logfile ) {
	my $state = {};
	$$state{"flag"}=0;
	$$state{"origin"}=0;
	$$state{"title"}="";
	$$state{"report"}=[];
	$$state{"logfile"} = $logfile;
	return bless $state;
    }
    return undef;
}

sub begin {
    my $self = shift;
    my $timeval = pack("LL", ());;

    syscall(&SYS_gettimeofday, $timeval, 0) != -1 
        || die "gettimeofday: $!";
    my @time = unpack("LL",$timeval);
    $$self{"flag"} = 0;
    $$self{"origin"} = $time[0] + ( $time[1] /1000000 );
    $$self{"title"} = $_[0];
    return bless $self;
}

sub end {
    my $self = shift;
    my $timeval = pack("LL", ());

    if ($$self{"flag"} == 0) {
	syscall(&SYS_gettimeofday, $timeval, 0) != -1 
	    || die "gettimeofday: $!";
	my @time = unpack("LL",$timeval);
	$$self{"flag"} = 1;
	my $result = sprintf("%s %f",
			     $$self{"title"},
			     $time[0] + ( $time[1] /1000000 ) - 
			     $$self{"origin"}
			     );
	push @{$$self{"report"}}, $result;
    }
    return;
}

sub flush {
    my $self = shift;
    open LOG, ">> $$self{logfile}" ||
	die "Can't write to logfile\n";
    for ( @{$$self{"report"}} ) {
	print LOG "$_\n";
    }
    $$self{"report"}=[];
}

##################################
#
#     P A C K A G E    S T A N Z A
#
##################################
# Processing of configuration file
package stanza;
use strict;

sub new {
    shift;
    my ( $filename, $name ) = @_;
    my $mapping = {};
    open LINE, $filename
	|| die "Can't open $filename for input: $!";
    my $flag=0;
    while ( <LINE> ) {
      SWITCH: {
	  if ( /^#/ || /^\s*$/) { 
	       last SWITCH; 
	   }
	  if ( /^:$name/ ) {
	      $flag=1; 
	      last SWITCH; 
	  } 
	  if ( ! ( /^:$name/ ) && /^:/  ) { 
	      $flag=0; 
	      last SWITCH; 
	  }
	  if ( $flag ) { 
	      my ($in, $out)=m/(\S+)\s+(.+)/;
	      $in =~ s/([A-Z])/\l$1/g;
	      $$mapping{$in}=$out;
#test	      print "$in => $$mapping{$in}\n";;
	      last SWITCH;
	  }
      }
    }
    close LINE;
    return bless $mapping;
}

#############
# Given an input and a mapping, represented as a hash, returns the
# string associated to the input in the hash. If the string is "nil"
# returns undefined, and dies if the hash is undefined for that input.
sub map {
    my ( $self, $input ) = @_;;
    my %mapping=%$self;

    $input =~ s/([A-Z])/\l$1/g ;
    exists $mapping{$input} ||
	die "Unknown input name $input: abort\n";
    if ( ! ( $mapping{$input} =~ /nil/ ) ) {
	    return $mapping{$input};
	}
    return;
}

##################################
#
#     P A C K A G E    N W S I P
#
##################################

package nwsip;
use strict;

my $config;
if ( defined $ARGV[0] ) {
    $config = $ARGV[0];
} else {
    $config="/var/local/nwsip.conf";
}

# loading configuration (global hash) 
my $cfg="stanza"->new($config,"configuration");
my $nameserver=$cfg->map("nameserver");
my $root=$cfg->map("root"); 
# mapping from sensor to Site name(global hash) 
my $ou="stanza"->new($config,"sensors");
# mapping from nws resource to standard MetricName value(global hash) 
my $class="stanza"->new($config,"classes");
# mapping from nws option to standard NMMeasureId component (global hash) 
my $option="stanza"->new($config,"options");
# mapping from nws metric units to standard units (global hash) 
my $units="stanza"->new($config,"units");
# nws field to MDS attribute mapping (global hash) 
my $field="stanza"->new($config,"fields");
# Internal representation of an entry is with a hash
# The directory of nws binaries (configurable global)
my $NWSBINDIR="/usr/local/nws/bin";
# Leave undefined to avoid NWS query delays recording (configurable global)
my $LOGFILE="nwstiming.log";
# A timer 
my $timer = "interval"->new($LOGFILE);

#############
# Deletes the port number from a NWS host name

sub hostname {
    my @x = split /:/,$_[0];
    return $x[0];
}

#############
# Put on the standard output the LDIF for a NetworkMonitorElement
# objectclass entry

sub printNetworkMonitorElement {
    my $r = $_[0];
    print "dn: $root\n";
    print "objectclass: NetworkMonitorElement\n";
    print "nmId: \t$nameserver/nws\n";
    print "LocalHost: \t$nameserver\n";
    print "MonitorToolIsLocal: \tfalse\n";
    print "MonitorTool: \tNetwork Weather Service (NWS)\n";
    print "\n";
}

#############
# Put on the standard output the LDIF for a NetworkMonitorSourceHost
# objectclass entry

sub printNetworkMonitorSourceHost {
    my ( $r, $h, $s ) = @_;
    print "dn: $r\n";
    print "objectclass: \tNetworkMonitorSourceHost\n";
    print "SourceHost: \t".hostname($h)."\n";
    ( defined $s) && print "SourceSite: \t$s\n";
    print "\n";
}

#############
# Put on the standard output the LDIF for a NetworkMonitorDestHost
# objectclass entry

sub printNetworkMonitorDestHost {
    my ( $r, $h, $s ) = @_;
    print "dn: $r\n";
    print "objectclass: \tNetworkMonitorDestHost\n";
    print "DestHost: \t".hostname($h)."\n";
    ( defined $s) && print "DestSite: \t$s\n";
    print "\n";
}

#############
# Reads a nws entry from a nws_search command from a handle
# passed as a parameter and returns an hash (attribute => value)
# Assumes that entries are separated by a single blank line
sub get_entry {
    my $file=shift;
    my %entry=();

    while ( my $attribute = <$file> ){
	chomp $attribute;
	if ( $attribute eq "" ) {
	    last;
	}
	my $name=substr($attribute,0,(index($attribute,':')));
	$name =~ s/\s+$//;
	$entry{$name}=substr($attribute,(index($attribute,':'))+1);
    }
    return %entry;
}

#############
# Given the name of a resource, returns a hash containing the
# attributes coresponding to a measure.

sub format_measures {
    my %month = ( 
		  "Jan", "01", 
		  "Feb", "02",
		  "Mar", "03",
		  "Apr", "04",
		  "May", "05",
		  "Jun", "06",
		  "Jul", "07",
		  "Aug", "08",
		  "Sep", "09",
		  "Oct", "10",
		  "Nov", "11",
		  "Dec", "12"
		  );
    my ( $resource, $host ) = @_;
    my $cmd="$NWSBINDIR/nws_extract -n1 -N$nameserver $resource $host";
    ( defined $timer ) && $timer->begin($cmd);
    open QUERY,"$cmd |"
	|| die "can't fork: $!";
    my @fields = split /\s+/, <QUERY>;
    my @vals = split /\s+/, <QUERY>;
    my $i=0;    
    my %measures=();
    foreach (@fields) {
	( defined $timer ) && $timer->end;
# The "Time" is converted into a readable format 
	if ( $_ eq "Time" ) {
	    open TIME,"$NWSBINDIR/whattime $vals[$i++]|";
	    my ($dontcare,$time) = split /==/,<TIME>;
	    chomp $time;
	    $time =~ s/^\s+//;
# ... and again to an unreadable one 
	    ( $time =~ /(...) (...) (..) (..):(..):(..) (....)/ ) ||
		die "Can't parse date $time\n";
	    $measures{$_}=$7.$month{$2}.$3.$4.$5.$6."Z";
	    next;
	}
# Other columns are left unchanged
	$measures{$_}=$vals[$i++]; 
    }
    close QUERY;
    return %measures;
}

#############
# Takes a hash describing an NWS series, and a parameter indicating 
# either the local host, or a sensor and a peer, and print as an 
# LDIF entry (except "dn" attribute) the data about the series and
# the last measures.

sub process_NWS_series {
    my ( $refseries, $parameter, $root, $header ) = @_;
    my %series=%$refseries;
    my $measureId="";

# Compute the MDS resource name (to use also as prefix)	
    defined ( my $resource_prefix = 
	         $class->map($series{resource}) ) ||
        return;
    $measureId = "metric:$resource_prefix";
    print "dn: NMMeasureId=$measureId, $root\n";
    print $header;
    printf "NMMeasureId: \t$measureId\n";
    printf "MetricUnit: \t%s\n",$units->map($series{label});

# Output measures for local resources
    my %m=format_measures($series{"resource"}, $parameter);
    foreach my $name ( sort { $b cmp $a } keys %m ) {
	if ( defined ( my $objectclass = $field->map($name) ) ) {
	    printf "$objectclass: \t$m{$name}\n",$resource_prefix;
	}
    }
    print "MonitorTool: \tNetwork Weather Service (NWS)\n";
    print "\n";
}

#############
# Queries the nameserver for all series related to a sensor. Those series
# related to local resources are examined and translated into LDIF entries.
# (indeed these entries are skipped in the datagrid/wp7 case, but this is
# controlled through the configuration file). Those that are related to
# network resources contribute to a hash containing peer hosts. The
# subroutine returns a pointer to this hash.

sub scanSeries {
    my ( $root, $sensor ) = @_;
    my %peers=();
    my $cmd="$NWSBINDIR/nws_search -N$nameserver \"&((objectclass=nwsSeries)(host=$sensor))\" ";

    ( defined $timer ) && $timer->begin($cmd);

    open READ_SERIES, "$cmd |"
	|| die "can't fork: $!";
# For each series...
    while ( my %series = get_entry \*READ_SERIES ) {
	( defined $timer ) && $timer->end;
# If the "target" option is present, then treat each (sensor,member)
# pair. A root entry is created that indicates the type of resource,
# and each leaf contains a measure for a peer.
	if ( exists $series{target} ) {
	    if ( ! exists $peers{$series{target}} ) {
		$peers{$series{target}} = "true";
	    }
	} else {
# Otherwise treat as a local resource
	    process_NWS_series 
		\%series,
		$sensor, 
		"name=$series{name}, $root";
	}
    }
    close READ_SERIES;

# Delete this sensor from peers (just in case...)
    delete $peers{$sensor};

    return \%peers;
}

#############
# Queries the nameserver for all network monitoring series related to
# a sensor-peer pair.

sub scanNMSeries {
    my ( $root, $sensor, $site, $peer, $peersite ) = @_;
	
# Reads all series of this sensor related to that peer
    my $cmd="$NWSBINDIR/nws_search -N$nameserver \"&((objectclass=nwsSeries)(target=*$peer*))\"";
    ( defined $timer ) && $timer->begin($cmd);
    open READ_SERIES, "$cmd |"
	|| die "can't fork: $!";
    while ( my %series = get_entry \*READ_SERIES ) {
	( defined $timer ) && $timer->end;
	my $nextheader="";
	$nextheader=$nextheader."SourceHost: \t".hostname($sensor)."\n";
	if ( defined $site ) {
	    $nextheader=
		$nextheader."SourceSite: \t".$ou->map($sensor)."\n";
	}
	$nextheader=$nextheader."DestHost: \t".hostname($peer)."\n";
	if ( defined $peersite ) {
	    $nextheader=
		$nextheader."DestSite: \t".$peersite."\n";
	}
	$nextheader="objectclass: \tNetworkMonitorMeasurement\n".$nextheader;
	process_NWS_series 
	    \%series, 
	    "$sensor $peer", 
	    "dest=$peer, $root",
	    "$nextheader";
    }
    close READ_SERIES;
}


#############
# Scan all series registered for a given sensor (1st argument). The 
# most recent  measure of each monitored resource is queried, and 
# a LDIF registration is generated  

sub scanSensor {
    my ( $root, $sensor ) = @_;
    my %peers=();

    my $site=eval '$ou->map($sensor)';
    printNetworkMonitorSourceHost($root,$sensor,$site);
    my $peers=scanSeries ($root, $sensor);
    foreach my $peer ( keys %$peers ) {
	my $peersite=eval '$ou->map($peer)';
	printNetworkMonitorDestHost("dest=$peer, $root", $peer, $peersite);
	scanNMSeries($root, $sensor, $site, $peer, $peersite);
    }   
}

###################
# MAIN PROGRAM
###################

$|=1;

# The commandline
my $cmd="";

# Root nws entry
$root="nmId=$nameserver/nws, $root";

printNetworkMonitorElement $root;

# Scans all sensor registrations. Each registration is converted into
# an LDIF entry, all series registered for this sensor are scanned

$cmd="$NWSBINDIR/nws_search -N$nameserver \"&((objectclass=nwsHost)(hostType=sensor))\" ";
( defined $timer ) && $timer->begin($cmd);
open READ_SENSORS, "$cmd |"
   || die "can't fork: $!";
while ( my %sensor = get_entry \*READ_SENSORS ) {
    ( defined $timer ) && $timer->end;
    scanSensor "source=$sensor{name}, $root", $sensor{name};
}
close READ_SENSORS;

( defined $timer ) && $timer->flush();












