# $Id: HealthConfig.pm,v 1.19 2005/02/08 20:34:28 sbrooks Exp $
# Copyright 2004 Sun Microsystems, Inc., All Rights Reserved.
# Sun Proprietary/Confidential: For Internal Use Only
package Catalog::HealthConfig;

use strict;

use Catalog::Entry;
use Catalog::Message;
use Catalog::Model;
use Catalog::ReportClass;
use Catalog::Revision;
use Data::Dumper;
use Debug;
use Labels;
use Message;
use RasDB;
use Thresholds;
use XML::LibXML;

use base 'Catalog::Entry';

sub new {
  my ($class, $type, $dir) = @_;

  my $this = { "type"   => $type,
               "entry"  => "health.xml",
					"dir"    => $dir,
					"model"  => new Catalog::Model($type, $dir),
					"revision" => new Catalog::Revision($type, $dir)
				 };
  bless($this, "Catalog::HealthConfig");
  return $this->loadEntry();
}

sub load {
  my ($this) = @_;

  my $parser = XML::LibXML->new();
  my $dom;
  eval {
	 $dom  = $parser->parse_file($this->{file});
  };
  if ((!$dom)||($@)){
	 $this->setError("Error parsing XML document element. $@");
	 return;
  }
  my $cmap = {};
  my $dmap = {};
  my @policies;
  my $elem = $dom->getDocumentElement();
  my $pindex = 1;

  foreach my $el ($elem->getChildNodes()){
	 if ($el->getType() == &XML::LibXML::ELEMENT_NODE() ){
		if ($el->getName() eq "class"){
		  $cmap->{$el->getAttribute("name")} = $el;
		}
		if ($el->getName() eq "define"){
		  $dmap->{$el->getAttribute("name")} = $el;
		}
		if ($el->getName() eq "message"){
		  push(@policies, $el);
		}
	 }
  }
  $this->{cmap} = $cmap;
  $this->{dmap} = $dmap;
  $this->{policies} = \@policies;
}


sub mapSeverity {
  my($this, $severity) = @_;
  if ("ignore" eq $severity){
	 return -1;
  }
  elsif ("warn" eq $severity){
    return Message::SEVERITY_WARNING;
  }
  elsif ("error" eq $severity){
    return Message::SEVERITY_ERROR;
  }
  elsif ("down" eq $severity){
    return Message::SEVERITY_DOWN;
  }
  return 0;
}

sub getIDU {
  my($this, $rep, $orep) = @_;

  my $rmap = $rep->getObjectMap();
  if (!$orep){
	 return ($rmap, undef, undef);
  }
  my (%INSERT, %DELETE, %UPDATE);
  my $omap = $orep->getObjectMap();

  foreach my $name (keys %$rmap) {
	 my $comp = $rmap->{$name};
	 next if (! $this->{model}->isLogical($comp->getClassName()));
	 if (!exists $omap->{$name}) {
		$INSERT{$name} = $comp;
    } else {
		my $ocomp      = $omap->{$name};
		$UPDATE{$name} = [$comp, $ocomp];
    }
  }
  foreach my $name (keys %$omap) {
	 my $ocomp = $omap->{$name};
	 next if (! $this->{model}->isLogical($ocomp->getClassName()));
    if (!exists $rmap->{$name}) {
		$DELETE{$name} = $omap->{$name};
	 }
  }
  return(\%INSERT, \%DELETE, \%UPDATE);
}


sub evaluateMessages {
  my ($this, $msgs, $device) = @_;

  my @err;
  for(my $index = 0; $index <= $#$msgs; $index++) {
	 $this->evaluateMessage(\@err, $msgs, $index, $device);
  }
  return (\@err);
}


sub _calcMsgVar {
  my ($this, $msgVal, $pVal, $args) = @_;
  if (!$pVal){  # if policy doesn't have a value then use message.
	 return $msgVal;
  }
  for(my $index = 0; $index <= $#$args; $index++){
	 my $arg = $index + 1;
	 my $s = "\\\$".$arg;
	 my $r = $args->[$index];
	 $pVal =~ s/$s/$r/g;
  }	
  return $pVal;
}

