package PDM;
#<copyright>
# ----------------------------------------------------------
# Sun Proprietary/Confidential Code
# Copyright 2001, Sun Microsystems, Inc. All rights reserved.
# ----------------------------------------------------------
#</copyright>

#  $Name:  $ 
#  $Id: PDM.pm,v 1.39 2002/10/29 01:44:36 ccadieux Exp $

use Data::Dumper;
use strict;
use Thresholds;
use Carp;
use Net::Ping;
use Report;
use Debug;
use Util::Http;
use PDM::ConfigFile;
use System;
use NWS::Schema;
use Map;
use State;
use Catalog;

#
#  Interface:
#   new
#   serialize : called by the displatcher.
#   saveReport:
#   getDocSequence:
#   getLastReport:
#   saveReport:
#   saveMessage:
#   getCacheHandle;
#   inCache :  boolean
#      
use vars qw($Error %V @Reports_CB $CACHE @EVENT_DOCS $MAP %GLOBAL);

$Error      = 0;
%V          = ();
@Reports_CB = ();
@EVENT_DOCS = ();

sub error {
  return $Error;
}


#  my($pdm) = PDM->new({  dir => "$HOME/DATA",
#                      renv => $renv ,
#                   devices => $devices,
#                     hosts => $hosts,
#                    notifs => $notifs, http => 1});

sub new {
  my($class, $arg) = @_;
  my( $N, %T);
  my($pdm) = $arg;
  my($renv) = System->get_renv;

  my $D = System->get_home() . "/DATA";

  $pdm->{dir} = System->get_home() . "/DATA" if (!$pdm->{dir});
  Report->clearNew();

  if ($arg->{http}) {
    $N = "$D/CACHE";
    $CACHE = &_read($N);
 
  } else {
#    $N = "$D/OLD_REPORTS";
#    $OLD_REPORTS = &_read($N, ":");

    $N = "$D/CACHE";
    $CACHE = &_read($N);
    State->init(); 
    NWS::Schema->init();
  }

    my $master = Util->findMaster();  # erase master catalog if config was pushed recently.
    if ($master) {
      my $config_data_age  = (stat(System->get_home() . "/DATA/rasagent.conf"))[9];

      if ($config_data_age > Catalog->catalogAge($master) ) {
         Debug->print2("Catalog is old: erasing");
         Catalog->clearCatalog($master);
      }
      TO->resetIncomplete();
    }

    my($devs) = System->get_configDevices();
    foreach my $d (@$devs) {
       next if (!Util->isMineToMonitor($d));
       $T{lc($d->{type})}{$d->{key}} = 1;
    }
    $D = System->get_home() . "/DATA/OLD_REPORTS";
    opendir(O1, $D) ;
    my(@a) = readdir(O1); closedir(O1);
    my($VAR1);
    foreach my $f (@a) {
       next if ($f !~ /.+:.+/);
       open(O1, "$D/$f");
       my(@b) = <O1>; close(O1);
       eval "@b";
       my($name) = $VAR1->{_id}{deviceName};
       my($type) = lc($VAR1->{_id}{category});
       if (!$renv->{keep_old_reports}) {
         if ( (index(",se,message,san,topo,host,backup,", ",$type,") < 0) &&
               $name && $type && !$T{$type}{$name}) {
           unlink("$D/$f");
           State->clearComponent($f);
           Debug->print2("Report $f not in config file, removed.");
         }
       }
    }

  bless($pdm, "PDM");
  return $pdm;
}


sub serialize {
  my($pdm) = @_;

# Each reports has it's own file in the OLD_REPORTS dir.
  Report->new2old();

  Thresholds->serialize();
  $DB::single = 1;
  $pdm->_write($CACHE, "CACHE");
  Catalog->write();
  State->write();
}