# Handle a health XML message element.
#<message pattern="/0x1001/" severity="error" topic="channel_fail_log">
#  <code class="com.acme.MyMessagePolicy"/>
#  <match other="true" source="line" pattern="/regexp/" severity="info"/>
#</message>
sub evaluateMessage {
  my ($this, $err, $msgs, $index, $device) = @_;
  my $mnodes = $this->{policies};

  my $event = {};
  my $msg = $msgs->[$index];

  for(my $index = 0; $index <= $#$mnodes; $index++){
	 my $mel = $mnodes->[$index];
	 my $policy = $mel->getAttribute('name');
	 if (!$policy){
		$policy = "Policy_".$index;
	 }
	 my $lineMatch = $mel->getAttribute('pattern');
	 my $line = $msg->{line};
	 my $match = 0;
	 my @PT;
	 # todo if no pattern look for child match elements.
	 if ("//" eq $lineMatch){
		$match = 1;
	 }
	 else {
		if ($lineMatch =~ /\/(.*)\//){
		  $lineMatch = $1;
		  if ($line =~ /$lineMatch/){
			 $match = 1;
			 @PT = ($line =~ /$lineMatch/);
		  }
		} else {
		  $match = ($line eq $lineMatch);
		}
	 }
	 if ($match){
		my $severity = $mel->getAttribute('severity');
		my $sev      = $this->mapSeverity($severity);
		my $key      = $device->{type} . ":" . $device->{key};
		my $comp     = $this->_calcMsgVar($msg->{component},
													 $mel->getAttribute('component'),
													 \@PT);
		my $syskey = $key;
		if ($comp){
		  $key = "$key:$comp";
		}
		my $action;
		$action = 1 if ($sev >= 2);

		# Check threshold
		my @thresh = $mel->findnodes("threshold");
		if ($#thresh > 0) {
		  my $thel = $thresh[0];
		  my $desc = $mel->getAttribute('description') ||
			 $mel->getAttribute('topic') || $line;
		  my $count = $thel->getAttribute('count');
		  my $period = $thel->getAttribute('period');
		  my $quietPeriod = $thel->getAttribute('quietPeriod');
		  my $thseverity  =  $thel->getAttribute('severity');
		  my $th = "$count,$period,$quietPeriod,$sev,$desc";
		  my $cnt = 1;
		  my $threshType = $mel->getAttribute('name');
		  Thresholds->init();
		  Thresholds->add($this->{type}, $comp, $th);
		  my ($th_sev, $th_ret, $th_desc, $th_mins, $th_count, $th_period) =
			 Thresholds->test($this->{type}, $threshType, $comp,
									$cnt, $syskey);
		  if ($th_sev){
			 if ($thseverity){
				$severity = $thseverity;
				next if ($severity eq "ignore");
				$sev = $this->mapSeverity($severity);
			 }
			 $err->[$sev]{$key}{line} =
				"Received $th_ret similar log messages in $th_mins minute(s).\n";
		  }
		}
		next if ($severity eq "ignore");

		$err->[$sev]{$key}{action}   = $action;
		$err->[$sev]{$key}{section}  = $this->{type}.":".$policy;
		$err->[$sev]{$key}{egrid}    = $mel->getAttribute('topic');
		$err->[$sev]{$key}{name}     = $device->{name};
		$err->[$sev]{$key}{priority} = $mel->getAttribute('priority');
		$err->[$sev]{$key}{ip}       = $device->{ipno} || $device->{ip};
		$err->[$sev]{$key}{count}++;
		if ($err->[$sev]{$key}{count} < 100){
		  $err->[$sev]{$key}{line}.= $line."\n";
		}
		
		#todo run code PolicyMsgAction.run(String name, LogMessage msg,
		#  ErrorMsg emsg);
		last;
	 }
  }
}

# Fru removed or logical element deleted.
sub evaluateRemoval {
  my ($this, $orep) = @_;

  my @events;
  my $event =
	 { type     => "ComponentRemoveEvent",
		severity => "warn",
		topic    => $orep->{className}
	 };

  my $celem = $this->{cmap}->{$orep->{className}};
  if ($celem){
	 my $cseverity = $celem->getAttribute('severity');
	 $event->{severity} = $cseverity if ($cseverity);
	 my $ctopic = $celem->getAttribute('topic');
	 $event->{topic} = $ctopic if ($ctopic);
	 foreach my $el ($celem->getChildNodes()){
		if ($el->getType() == &XML::LibXML::ELEMENT_NODE() ){
		  if ($el->getName() eq "remove"){
			 $event = $this->_fillEventData($el, $event);
		  }
		}
	 }
  }

  $event->{previous}  = $orep->getComponentKey();
  $event->{component} = $orep->getKey();
  if (!$event->{description}){
	 my $LB = Labels->read("CommonDesc");
	 my $elemName = $orep->getDisplayName();
	 my $sysName  = $orep->getSystemDisplayName();
	 my $compKey  = $orep->getComponentKey();
	 my($descArgs, $description) =
		$LB->expand2(removeComp => "$elemName", "$sysName", "$compKey", undef);
	 $event->{description} = $description;
	 $event->{descArgs}    = $descArgs;
  }
  else {
	 $this->_eventDescription($event, $orep);
  }
  if (Debug->level() > 1){
	 my $msg = "Event: ";
	 foreach my $name (keys %$event){
		$msg .= $name . "=".$event->{$name}.", ";
	 }
	 Debug->print2($msg);
  }	
  push (@events, $event);
  return \@events;
}

# New class or new realized association.
# comp is a ReportClass of a logical component.
# Event not generated if start is set because a discovery event is issued.
# If changeEvaled is set then the health rules for change will be applied.
sub evaluateInsert {
  my ($this, $comp, $start, $changeEvaled) = @_;

  my @events;
  if (!$start){
	 my $event = { type      => "ComponentInsertEvent",
						severity  => "info", # Default is an info event.
						topic     => $comp->{className}
					 };

	 my $celem = $this->{cmap}->{$comp->{className}};
	 if ($celem){
		my $cseverity = $celem->getAttribute('severity');
		$event->{severity} = $cseverity if ($cseverity);
		my $ctopic = $celem->getAttribute('topic');
		$event->{topic} = $ctopic if ($ctopic);
		foreach my $el ($celem->getChildNodes()){
		  if ($el->getType() == &XML::LibXML::ELEMENT_NODE() ){
			 if ($el->getName() eq "create"){
				$event = $this->_fillEventData($el, $event);
			 }
		  }
		}
	 }
	 if (!$event->{description}){
		my $LB = Labels->read("CommonDesc");
		my $elemName = $comp->getDisplayName();
		my $sysName  = $comp->getSystemDisplayName();
		my $compKey  = $comp->getComponentKey();
		my $status   = $comp->getStatus();
		my($descArgs, $description) =
		  $LB->expand2(insertComp2 => "$elemName", "$sysName",
							"$status", "$compKey", undef);
		$event->{description} = $description;
		$event->{descArgs}    = $descArgs;
	 }
	 else {
		$this->_eventDescription($event, $comp);
	 }
	 $event->{component} = $comp->getKey();
	 $event->{value}     = $comp->getComponentKey();

	 if (Debug->level() > 1){
		my $msg = "Event: ";
		foreach my $name (keys %$event){
		  $msg .= $name . "=".$event->{$name}.", ";
		}
		Debug->print2($msg);
	 }	
	 push (@events, $event);
  }
  if (!$changeEvaled){
	 my $changes = $this->evaluateChange($comp, undef, 1);
	 if ($#$changes >= 0){
		push(@events, @$changes);
	 }
  }
  #TODO evaluate revision of new components!
  return \@events;
}