sub _write {
  my($pdm, $x, $dir) = @_;
  my($k);
  my($f) = System->get_home() . "/DATA/$dir";
  mkdir $f, 0777 if (!-d $f);
  $Data::Dumper::Indent = 1;
  foreach $k (keys %$x) {
     open(O, ">$f/$k");
     print O Dumper($x->{$k});
     close(O);
  }
}


sub _read {
  my($dir, $pat) = @_;
  my(%O) = ();
  my($file, $v, $l1);
  my($f) = $dir;
  opendir(O, $f);
  my(@f) = readdir(O); closedir(O);

  foreach $file (@f) {
    next if (substr($file, 0,1) eq ".");
    next if ($pat && $file !~ /$pat/);
    open(O, "$f/$file");
    my($VAR1) = undef;
    my($l) = "";
    while ($l1 = <O>) { $l .= $l1}
    close(O);
    eval "$l";
    $O{$file} = $VAR1;
  }
  return \%O; 
    
}


sub readCache {
  my($class, $name) = @_;
  my($VAR1);
  my($file) = System->get_home() . "/DATA/CACHE/$name";
  if (open(O, $file)) {
    my(@L) = <O>; close(O);
    eval "@L";
    return $VAR1;
  } else {
    return undef;
  }
}

sub writeCache {
  my($class, $cache, $file) = @_;
  open(O, ">" . System->get_home() . "/DATA/CACHE/$file");
  print O Dumper($cache);
  close(O);
}

sub writeCacheObject {
  my($class, $file) = @_;
  open(O, ">" . System->get_home() . "/DATA/CACHE/$file");
  print O Dumper($CACHE->{$file});
  close(O);
}

use vars qw(%Stale);

sub setStaleCatalog {
  my($class ,$host) = @_;

  $Stale{$host} = 1;
}

sub getStaleCatalog {
  my($class) = @_;
  my(@a);
  foreach my $k (keys %Stale) {
    push(@a, $k);
  }
  return @a;
}

# change the key of a  component in the configfile

sub fixConfig {
  my($class, $type, $old, $new, $extra) = @_;
  my $rc;
  my $master  = Util->findMaster();
  $rc = &get_fixConfig({ type => $type, old => $old, new => $new, extra => $extra});
  if ($master) {
     my $url = "PDM::fixConfig&type=$type&old=$old&new=$new&extra=$extra&HTTP=1";
     $rc = Util::Http->getCommand($master, $url);
  }
  return $rc;
}

sub get_fixConfig {
  my($q) = @_;
  my $warning;
  my ($renv, $devs, $hosts, $notifs) = PDM::ConfigFile->read();
  foreach my $dev (@$devs) {
     if ($dev->{type} eq $q->{type} && $dev->{key} eq $q->{old}) {
        if (substr($dev->{class} ,0,6) eq "switch") {
           $dev->{wwn} = $dev->{key} = $q->{new};
           $warning .= "switch $dev->{name}/$dev->{ipno} wwn was changed to $q->{new}\n";
        } elsif (substr($dev->{class} ,0,2) eq "t3") {
           $dev->{key} = $q->{new};
           my($wwn1,$wwn2) = split(/\|/, $q->{extra});
           $dev->{wwn} = $wwn1;
           $dev->{wwn2} = $wwn2;
           $warning .= "t3 $dev->{name}/$dev->{ipno} key was changed to $q->{new}\n";
        }
     }
  }
  if ($warning) {
    open(O, ">>" . System->get_home() . "/DATA/master_warning");
    print O $warning;
    close(O);
    PDM::ConfigFile->write($renv, $devs, $hosts, $notifs);
  }
  my $rc = $warning ? "OK" : "ERR No device found";
  if ($q->{HTTP}) {
     print "$rc\n";
  } else {
     return $rc;
  }
}
  


###########################################################
#  DEVICE STATE
###########################################################
$MAP = undef;

# static method