# Check to see if a fru association has changed.
sub evaluateRealized {
  my ($this, $comp, $ocomp, $start) = @_;
  my ($realized, $orealized);

  if ($comp){
	 $realized = $comp->getProperty("_Realized");
  }
  if ($ocomp){
	 $orealized = $ocomp->getProperty("_Realized");
  }
  if ($realized && !$orealized){ #addition
	 return $this->evaluateInsert($comp, $start, 1);
  }
  if ($orealized && !$realized){ #removal
	 return $this->evaluateRemoval($ocomp);
  }
  if ($orealized ne $realized){ # both
	 my @events;
	 my $changes = $this->evaluateRemoval($ocomp);
	 if ($#$changes >= 0){
		push(@events, @$changes);
	 }
	 $changes = $this->evaluateInsert($comp, $start, 1);
	 if ($#$changes >= 0){
		push(@events, @$changes);
	 }
	 return \@events;
  }
}

sub evaluateChange {
  my ($this, $comp, $ocomp, $start) = @_;

  my $changeMap  = $this->_getDiffMap($comp, $ocomp);
  my $changes =  scalar keys %$changeMap;
  return undef if ($changes == 0);

  if (Debug->level() > 1){
	 foreach my $cvar (keys %$changeMap){
		 Debug->print2("Change:".$comp->{className}.":".
							$cvar." ".$changeMap->{$cvar}->[1]." => ".
							$changeMap->{$cvar}->[0]);
	 }
  }

  my @events;

  # Check Revision
  my $rev = $this->{revision};
  if ($rev && $rev->enabled()){
	 my $revEvent = $rev->evaluateRevision($comp->getDevice(), $comp, $ocomp);
	 if ($revEvent){
		my $severity = "info";
		if ("PASS" eq $revEvent->{status}){
		  $severity = "OK";
		}
		my $LB  = Labels->read("CommonDesc");
		my $display = $revEvent->{device}.":".$revEvent->{component};
		my $rev     = $revEvent->{curr_version};
		my $orev    = $revEvent->{prev_version};
		my($descArgs, $descE) = $LB->expand2
		  (revision => "$display",
			$this->{type} ." ". $revEvent->{type}, "'$orev'", "'$rev'");
		my $event =
			 {
			  type        => "ValueChangeEvent",
			  severity    => $severity,
			  topic       => "revision",
			  component   => $comp->getKey(),
			  description => $descE,
			  descArgs    => $descArgs,
			  value       => $revEvent->{curr_version},
			  previous    => $revEvent->{prev_version},
			  info        => $revEvent->{description}
			 };
		if ($start && ($severity eq "OK")){
		  Debug->print3("Initial revision of " . $display . " is OK.");
		} 
		elsif(($revEvent->{curr_version} && !$revEvent->{prev_version}) || 
		      (!$revEvent->{curr_version} && $revEvent->{prev_version}))
		{
		  Debug->print3("Skipping revision event for inserted or removed FRU");
		}
		else {
		  push(@events, $event);
		}
	 }	
  }

  if ($changeMap->{_Realized}){
	 my $revents = $this->evaluateRealized($comp, $ocomp, $start);
	 if ($#$revents >= 0){
		push(@events, @$revents);
	 }
  }

  my $celem = $this->{cmap}->{$comp->{className}};
  if ($celem){
	 my $seed =
		{ type      => "ValueChangeEvent",
		  severity  => "info",
		  topic     => $celem->getAttribute("topic") ||
		  $comp->{className}
		};
	 foreach my $el ($celem->getChildNodes()){
		if ($el->getType() == &XML::LibXML::ELEMENT_NODE() ){
		  if ($el->getName() eq "property"){
			 my $changes = $this->_handlePropertyNode
				($el, $seed, $comp, $ocomp, $start);
			 if ($#$changes >= 0){
				push(@events, @$changes);
			 }
		  }
		  elsif ($el->getName() eq "include"){
			 my $defname = $el->getAttribute("define");
			 my $incel = $this->{dmap}->{$defname};
			 foreach my $iel ($incel->getChildNodes()){
				if ($iel->getType() == &XML::LibXML::ELEMENT_NODE() ){
				  if ($iel->getName() eq "property"){
					 my $changes = $this->_handlePropertyNode
						($iel, $seed, $comp, $ocomp, $start);
					 if ($#$changes >= 0){
						push(@events, @$changes);
					 }
				  }
				}
			 }
		  }
		}
	 }
  }

  if ($#events >= 0){
	 if (Debug->level() > 1){
		foreach my $e (@events){
		  my $msg = "Event: ";
		  foreach my $name (keys %$e){
			 $msg .= $name . "=" . $e->{$name} .", ";
		  }
		  Debug->print2($msg);
		}
	 }
  }	

  return \@events;
}