sub getDeviceStateMap {
  my($pdm, $section) = @_;
  my($l, %MAP);
  return $MAP->section($section) if ($MAP);

  my($F) = System->get_home() . "/System/Map";
  opendir(O, $F);
  my @L = readdir(O); closedir(O);
  foreach my $d (@L) {
     next if ($d !~ /.map$/);
     open(O, "$F/$d");
     my $ix = index($d, ".");
     my $fname = substr($d, 0, $ix);
     my($sec) = "";
     while ($l = <O>) {
        chop($l);
        next if (substr($l,0,1) eq "#");
        if (substr($l,0,1) eq "[") {
           my $v = substr($l,1,-1);
           $ix = index($v, ".");
           $v = substr($v,$ix+1) if ($ix > 0);
           $sec = "$fname.$v";
        } else {
           my(@a) = split(/\s*\=\s*/, $l);
           next if ($#a < 1);
           $MAP{$sec}{$a[0]} = $a[1];
        }
     }
     close(O);
  }
  $MAP = Map->new(\%MAP);
  return $MAP->section($section);
}




###########################################################
#  REPORTS STUFF
###########################################################


sub getOldReport {
  my($pdm, $key) = @_;
  my $VAR1;

  my $D = System->get_home() . "/DATA/OLD_REPORTS/$key";
  if (-f $D) {
    open(O, $D); my @l = <O>; close(O);
    eval "@l";
    return $VAR1;

  } else {
    return undef;
  }

}

#  name, category, ip, key_type/key_value, report_type, report_value => {};
# Save a new report.
# $arg = {skip_health => 1}

sub _saveReport {
  my($pdm, $report) = @_;

# Only update reports if you got a good one.

  if ($report->status eq Report->STATUS_OK) {
      $report->saveNew();

  } else {  # update report
      $report->updateStatus(); # change the status of the last good report.
  }
  return;
}

sub saveReport {
  my($pdm,  $report, $arg) = @_;
  my($i, $hm, $filter, $cb, $rcb);
  $DB::single = 1;

  if ($arg->{skip_health}) {
    $pdm->_saveReport($report);
    return;
  }

# read all callback, check the filter, call if good.
  my %DONE;
  for ($i=0; $i <= $#Reports_CB; $i++) {  
      $rcb    = $Reports_CB[$i];
      $hm     = $rcb->[0];
      my($hm0, $rest) = split(/\=/, $hm);
      $cb     = $rcb->[1];
      $filter = $rcb->[2];
      next if ($DONE{"$hm0.$cb"});
      next if ($filter->{INITIAL} || $filter->{FINAL});
      next if ($filter->{category} && $filter->{category} ne $report->category);
      next if ($filter->{report_type} && $filter->{report_type} ne $report->get_report_type);
      $hm->$cb($report);  # call callback with this report.
      $DONE{"$hm0.$cb"} = 1;
  }
  $pdm->_saveReport($report);

}

sub initialHealth {
  my($pdm) = @_;
  my($i, $rcb, $hm, $cb, $filter);

  for ($i=0; $i <= $#Reports_CB; $i++) {  
      $rcb    = $Reports_CB[$i];
      $hm     = $rcb->[0];
      $cb     = $rcb->[1];
      $filter = $rcb->[2];
      if ($filter->{INITIAL}) {
         $hm->$cb(undef) ;
      }
  }

}

sub finalHealth {
  my($pdm) = @_;
  my($i, $rcb, $hm, $cb, $filter);

  for ($i=0; $i <= $#Reports_CB; $i++) {  
      $rcb    = $Reports_CB[$i];
      $hm     = $rcb->[0];
      $cb     = $rcb->[1];
      $filter = $rcb->[2];
      if ($filter->{FINAL}) {
         $hm->$cb(undef);
      }
  }

}



###########################################################
#  EVENT STUFF
###########################################################
# 
# Executed on the slave, appends to the SLAVE_DATA file all new events found.
#
sub push {
  my($class, $master) = @_;

  my($renv) = System->get_renv();
  my($F) = System->get_home() . "/DATA/Events/" . $renv->{hostname} . ".push";


  my($x, $ed, $push, $l, $newpush);
  open(O, $F);
  while ($l = <O>) {
     $push .= $l;
  }
  close(O);

  for ($x = 0; $x <= $#EVENT_DOCS; $x++) {
     $ed = $EVENT_DOCS[$x];    
     $newpush .= $ed->toC;
  }
  Debug->print2("  State: push Events to master");

  my($err, $ans)  = Util::Http->appendFile($master, "Events/$renv->{hostname}.push",
                          $push . $newpush, 20);

  if ($ans !~ /OK/) {
     my $max = $renv->{max_event_file_size} || 2;
     Debug->err(HTTP_ERROR => "Failed to push Events info: $err");
     if (Util->fileSize($F) < $max * 1000000) {
       open(O, ">$F");
       print O $newpush;
       close(O);
     }

  } else {
     unlink $F;
  }
}

# save locally in case the master is down, 
# send everything when the master is back
#
sub sendMessages {
  my($pdm, $MASTER) = @_;
  my($x, $ed);
  my $renv = System->get_renv();

  my $SD = System->get_home() . "/DATA/SLAVEDATA/$renv->{hostname}";

  if (!open(O3,  ">>$SD")) {
    Debug->err(CANNOT_READ => "SLAVE_DATA $!");
    return;
  }

  for ($x = 0; $x <= $#EVENT_DOCS; $x++) {
     $ed = $EVENT_DOCS[$x];    
     print O3 $ed->toC;
  }
  close(O3);
       
  my $avail =  Util::Http->getCommand($MASTER, "INFO1", 10);

  if ($avail =~ /version/) {
     my($l, $out);
     if (open(O3, $SD)) {
        while ($l = <O3>) {
          $out .= $l;
        }
        if ($out) {
           Util::Http->appendFile($MASTER, "SLAVEDATA/$renv->{hostname}",
                        $out, 30);
        }
        close(O3);
        unlink $SD;
     }
  }
}

sub storeMessages {
  my($pdm) = @_;
  my($x, $ed);

  if (open(O, ">>" . System->get_home() . "/DATA/SLAVE_DATA")) {
    for ($x = 0; $x <= $#EVENT_DOCS; $x++) {
       $ed = $EVENT_DOCS[$x];    
       print O $ed->toC;
    }
    close(O);
  } else {
    Debug->err(CANNOT_READ => "SLAVE_DATA $!");
  }
}

#
# store toString as html file
#
sub docMessage {
  my($class, $edoc) = @_;
  my($etype, $desc);
  my($ev)   = $edoc->instances(0);
  my($cat)  = $edoc->id('category');
  my($renv) = System->get_renv;

  return if (Debug->level() <= 2 && $renv->{hostname} !~ /sun.com/);

  if ($edoc->type() eq 'E') {
    $etype = $ev->propertyByName('EventType')->value;
    $desc = $ev->propertyByName('Caption')->value;
    $etype .= ".$desc" if ($desc);
    my $D;
    if (-d "/net/code/usr6/Events") {
       $D = "/net/code/usr6/Events/toc";
    } else {
       $D = System->get_home() . "/DATA/tos";
    }
    open(O, ">$D/$etype.toc");
    print O $edoc->toC();
    close(O);

  } else {
    $etype = $ev->className();
    $desc = $ev->propertyByName('AlertType')->value;
    $etype .= ".$desc" if ($desc);
  }
}

sub saveMessage {
  my($class, $edoc, $fromSlave) = @_;
  my($xx);
  if (!$edoc->instances(0)->type()) {
     Debug->err(CIM_ERR => "Event has no type");
     return;
  }
  croak("Invalid type for event: " . ref($edoc)) if (ref($edoc) ne "Message");

  $class->docMessage($edoc) ;

  if (!$fromSlave) {
    if ($edoc->type eq "E") {  # log only local events
       my($ev) = $edoc->instances(0);
       my($time) = $ev->value("EventTime");
       my($desc) = $ev->value("Description");
       $desc =~ s/\n/| /g;
       my($data) = $time . "\t" . 
              $ev->value("EventType") . "\t" .
              $ev->value("Caption") . "\t" .  $desc . 
              "\tSev:"    . $ev->value("Severity") . 
              "\tAction:" . $ev->value("Actionable") . 
                "\tEnc:"  . $edoc->id('deviceName') .  
          "\tmgmtLevel:"  . $ev->value('MgmtLevel');

       if (substr($desc,-1) eq ":") {  # get more
         my @d1 = split(/\n/, $ev->value("Data"));
         $data .= "\n" if ($#d1 >= 0);
         for ($xx = 0; $xx <= $#d1 && $xx < 21; $xx++) {
           $data .= "L$d1[$xx]\n";
         }
       }
       $class->writeTruncate(System->get_home() . "/DATA/Events.log", $data);

       my $ev_name = $ev->value("EventType");
       my $ev_desc = $ev->value("Description");
       Debug->print1("==> Saving Message: $ev_name: $ev_desc");

       my $renv = System->get_renv();
       if ($renv->{event_trace_dir} && -d $renv->{event_trace_dir}) {
          my $et = $ev->value("EventType");
          my $out = $edoc->toString();
          my $ts = Util->get_today();
          $ts =~ s/[ \:]//g;
          if (open(OO, ">$renv->{event_trace_dir}/$ts.$et")) {
             print OO $out;
             close(OO);
          }
       }

    } elsif ($edoc->type eq "A") {
       my($ev) = $edoc->instances(0);
       my($time) = $ev->value("EventTime");
       my($sev) = $ev->value("Severity");
       my($data) = $time . "\t" . 
              $ev->value("AlertType") . "\t\t" .
              $ev->value("Description") . "\t" . "Sev:$sev" . "\t\t" . 
              $edoc->id('deviceName');

       $class->writeTruncate(System->get_home() . "/DATA/Alerts.log", $data);
    }
  }
  push(@EVENT_DOCS, $edoc);
}


sub writeTruncate {
  my($pdm, $file, $data) = @_;
  my($chmod);
  if ($data) {
     my($size) = (stat($file))[7];
     if ($size > 1 * 1000000) {
        rename $file, $file . ".2";
     }
     $chmod = 1 if (!-f $file);
     if (open(O,">>$file")) {
       print O "$data\n";
       close(O);
     } else {
       Debug->err(CANNOT_WRITE => $file);
     }
     chmod 0666, $file if ($chmod);
  }
}


#  Request event-docs from the PDM, executed by providers.
#  $edocs = $pdm->getMessages({ ClassName => "className" });
#  $edocs = $pdm->getMessages({ ClassName => "className" });
#  $edocs = $pdm->getMessages({ ClassName => [<set of classes>] });
#  foreach $ed (@$edocs) {
#    ...
#
sub getMessages {
  my($pdm, $arg) = @_;
  my($x, $ed);
  my(@found);
  return \@EVENT_DOCS if (!$arg->{ClassName});

  for ($x=0; $x <= $#EVENT_DOCS; $x++) {
     $ed = $EVENT_DOCS[$x];
     next if ($arg->{ClassName} && 
             ! &set_equals($ed->instances->[0]->className, $arg->{ClassName}));
     push(@found, $ed);
  }
  return \@found;
} 

sub getEventCount {
  my($pdm) = @_;
  return $#EVENT_DOCS + 1;
}

#
# Reports_CB = [health_monitor, $function, $filter]
# filter can be on category, report_type or 'INITIAL'
# INITIAL : will execute automatically at the request of the dispatcher.
#

sub reportRequest {
  my($pdm, $hm, $callback, $filter) = @_;

  push(@Reports_CB, [$hm, $callback, $filter]);

}



%GLOBAL= ();

sub getGlobal {
  my($pdm, $key) = @_;
  my($obj) = {};

  if (!defined($GLOBAL{$key})) {
    $GLOBAL{$key} = $obj;
    return $obj;
  } else {
    return $GLOBAL{$key};
  }
}

  
sub dumpCache {
  my($pdm) = @_;
  $CACHE = undef;
}

# always returns a handle to an object or a new empty object.
#
sub getCacheHandle {
  my($pdm, $key) = @_;
  my($obj) = {};

  if (!$CACHE) {
    my($D) = System->get_home() . "/DATA/CACHE";
    $CACHE = &_read($D);
  }

  if (!defined($CACHE->{$key})) {
    $CACHE->{$key} = $obj;
    return $obj;
  } else {
    return $CACHE->{$key};
  }
}

sub inCache {
  my($pdm, $key) = @_;
  if ($CACHE->{$key}) {
    return 1;
  } else {
    return 0;
  }
}

###########################################################
# MISC.
###########################################################
use vars qw($SEQ);

sub getEventSequence {
  my($pdm) = @_;
  my($s);
 
  $SEQ++;
  return System->hostid() . "." . time . ".$SEQ";
  
  my($file) = "EventSequence";
  if (open(O, System->get_home() . "/System/$file") ) {
    $s = <O>;
    $s++;
    close(O);
  } else {
    $s = 1;
  }
  open(O , ">" . System->get_home() . "/System/$file");
  print O $s;
  close(O);
  return System->hostid . "." . $s;
}



1;

__END__

=head1 NAME

PDM.pm - Persistent Data Manager


=head3 SYNOPSIS

 use PDM;

 $rep = PDM->new( {...} );


=head3 DESCRIPTION

This module include persistence functions.


=head3 CONSTRUCTOR

=over 1

=item new()


 $pdm => PDM->new( {dir => "cache dir", renv => , $devices => , hosts => , notifs => });

=back 1

=head3 METHODS

=over 4

=item serialize();

Serialize the PDM data stores, called by the dispatcher.

  $pdm->serialize();


=item saveReport();

Saves a report, called by instrumentation

=item getLastReport();

returns an old report.

=item initialHealth();

Execute initial health modules, created with the INITIAL flag.

=item finalHealth();

Execute final health modules, created with the FINAL flag.



=item saveMessage();

Store one Message in memory.

=item storeMessages();

Serialize to the disk all eventDocs, called by slaves dispatchers.

=item getMessages();

returns a list of eventDocs available.

=item reportRequest();

called at the creation of a health monitor to register a callback.

=item getCacheHandle();

Returns a handle to an object given a cache key. If the object does not
exist, it will create an empty one and return a handle so that the caller
can still use the handle to store stuff. This will be serialized by the
dispatcher at the end of the rasagent run.

  Example:
    $h1 = PDM->getCacheHandle('savethis');
    $h1->{'here'} = "some stuff";
    $h1->{'there'} = "more stuff";
    $h1->{'count'}++;

    # This information will be available on the next run of rasagent.>

=item inCache();

returns TRUE if the key has an object in the cache.

=item getTimer($timer);

Will initialize a timer or return the number of minutes since the last reset
of the timer. By default, the timer is 'heartbeat' unless 'timer' is defined.

=item resetTimer($timer);

Will reset a timer to zero. Called manually and by getTimer when no timer is
found.

=item getEventSequence();

Get a unique event sequence.

=item getDocSequence();

Get a unique Document sequence for a set of events.

=back 4

=head3 SEE ALSO




=head3 AUTHOR

 Christian Cadieux (ccadieux@central.sun)



=head3 COPYRIGHT

Copyright (c) 2000 Sun Microsystems

=cut