sub _matchValue {
  my ($this, $value, $exp) = @_;
  if ($value eq $exp) {
	 return 1;
  }
  else {
	return $value =~ $exp;
 }
}

sub _handlePropertyNode {
  my ($this, $el, $seed, $comp, $ocomp, $start) = @_;

  my @events;
  my $event    = $this->_fillEventData($el, $seed);
  my $property = $el->getAttribute("name");

  if (!$el->hasChildNodes()){
	 return $this->_handleChangeNode($el, $event, $comp, $ocomp, 
												$property, $start);
  }

  my $value = $el->getAttribute("value");
  if (($value) && (!$this->_matchValue($comp->getProperty($property),
													$value))){
	 return undef;
  }
  $value = $el->getAttribute("valueNot");
  if (($value) && ($this->_matchValue($comp->getProperty($property), $value))){
	 return undef;
  }

  foreach my $cel ($el->getChildNodes()){
	 if ($cel->getType() == &XML::LibXML::ELEMENT_NODE() ){
		if ($cel->getName() eq "change"){
		  my $e = $this->_handleChangeNode($cel, $event, $comp, $ocomp, 
													  $property, $start);
		  if ($e){
			 if ($e->{severity} eq "ignore"){
				return undef;
			 }
			 if ($start && $e->{severity} eq "OK"){
				return undef;
			 }
			 push (@events, $e) if ($e);
		  }	
		}
		elsif ($el->getName() eq "threshold"){
		  my $e = $this->_handleThreshNode($cel, $event, $comp, $ocomp,
													  $property, $start);
		  push (@events, $e) if ($e);
		}
		elsif ($el->getName() eq "property"){
		  my $e = $this->_handlePropertyNode($cel, $event, 
														 $comp, $ocomp, $start);
		  if ($#$e >= 0){
			 push(@events, @$e);
		  }
		}
		elsif ($el->getName() eq "default"){
		  my $devent = $this->_fillEventData($el, $event);
		  $devent->{property} = $property;
		  $devent->{value}    = $comp->getProperty($property);
		  $devent->{previous} = $ocomp->getProperty($property) if $ocomp;
		  push(@events, $devent);
		}
	 }
  }
  return \@events;
}

# return change event if detected.
sub _handleChangeNode {
  my ($this, $el, $seed, $comp, $ocomp, $property, $start) = @_;

  my $value    = $comp->getProperty($property);
  my $previous = $ocomp->getProperty($property) if $ocomp;
  if ($value eq $previous){
	 return undef;
  }

  if (!$start && ("true" eq $el->getAttribute("start"))){
	 return undef;
  }
  my $undefvar =  $el->getAttribute("undefined");

  if ("value" eq $undefvar){
	 return undef if (defined $value);
  }
  else {
	 my $cvalue    = $el->getAttribute("value");
	 if ($cvalue && (!$this->_matchValue($value, $cvalue))){
		return undef;
	 }
	 my $cvalueNot = $el->getAttribute("valueNot");
	 if ($cvalueNot && ($this->matchValue($value, $cvalueNot))){
		  return undef;
	 }
  }

  if ("previous" eq $undefvar){
	 return undef if (defined $previous);
  }
  else {
	 my $pvalue = $el->getAttribute("previous");
	 if ($pvalue && (!$this->_matchValue($previous, $pvalue))){
		return undef;
	 }
	 my $pvalueNot = $el->getAttribute("previousNot");
	 if ($pvalueNot && ($this->matchValue($previous, $pvalueNot))){
		return undef;
	 }
  }
  my $event = $this->_fillEventData($el, $seed);
  $event->{value}    = $value;
  $event->{previous} = $previous;
  $event->{property} = $property;
  $event->{component} = $comp->getKey();
  if ($event->{description}){
	 $this->_eventDescription($event, $comp);
  }
  else {
	 my $LB       = Labels->read("CommonDesc");
	 my $propName = $this->{model}->localizeProperty($comp, $event->{property});
	 my $value    = $event->{value};
	 my $previous = $event->{previous};
	 my $elemName = $comp->getDisplayName();
	 my $sysName  = $comp->getSystemDisplayName();
	 my $compKey  = $comp->getComponentKey();

	 my($descArgs, $description);
	 if ($previous){
		($descArgs, $description)=
		  $LB->expand2(valueChange => "$propName", "$elemName", "$sysName",
							"$value", "$propName", "$previous", undef);
	 }
	 else {
		($descArgs, $description)=
		  $LB->expand2(valueChange2 => "$propName", "$elemName", "$sysName",
							"$value", undef);
	 }
	 $event->{description} = $description;
	 $event->{descArgs}    = $descArgs;
  }

  return $event;
}

sub _handleThreshNode {
  my ($this, $el, $seed, $comp, $ocomp, $property, $start) = @_;

  my $value    = $comp->getProperty($property);
  my $previous = $ocomp->getProperty($property) if $ocomp;
  if ($value eq $previous){
	 return undef;
  }

  my $count       = $el->getAttribute("count");
  my $period      = $el->getAttribute("period");
  my $quietPeriod = $el->getAttribute("quietPeriod");
  my $severity    = $this->_mapThreshSev($el->getAttribute("severity"));
  my $text        = $el->getAttribute("description") || $property;
  my $th          = "$count,$period,$quietPeriod,$severity,$text";
  my $event       = $this->_fillEventData($el, $seed);
  $event->{value} = $value;
  $event->{previous} = $previous;
  $event->{property} = $property;
  my $cnt = $value - $previous;
  my $type = $comp->{className} . ".$property.";
  Thresholds->init();
  Thresholds->add($this->{type}, $type, $th);
  my ($sev, $ret, $desc, $mins, $th_count, $th_period) =
	 Thresholds->test($this->{type}, $type, $comp->getComponentKey(),
							$cnt, $comp->getSystemKey());
  if ($sev){
	 $event->{info} = "Received $ret $property in $mins mins (value=$value)";
	 return $event;
  }
}

sub _mapThreshSev {
  my ($this, $sev);
  my $map = { "error" => "E", warn => "W", info => "I" };
  my $msev = $map->{$sev};
  if ($msev){
	 return $msev;
  }
  return "W";
}

# return hash of values that contain [new, old] array.
sub _getDiffMap {
  my ($this, $cur, $old) = @_;
  my %map;

  my $v  = $cur->{vars};

  if (!$old){
	 foreach my $name (keys %$v) {
		$map{$name} = [$v->{$name}, undef];
	 }
  }
  else {
	 my $ov = $old->{vars};

	 foreach my $name (keys %$v) {
		if (!($ov->{$name} eq $v->{$name})){
		  $map{$name} = [$v->{$name}, $ov->{$name}];
		}
	 }
	 foreach my $name (keys %$ov) {
		if (!exists $v->{$name}) {
		  $map{$name} = [undef, $ov->{$name}];
		}
	 }
  }
  return \%map;
}

sub _eventDescription {
  my ($this, $event, $comp) = @_;
  my $msgs = new Catalog::Message($this->{type}, $this->{dir});
  my $desc = $event->{description};
  my($descArgs, $description) = 
	 $msgs->getComponentDescription($comp, $desc);
  $event->{description} = $description;
  $event->{descArgs}    = "@".$descArgs;
}


sub _fillEventData {
  my ($this, $el, $seed) = @_;
  if ($el){
	 return
		{
		 type        => $el->getAttribute('event')       || $seed->{type},
		 topic       => $el->getAttribute('topic')       || $seed->{topic},
		 severity    => $el->getAttribute('severity')    || $seed->{severity},
		 available   => $el->getAttribute('available')   || $seed->{available},
		 description => $el->getAttribute('description') || $seed->{description}
		};
  }
  return 
	 {
	  type        => $seed->{type},
	  topic       => $seed->{topic},
	  severity    => $seed->{severity},
	  available   => $seed->{available},
	  description => $seed->{description}
	 };
}

1;
