#! /usr/local/bin/perl -w

package Nagios::MiniPlugin;

use strict;
use Getopt::Long qw(:config no_ignore_case bundling);

our @STATUS_CODES = qw(OK WARNING CRITICAL UNKNOWN DEPENDENT);

require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = (@STATUS_CODES, qw(nagios_exit nagios_die check_messages));
our @EXPORT_OK = qw(%ERRORS);

use constant OK         => 0;
use constant WARNING    => 1;
use constant CRITICAL   => 2;
use constant UNKNOWN    => 3;
use constant DEPENDENT  => 4;

our %ERRORS = (
    'OK'        => OK,
    'WARNING'   => WARNING,
    'CRITICAL'  => CRITICAL,
    'UNKNOWN'   => UNKNOWN,
    'DEPENDENT' => DEPENDENT,
);

our %STATUS_TEXT = reverse %ERRORS;

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
       perfdata => [],
       messages => {
         ok => [],
         warning => [],
         critical => [],
         unknown => [],
       },
       args => [],
       opts => Nagios::MiniPlugin::Getopt->new(%params),
  };
  foreach (qw(shortname usage version url plugin blurb extra
      license timeout)) {
    $self->{$_} = $params{$_};
  }
  bless $self, $class;
}

sub add_arg {
  my $self = shift;
  $self->{opts}->add_arg(@_);
}

sub getopts { 
       my $self = shift;
       $self->{opts}->getopts();
}

sub opts {
       my $self = shift;
       return $self->{opts};
}

sub add_message {
  my $self = shift;
  my ($code, @messages) = @_;
  $code = (qw(ok warning critical unknown))[$code] if $code =~ /^\d+$/;
  $code = lc $code;
  push @{$self->{messages}->{$code}}, @messages;
}

sub remove_message {
  my $self = shift;
  my ($code, @messages) = @_;
  $code = (qw(ok warning critical unknown))[$code] if $code =~ /^\d+$/;
  $code = lc $code;
  pop @{$self->{messages}->{$code}};
}

sub add_perfdata {
  my ($self, %args) = @_;
  my $str = $args{label}.'='.$args{value};
  if ($args{uom}) {
    $str .= $args{uom};
  }
  if ($args{warning}) {
    $str .= ';'.$args{warning};
  }
  if ($args{critical}) {
    $str .= ';'.$args{critical};
  }
  push @{$self->{perfdata}}, $str;
}


sub check_messages {
  my $self = shift;
  my %args = @_;

  # Add object messages to any passed in as args
  for my $code (qw(critical warning unknown ok)) {
    my $messages = $self->{messages}->{$code} || [];
    if ($args{$code}) {
      unless (ref $args{$code} eq 'ARRAY') {
        if ($code eq 'ok') {
          $args{$code} = [ $args{$code} ];
        }
      }
      push @{$args{$code}}, @$messages;
    } else {
      $args{$code} = $messages;
    }
  }
  my %arg = %args;
  $arg{join} = ' ' unless defined $arg{join};

  # Decide $code
  my $code = OK;
  $code ||= CRITICAL  if @{$arg{critical}};
  $code ||= WARNING   if @{$arg{warning}};
  $code ||= UNKNOWN   if @{$arg{unknown}};
  return $code unless wantarray;

  # Compose message
  my $message = '';
  if ($arg{join_all}) {
      $message = join( $arg{join_all},
          map { @$_ ? join( $arg{'join'}, @$_) : () }
              $arg{critical},
              $arg{warning},
              $arg{unknown},
              $arg{ok} ? (ref $arg{ok} ? $arg{ok} : [ $arg{ok} ]) : []
      );
  }

  else {
      $message ||= join( $arg{'join'}, @{$arg{critical}} )
          if $code == CRITICAL;
      $message ||= join( $arg{'join'}, @{$arg{warning}} )
          if $code == WARNING;
      $message ||= join( $arg{'join'}, @{$arg{unknown}} )
          if $code == UNKNOWN;
      $message ||= ref $arg{ok} ? join( $arg{'join'}, @{$arg{ok}} ) : $arg{ok}
          if $arg{ok};
  }

  return ($code, $message);
}

sub nagios_exit {
  my $self = shift;
  my ($code, $message, $arg) = @_;
  $code = $ERRORS{$code} if defined $code && exists $ERRORS{$code};
  $code = UNKNOWN unless defined $code && exists $STATUS_TEXT{$code};
  $message = '' unless defined $message;
  if (ref $message && ref $message eq 'ARRAY') {
      $message = join(' ', map { chomp; $_ } @$message);
  } else {
      chomp $message;
  }
  my $output = "$STATUS_TEXT{$code}";
  $output .= " - $message" if defined $message && $message ne '';
  if (scalar (@{$self->{perfdata}})) {
    $output .= " | ".join(" ", @{$self->{perfdata}});
  }
  $output .= "\n";
  print $output;
  exit $code;
}

package Nagios::MiniPlugin::Getopt;

use strict;
use File::Basename;
use Data::Dumper;
use Getopt::Long qw(:config no_ignore_case bundling);

# Standard defaults
my %DEFAULT = (
  timeout => 60,
  verbose => 0,
  license =>
"This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY.
It may be used, redistributed and/or modified under the terms of the GNU
General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt).",
);
# Standard arguments
my @ARGS = ({
    spec => 'usage|?',
    help => "-?, --usage\n   Print usage information",
  }, {
    spec => 'help|h',
    help => "-h, --help\n   Print detailed help screen",
  }, {
    spec => 'version|V',
    help => "-V, --version\n   Print version information",
  }, {
    #spec => 'extra-opts:s@',
    #help => "--extra-opts=[<section>[@<config_file>]]\n   Section and/or config_file from which to load extra options (may repeat)",
  }, {
    spec => 'timeout|t=i',
    help => "-t, --timeout=INTEGER\n   Seconds before plugin times out (default: %s)",
    default => $DEFAULT{timeout},
  }, {
    spec => 'verbose|v+',
    help => "-v, --verbose\n   Show details for command-line debugging (can repeat up to 3 times)",
    default => $DEFAULT{verbose},
  },
);
# Standard arguments we traditionally display last in the help output
my %DEFER_ARGS = map { $_ => 1 } qw(timeout verbose);

sub _init
{
  my $self = shift;
  my %params = @_;
  # Check params
  my $plugin = basename($ENV{NAGIOS_PLUGIN} || $0);
  #my %attr = validate( @_, {
  my %attr = (
    usage => 1,
    version => 0,
    url => 0,
    plugin => { default => $plugin },
    blurb => 0,
    extra => 0,
    'extra-opts' => 0,
    license => { default => $DEFAULT{license} },
    timeout => { default => $DEFAULT{timeout} },
  );

  # Add attr to private _attr hash (except timeout)
  $self->{timeout} = delete $attr{timeout};
  $self->{_attr} = { %attr };
  foreach (keys %{$self->{_attr}}) {
    if (exists $params{$_}) {
      $self->{_attr}->{$_} = $params{$_};
    } else {
      $self->{_attr}->{$_} = $self->{_attr}->{$_}->{default} 
          if ref ($self->{_attr}->{$_}) eq 'HASH' &&
              exists $self->{_attr}->{$_}->{default};
    }
  }
  # Chomp _attr values
  chomp foreach values %{$self->{_attr}};

  # Setup initial args list
  $self->{_args} = [ grep { exists $_->{spec} } @ARGS ];

  $self
}

sub new
{
  my $class = shift;
  my $self = bless {}, $class;
  $self->_init(@_);
}

sub add_arg {
       my $self = shift;
       my %arg = @_;
       push (@{$self->{_args}}, \%arg);
}

sub getopts { 
  my $self = shift;
  my %commandline = ();
  my @params = map { $_->{spec} } @{$self->{_args}};
  if (! GetOptions(\%commandline, @params)) {
    $self->print_help();
    exit 0;
  } else {
    no strict 'refs';
    do { $self->print_help(); exit 0; } if $commandline{help};
    do { $self->print_version(); exit 0 } if $commandline{version};
    do { $self->print_usage(); exit 0 } if $commandline{usage};
    foreach (map { $_->{spec} =~ /^([\w\-]+)/; $1; } @{$self->{_args}}) {
      my $field = $_;
      *{"$field"} = sub {
        return $self->{opts}->{$field};
      };
    }
    foreach (grep { exists $_->{default} } @{$self->{_args}}) {
      $_->{spec} =~ /^([\w\-]+)/;
      my $spec = $1;
      $self->{opts}->{$spec} = $_->{default};
    }
    foreach (keys %commandline) {
      $self->{opts}->{$_} = $commandline{$_};
    }
  }
}

sub get {
       my $self = shift;
       my $opt = shift;
       return $self->{opts}->{$opt};
}

sub print_help {
  my $self = shift;
  $self->print_version();
  printf "\n%s\n", $self->{_attr}->{license};
  printf "\n%s\n\n", $self->{_attr}->{blurb};
  $self->print_usage();
  foreach (@{$self->{_args}}) {
    printf " %s\n", $_->{help};
  }
  exit 0;
}

sub print_usage {
  my $self = shift;
  printf $self->{_attr}->{usage}, $self->{_attr}->{plugin};
  print "\n";
}

sub print_version {
  my $self = shift;
  printf "%s %s", $self->{_attr}->{plugin}, $self->{_attr}->{version};
  printf " [%s]", $self->{_attr}->{url} if $self->{_attr}->{url};
  print "\n";
}

sub print_license {
  my $self = shift;
  printf "%s\n", $self->{_attr}->{license};
  print "\n";
}


package SNMP::Utils;

use strict;

{
  sub get_indices {
    my $oids = shift;
    my $entry = shift;
    my $numindices = shift;
    # find all oids beginning with $entry
    # then skip one field for the sequence
    # then read the next numindices fields
    my $entrypat = $entry;
    $entrypat =~ s/\./\\\./g;
    my @indices = map {
        /^$entrypat\.\d+\.(.*)/ && $1;
    } grep {
        /^$entrypat/
    } keys %{$oids};
    my %seen = ();
    my @o = map {[split /\./]} sort grep !$seen{$_}++, @indices;
    return @o;
  }

  sub get_size {
    my $oids = shift;
    my $entry = shift;
    my $entrypat = $entry;
    $entrypat =~ s/\./\\\./g;
    my @entries = grep {
        /^$entrypat/
    } keys %{$oids};
    return scalar(@entries);
  }

  sub get_object {
    my $oids = shift;
    my $object = shift;
    my @indices = @_;
    #my $oid = $object.'.'.join('.', @indices);
    my $oid = $object;
    $oid .= '.'.join('.', @indices) if (@indices);
    return $oids->{$oid};
  }

  sub get_object_value {
    my $oids = shift;
    my $object = shift;
    my $values = shift;
    my @indices = @_;
    my $key = get_object($oids, $object, @indices);
    if (defined $key) {
      return $values->{$key};
    } else {
      return undef;
    }
  }

  #SNMP::Utils::counter([$idxs1, $idxs2], $idx1, $idx2),
  # this flattens a n-dimensional array and returns the absolute position
  # of the element at position idx1,idx2,...,idxn
  # element 1,2 in table 0,0 0,1 0,2 1,0 1,1 1,2 2,0 2,1 2,2 is at pos 6
  sub get_number {
    my $indexlists = shift; #, zeiger auf array aus [1, 2]
    my @element = @_;
    my $dimensions = scalar(@{$indexlists->[0]});
    my @sorted = ();
    my $number = 0;
    if ($dimensions == 1) {
      @sorted =
          sort { $a->[0] <=> $b->[0] } @{$indexlists};
    } elsif ($dimensions == 2) {
      @sorted =
          sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] } @{$indexlists};
    } elsif ($dimensions == 3) {
      @sorted =
          sort { $a->[0] <=> $b->[0] || 
                 $a->[1] <=> $b->[1] ||
                 $a->[2] <=> $b->[2] } @{$indexlists};
    }
    foreach (@sorted) {
      if ($dimensions == 1) {
        if ($_->[0] == $element[0]) {
          last;
        }
      } elsif ($dimensions == 2) {
        if ($_->[0] == $element[0] && $_->[1] == $element[1]) {
          last;
        }
      } elsif ($dimensions == 3) {
        if ($_->[0] == $element[0] && 
            $_->[1] == $element[1] &&
            $_->[2] == $element[2]) {
          last;
        }
      }
      $number++;
    }
    return ++$number;
  }

}

package HP::Proliant::Component::EventSubsystem;
our @ISA = qw(HP::Proliant::Component);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    condition => $params{condition},
    status => $params{status},
    events => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
    boottime => 0,
  };
  bless $self, $class;
  if ($self->{method} eq 'snmp') {
    $self = HP::Proliant::Component::EventSubsystem::SNMP->new(%params);
    my $sysUpTime = SNMP::Utils::get_object(
        $self->{rawdata}, '1.3.6.1.2.1.1.3.0') || 3600*24*100;
    $self->{boottime} = int(time - $sysUpTime / 100);
  } elsif ($self->{method} eq 'cli') {
    $self = HP::Proliant::Component::EventSubsystem::CLI->new(%params);
    my $uptime = do { local (@ARGV, $/) = "/proc/uptime"; my $x = <>; close ARGV; $x };
    # also watch 10 minutes of booting before the operating system starts ticking
    $self->{boottime} = time - int((split(/\s+/, $uptime))[0]) - 600;
  } else {
    die "unknown method";
  }
  # repair dates
  my $lasttime = 0;
  for my $event (reverse @{$self->{events}}) {
    if ($event->{cpqHeEventLogUpdateTime} != 0) {
      $lasttime = $event->{cpqHeEventLogUpdateTime};
    } else {
      $event->{cpqHeEventLogUpdateTime} = $lasttime;
    }
  }
  # maybe the most recent events had zero timestamps.
  # fill them up with timestamps from the past.
  for my $event (@{$self->{events}}) {
    if ($event->{cpqHeEventLogUpdateTime} != 0) {
      $lasttime = $event->{cpqHeEventLogUpdateTime};
    } else {
      $event->{cpqHeEventLogUpdateTime} = $lasttime;
    }
  }
  # we need the boottime in the event's check method
  for my $event (@{$self->{events}}) {
    $event->{boottime} = $self->{boottime};
  }
  return $self;
}

sub check {
  my $self = shift;
  my $errorfound = 0;
  $self->add_info('checking events');
  if (scalar (@{$self->{events}}) == 0) {
    #$self->overall_check(); 
    $self->add_info('no events found');
  } else {
    foreach (sort { $a->{cpqHeEventLogEntryNumber} <=> $b->{cpqHeEventLogEntryNumber}}
        @{$self->{events}}) {
      $_->check($self->{warningtime}, $self->{criticaltime});
    }
  }
}

sub dump {
  my $self = shift;
  foreach (sort { $a->{cpqHeEventLogEntryNumber} <=> $b->{cpqHeEventLogEntryNumber} } @{$self->{events}}) {
    $_->dump();
  }
}


package HP::Proliant::Component::EventSubsystem::Event;
our @ISA = qw(HP::Proliant::Component::EventSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

{
  our $interesting_events = {
    # POST Error: 201-Memory Error Multi-bit error occurred during memory initialization, Board 1, Bank B. Bank containing DIMM(s) has been disabled..
    # POST Error: 201-Memory Error Single-bit error occured during memory initialization, Board 1, DIMM 1. Bank containing DIMM(s) has been disabled..
    # POST Error: 207-Memory initialization error on Memory Board 5 DIMM 7. The operating system may not have access to all of the memory installed in the system..
    # POST Error: 207-Invalid Memory Configuration-Mismatched DIMMs within DIMM Bank Memory in Bank A Not Utilized..  
    # POST Error: 210 - Quick Path Interconnect (QPI) Link Degradation. A QPI link is operating in a degraded performace state..
    'POST Messages' => [
      '201-Memory', '207-Memory',
      '210\s*-\s*Quick Path Interconnect.*degraded.*'
    ],
    'Main Memory' => [
      'Corrected Memory Error threshold exceeded',
      'Uncorrectable Memory Error',
    ],
  };
}

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqHeEventLogEntryNumber => $params{cpqHeEventLogEntryNumber},
    cpqHeEventLogEntrySeverity => lc $params{cpqHeEventLogEntrySeverity},
    cpqHeEventLogEntryClass => $params{cpqHeEventLogEntryClass},
    cpqHeEventLogEntryCount => $params{cpqHeEventLogEntryCount} || 1,
    cpqHeEventLogInitialTime => $params{cpqHeEventLogInitialTime},
    cpqHeEventLogUpdateTime => $params{cpqHeEventLogUpdateTime},
    cpqHeEventLogErrorDesc => $params{cpqHeEventLogErrorDesc},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  if (! $self->{cpqHeEventLogInitialTime}) {
    $self->{cpqHeEventLogInitialTime} = $self->{cpqHeEventLogUpdateTime};
  }
  # 
  #
  #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  #                      |warn                 |crit                          |now
  #
  #<----- ignore -------><----- warning ------><---------- critical --------->
  #
  # If we have --eventrange <warnlookback>/<critlookback>
  #  Very young events are shown as critical
  #  If the event gets older, it is shown as warning
  #  At some time, the event is no longer shown
  # Without --eventrange the event is shown as critical until you manually repair it
  if ($params{runtime}->{options}->{eventrange}) {
    my ($warningrange, $criticalrange) = split(/\//, $params{runtime}->{options}->{eventrange});
    if (! $criticalrange) {
      $criticalrange = $warningrange;
    }
    if ($criticalrange =~ /^(\d+)[s]*$/) {
      $criticalrange = $1;
    } elsif ($criticalrange =~ /^(\d+)m$/) {
      $criticalrange = $1 * 60;
    } elsif ($criticalrange =~ /^(\d+)h$/) {
      $criticalrange = $1 * 3600;
    } elsif ($criticalrange =~ /^(\d+)d$/) {
      $criticalrange = $1 * 3600 * 24;
    } else {
      die "range has to be <number>[smhd]";
    }
    if ($warningrange =~ /^(\d+)[s]*$/) {
      $warningrange = $1;
    } elsif ($warningrange =~ /^(\d+)m$/) {
      $warningrange = $1 * 60;
    } elsif ($warningrange =~ /^(\d+)h$/) {
      $warningrange = $1 * 3600;
    } elsif ($warningrange =~ /^(\d+)d$/) {
      $warningrange = $1 * 3600 * 24;
    } else {
      die "range has to be <number>[smhd]";
    }
    $self->{warningtime} = time - $warningrange;
    $self->{criticaltime} = time - $criticalrange;
  } else {
    $self->{warningtime} = 0;
    $self->{criticaltime} = 0;
  }
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('evt', $self->{cpqHeEventLogEntryNumber});
  # only check severity "critical" and "caution"
  # optional: only check interesting events
  # POST events only if they date maximum from reboot-5min
  # younger than critical? -> critical
  # 
  $self->add_info(sprintf "Event: %d Added: %s Class: (%s) %s %s",
      $self->{cpqHeEventLogEntryNumber},
      $self->{cpqHeEventLogUpdateTime},
      $self->{cpqHeEventLogEntryClass},
      $self->{cpqHeEventLogEntrySeverity},
      $self->{cpqHeEventLogErrorDesc});
  if ($self->{cpqHeEventLogEntrySeverity} eq "caution" ||
      $self->{cpqHeEventLogEntrySeverity} eq "critical") {
    # also watch 10 minutes of booting before the operating system starts ticking
    if ($self->{cpqHeEventLogUpdateTime} >= $self->{boottime}) {
      foreach my $class (keys %{$HP::Proliant::Component::EventSubsystem::Event::interesting_events}) {
        foreach my $pattern (@{$HP::Proliant::Component::EventSubsystem::Event::interesting_events->{$class}}) {
          if ($self->{cpqHeEventLogErrorDesc} =~ /$pattern/) {
            if ($self->{cpqHeEventLogUpdateTime} < $self->{warningtime}) {
              # you didn't care for this problem too long.
              # don't say i didn't warn you.
              if (0) {
                # auto-ack?
              } 
              last;
            } elsif ($self->{cpqHeEventLogUpdateTime} < $self->{criticaltime}) {
              $self->add_message(WARNING, $self->{info});
              last;
            } else {
              $self->add_message(CRITICAL, $self->{info});
              last;
            }
          }
        }
      }
    }
  } else {
    # info, repair...
  }
}

sub dump { 
  my $self = shift;
  printf "[EVENT_%s]\n", $self->{cpqHeEventLogEntryNumber};
  foreach (qw(cpqHeEventLogEntryNumber cpqHeEventLogEntrySeverity
      cpqHeEventLogEntryCount cpqHeEventLogInitialTime
      cpqHeEventLogUpdateTime cpqHeEventLogErrorDesc)) {
    if ($_ =~ /.*Time$/) {
      printf "%s: %s\n", $_, scalar localtime $self->{$_};
    } else {
      printf "%s: %s\n", $_, $self->{$_};
    }
  }
  printf "info: %s\n\n", $self->{info};
}



package HP::Proliant::Component::EventSubsystem::CLI;
our @ISA = qw(HP::Proliant::Component::EventSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };
use Time::Local;

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    events => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init(%params);
  return $self;
}


sub init {
  my $self = shift;
  my %params = @_;
  my %tmpevent = (
    runtime => $params{runtime},
  );
  my $inblock = 0;
  foreach (grep(/^iml/, split(/\n/, $self->{rawdata}))) {
    s/^iml\s*//g;
    if (/^Event:\s+(\d+)\s+[\w]+:\s+(\d+)\/(\d+)\/(\d+)\s+(\d+):(\d+)/) {
      # Event: 31 Added: 09/22/2011 05:11
      #         1         2  3    4  5  6
      $tmpevent{cpqHeEventLogEntryNumber} = $1;
      if ($4 == 0) {
        # Event: 29 Added: 00/00/0000 00:00
        $tmpevent{cpqHeEventLogUpdateTime} = 0;
      } else {
        eval {
          $tmpevent{cpqHeEventLogUpdateTime} = timelocal(0, $6, $5, $3, $2 - 1, $4);
        };
        if ($@) {
          # Event: 10 Added: 27/27/2027 27:27
          $tmpevent{cpqHeEventLogUpdateTime} = 0;
        }
      }
      $inblock = 1;
    } elsif (/^(\w+):\s+(.*?)\s+\-\s+(.*)/) {
      $tmpevent{cpqHeEventLogEntrySeverity} = $1;
      $tmpevent{cpqHeEventLogEntryClass} = $2;
      $tmpevent{cpqHeEventLogErrorDesc} = $3;
      if ($tmpevent{cpqHeEventLogErrorDesc} =~ /.*?:\s+(\d+)/) {
          $tmpevent{cpqHeEventLogEntryCode} = $1;
      } else {
          $tmpevent{cpqHeEventLogEntryCode} = 0;
      }
    } elsif (/^\s*$/) {
      if ($inblock) {
        $inblock = 0;
        push(@{$self->{events}},
            HP::Proliant::Component::EventSubsystem::Event->new(%tmpevent));
        %tmpevent = (
          runtime => $params{runtime},
        );
      }
    }
  }
  if ($inblock) {
    push(@{$self->{events}},
        HP::Proliant::Component::EventSubsystem::Event->new(%tmpevent));
  }
}



package HP::Proliant::Component::EventSubsystem::SNMP;
our @ISA = qw(HP::Proliant::Component::EventSubsystem
    HP::Proliant::Component::SNMP);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };
use Time::Local;

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    events => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->overall_init(%params);
  $self->init(%params);
  return $self;
}

sub overall_init {
  my $self = shift;
  my %params = @_;
  my $snmpwalk = $params{rawdata};
  # overall
  my $cpqHeEventLogSupported  = '1.3.6.1.4.1.232.6.2.11.1.0';
  my $cpqHeEventLogSupportedValue = {
    1 => 'other',
    2 => 'notSupported',
    3 => 'supported',
    4 => 'clear',
  };
  my $cpqHeEventLogCondition  = '1.3.6.1.4.1.232.6.2.11.2.0';
  my $cpqHeEventLogConditionValue = {
    1 => 'other',
    2 => 'ok',
    3 => 'degraded',
    4 => 'failed',
  };
  $self->{eventsupp} = SNMP::Utils::get_object_value(
      $snmpwalk, $cpqHeEventLogSupported,
      $cpqHeEventLogSupportedValue);
  $self->{eventstatus} = SNMP::Utils::get_object_value(
      $snmpwalk, $cpqHeEventLogCondition,
      $cpqHeEventLogConditionValue);
  $self->{eventsupp} |= lc $self->{eventsupp};
  $self->{eventstatus} |= lc $self->{eventstatus};
}

sub init {
  my $self = shift;
  my %params = @_;
  my $snmpwalk = $self->{rawdata};
  my $oids = {
      cpqHeEventLogEntry => "1.3.6.1.4.1.232.6.2.11.3.1",
      cpqHeEventLogEntryNumber => "1.3.6.1.4.1.232.6.2.11.3.1.1",
      cpqHeEventLogEntrySeverity => "1.3.6.1.4.1.232.6.2.11.3.1.2",
      cpqHeEventLogEntryClass => "1.3.6.1.4.1.232.6.2.11.3.1.3",
      cpqHeEventLogEntryCode => "1.3.6.1.4.1.232.6.2.11.3.1.4",
      cpqHeEventLogEntryCount => "1.3.6.1.4.1.232.6.2.11.3.1.5",
      cpqHeEventLogInitialTime => "1.3.6.1.4.1.232.6.2.11.3.1.6",
      cpqHeEventLogUpdateTime => "1.3.6.1.4.1.232.6.2.11.3.1.7",
      cpqHeEventLogErrorDesc => "1.3.6.1.4.1.232.6.2.11.3.1.8",

      cpqHeEventLogEntryClassValue => {
          #  2 Fan Failure (Fan 1, Location I/O Board)
          #    Internal Storage System Overheating (Slot 0, Zone 1, Location Storage, Temperature Unknown)
          #    System Fans Not Redundant (Location I/O Board)
          #  MY MUSTARD: only critical events should lead to an alert, if at all. The caution events mean "loss of redundancy".
          #              We monitor temperatures and fan status anyway.
          #2 => "",
          #  3 Corrected Memory Error threshold exceeded (System Memory, Memory Module 1)
          #    Uncorrectable Memory Error detected by ROM-based memory validation (Board 1, Memory Module 4)
          #  MY MUSTARD: threshold exceeded is caution. Uncorrectable errors are critical. Both should be detected anyway.
          3 => "Main Memory",
          #  4 Accelerator Cache Memory Parity Error (Socket 1)
          #4 => "",
          #  5 Processor Correctable error threshold exceeded (Board 0, Processor 2)
          #5 => "",
          #  6 Unrecoverable Intermodule Bus error (Error code 0x00000000)
          #6 => "",
          #  8 PCI Bus Error (Slot 0, Bus 0, Device 0, Function 0)
          8 => "PCI Bus",
          # 10 1720-S.M.A.R.T. Hard Drive Detects Imminent Failure
          #    POST Error: 201-Memory Error Multi-bit error occurred during memory initialization, Board 1, Bank B. Bank containing DIMM(s) has been disabled..
          #    POST Error: 201-Memory Error Single-bit error occured during memory initialization, Board 1, DIMM 1. Bank containing DIMM(s) has been disabled..
          #    POST Error: 207-Memory Configuration Warning - memory boards should be installed sequentially.
          #    POST Error: 210-Memory Board Failure on board 4.
          #    POST Error: 210-Memory Board Power Fault on board 3.
          #    POST Error: 207-Memory initialization error on Memory Board 5 DIMM 7. The operating system may not have access to all of the memory installed in the system..         
          #    POST Error: 207-Invalid Memory Configuration-Mismatched DIMMs within DIMM Bank Memory in Bank A Not Utilized..
          10 => "POST Messages",
          11 => "Power Subsystem",
          13 => "ASR",
          # 14 Automatic Operating System Shutdown Initiated Due to Overheat Condition
          #    Automatic Operating System Shutdown Initiated Due to Fan Failure
          #    Blue Screen Trap (BugCheck, STOP: 0x00000050 (0x9CB2C5B4, 0x00000001, 0x00000004, 0x00000000))
          #    Operating System failure (BugCheck, STOP: 0x000000AB (0x00000005, 0x00000488, 0x00000000, 0x00000002))
          14 => "OS Class",
          # 15 Unknown Event (Class 15, Code 255)
          #15 => "",
          # 17 Network Adapter Link Down (Slot 0, Port 4)
          #    Network Adapters Redundancy Reduced (Slot 0, Port 1)
          17 => "Network Adapter",
          # 19 Drive Array Device Failure (Slot 0, Bus 2, Bay 4)
          #    Internal SAS Enclosure Device Failure (Bay 1, Box 1, Port 2I, Slot 1)
          #19 => "",
          # 20 An Unrecoverable System Error (NMI) has occurred
          #    Unrecoverable System Error has occurred (Error code 0x01AE0E2F, 0x00000000)
          20 => "Unrecoverable System Error",
          # 32 ROM flashed (New version: 01/09/2008)
          32 => "System Revision",
          # 33 IML Cleared (Administrator)
          #    IML cleared through HP ProLiant Health Agent (cmahealthd)
          #    Insight Diagnostics Note: Physisches Festplattenlaufwerk 5, Controller Steckplatz 0-Diagnosis: Fehlgeschlagen
          33 => "Maintenance Note",
          # 34 New Chassis Connected (Enclosure Address 27AC)
          #    Loss Of Chassis Connectivity (Enclosure Serial Number 8004******)
          #    Server Blade Enclosure Server Blade Inserted (Slot 16, Enclosure Address 0000)
          #34 => "",
      },
      cpqHeEventLogEntrySeverityValue => {
          2 => "informational",
          3 => "infoWithAlert",
          6 => "repaired",
          9 => "caution",
          15 => "critical",
      },
      # Time 
      # 07 D8 09 02 11 11
  };
  # INDEX { cpqHeEventLogEntryNumber }
  foreach ($self->get_entries($oids, 'cpqHeEventLogEntry')) {
    if ($_->{cpqHeEventLogInitialTime} =~ /^(([0-9a-fA-F]{2})( [0-9a-fA-F]{2})*)\s*$/) {
      $_->{cpqHeEventLogInitialTime} =~ s/ //;
      my  ($year, $month, $day, $hour, $min) = map { hex($_) } split(/\s+/, $_->{cpqHeEventLogInitialTime});
      if ($year == 0) {
        $_->{cpqHeEventLogInitialTime} = 0;
      } else {
        eval {
          $_->{cpqHeEventLogInitialTime} = timelocal(0, $min, $hour, $day, $month - 1, $year);
        };
        if ($@) {
          $_->{cpqHeEventLogInitialTime} = 0;
        }
      }
    } elsif ($_->{cpqHeEventLogInitialTime} =~ /^0x([0-9a-fA-F]{4})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/) {
      my  ($year, $month, $day, $hour, $min) = map { hex($_) } ($1, $2, $3, $4, $5);
      if ($year == 0) {
        $_->{cpqHeEventLogInitialTime} = 0;
      } else {
        eval {
          $_->{cpqHeEventLogInitialTime} = timelocal(0, $min, $hour, $day, $month - 1, $year);
        };
        if ($@) {
          $_->{cpqHeEventLogInitialTime} = 0;
        }
      }
    } elsif ($_->{cpqHeEventLogInitialTime} =~ /^\0\0\0\0\0\0/) {
      $_->{cpqHeEventLogInitialTime} = 0;
    }
    if ($_->{cpqHeEventLogUpdateTime} =~ /^(([0-9a-fA-F]{2})( [0-9a-fA-F]{2})*)\s*$/) {
      $_->{cpqHeEventLogUpdateTime} =~ s/ //;
      my  ($year, $month, $day, $hour, $min) = map { hex($_) } split(/\s+/, $_->{cpqHeEventLogUpdateTime});
      if ($year == 0) {
        $_->{cpqHeEventLogUpdateTime} = 0;
      } else {
        eval {
          $_->{cpqHeEventLogUpdateTime} = timelocal(0, $min, $hour, $day, $month - 1, $year);
        };
        if ($@) {
          $_->{cpqHeEventLogUpdateTime} = 0;
        }
      }
    } elsif ($_->{cpqHeEventLogUpdateTime} =~ /^0x([0-9a-fA-F]{4})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/) {
      my  ($year, $month, $day, $hour, $min) = map { hex($_) } ($1, $2, $3, $4, $5);
      if ($year == 0) {
        $_->{cpqHeEventLogUpdateTime} = 0;
      } else {
        eval {
          $_->{cpqHeEventLogUpdateTime} = timelocal(0, $min, $hour, $day, $month - 1, $year);
        };
        if ($@) {
          $_->{cpqHeEventLogUpdateTime} = 0;
        }
      }
    } elsif ($_->{cpqHeEventLogUpdateTime} =~ /^\0\0\0\0\0\0/) {
      $_->{cpqHeEventLogUpdateTime} = 0;
    }
    if ($_->{cpqHeEventLogErrorDesc} =~ /^(([0-9a-fA-F]{2})(\s+[0-9a-fA-F]{2})*)\s*$/) {
      $_->{cpqHeEventLogErrorDesc} = join "", map { chr($_) } map { if (hex($_) > 127) { 20; } else { hex($_) } } split(/\s+/, $_->{cpqHeEventLogErrorDesc});
    }
    push(@{$self->{events}},
        HP::Proliant::Component::EventSubsystem::Event->new(%{$_}));
  }
}

sub overall_check {
  my $self = shift;
  my $result = 0;
  $self->blacklist('oe', '');
  if ($self->{eventsupp} && $self->{eventsupp} eq "supported") {
    if ($self->{eventstatus} eq "ok") {
      $result = 0;
      $self->add_info('eventlog system is ok');
    } else {
      $result = 0;
      $self->add_info(sprintf "eventlog system is %s", $self->{eventstatus});
    }
  } else {
    $result = 0;
    $self->add_info('no event status found');
  }
}


package HP::Proliant::Component::PowersupplySubsystem;
our @ISA = qw(HP::Proliant::Component);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    condition => $params{condition},
    status => $params{status},
    powersupplies => [],
    powerconverters => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  if ($self->{method} eq 'snmp') {
    return HP::Proliant::Component::PowersupplySubsystem::SNMP->new(%params);
  } elsif ($self->{method} eq 'cli') {
    return HP::Proliant::Component::PowersupplySubsystem::CLI->new(%params);
  } else {
    die "unknown method";
  }
  return $self;
}

sub check {
  my $self = shift;
  my $errorfound = 0;
  $self->add_info('checking power supplies');
  if (scalar (@{$self->{powersupplies}}) == 0) {
    #$self->overall_check();
  } else {
    foreach (@{$self->{powersupplies}}) {
      $_->check();
    }
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{powersupplies}}) {
    $_->dump();
  }
}


package HP::Proliant::Component::PowersupplySubsystem::Powersupply;
our @ISA = qw(HP::Proliant::Component::PowersupplySubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqHeFltTolPowerSupplyChassis => $params{cpqHeFltTolPowerSupplyChassis},
    cpqHeFltTolPowerSupplyBay => $params{cpqHeFltTolPowerSupplyBay},
    cpqHeFltTolPowerSupplyPresent => $params{cpqHeFltTolPowerSupplyPresent},
    cpqHeFltTolPowerSupplyCondition => $params{cpqHeFltTolPowerSupplyCondition},
    cpqHeFltTolPowerSupplyRedundant => $params{cpqHeFltTolPowerSupplyRedundant},
    cpqHeFltTolPowerSupplyCapacityUsed => $params{cpqHeFltTolPowerSupplyCapacityUsed} || 0,
    cpqHeFltTolPowerSupplyCapacityMaximum => $params{cpqHeFltTolPowerSupplyCapacityMaximum} || 0,
    blacklisted => 0,
    info => undef,
    extendexinfo => undef,
  };
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('p', $self->{cpqHeFltTolPowerSupplyBay});
  if ($self->{cpqHeFltTolPowerSupplyPresent} eq "present") {
    if ($self->{cpqHeFltTolPowerSupplyCondition} ne "ok") {
      if ($self->{cpqHeFltTolPowerSupplyCondition} eq "other") {
        $self->add_info(sprintf "powersupply %d is missing",
            $self->{cpqHeFltTolPowerSupplyBay});
      } else {
        $self->add_info(sprintf "powersupply %d needs attention (%s)",
            $self->{cpqHeFltTolPowerSupplyBay},
            $self->{cpqHeFltTolPowerSupplyCondition});
      }
      $self->add_message(CRITICAL, $self->{info});
    } else {
      $self->add_info(sprintf "powersupply %d is %s",
          $self->{cpqHeFltTolPowerSupplyBay},
          $self->{cpqHeFltTolPowerSupplyCondition});
    }
    $self->add_extendedinfo(sprintf "ps_%s=%s",
        $self->{cpqHeFltTolPowerSupplyBay},
        $self->{cpqHeFltTolPowerSupplyCondition});
    if ($self->{cpqHeFltTolPowerSupplyCapacityUsed} &&
        $self->{cpqHeFltTolPowerSupplyCapacityMaximum}) {
      if ($self->{runtime}->{options}->{perfdata}) {
        $self->{runtime}->{plugin}->add_perfdata(
            label => sprintf("pc_%s", $self->{cpqHeFltTolPowerSupplyBay}),
            value => $self->{cpqHeFltTolPowerSupplyCapacityUsed},
            warning => $self->{cpqHeFltTolPowerSupplyCapacityMaximum},
            critical => $self->{cpqHeFltTolPowerSupplyCapacityMaximum}
        );
      }
    } elsif ($self->{cpqHeFltTolPowerSupplyCapacityUsed}) {
      if ($self->{runtime}->{options}->{perfdata}) {
        $self->{runtime}->{plugin}->add_perfdata(
            label => sprintf("pc_%s", $self->{cpqHeFltTolPowerSupplyBay}),
            value => $self->{cpqHeFltTolPowerSupplyCapacityUsed}
        );
      }
    }
  } else {
    $self->add_info(sprintf "powersupply %d is %s",
        $self->{cpqHeFltTolPowerSupplyBay},
        $self->{cpqHeFltTolPowerSupplyPresent});
    $self->add_extendedinfo(sprintf "ps_%s=%s",
        $self->{cpqHeFltTolPowerSupplyBay},
        $self->{cpqHeFltTolPowerSupplyPresent});
  }
}


sub dump {
  my $self = shift;
  printf "[PS_%s]\n", $self->{cpqHeFltTolPowerSupplyBay};
  foreach (qw(cpqHeFltTolPowerSupplyBay cpqHeFltTolPowerSupplyChassis
      cpqHeFltTolPowerSupplyPresent cpqHeFltTolPowerSupplyCondition
      cpqHeFltTolPowerSupplyRedundant cpqHeFltTolPowerSupplyCapacityUsed
      cpqHeFltTolPowerSupplyCapacityMaximum)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "info: %s\n\n", $self->{info};
}


package HP::Proliant::Component::PowersupplySubsystem::Powerconverter;
our @ISA = qw(HP::Proliant::Component::PowersupplySubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},

    cpqHePowerConvEntry => $params{cpqHePowerConvEntry},
    cpqHePowerConvChassis => $params{cpqHePowerConvChassis},
    cpqHePowerConvIndex => $params{cpqHePowerConvIndex},
    cpqHePowerConvPresent => $params{cpqHePowerConvPresent},
    cpqHePowerConvRedundant => $params{cpqHePowerConvRedundant},
    cpqHePowerConvCondition => $params{cpqHePowerConvCondition},
    cpqHePowerConvHwLocation => $params{cpqHePowerConvHwLocation},
    blacklisted => 0,
    info => undef,
    extendexinfo => undef,
  };
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('pc', $self->{cpqHePowerConvIndex});
  if ($self->{cpqHePowerConvPresent} eq "present") {
    if ($self->{cpqHePowerConvCondition} ne "ok") {
      if ($self->{cpqHePowerConvCondition} eq "other") {
        $self->add_info(sprintf "powerconverter %d is missing",
            $self->{cpqHePowerConvIndex});
      } else {
        $self->add_info(sprintf "powerconverter %d needs attention (%s)",
            $self->{cpqHePowerConvIndex},
            $self->{cpqHePowerConvCondition});
      }
      $self->add_message(CRITICAL, $self->{info});
    } else {
      $self->add_info(sprintf "powerconverter %d is %s",
          $self->{cpqHePowerConvIndex},
          $self->{cpqHePowerConvCondition});
    }
    $self->add_extendedinfo(sprintf "pc_%s=%s",
        $self->{cpqHePowerConvIndex},
        $self->{cpqHePowerConvCondition});
  } else {
    $self->add_info(sprintf "powerconverter %d is %s",
        $self->{cpqHePowerConvIndex},
        $self->{cpqHePowerConvPresent});
    $self->add_extendedinfo(sprintf "pc_%s=%s",
        $self->{cpqHePowerConvIndex},
        $self->{cpqHePowerConvPresent});
  }
}


sub dump {
  my $self = shift;
  printf "[PS_%s]\n", ($self->{cpqHePowerConvChassis} ? $self->{cpqHePowerConvChassis}.":" : "").$self->{cpqHePowerConvIndex};
  foreach (qw(cpqHePowerConvIndex cpqHePowerConvPresent cpqHePowerConvRedundant cpqHePowerConvCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "info: %s\n\n", $self->{info};
}



package HP::Proliant::Component::PowersupplySubsystem::CLI;
our @ISA = qw(HP::Proliant::Component::PowersupplySubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    powersupplies => [],
    powerconverters => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init(%params);
  return $self;
}

sub init {
  my $self = shift;
  my %params = @_;
  my %tmpps = (
    runtime => $self->{runtime},
    cpqHeFltTolPowerSupplyChassis => 1,
  );
  my $inblock = 0;
  foreach (grep(/^powersupply/, split(/\n/, $self->{rawdata}))) {
    s/^powersupply\s*//g;
    if (/^Power supply #(\d+)/) {
      if ($inblock) {
        $inblock = 0;
        push(@{$self->{powersupplies}},
            HP::Proliant::Component::PowersupplySubsystem::Powersupply->new(%tmpps));
        %tmpps = (
          runtime => $self->{runtime},
          cpqHeFltTolPowerSupplyChassis => 1,
        );
      }
      $tmpps{cpqHeFltTolPowerSupplyBay} = $1;
      $inblock = 1;
    } elsif (/\s*Present\s+:\s+(\w+)/) {
      $tmpps{cpqHeFltTolPowerSupplyPresent} = lc $1 eq 'yes' ? 'present' :
          lc $1 eq 'no' ? 'absent': 'other';
    } elsif (/\s*Redundant\s*:\s+(\w+)/) {
      $tmpps{cpqHeFltTolPowerSupplyRedundant} = lc $1 eq 'yes' ? 'redundant' :
          lc $1 eq 'no' ? 'notRedundant' : 'other';
    } elsif (/\s*Condition\s*:\s+(\w+)/) {
      $tmpps{cpqHeFltTolPowerSupplyCondition} = lc $1;
    } elsif (/\s*Power\s*:\s+(\d+)/) {
      $tmpps{cpqHeFltTolPowerSupplyCapacityUsed} = $1;
    } elsif (/\s*Power Supply not present/) {
      $tmpps{cpqHeFltTolPowerSupplyPresent} = "absent";
      $tmpps{cpqHeFltTolPowerSupplyCondition} = "other";
      $tmpps{cpqHeFltTolPowerSupplyRedundant} = "notRedundant";
    } elsif (/^\s*$/) {
      if ($inblock) {
        $inblock = 0;
        push(@{$self->{powersupplies}},
            HP::Proliant::Component::PowersupplySubsystem::Powersupply->new(%tmpps));
        %tmpps = (
          runtime => $self->{runtime},
          cpqHeFltTolPowerSupplyChassis => 1,
        );
      }
    }
  }
  if ($inblock) {
    push(@{$self->{powersupplies}},
        HP::Proliant::Component::PowersupplySubsystem::Powersupply->new(%tmpps));
    %tmpps = (
      runtime => $params{runtime},
    );
  }
}


package HP::Proliant::Component::PowersupplySubsystem::SNMP;
our @ISA = qw(HP::Proliant::Component::PowersupplySubsystem
    HP::Proliant::Component::SNMP);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    powersupplies => [],
    powerconverters => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init(%params);
  return $self;
}

sub init {
  my $self = shift;
  my %params = @_;
  my $snmpwalk = $self->{rawdata};
  my $oids = {
      cpqHeFltTolPowerSupplyEntry => "1.3.6.1.4.1.232.6.2.9.3.1",
      cpqHeFltTolPowerSupplyChassis => "1.3.6.1.4.1.232.6.2.9.3.1.1",
      cpqHeFltTolPowerSupplyBay => "1.3.6.1.4.1.232.6.2.9.3.1.2",
      cpqHeFltTolPowerSupplyPresent => "1.3.6.1.4.1.232.6.2.9.3.1.3",
      cpqHeFltTolPowerSupplyCondition => "1.3.6.1.4.1.232.6.2.9.3.1.4",
      cpqHeFltTolPowerSupplyRedundant => "1.3.6.1.4.1.232.6.2.9.3.1.9",
      cpqHeFltTolPowerSupplyPresentValue => {
          1 => "other",
          2 => "absent",
          3 => "present",
      },
      cpqHeFltTolPowerSupplyConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
      cpqHeFltTolPowerSupplyCapacityUsed => '1.3.6.1.4.1.232.6.2.9.3.1.7',
      cpqHeFltTolPowerSupplyCapacityMaximum => '1.3.6.1.4.1.232.6.2.9.3.1.8',
      cpqHeFltTolPowerSupplyRedundantValue => {
          1 => "other",
          2 => "notRedundant",
          3 => "redundant",
      },
  };

  # INDEX { cpqHeFltTolPowerSupplyChassis, cpqHeFltTolPowerSupplyBay }
  foreach ($self->get_entries($oids, 'cpqHeFltTolPowerSupplyEntry')) {
    push(@{$self->{powersupplies}},
        HP::Proliant::Component::PowersupplySubsystem::Powersupply->new(%{$_}));
  }

  $oids = {
      cpqHePowerConvEntry => "1.3.6.1.4.1.232.6.2.13.3.1",
      cpqHePowerConvChassis => "1.3.6.1.4.1.232.6.2.13.3.1.1",
      cpqHePowerConvIndex => "1.3.6.1.4.1.232.6.2.13.3.1.2",
      cpqHePowerConvPresent => "1.3.6.1.4.1.232.6.2.13.3.1.3",
      cpqHePowerConvRedundant => "1.3.6.1.4.1.232.6.2.13.3.1.6",
      cpqHePowerConvCondition => "1.3.6.1.4.1.232.6.2.13.3.1.8",
      cpqHePowerConvPresentValue => {
          1 => "other",
          2 => "absent",
          3 => "present",
      },
      cpqHePowerConvRedundantValue => {
          1 => "other",
          2 => "notRedundant",
          3 => "redundant",
      },
      cpqHePowerConvConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
      cpqHePowerConvHwLocation => "1.3.6.1.4.1.232.6.2.13.3.1.9",
  };

  # INDEX { cpqHePowerConvChassis cpqHePowerConvIndex }
  foreach ($self->get_entries($oids, 'cpqHePowerConvEntry')) {
    push(@{$self->{powerconverters}},
        HP::Proliant::Component::PowersupplySubsystem::Powerconverter->new(%{$_}));
  }
  # keine ahnung, was man damit machen kann

}

package HP::Proliant::Component::TemperatureSubsystem;
our @ISA = qw(HP::Proliant::Component);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
################################## custom_thresholds
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    condition => $params{condition},
    status => $params{status},
    temperatures => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  if ($params{runtime}->{options}->{customthresholds}) {
    if (-f $params{runtime}->{options}->{customthresholds}) {
      $params{runtime}->{options}->{customthresholds} = 
          do { local (@ARGV, $/) = 
              $params{runtime}->{options}->{customthresholds}; <> };
    }
    foreach my $ct_items
        (split(/\//, $params{runtime}->{options}->{customthresholds})) {
      if ($ct_items =~ /^(\d+):(\d+)$/) {
        $params{runtime}->{options}->{thresholds}->{$1} = $2;
      } else {
        die sprintf "invalid threshold %s", $ct_items;
      }
    }
  }
  if ($self->{method} eq 'snmp') {
    return HP::Proliant::Component::TemperatureSubsystem::SNMP->new(%params);
  } elsif ($self->{method} eq 'cli') {
    return HP::Proliant::Component::TemperatureSubsystem::CLI->new(%params);
  } else {
    die "unknown method";
  }
  return $self;
}

sub check {
  my $self = shift;
  my $errorfound = 0;
  $self->add_info('checking temperatures');
  if (scalar (@{$self->{temperatures}}) == 0) {
    #$self->overall_check(); 
    $self->add_info('no temperatures found');
  } else {
    foreach (sort { $a->{cpqHeTemperatureIndex} <=> $b->{cpqHeTemperatureIndex}}
        @{$self->{temperatures}}) {
      $_->check();
    }
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{temperatures}}) {
    $_->dump();
  }
}


package HP::Proliant::Component::TemperatureSubsystem::Temperature;
our @ISA = qw(HP::Proliant::Component::TemperatureSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqHeTemperatureChassis => $params{cpqHeTemperatureChassis},
    cpqHeTemperatureIndex => $params{cpqHeTemperatureIndex},
    cpqHeTemperatureLocale => $params{cpqHeTemperatureLocale},
    cpqHeTemperatureCelsius => $params{cpqHeTemperatureCelsius},
    cpqHeTemperatureThresholdCelsius => $params{cpqHeTemperatureThresholdCelsius},
    cpqHeTemperatureCondition => $params{cpqHeTemperatureCondition},
    cpqHeTemperatureThresholdType => $params{cpqHeTemperatureThresholdType} || "unknown",
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  if ($params{runtime}->{options}->{celsius}) {
    $self->{cpqHeTemperatureUnits} = 'C';
    $self->{cpqHeTemperature} = $self->{cpqHeTemperatureCelsius};
    $self->{cpqHeTemperatureThreshold} = 
        $self->{cpqHeTemperatureThresholdCelsius};
  } else {
    $self->{cpqHeTemperatureUnits} = 'F';
    $self->{cpqHeTemperature} = 
        (($self->{cpqHeTemperatureCelsius} * 9) / 5) + 32;
    $self->{cpqHeTemperatureThreshold} = 
        (($self->{cpqHeTemperatureThresholdCelsius} * 9) / 5) + 32;
  }
  my $index = $self->{cpqHeTemperatureIndex};
  if (exists $params{runtime}->{options}->{thresholds}->{$index}) {
    $self->{cpqHeTemperatureThreshold} = 
        $params{runtime}->{options}->{thresholds}->{$index};
        
  }
  if ($self->{cpqHeTemperatureThresholdCelsius} == -99) {
    bless $self, 'HP::Proliant::Component::TemperatureSubsystem::SoSTemperature';
  } elsif ($self->{cpqHeTemperatureThresholdCelsius} == 0) {
    # taucht auf, seit man gen8 ueber das ilo abfragen kann
    bless $self, 'HP::Proliant::Component::TemperatureSubsystem::SoSTemperature';
  }
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('t', $self->{cpqHeTemperatureIndex});
  if ($self->{cpqHeTemperature} > $self->{cpqHeTemperatureThreshold}) {
    $self->add_info(sprintf "%d %s temperature too high (%d%s, %d max)",
        $self->{cpqHeTemperatureIndex}, $self->{cpqHeTemperatureLocale},
        $self->{cpqHeTemperature}, $self->{cpqHeTemperatureUnits},
        $self->{cpqHeTemperatureThreshold});
    $self->add_message(CRITICAL, $self->{info});
  } elsif ($self->{cpqHeTemperature} < 0) {
    # #21 SCSI_BACKPLANE_ZONE -1C/31F 60C/140F OK - can't be true
    $self->add_info(sprintf "%d %s temperature too low (%d%s)",
        $self->{cpqHeTemperatureIndex}, $self->{cpqHeTemperatureLocale},
        $self->{cpqHeTemperature}, $self->{cpqHeTemperatureUnits});
    $self->add_message(CRITICAL, $self->{info});
  } else {
    $self->add_info(sprintf "%d %s temperature is %d%s (%d max)",
        $self->{cpqHeTemperatureIndex}, $self->{cpqHeTemperatureLocale}, 
        $self->{cpqHeTemperature}, $self->{cpqHeTemperatureUnits},
        $self->{cpqHeTemperatureThreshold});
  }
  if ($self->{runtime}->{options}->{perfdata} == 2) {
    $self->{runtime}->{plugin}->add_perfdata(
        label => sprintf('temp_%s', $self->{cpqHeTemperatureIndex}),
        value => $self->{cpqHeTemperature},
        warning => $self->{cpqHeTemperatureThreshold},
        critical => $self->{cpqHeTemperatureThreshold}
    );
  } elsif ($self->{runtime}->{options}->{perfdata} == 1) {
    $self->{runtime}->{plugin}->add_perfdata(
        label => sprintf('temp_%s_%s', $self->{cpqHeTemperatureIndex},
            $self->{cpqHeTemperatureLocale}),
        value => $self->{cpqHeTemperature},
        warning => $self->{cpqHeTemperatureThreshold},
        critical => $self->{cpqHeTemperatureThreshold}
    );
  } 
  $self->add_extendedinfo(sprintf "temp_%s=%d",
      $self->{cpqHeTemperatureIndex},
      $self->{cpqHeTemperature});
}

sub dump { 
  my $self = shift;
  printf "[TEMP_%s]\n", $self->{cpqHeTemperatureIndex};
  foreach (qw(cpqHeTemperatureChassis cpqHeTemperatureIndex 
      cpqHeTemperatureLocale cpqHeTemperatureCelsius cpqHeTemperatureThreshold
      cpqHeTemperatureThresholdType cpqHeTemperatureCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "info: %s\n\n", $self->{info};
}


package HP::Proliant::Component::TemperatureSubsystem::SoSTemperature;
our @ISA = qw(HP::Proliant::Component::TemperatureSubsystem::Temperature);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub check {
  my $self = shift;
  $self->blacklist('t', $self->{cpqHeTemperatureIndex});
  $self->add_info(sprintf "%d %s temperature is %d%s (no thresh.)",
      $self->{cpqHeTemperatureIndex}, $self->{cpqHeTemperatureLocale}, 
      $self->{cpqHeTemperature}, $self->{cpqHeTemperatureUnits});
  if ($self->{runtime}->{options}->{perfdata} == 2) {
    $self->{runtime}->{plugin}->add_perfdata(
        label => sprintf('temp_%s', $self->{cpqHeTemperatureIndex}),
        value => $self->{cpqHeTemperature},
    );
  } elsif ($self->{runtime}->{options}->{perfdata} == 1) {
    $self->{runtime}->{plugin}->add_perfdata(
        label => sprintf('temp_%s_%s', $self->{cpqHeTemperatureIndex},
            $self->{cpqHeTemperatureLocale}),
        value => $self->{cpqHeTemperature},
    );
  } 
  $self->add_extendedinfo(sprintf "temp_%s=%d",
      $self->{cpqHeTemperatureIndex},
      $self->{cpqHeTemperature});
}



package HP::Proliant::Component::TemperatureSubsystem::CLI;
our @ISA = qw(HP::Proliant::Component::TemperatureSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    temperatures => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init(%params);
  return $self;
}

sub init {
  my $self = shift;
  my %params = @_;
  my $tempcnt = 1;
  foreach (grep(/^temp/, split(/\n/, $params{rawdata}))) {
    s/^temp\s*//g;
    if (/^#(\d+)\s+([\w_\/\-#]+)\s+(-*\d+)C\/(\d+)F\s+(\d+)C\/(\d+)F/) {
      my %params = ();
      $params{runtime} = $self->{runtime};
      $params{cpqHeTemperatureChassis} = 1;
      $params{cpqHeTemperatureIndex} = $1;
      $params{cpqHeTemperatureLocale} = lc $2;
      $params{cpqHeTemperatureCelsius} = $3;
      $params{cpqHeTemperatureThresholdCelsius} = $5;
      $params{cpqHeTemperatureCondition} = 'unknown';
      push(@{$self->{temperatures}},
          HP::Proliant::Component::TemperatureSubsystem::Temperature->new(
              %params));
    } elsif (/^#(\d+)\s+([\w_\/\-#]+)\s+\-\s+(\d+)C\/(\d+)F/) {
      # #3        CPU#2                -       0C/0F
      $self->trace(2, sprintf "skipping temperature %s", $_);
    } elsif (/^#(\d+)\s+([\w_\/\-#]+)\s+(\d+)C\/(\d+)F\s+\-/) {
      # #3        CPU#2                0C/0F       -
      $self->trace(2, sprintf "skipping temperature %s", $_);
    } elsif (/^#(\d+)\s+([\w_\/\-#]+)\s+\-\s+\-/) {
      # #3        CPU#2                -       -
      $self->trace(2, sprintf "skipping temperature %s", $_);
    } elsif (/^#(\d+)/) {
      $self->trace(0, sprintf "send this to lausser: %s", $_);
    }
  }
}


package HP::Proliant::Component::TemperatureSubsystem::SNMP;
our @ISA = qw(HP::Proliant::Component::TemperatureSubsystem
    HP::Proliant::Component::SNMP);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    temperatures => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->overall_init(%params);
  $self->init(%params);
  return $self;
}

sub overall_init {
  my $self = shift;
  my %params = @_;
  my $snmpwalk = $params{rawdata};
  # overall
  my $cpqHeThermalTempStatus  = '1.3.6.1.4.1.232.6.2.6.3.0';
  my $cpqHeThermalTempStatusValue = {
    1 => 'other',
    2 => 'ok',
    3 => 'degraded',
    4 => 'failed',
  };
  $self->{tempstatus} = lc SNMP::Utils::get_object_value(
      $snmpwalk, $cpqHeThermalTempStatus,
      $cpqHeThermalTempStatusValue);
  $self->{tempstatus} |= lc $self->{tempstatus};
}

sub init {
  my $self = shift;
  my %params = @_;
  my $snmpwalk = $self->{rawdata};
  my $oids = {
      cpqHeTemperatureEntry => "1.3.6.1.4.1.232.6.2.6.8.1",
      cpqHeTemperatureChassis => "1.3.6.1.4.1.232.6.2.6.8.1.1",
      cpqHeTemperatureIndex => "1.3.6.1.4.1.232.6.2.6.8.1.2",
      cpqHeTemperatureLocale => "1.3.6.1.4.1.232.6.2.6.8.1.3",
      cpqHeTemperatureCelsius => "1.3.6.1.4.1.232.6.2.6.8.1.4",
      cpqHeTemperatureThresholdCelsius => "1.3.6.1.4.1.232.6.2.6.8.1.5",
      cpqHeTemperatureCondition => "1.3.6.1.4.1.232.6.2.6.8.1.6",
      cpqHeTemperatureThresholdType => "1.3.6.1.4.1.232.6.2.6.8.1.7",
      cpqHeTemperatureLocaleValue => {
          1 => "other",
          2 => "unknown",
          3 => "system",
          4 => "systemBoard",
          5 => "ioBoard",
          6 => "cpu",
          7 => "memory",
          8 => "storage",
          9 => "removableMedia",
          10 => "powerSupply",
          11 => "ambient",
          12 => "chassis",
          13 => "bridgeCard",
      },
      cpqHeTemperatureConditionValue => {
          1 => 'other',
          2 => 'ok',
          3 => 'degraded',
          4 => 'failed',
      },
      cpqHeTemperatureThresholdTypeValue => {
          1 => 'other',
          5 => 'blowout',
          9 => 'caution',
          15 => 'critical',
      },
  };
  # INDEX { cpqHeTemperatureChassis, cpqHeTemperatureIndex }
  foreach ($self->get_entries($oids, 'cpqHeTemperatureEntry')) {
    # sieht aus, als wurden die gar nicht existieren.
    # im ilo4 werden sie als n/a angezeigt
    next if $_->{cpqHeTemperatureThresholdType} eq "caution" && $_->{cpqHeTemperatureThresholdCelsius} == 0;
    push(@{$self->{temperatures}},
        HP::Proliant::Component::TemperatureSubsystem::Temperature->new(%{$_}));
  }
}

sub overall_check {
  my $self = shift;
  my $result = 0;
  $self->blacklist('ots', '');
  if ($self->{tempstatus}) {
    if ($self->{tempstatus} eq "ok") {
      $result = 0;
      $self->add_info('all temp sensors are within normal operating range');
    } elsif ($self->{tempstatus} eq "degraded") {
      $result = 1;
      $self->add_info('a temp sensor is outside of normal operating range');
    } elsif ($self->{tempstatus} eq "failed") {
      $result = 2;
      $self->add_info('a temp sensor detects a condition that could permanently
damage the system');
    } elsif ($self->{tempstatus} eq "other") {
      $result = 0;
      $self->add_info('temp sensing is not supported by this system or driver');
    }
  } else {
    $result = 0;
    $self->add_info('no global temp status found');
  }
}


package HP::Proliant::Component::CpuSubsystem;
our @ISA = qw(HP::Proliant::Component);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
################################## scrapiron ##########
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    condition => $params{condition},
    status => $params{status},
    cpus => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  if ($self->{method} eq 'snmp') {
    return HP::Proliant::Component::CpuSubsystem::SNMP->new(%params);
  } elsif ($self->{method} eq 'cli') {
    return HP::Proliant::Component::CpuSubsystem::CLI->new(%params);
  } else {
    die "unknown method";
  }
  return $self;
}

sub check {
  my $self = shift;
  my $errorfound = 0;
  $self->add_info('checking cpus');
  if (scalar (@{$self->{cpus}}) == 0) {
    # sachen gibts.....
  #  $self->overall_check(); # sowas ist mir nur einmal untergekommen
  } else {
    foreach (@{$self->{cpus}}) {
      $_->check();
    }
  }
}

sub num_cpus {
  my $self = shift;
  return scalar @{$self->{cpus}};
}

sub dump {
  my $self = shift;
  foreach (@{$self->{cpus}}) {
    $_->dump();
  }
}


package HP::Proliant::Component::CpuSubsystem::Cpu;
our @ISA = qw(HP::Proliant::Component::CpuSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqSeCpuSlot => $params{cpqSeCpuSlot},
    cpqSeCpuUnitIndex => $params{cpqSeCpuUnitIndex},
    cpqSeCpuName => $params{cpqSeCpuName},
    cpqSeCpuStatus => $params{cpqSeCpuStatus},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('c', $self->{cpqSeCpuUnitIndex});
  if ($self->{cpqSeCpuStatus} ne "ok") {
    if ($self->{runtime}->{options}{scrapiron} &&
        ($self->{cpqSeCpuStatus} eq "unknown")) {
      $self->add_info(sprintf "cpu %d probably ok (%s)",
          $self->{cpqSeCpuUnitIndex}, $self->{cpqSeCpuStatus});
    } else {
      $self->add_info(sprintf "cpu %d needs attention (%s)",
          $self->{cpqSeCpuUnitIndex}, $self->{cpqSeCpuStatus});
      $self->add_message(CRITICAL, $self->{info});
    }
  } else {
    $self->add_info(sprintf "cpu %d is %s", 
        $self->{cpqSeCpuUnitIndex}, $self->{cpqSeCpuStatus});
  }
  $self->add_extendedinfo(sprintf "cpu_%s=%s",
      $self->{cpqSeCpuUnitIndex}, $self->{cpqSeCpuStatus});
}

sub dump {
  my $self = shift;
  printf "[CPU_%s]\n", $self->{cpqSeCpuUnitIndex};
  foreach (qw(cpqSeCpuSlot cpqSeCpuUnitIndex cpqSeCpuName cpqSeCpuStatus)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "info: %s\n", $self->{info};
  printf "\n";
}
package HP::Proliant::Component::CpuSubsystem::CLI;
our @ISA = qw(HP::Proliant::Component::CpuSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    cpus => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init(%params);
  return $self;
}

sub init {
  my $self = shift;
  my %params = @_;
  my %tmpcpu = (
    runtime => $params{runtime},
  );
  my $inblock = 0;
  foreach (grep(/^server/, split(/\n/, $self->{rawdata}))) {
    if (/Processor:\s+(\d+)/) {
      $tmpcpu{cpqSeCpuUnitIndex} = $1;
      $inblock = 1;
    } elsif (/Name\s*:\s+(.+?)\s*$/) {
      $tmpcpu{cpqSeCpuName} = $1;
    } elsif (/Status\s*:\s+(.+?)\s*$/) {
      $tmpcpu{cpqSeCpuStatus} = lc $1;
    } elsif (/Socket\s*:\s+(.+?)\s*$/) {
      $tmpcpu{cpqSeCpuSlot} = $1;
    } elsif (/^server\s*$/) {
      if ($inblock) {
        $inblock = 0;
        push(@{$self->{cpus}},
            HP::Proliant::Component::CpuSubsystem::Cpu->new(%tmpcpu));
        %tmpcpu = (
          runtime => $params{runtime},
        );
      }
    }
  }
  if ($inblock) {
    push(@{$self->{cpus}},
        HP::Proliant::Component::CpuSubsystem::Cpu->new(%tmpcpu));
  }
}


package HP::Proliant::Component::CpuSubsystem::SNMP;
our @ISA = qw(HP::Proliant::Component::CpuSubsystem
    HP::Proliant::Component::SNMP);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    cpus => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init();
  return $self;
}

sub init {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};
  # CPQSTDEQ-MIB
  my $oids = {
      cpqSeCpuEntry => '1.3.6.1.4.1.232.1.2.2.1.1',
      cpqSeCpuUnitIndex => '1.3.6.1.4.1.232.1.2.2.1.1.1',
      cpqSeCpuSlot => '1.3.6.1.4.1.232.1.2.2.1.1.2',
      cpqSeCpuName => '1.3.6.1.4.1.232.1.2.2.1.1.3',
      cpqSeCpuStatus => '1.3.6.1.4.1.232.1.2.2.1.1.6',
      cpqSeCpuStatusValue => {
          1 => "unknown",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
          5 => "disabled",
      },
  };

  # INDEX { cpqSeCpuUnitIndex }
  foreach ($self->get_entries($oids, 'cpqSeCpuEntry')) {
    push(@{$self->{cpus}},
        HP::Proliant::Component::CpuSubsystem::Cpu->new(%{$_}));
  }
}


package HP::Proliant::Component::FanSubsystem;
our @ISA = qw(HP::Proliant::Component);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
################################## fan_redundancy ##########
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    condition => $params{condition},
    status => $params{status},
    fans => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  if ($self->{method} eq 'snmp') {
    return HP::Proliant::Component::FanSubsystem::SNMP->new(%params);
  } elsif ($self->{method} eq 'cli') {
    return HP::Proliant::Component::FanSubsystem::CLI->new(%params);
  } else {
    die 'unknown method';
  }
  return $self;
}

sub check {
  my $self = shift;
  my $errorfound = 0;
  $self->add_info('checking fans');
  $self->blacklist('ff', '');
  if (scalar (@{$self->{fans}}) == 0) {
    $self->overall_check(); # sowas ist mir nur einmal untergekommen
    # die maschine hatte alles in allem nur 2 oids (cpqHeFltTolFanChassis)
    # SNMPv2-SMI::enterprises.232.6.2.6.7.1.1.0.1 = INTEGER: 0
    # SNMPv2-SMI::enterprises.232.6.2.6.7.1.1.0.2 = INTEGER: 0
  } else {
    my $overallhealth = $self->overall_check(); 
    foreach (@{$self->{fans}}) {
      $_->{overallhealth} = $overallhealth;
      $_->check();
    }
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{fans}}) {
    $_->dump();
  }
}

sub get_fan_by_index {
  my $self = shift;
  my $index;
  foreach (@{$self->{fans}}) {
    return $_ if exists $_->{cpqHeFltTolFanIndex} && 
        $_->{cpqHeFltTolFanIndex} == $index;
  }
  return undef;
}


package HP::Proliant::Component::FanSubsystem::Fan;
our @ISA = qw(HP::Proliant::Component::FanSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  if (exists $params{cpqHeFltTolFanRedundant}) {
    return HP::Proliant::Component::FanSubsystem::Fan::FTol->new(%params);
  } else {
    return HP::Proliant::Component::FanSubsystem::Fan::Thermal->new(%params);
  }
}


package HP::Proliant::Component::FanSubsystem::Fan::FTol;
our @ISA = qw(HP::Proliant::Component::FanSubsystem::Fan);


use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqHeFltTolFanChassis => $params{cpqHeFltTolFanChassis},
    cpqHeFltTolFanIndex => $params{cpqHeFltTolFanIndex},
    cpqHeFltTolFanLocale => $params{cpqHeFltTolFanLocale},
    cpqHeFltTolFanPresent => $params{cpqHeFltTolFanPresent},
    cpqHeFltTolFanType => $params{cpqHeFltTolFanType},
    cpqHeFltTolFanSpeed => $params{cpqHeFltTolFanSpeed},
    cpqHeFltTolFanRedundant => $params{cpqHeFltTolFanRedundant},
    cpqHeFltTolFanRedundantPartner => $params{cpqHeFltTolFanRedundantPartner},
    cpqHeFltTolFanCondition => $params{cpqHeFltTolFanCondition},
    cpqHeFltTolFanPctMax => $params{cpqHeFltTolFanPctMax}, #!!!
    cpqHeFltTolFanHotPlug => $params{cpqHeFltTolFanHotPlug}, #!!!
    partner => $params{partner},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  if (($self->{cpqHeFltTolFanRedundant} eq 'redundant') &&
     ((! defined $self->{cpqHeFltTolFanRedundantPartner}) ||
     (! $self->{cpqHeFltTolFanRedundantPartner}))) {
    $self->{cpqHeFltTolFanRedundant} = 'notRedundant';
      # cpqHeFltTolFanRedundantPartner=0: partner not avail
  }
  return $self;
} 

sub check { 
  my $self = shift;
  $self->blacklist('f', $self->{cpqHeFltTolFanIndex});
  $self->add_info(sprintf 'fan %d is %s, speed is %s, pctmax is %s%%, '.
      'location is %s, redundance is %s, partner is %s',
      $self->{cpqHeFltTolFanIndex}, $self->{cpqHeFltTolFanPresent},
      $self->{cpqHeFltTolFanSpeed}, $self->{cpqHeFltTolFanPctMax},
      $self->{cpqHeFltTolFanLocale}, $self->{cpqHeFltTolFanRedundant},
      $self->{cpqHeFltTolFanRedundantPartner});
  $self->add_extendedinfo(sprintf 'fan_%s=%d%%',
      $self->{cpqHeFltTolFanIndex}, $self->{cpqHeFltTolFanPctMax});
  if ($self->{cpqHeFltTolFanPresent} eq 'present') {
    if ($self->{cpqHeFltTolFanSpeed} eq 'high') { 
      $self->add_info(sprintf 'fan %d (%s) runs at high speed',
          $self->{cpqHeFltTolFanIndex}, $self->{cpqHeFltTolFanLocale});
      $self->add_message(CRITICAL, $self->{info});
    } elsif ($self->{cpqHeFltTolFanSpeed} ne 'normal') {
      $self->add_info(sprintf 'fan %d (%s) needs attention',
          $self->{cpqHeFltTolFanIndex}, $self->{cpqHeFltTolFanLocale});
      $self->add_message(CRITICAL, $self->{info});
    }
    if ($self->{cpqHeFltTolFanCondition} eq 'failed') {
      $self->add_info(sprintf 'fan %d (%s) failed',
          $self->{cpqHeFltTolFanIndex}, $self->{cpqHeFltTolFanLocale});
      $self->add_message(CRITICAL, $self->{info});
    } elsif ($self->{cpqHeFltTolFanCondition} eq 'degraded') {
      $self->add_info(sprintf 'fan %d (%s) degraded',
          $self->{cpqHeFltTolFanIndex}, $self->{cpqHeFltTolFanLocale});
      $self->add_message(WARNING, $self->{info});
    } elsif ($self->{cpqHeFltTolFanCondition} ne 'ok' &&
        $self->{cpqHeFltTolFanCondition} ne 'other') {
      $self->add_info(sprintf 'fan %d (%s) is not ok',
          $self->{cpqHeFltTolFanIndex}, $self->{cpqHeFltTolFanLocale});
      $self->add_message(WARNING, $self->{info});
    }
    if ($self->{cpqHeFltTolFanRedundant} eq 'notRedundant') {
      # sieht so aus, als waere notRedundant und partner=0 normal z.b. dl360
      # das duerfte der fall sein, wenn nur eine cpu verbaut wurde und
      # statt einem redundanten paar nur dummies drinstecken.
      # "This specifies if the fan is in a redundant configuration"
      # notRedundant heisst also sowohl nicht redundant wegen ausfall
      # des partners als auch von haus aus nicht redundant ausgelegt
      if ($self->{cpqHeFltTolFanRedundantPartner}) {
        # nicht redundant, hat aber einen partner. da muss man genauer
        # hinschauen
        #if (my $partner = $self->{partner}) {
        #}
        if ($self->{overallhealth}) {
          # da ist sogar das system der meinung, dass etwas faul ist
          if (! $self->{runtime}->{options}->{ignore_fan_redundancy}) {
            $self->add_info(sprintf 'fan %d (%s) is not redundant',
                $self->{cpqHeFltTolFanIndex}, $self->{cpqHeFltTolFanLocale});
            $self->add_message(WARNING, $self->{info});
          }
        } else {
          # das ist wohl so gewollt, dass einzelne fans eingebaut werden,
          # obwohl redundante paerchen vorgesehen sind.
          # scheint davon abzuhaengen, wieviele cpus geordert wurden.
        }
      }
    } elsif ($self->{cpqHeFltTolFanRedundant} eq 'other') {
      #seen on a dl320 g5p with bios from 2008.
      # maybe redundancy is not supported at all
    }
  } elsif ($self->{cpqHeFltTolFanPresent} eq 'failed') { # from cli
    $self->add_info(sprintf 'fan %d (%s) failed',
        $self->{cpqHeFltTolFanIndex}, $self->{cpqHeFltTolFanLocale});
    $self->add_message(CRITICAL, $self->{info});
  } elsif ($self->{cpqHeFltTolFanPresent} eq 'absent') {
    $self->add_info(sprintf 'fan %d (%s) needs attention (is absent)',
        $self->{cpqHeFltTolFanIndex}, $self->{cpqHeFltTolFanLocale});
    # weiss nicht, ob absent auch kaputt bedeuten kann
    # wenn nicht, dann wuerde man sich hier dumm und daemlich blacklisten
    #$self->add_message(CRITICAL, $self->{info});
    $self->add_message(WARNING, $self->{info}) if $self->{overallhealth};
  }
  if ($self->{runtime}->{options}->{perfdata}) {
    $self->{runtime}->{plugin}->add_perfdata(
        label => sprintf('fan_%s', $self->{cpqHeFltTolFanIndex}),
        value => $self->{cpqHeFltTolFanPctMax},
        uom => '%',
    );
  }
}

sub dump {
  my $self = shift;
  printf "[FAN_%s]\n", $self->{cpqHeFltTolFanIndex};
  foreach (qw(cpqHeFltTolFanChassis cpqHeFltTolFanIndex cpqHeFltTolFanLocale
      cpqHeFltTolFanPresent cpqHeFltTolFanType cpqHeFltTolFanSpeed
      cpqHeFltTolFanRedundant cpqHeFltTolFanRedundantPartner
      cpqHeFltTolFanCondition cpqHeFltTolFanHotPlug)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "info: %s\n", $self->{info};
  printf "\n";
}


package HP::Proliant::Component::FanSubsystem::Fan::Thermal;
our @ISA = qw(HP::Proliant::Component::FanSubsystem::Fan);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqHeThermalFanIndex => $params{cpqHeThermalFanIndex},
    cpqHeThermalFanRequired => $params{cpqHeThermalFanRequired},
    cpqHeThermalFanPresent => $params{cpqHeThermalFanPresent},
    cpqHeThermalFanCpuFan => $params{cpqHeThermalFanCpuFan},
    cpqHeThermalFanStatus => $params{cpqHeThermalFanStatus},
    cpqHeThermalFanHwLocation => $params{cpqHeThermalFanHwLocation},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
}

sub dump {
  my $self = shift;
  printf "[FAN_%s]\n", $self->{cpqHeThermalFanIndex};
  foreach (qw(cpqHeThermalFanIndex cpqHeThermalFanRequired 
      cpqHeThermalFanPresent cpqHeThermalFanCpuFan cpqHeThermalFanStatus
      cpqHeThermalFanHwLocation)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "info: %s\n", $self->{info};
  printf "\n";
}
package HP::Proliant::Component::FanSubsystem::CLI;
our @ISA = qw(HP::Proliant::Component::FanSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    fans => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init(%params);
  return $self;
}

# partner not available = cpqHeFltTolFanRedundantPartner=0
# cpqHeFltTolFanTypeValue = other
sub init {
  my $self = shift;
  my %params = @_;
  my %tmpfan = ();
  foreach (grep(/^fans/, split(/\n/, $self->{rawdata}))) {
    s/^fans //g;
    if (/^#(\d+)\s+([\w#_\/\-]+)\s+(\w+)\s+(\w+)\s+(FAILED|[N\/A\d]+)%*\s+([\w\/]+)\s+(FAILED|[N\/A\d]+)\s+(\w+)/) {
      %tmpfan = (
          cpqHeFltTolFanIndex => $1, 
          cpqHeFltTolFanLocale => lc $2,
          cpqHeFltTolFanPresent => lc $3,
          cpqHeFltTolFanSpeed => lc $4,
          cpqHeFltTolFanPctMax => lc $5,                 # (FAILED|[N\/A\d]+)
          cpqHeFltTolFanRedundant => lc $6,
          cpqHeFltTolFanRedundantPartner => lc $7,       # (FAILED|[N\/A\d]+)
          cpqHeFltTolFanHotPlug => lc $8,
      ); 
    } elsif (/^#(\d+)\s+([\w#_\/\-]+?)(Yes|No|N\/A)\s+(\w+)\s+(FAILED|[N\/A\d]+)%*\s+([\w\/]+)\s+(FAILED|[N\/A\d]+)\s+(\w+)/) { 
      # #5   SCSI_BACKPLANE_ZONEYes     NORMAL N/A  .... 
      %tmpfan = (
          cpqHeFltTolFanIndex => $1,
          cpqHeFltTolFanLocale => lc $2,
          cpqHeFltTolFanPresent => lc $3,
          cpqHeFltTolFanSpeed => lc $4, 
          cpqHeFltTolFanPctMax => lc $5,
          cpqHeFltTolFanRedundant => lc $6,
          cpqHeFltTolFanRedundantPartner => lc $7,
          cpqHeFltTolFanHotPlug => lc $8,
      );
    } elsif (/^#(\d+)\s+([\w#_\/\-]+)\s+[NOno]+\s/) {
      # Fan is not installed. #2   CPU#2   No   -   -    No      N/A      -
    } elsif (/^#(\d+)/) {
      main::contact_author("FAN", $_); 
    }
    if (%tmpfan) {
      $tmpfan{runtime} = $params{runtime};
      $tmpfan{cpqHeFltTolFanChassis} = 1; # geht aus hpasmcli nicht hervor
      $tmpfan{cpqHeFltTolFanType} = 'other';
      if ($tmpfan{cpqHeFltTolFanPctMax} !~ /^\d+$/) {
        if ($tmpfan{cpqHeFltTolFanSpeed} eq 'normal') {
          $tmpfan{cpqHeFltTolFanPctMax} = 50;
        } elsif ($tmpfan{cpqHeFltTolFanSpeed} eq 'high') {
          $tmpfan{cpqHeFltTolFanPctMax} = 100;
        } else {
          $tmpfan{cpqHeFltTolFanPctMax} = 0;
        }
      }
      if($tmpfan{cpqHeFltTolFanSpeed} eq 'failed') {
        $tmpfan{cpqHeFltTolFanCondition} = 'failed';
      } elsif($tmpfan{cpqHeFltTolFanSpeed} eq 'n/a') {
        $tmpfan{cpqHeFltTolFanCondition} = 'other';
      } else {
        $tmpfan{cpqHeFltTolFanCondition} = 'ok';
      }
      $tmpfan{cpqHeFltTolFanRedundant} = 
          $tmpfan{cpqHeFltTolFanRedundant} eq 'yes' ? 'redundant' :
          $tmpfan{cpqHeFltTolFanRedundant} eq 'no' ? 'notRedundant' : 'other';
      $tmpfan{cpqHeFltTolFanPresent} = 
          $tmpfan{cpqHeFltTolFanPresent} eq 'yes' ? 'present' :
          $tmpfan{cpqHeFltTolFanPresent} eq 'failed' ? 'present' :
          $tmpfan{cpqHeFltTolFanPresent} eq 'no' ? 'absent' : 'other';
      $tmpfan{cpqHeFltTolFanHotPlug} = 
          $tmpfan{cpqHeFltTolFanHotPlug} eq 'yes' ? 'hotPluggable' :
          $tmpfan{cpqHeFltTolFanHotPlug} eq 'no' ? 'nonHotPluggable' : 'other';
      push(@{$self->{fans}},
          HP::Proliant::Component::FanSubsystem::Fan->new(%tmpfan));
      %tmpfan = ();
    }
  }
}

sub overall_check {
  my $self = shift;
  # nix. nur wegen der gleichheit mit snmp
  return 0;
}

package HP::Proliant::Component::FanSubsystem::SNMP;
our @ISA = qw(HP::Proliant::Component::FanSubsystem
    HP::Proliant::Component::SNMP);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    fans => [],
    he_fans => [],
    th_fans => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->overall_init(%params);
  $self->he_init(%params);
  $self->te_init(%params);
  $self->unite();
  return $self;
}

sub overall_init {
  my $self = shift;
  my %params = @_;
  my $snmpwalk = $params{rawdata};
  # overall
  my $cpqHeThermalSystemFanStatus = '1.3.6.1.4.1.232.6.2.6.4.0';
  my $cpqHeThermalSystemFanStatusValue = {
    1 => 'other',
    2 => 'ok',
    3 => 'degraded',
    4 => 'failed',
  };
  my $cpqHeThermalCpuFanStatus = '1.3.6.1.4.1.232.6.2.6.5.0';
  my $cpqHeThermalCpuFanStatusValue = {
    1 => 'other',
    2 => 'ok',
    4 => 'failed', # shutdown
  };
  $self->{sysstatus} = SNMP::Utils::get_object_value(
      $snmpwalk, $cpqHeThermalSystemFanStatus,
      $cpqHeThermalSystemFanStatusValue);
  $self->{cpustatus} = SNMP::Utils::get_object_value(
      $snmpwalk, $cpqHeThermalCpuFanStatus,
      $cpqHeThermalCpuFanStatusValue);
  $self->{sysstatus} |= lc $self->{sysstatus};
  $self->{cpustatus} |= lc $self->{cpustatus};
}

sub te_init {
  my $self = shift;
  my %params = @_;
  my $snmpwalk = $params{rawdata};
  my $ignore_redundancy = $params{ignore_redundancy};
  # cpqHeThermalFanTable
  my $oids = {
      cpqHeThermalFanEntry => '1.3.6.1.4.1.232.6.2.6.6.1',
      cpqHeThermalFanIndex => '1.3.6.1.4.1.232.6.2.6.6.1.1',
      cpqHeThermalFanRequired => '1.3.6.1.4.1.232.6.2.6.6.1.2',
      cpqHeThermalFanPresent => '1.3.6.1.4.1.232.6.2.6.6.1.3',
      cpqHeThermalFanCpuFan => '1.3.6.1.4.1.232.6.2.6.6.1.4',
      cpqHeThermalFanStatus => '1.3.6.1.4.1.232.6.2.6.6.1.5',
      cpqHeThermalFanHwLocation => '1.3.6.1.4.1.232.6.2.6.6.1.6',
      cpqHeThermalFanRequiredValue => {
        1 => 'other',
        2 => 'nonRequired',
        3 => 'required',
      },
      cpqHeThermalFanPresentValue => {
        1 => 'other',
        2 => 'absent',
        3 => 'present',
      },
      cpqHeThermalFanCpuFanValue => {
        1 => 'other',
        2 => 'systemFan',
        3 => 'cpuFan',
      },
      cpqHeThermalFanStatusValue => {
        1 => 'other',
        2 => 'ok',
        4 => 'failed',
      },
  };
  # INDEX { cpqHeThermalFanIndex }
  foreach ($self->get_entries($oids, 'cpqHeThermalFanEntry')) {
    next if ! $_->{cpqHeThermalFanPresent};
    push(@{$self->{th_fans}},
        HP::Proliant::Component::FanSubsystem::Fan->new(%{$_}));
  }
}

sub he_init {
  my $self = shift;
  my %params = @_;
  my $snmpwalk = $params{rawdata};
  my $ignore_redundancy = $params{ignore_redundancy};
  # cpqHeFltTolFanTable
  my $oids = {
      cpqHeFltTolFanEntry => '1.3.6.1.4.1.232.6.2.6.7.1',
      cpqHeFltTolFanChassis => '1.3.6.1.4.1.232.6.2.6.7.1.1',
      cpqHeFltTolFanIndex => '1.3.6.1.4.1.232.6.2.6.7.1.2',
      cpqHeFltTolFanLocale => '1.3.6.1.4.1.232.6.2.6.7.1.3',
      cpqHeFltTolFanPresent => '1.3.6.1.4.1.232.6.2.6.7.1.4',
      cpqHeFltTolFanType => '1.3.6.1.4.1.232.6.2.6.7.1.5',
      cpqHeFltTolFanSpeed => '1.3.6.1.4.1.232.6.2.6.7.1.6',
      cpqHeFltTolFanRedundant => '1.3.6.1.4.1.232.6.2.6.7.1.7',
      cpqHeFltTolFanRedundantPartner => '1.3.6.1.4.1.232.6.2.6.7.1.8',
      cpqHeFltTolFanCondition => '1.3.6.1.4.1.232.6.2.6.7.1.9',
      cpqHeFltTolFanHotPlug => '1.3.6.1.4.1.232.6.2.6.7.1.10',
      cpqHeFltTolFanHwLocation => '1.3.6.1.4.1.232.6.2.6.7.1.11',
      cpqHeFltTolFanCurrentSpeed => '1.3.6.1.4.1.232.6.2.6.7.1.12',
      cpqHeFltTolFanLocaleValue => {
          1 => "other",
          2 => "unknown",
          3 => "system",
          4 => "systemBoard",
          5 => "ioBoard",
          6 => "cpu",
          7 => "memory",
          8 => "storage",
          9 => "removableMedia",
          10 => "powerSupply", 
          11 => "ambient",
          12 => "chassis",
          13 => "bridgeCard",
      },
      cpqHeFltTolFanPresentValue => {
          1 => "other",
          2 => "absent",
          3 => "present",
      },
      cpqHeFltTolFanSpeedValue => {
          1 => "other",
          2 => "normal",
          3 => "high",
      },
      cpqHeFltTolFanRedundantValue => {
          1 => "other",
          2 => "notRedundant",
          3 => "redundant",
      },
      cpqHeFltTolFanTypeValue => {
          1 => "other",
          2 => "tachInput",
          3 => "spinDetect",
      },
      cpqHeFltTolFanConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
      cpqHeFltTolFanHotPlugValue => {
          1 => "other",
          2 => "nonHotPluggable",
          3 => "hotPluggable",
      },
  };
  # INDEX { cpqHeFltTolFanChassis, cpqHeFltTolFanIndex }
  foreach ($self->get_entries($oids, 'cpqHeFltTolFanEntry')) {
    next if ! defined $_->{cpqHeFltTolFanIndex};
    # z.b. USM65201WS hat nur solche fragmente. die werden erst gar nicht
    # als fans akzeptiert. dafuer gibts dann die overall condition
    # SNMPv2-SMI::enterprises.232.6.2.6.7.1.1.0.1 = INTEGER: 0
    # SNMPv2-SMI::enterprises.232.6.2.6.7.1.1.0.2 = INTEGER: 0
    $_->{cpqHeFltTolFanPctMax} = ($_->{cpqHeFltTolFanPresent} eq 'present') ?
        50 : 0;
    push(@{$self->{he_fans}},
        HP::Proliant::Component::FanSubsystem::Fan->new(%{$_}));
  }

}

sub unite {
  my $self = shift;
  my $tmpfans = {};
  foreach (@{$self->{he_fans}}) {
    $tmpfans->{$_->{cpqHeFltTolFanIndex}} = $_;
  }
  foreach (@{$self->{he_fans}}) {
    if (exists $tmpfans->{$_->{cpqHeFltTolFanRedundantPartner}}) {
      $_->{partner} = $tmpfans->{$_->{cpqHeFltTolFanRedundantPartner}};
    } else {
      $_->{partner} = undef;
    }
  }
  @{$self->{fans}} = @{$self->{he_fans}};
}

sub overall_check {
  my $self = shift;
  my $result = 0;
  $self->blacklist('ofs', '');
  if ($self->{sysstatus} && $self->{cpustatus}) {
    if ($self->{sysstatus} eq 'degraded') {
      $result = 1;
      $self->add_message(WARNING,
          sprintf 'system fan overall status is %s', $self->{sysstatus});
    } elsif ($self->{sysstatus} eq 'failed') {
      $result = 2;
      $self->add_message(CRITICAL,
          sprintf 'system fan overall status is %s', $self->{sysstatus});
    } 
    if ($self->{cpustatus} eq 'degraded') {
      $result = 1;
      $self->add_message(WARNING,
          sprintf 'cpu fan overall status is %s', $self->{cpustatus});
    } elsif ($self->{cpustatus} eq 'failed') {
      $result = 2;
      $self->add_message(CRITICAL,
          sprintf 'cpu fan overall status is %s', $self->{cpustatus});
    } 
    $self->add_info(sprintf 'overall fan status: system=%s, cpu=%s',
        $self->{sysstatus}, $self->{cpustatus});
  } else {
    $result = 0;
    $self->add_info('this system seems to be water-cooled. no fans found');
  }
  return $result;
}



package HP::Proliant::Component::MemorySubsystem::CLI;
our @ISA = qw(HP::Proliant::Component::MemorySubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    dimms => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init(%params);
  return $self;
}

sub init {
  my $self = shift;
  my %params = @_;
  $self->{dimms} = [];
  my %tmpdimm = (
    runtime => $params{runtime},
  );
  my $inblock = 0;
  foreach (grep(/^dimm/, split(/\n/, $self->{rawdata}))) {
    s/^dimm\s*$//g;
    if (/Cartridge #:\s+(\d+)/ || /Processor #:\s+(\d+)/) {
      # neuerdings (g6) tauchen hier prozessor- statt cartridge-angaben auf
      $tmpdimm{cartridge} = $1;
      $tmpdimm{board} = $1;
      $inblock = 1;
    } elsif (/Module #:\s+(\d+)/) {
      $tmpdimm{module} = $1;
    } elsif (/Present:\s+(\w+)/) {
      $tmpdimm{status} = lc $1 eq 'yes' ? 'present' :
          lc $1 eq 'no' ? 'notPresent' : 'other';
    } elsif (/Status:\s+(.+?)\s*$/) {
      $tmpdimm{condition} = lc $1 =~ /degraded/ ? 'degraded' :
          lc $1 eq 'ok' ? 'ok' : lc $1 =~ /n\/a/ ? 'n/a' : 'other';
    } elsif (/Size:\s+(\d+)\s*(.+?)\s*$/) {
      $tmpdimm{size} = $1 * (lc $2 eq 'mb' ? 1024*1024 :
          lc $2 eq 'gb' ? 1024*1024*1024 : 1);
    } elsif (/^\s*$/) {
      if ($inblock) {
        $inblock = 0;
        push(@{$self->{dimms}},
            HP::Proliant::Component::MemorySubsystem::Dimm->new(%tmpdimm));
        %tmpdimm = (
          runtime => $params{runtime},
        );
      }
    } elsif (/(\d+)\s+(\d+)\s+(\w+)\s+(0x\w+)\s+(0x\w+)\s+(\d+[MGT]B)\s+(\d+MHz)\s+(\w+)/) {
      $tmpdimm{cartridge} = $1;
      $tmpdimm{module} = $2;
      $tmpdimm{status} = lc $3 eq 'yes' ? 'present' :
          lc $3 eq 'no' ? 'notPresent' : 'other';
      my $formfactor = $4;
      my $memorytype = $5;
      my $memorysize = $6;
      my $memoryspeed = $7;
      $tmpdimm{condition} = lc $8 =~ /degraded/ ? 'degraded' :
          lc $8 eq 'ok' ? 'ok' : lc $8 =~ /n\/a/ ? 'n/a' : 'other';
      $memorysize =~ /(\d+)([MGT]B)/;
      $tmpdimm{size} = $1 * (lc $2 eq 'mb' ? 1024*1024 :
          lc $2 eq 'gb' ? 1024*1024*1024 : 1);
      push(@{$self->{dimms}},
          HP::Proliant::Component::MemorySubsystem::Dimm->new(%tmpdimm));
    }
  }
  if ($inblock) {
    push(@{$self->{dimms}},
        HP::Proliant::Component::MemorySubsystem::Dimm->new(%tmpdimm));
  }
}

sub is_faulty {
  my $self = shift;
  return 0; # cli hat so einen globalen status nicht
}


package HP::Proliant::Component::MemorySubsystem::SNMP;
our @ISA = qw(HP::Proliant::Component::MemorySubsystem
    HP::Proliant::Component::SNMP);

use strict;

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    dimms => [],
    si_dimms => [],
    he_dimms => [],
    h2_dimms => [],
    he_cartridges => [],
    h2_cartridges => [],
    si_overall_condition => undef,
    he_overall_condition => undef,
    h2_overall_condition => undef,
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->si_init();
  $self->he_init();
  $self->he_cartridge_init();
  $self->h2_init();
  #$self->h2_cartridge_init();
  $self->condense();
  return $self;
}

sub si_init {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};
  my $oids = {
      cpqSiMemModuleEntry => '1.3.6.1.4.1.232.2.2.4.5.1',
      cpqSiMemBoardIndex => '1.3.6.1.4.1.232.2.2.4.5.1.1',
      cpqSiMemModuleIndex => '1.3.6.1.4.1.232.2.2.4.5.1.2',
      cpqSiMemModuleSize => '1.3.6.1.4.1.232.2.2.4.5.1.3',
      cpqSiMemModuleType => '1.3.6.1.4.1.232.2.2.4.5.1.4',
      cpqSiMemECCStatus => '1.3.6.1.4.1.232.2.2.4.5.1.11',
      cpqSiMemModuleHwLocation => '1.3.6.1.4.1.232.2.2.4.5.1.12',
      cpqSiMemModuleTypeValue => {
          1 => 'other',
          2 => 'board',
          3 => 'cpqSingleWidthModule',
          4 => 'cpqDoubleWidthModule',
          5 => 'simm',
          6 => 'pcmcia',
          7 => 'compaq-specific',
          8 => 'dimm',
          9 => 'smallOutlineDimm',
          10 => 'rimm',
          11 => 'srimm',
      },
      cpqSiMemECCStatusValue => {
          0 => "n/a",
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "degradedModuleIndexUnknown",
          34 => 'n/a', # es ist zum kotzen...
          104 => 'n/a',
      },
  };
  # INDEX { cpqSiMemBoardIndex, cpqSiMemModuleIndex }
  foreach ($self->get_entries($oids, 'cpqSiMemModuleEntry')) {
    $_->{cartridge} = $_->{cpqSiMemBoardIndex};
    $_->{module} = $_->{cpqSiMemModuleIndex};
    next if (! defined $_->{cartridge} || ! defined $_->{module});
    $_->{size} = $_->{cpqSiMemModuleSize};
    $_->{type} = $_->{cpqSiMemModuleType};
    $_->{condition} = $_->{cpqSiMemECCStatus};
    $_->{status} = ($_->{cpqSiMemModuleSize} > 0) ? 'present' : 'notPresent';
    push(@{$self->{si_dimms}},
        HP::Proliant::Component::MemorySubsystem::Dimm->new(%{$_})
    );
  } 
  my $cpqSiMemECCCondition = '1.3.6.1.4.1.232.2.2.4.15.0';
  my $cpqSiMemECCConditionValue = {
    1 => 'other',
    2 => 'ok',
    3 => 'degraded',
  };
  $self->{si_overall_condition} = SNMP::Utils::get_object_value(
        $self->{rawdata}, $cpqSiMemECCCondition,
        $cpqSiMemECCConditionValue);
  $self->trace(2, sprintf 'overall si condition is %s', 
      $self->{si_overall_condition} || 'undefined');
}

sub he_init {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};
  my $oids = {
      cpqHeResMemModuleEntry => '1.3.6.1.4.1.232.6.2.14.11.1',
      cpqHeResMemBoardIndex => '1.3.6.1.4.1.232.6.2.14.11.1.1',
      cpqHeResMemModuleIndex => '1.3.6.1.4.1.232.6.2.14.11.1.2',
      cpqHeResMemModuleStatus => '1.3.6.1.4.1.232.6.2.14.11.1.4',
      cpqHeResMemModuleCondition => '1.3.6.1.4.1.232.6.2.14.11.1.5',
      cpqHeResMemModuleStatusValue => {
          1 => "other",         # unknown or could not be determined
          2 => "notPresent",    # not present or un-initialized
          3 => "present",       # present but not in use
          4 => "good",          # present and in use. ecc threshold not exceeded
          5 => "add",           # added but not yet in use
          6 => "upgrade",       # upgraded but not yet in use
          7 => "missing",       # expected but missing
          8 => "doesNotMatch",  # does not match the other modules in the bank
          9 => "notSupported",  # module not supported
          10 => "badConfig",    # violates add/upgrade configuration
          11 => "degraded",     # ecc exceeds threshold
      },
      # condition = status of the correctable memory errors
      cpqHeResMemModuleConditionValue => {
          0 => "n/a", # this appears only with buggy firmwares.
          # (only 1 module shows up)
          1 => "other",
          2 => "ok",
          3 => "degraded",
      },
  };
  my $tablesize = SNMP::Utils::get_size($snmpwalk, 
      $oids->{cpqHeResMemModuleEntry});
  # INDEX { cpqHeResMemBoardIndex, cpqHeResMemModuleIndex }
  foreach ($self->get_entries($oids, 'cpqHeResMemModuleEntry')) {
    $_->{cartridge} = $_->{cpqHeResMemBoardIndex};
    $_->{module} = $_->{cpqHeResMemModuleIndex};
    $_->{present} = $_->{cpqHeResMemModuleStatus};
    $_->{status} = $_->{cpqHeResMemModuleStatus};
    $_->{condition} = $_->{cpqHeResMemModuleCondition};
    if ((! defined $_->{module}) && ($_->{cartridge} == 0)) {
      $_->{module} = $_->{index2}; # auf dem systemboard verbaut
    }

    push(@{$self->{he_dimms}}, 
        HP::Proliant::Component::MemorySubsystem::Dimm->new(%{$_})
    ) unless (! defined $_->{cartridge} || ! defined $_->{module} ||
        $tablesize == 1);
  }
  my $cpqHeResilientMemCondition = '1.3.6.1.4.1.232.6.2.14.4.0';
  my $cpqHeResilientMemConditionValue = {
    1 => 'other',
    2 => 'ok',
    3 => 'degraded',
  };
  $self->{he_overall_condition} = SNMP::Utils::get_object_value(
        $self->{rawdata}, $cpqHeResilientMemCondition,
        $cpqHeResilientMemConditionValue);
  $self->trace(2, sprintf 'overall he condition is %s', 
      $self->{hei_overall_condition} || 'undefined');
}

sub he_cartridge_init {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};
  my $oids = {
      cpqHeResMemBoardEntry => '1.3.6.1.4.1.232.6.2.14.10.1',
      cpqHeResMemBoardSlotIndex => '1.3.6.1.4.1.232.6.2.14.10.1.1',
      cpqHeResMemBoardOnlineStatus => '1.3.6.1.4.1.232.6.2.14.10.1.2',
      cpqHeResMemBoardErrorStatus => '1.3.6.1.4.1.232.6.2.14.10.1.3',
      cpqHeResMemBoardNumSockets => '1.3.6.1.4.1.232.6.2.14.10.1.5',
      cpqHeResMemBoardOsMemSize => '1.3.6.1.4.1.232.6.2.14.10.1.6',
      cpqHeResMemBoardTotalMemSize => '1.3.6.1.4.1.232.6.2.14.10.1.7',
      cpqHeResMemBoardCondition => '1.3.6.1.4.1.232.6.2.14.10.1.8',
      # onlinestatus
      cpqHeResMemBoardOnlineStatusValue => {
          0 => "n/a", # this appears only with buggy firmwares.
          # (only 1 module shows up)
          1 => "other",
          2 => "present",
          3 => "absent",
      },
      cpqHeResMemBoardErrorStatusValue => {
          1 => "other",         #
          2 => "noError",       #
          3 => "dimmEccError",  #
          4 => "unlockError",   #
          5 => "configError",   #
          6 => "busError",      #
          7 => "powerError",    #
      },
      # condition = status of the correctable memory errors
      cpqHeResMemBoardConditionValue => {
          0 => "n/a", # this appears only with buggy firmwares.
          # (only 1 module shows up)
          1 => "other",
          2 => "ok",
          3 => "degraded",
      },
  };
  my $tablesize = SNMP::Utils::get_size($snmpwalk,
      $oids->{cpqHeResMemBoardEntry});
  # INDEX { cpqHeResMemBoardIndex, cpqHeResMemBoardIndex }
  foreach ($self->get_entries($oids, 'cpqHeResMemBoardEntry')) {
    push(@{$self->{he_cartridges}},
        HP::Proliant::Component::MemorySubsystem::Cartridge->new(%{$_})
    ) unless (! defined $_->{cpqHeResMemBoardSlotIndex} || $tablesize == 1);
  }
}

sub h2_init {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};
  my $oids = {
      cpqHeResMem2ModuleEntry => '1.3.6.1.4.1.232.6.2.14.13.1',
      cpqHeResMem2BoardNum => '1.3.6.1.4.1.232.6.2.14.13.1.2',
      cpqHeResMem2ModuleNum => '1.3.6.1.4.1.232.6.2.14.13.1.5',
      cpqHeResMem2ModuleStatus => '1.3.6.1.4.1.232.6.2.14.13.1.19',
      cpqHeResMem2ModuleCondition => '1.3.6.1.4.1.232.6.2.14.13.1.20',
      cpqHeResMem2ModuleSize => '1.3.6.1.4.1.232.6.2.14.13.1.6',
    
      cpqHeResMem2ModuleStatusValue => {
          1 => "other",         # unknown or could not be determined
          2 => "notPresent",    # not present or un-initialized
          3 => "present",       # present but not in use
          4 => "good",          # present and in use. ecc threshold not exceeded
          5 => "add",           # added but not yet in use
          6 => "upgrade",       # upgraded but not yet in use
          7 => "missing",       # expected but missing
          8 => "doesNotMatch",  # does not match the other modules in the bank
          9 => "notSupported",  # module not supported
          10 => "badConfig",    # violates add/upgrade configuration
          11 => "degraded",     # ecc exceeds threshold
      },
      # condition = status of the correctable memory errors
      cpqHeResMem2ModuleConditionValue => {
          0 => "n/a", # this appears only with buggy firmwares.
          # (only 1 module shows up)
          1 => "other",
          2 => "ok",
          3 => "degraded",
      },
  };
  # INDEX { cpqHeResMem2ModuleNum }
  my $lastboard = 0;
  my $lastmodule = 0;
  my $myboard= 0;
  my $hpboard = 0;
  foreach (sort { $a->{index1} <=> $b->{index1} }
      $self->get_entries($oids, 'cpqHeResMem2ModuleEntry')) {
    $hpboard = $_->{cpqHeResMem2BoardNum};
      # dass hier faelschlicherweise 0 zurueckkommt, wundert mich schon
      # gar nicht mehr
    $_->{module} = $_->{cpqHeResMem2ModuleNum};
    if ($_->{module} < $lastmodule) {
      # sieht so aus, als haette man es mit einem neuen board zu tun
      # da hp zu bloed ist, selber hochzuzaehlen, muss ich das tun
      $myboard++;
    }
    $lastmodule = $_->{cpqHeResMem2ModuleNum};
    $_->{cartridge} = ($myboard != $hpboard) ? $myboard : $hpboard;
    $_->{present} = $_->{cpqHeResMem2ModuleStatus};
    $_->{status} = $_->{cpqHeResMem2ModuleStatus};
    $_->{condition} = $_->{cpqHeResMem2ModuleCondition};
    $_->{size} = $_->{cpqHeResMem2ModuleSize};
    push(@{$self->{h2_dimms}},
        HP::Proliant::Component::MemorySubsystem::Dimm->new(%{$_})
    ) unless (! defined $_->{cpqHeResMem2BoardNum} ||
        ! defined $_->{cpqHeResMem2ModuleNum});
  }
}

sub h2_cartridge_init {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};
  my $oids = {
      cpqHeResMem2BoardEntry => '1.3.6.1.4.1.232.6.2.14.12.1',
      cpqHeResMem2BoardIndex => '1.3.6.1.4.1.232.6.2.14.12.1.1',
      cpqHeResMem2BoardOnlineStatus => '1.3.6.1.4.1.232.6.2.14.12.1.5',
      cpqHeResMem2BoardErrorStatus => '1.3.6.1.4.1.232.6.2.14.12.1.6',
      cpqHeResMem2BoardNumSockets => '1.3.6.1.4.1.232.6.2.14.12.1.8',
      cpqHeResMem2BoardOsMemSize => '1.3.6.1.4.1.232.6.2.14.12.1.9',
      cpqHeResMem2BoardTotalMemSize => '1.3.6.1.4.1.232.6.2.14.12.1.10',
      cpqHeResMem2BoardCondition => '1.3.6.1.4.1.232.6.2.14.12.1.11',
      # onlinestatus
      cpqHeResMem2BoardOnlineStatusValue => {
          0 => "n/a", # this appears only with buggy firmwares.
          # (only 1 module shows up)
          1 => "other",
          2 => "present",
          3 => "absent",
      },
      cpqHeResMem2BoardErrorStatusValue => {
          1 => "other",         #
          2 => "noError",       #
          3 => "dimmEccError",  #
          4 => "unlockError",   #
          5 => "configError",   #
          6 => "busError",      #
          7 => "powerError",    #
      },
      # condition = status of the correctable memory errors
      cpqHeResMem2BoardConditionValue => {
          0 => "n/a", # this appears only with buggy firmwares.
          # (only 1 module shows up)
          1 => "other",
          2 => "ok",
          3 => "degraded",
      },
  };
  my $tablesize = SNMP::Utils::get_size($snmpwalk,
      $oids->{cpqHeResMemBoardEntry});
  # INDEX { cpqHeResMem2BoardIndex, cpqHeResMem2BoardIndex }
  foreach ($self->get_entries($oids, 'cpqHeResMem2BoardEntry')) {
    push(@{$self->{h2_cartridges}},
        HP::Proliant::Component::MemorySubsystem::Cartridge->new(%{$_})
    ) unless (! defined $_->{cpqHeRes2MemBoardIndex} || $tablesize == 1);
  }
}

sub condense {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};
  # wenn saemtliche dimms n/a sind
  #  wenn ignore dimms: ignoring %d dimms with status 'n/a'
  #  wenn buggyfirmware: ignoring %d dimms with status 'n/a' because of buggy firmware
  # if buggy firmware : condition n/a ist normal
  # ignore-dimms :
  # es gibt si_dimms und he_dimms
  my $si_dimms = scalar(@{$self->{si_dimms}});
  my $he_dimms = scalar(@{$self->{he_dimms}});
  my $h2_dimms = scalar(@{$self->{h2_dimms}});
  $self->trace(2, sprintf "SI: %02d   HE: %02d   H2: %02d",
      $si_dimms, $he_dimms, $h2_dimms)
      if ($self->{runtime}->{options}->{verbose} >= 2);
  foreach ($self->get_si_boards()) {
    printf "SI%02d-> ", $_ if ($self->{runtime}->{options}->{verbose} >= 2);
    foreach ($self->get_si_modules($_)) {
      printf "%02d ", $_ if ($self->{runtime}->{options}->{verbose} >= 2);
    }
    printf "\n" if ($self->{runtime}->{options}->{verbose} >= 2);
  }
  foreach ($self->get_he_boards()) {
    printf "HE%02d-> ", $_ if ($self->{runtime}->{options}->{verbose} >= 2);
    foreach ($self->get_he_modules($_)) {
      printf "%02d ", $_ if ($self->{runtime}->{options}->{verbose} >= 2);
    }
    printf "\n" if ($self->{runtime}->{options}->{verbose} >= 2);
  }
  foreach ($self->get_h2_boards()) {
    printf "H2%02d-> ", $_ if ($self->{runtime}->{options}->{verbose} >= 2);
    foreach ($self->get_h2_modules($_)) {
      printf "%02d ", $_ if ($self->{runtime}->{options}->{verbose} >= 2);
    }
    printf "\n" if ($self->{runtime}->{options}->{verbose} >= 2);
  }
  if (($h2_dimms == 0) && ($he_dimms == 0) && ($si_dimms > 0)) {
    printf "TYP1 %s\n", $self->{runtime}->{product}
        if ($self->{runtime}->{options}->{verbose} >= 2);
    @{$self->{dimms}} = $self->update_si_with_si();
  } elsif (($h2_dimms == 0) && ($he_dimms > 0) && ($si_dimms > 0)) {
    printf "TYP2 %s\n", $self->{runtime}->{product}
        if ($self->{runtime}->{options}->{verbose} >= 2);
    @{$self->{dimms}} = $self->update_si_with_he();
  } elsif (($h2_dimms == 0) && ($he_dimms > 0) && ($si_dimms == 0)) {
    printf "TYP3 %s\n", $self->{runtime}->{product}
        if ($self->{runtime}->{options}->{verbose} >= 2);
    @{$self->{dimms}} = $self->update_he_with_he();
  } elsif (($h2_dimms > 0) && ($he_dimms == 0) && ($si_dimms == 0)) {
    printf "TYP4 %s\n", $self->{runtime}->{product}
        if ($self->{runtime}->{options}->{verbose} >= 2);
    @{$self->{dimms}} = $self->update_h2_with_h2();
  } elsif (($h2_dimms > 0) && ($he_dimms > 0) && ($si_dimms == 0)) {
    printf "TYP5 %s\n", $self->{runtime}->{product}
        if ($self->{runtime}->{options}->{verbose} >= 2);
    @{$self->{dimms}} = $self->update_he_with_h2();
  } elsif (($h2_dimms > 0) && ($he_dimms == 0) && ($si_dimms > 0)) {
    printf "TYP6 %s\n", $self->{runtime}->{product}
        if ($self->{runtime}->{options}->{verbose} >= 2);
    @{$self->{dimms}} = $self->update_si_with_h2();
  } elsif (($h2_dimms > 0) && ($he_dimms > 0) && ($si_dimms > 0)) {
    if ($h2_dimms > $si_dimms) {
      printf "TYP7 %s\n", $self->{runtime}->{product}
          if ($self->{runtime}->{options}->{verbose} >= 2);
      @{$self->{dimms}} = $self->update_he_with_h2();
    } else {
      printf "TYP8 %s\n", $self->{runtime}->{product}
          if ($self->{runtime}->{options}->{verbose} >= 2);
      @{$self->{dimms}} = $self->update_si_with_he();
    }
  } else {
    printf "TYPX %s\n", $self->{runtime}->{product}
        if ($self->{runtime}->{options}->{verbose} >= 2);
  }
  my $all_dimms = scalar(@{$self->{dimms}});
  $self->trace(2, sprintf "ALL: %02d", $all_dimms);
}

sub dump {
  my $self = shift;
  if ($self->{runtime}->{options}->{verbose} > 2) {
    printf "[SI]\n";
    foreach (@{$self->{si_dimms}}) {
      $_->dump();
    }
    printf "[HE]\n";
    foreach (@{$self->{he_dimms}}) {
      $_->dump();
    }
    printf "[H2]\n";
    foreach (@{$self->{h2_dimms}}) {
      $_->dump();
    }
  }
  $self->SUPER::dump();
}

sub update_si_with_si {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};
  my @dimms = ();
  my $repaircondition = undef;
  # wenn si keine statusinformationen liefert, dann besteht die chance
  # dass ein undokumentiertes he-fragment vorliegt
  # 1.3.6.1.4.1.232.6.2.14.11.1.1.0.<anzahl der dimms>
  my $cpqHeResMemModuleEntry = "1.3.6.1.4.1.232.6.2.14.11.1";
  if (SNMP::Utils::get_size($snmpwalk, $cpqHeResMemModuleEntry) == 1) {
    $repaircondition = lc SNMP::Utils::get_object(
        $snmpwalk, $cpqHeResMemModuleEntry.'.1.0.'.scalar(@{$self->{si_dimms}}));
    # repaircondition 0 (ok) biegt alles wieder gerade
  } else { 
    # anderer versuch
    if ($self->{si_overall_condition} &&
        $self->{si_overall_condition} eq 'ok') {
      $repaircondition = 0;
    }
  }
  foreach my $si_dimm (@{$self->{si_dimms}}) {
    if (($si_dimm->{condition} eq 'n/a') ||
        ($si_dimm->{condition} eq 'other')) {
      $si_dimm->{condition} = 'ok' if
          (defined $repaircondition && $repaircondition == 0);
    }
    push(@dimms,
        HP::Proliant::Component::MemorySubsystem::Dimm->new(
            runtime => $si_dimm->{runtime},
            cartridge => $si_dimm->{cartridge},
            module => $si_dimm->{module},
            size => $si_dimm->{size},
            status => $si_dimm->{status},
            condition => $si_dimm->{condition},
    ));
  }
  return @dimms;
}

sub update_si_with_he {
  my $self = shift;
  my @dimms = ();
  my $first_si_cartridge = ($self->get_si_boards())[0];
  my $first_he_cartridge = ($self->get_he_boards())[0];
  my $offset = 0;
  if (scalar(@{$self->{si_dimms}}) == scalar(@{$self->{he_dimms}})) {
    # aufpassen! sowas kann vorkommen: si cartridge 0...6, he cartridge 1...7
    if ($first_si_cartridge != $first_he_cartridge) {
      # README case 5
      $offset = $first_si_cartridge - $first_he_cartridge;
    }
  } elsif ((scalar(@{$self->{si_dimms}}) > 1) && 
      (scalar(@{$self->{he_dimms}}) == 1)) {
    # siehe update_si_with_si. he-fragment
    return $self->update_si_with_si();
  } else { 
    # z.b. 4 si notpresent, 4 si present, 4 he
  }
  foreach my $si_dimm (@{$self->{si_dimms}}) {
    if (($si_dimm->{condition} eq 'n/a') || 
        ($si_dimm->{condition} eq 'other')) {
      if (my $he_dimm = $self->get_he_module(
          $si_dimm->{cartridge} - $offset, $si_dimm->{module})) {
        # vielleicht hat he mehr ahnung
        $si_dimm->{condition} = $he_dimm->{condition};
        if (($si_dimm->{condition} eq 'n/a') || 
            ($si_dimm->{condition} eq 'other')) {
          # wenns immer noch kein brauchbares ergebnis gibt....
          if ($self->{he_overall_condition} &&
              $self->{he_overall_condition} eq 'ok') {
            # wird schon stimmen...
            $si_dimm->{condition} = 'ok';
          } else {
            # ansonsten stellen wir uns dumm
            $si_dimm->{status} = 'notPresent';
          }
        }
      } else {
        # in dem fall zeigt si unbestueckte cartridges an
      }
    }
    push(@dimms,
        HP::Proliant::Component::MemorySubsystem::Dimm->new(
            runtime => $si_dimm->{runtime},
            cartridge => $si_dimm->{cartridge},
            module => $si_dimm->{module},
            size => $si_dimm->{size},
            status => $si_dimm->{status},
            condition => $si_dimm->{condition},
    ));
  }
  return @dimms;
}

sub update_he_with_he {
  my $self = shift;
  my @dimms = ();
  foreach my $he_dimm (@{$self->{he_dimms}}) {
    push(@dimms,
        HP::Proliant::Component::MemorySubsystem::Dimm->new(
            runtime => $he_dimm->{runtime},
            cartridge => $he_dimm->{cartridge},
            module => $he_dimm->{module},
            size => $he_dimm->{size},
            status => $he_dimm->{status},
            condition => $he_dimm->{condition},
    ));
  }
  return @dimms;
}

sub update_si_with_h2 {
  my $self = shift;
  my @dimms = ();
  my $first_si_cartridge = ($self->get_si_boards())[0];
  my $first_h2_cartridge = ($self->get_h2_boards())[0];
  my $offset = 0;
  if (scalar(@{$self->{si_dimms}}) == scalar(@{$self->{h2_dimms}})) {
    # aufpassen! sowas kann vorkommen: si cartridge 0...6, he cartridge 1...7
    if ($first_si_cartridge != $first_h2_cartridge) {
      # README case 5
      $offset = $first_si_cartridge - $first_h2_cartridge;
    }
  } else { 
    # z.b. 4 si notpresent, 4 si present, 4 he
  }
  foreach my $si_dimm (@{$self->{si_dimms}}) {
    if (($si_dimm->{condition} eq 'n/a') || 
        ($si_dimm->{condition} eq 'other')) {
      if (my $h2_dimm = $self->get_h2_module(
          $si_dimm->{cartridge} - $offset, $si_dimm->{module})) {
        # vielleicht hat h2 mehr ahnung
        $si_dimm->{condition} = $h2_dimm->{condition};
        if (1) {
          # ist zwar da, aber irgendwie auskonfiguriert
          $si_dimm->{status} = 'notPresent' if $h2_dimm->{status} eq 'other';
        }
      } else {
        # in dem fall zeigt si unbestueckte cartridges an
      }
    }
    push(@dimms,
        HP::Proliant::Component::MemorySubsystem::Dimm->new(
            runtime => $si_dimm->{runtime},
            cartridge => $si_dimm->{cartridge},
            module => $si_dimm->{module},
            size => $si_dimm->{size},
            status => $si_dimm->{status},
            condition => $si_dimm->{condition},
    ));
  }
  return @dimms;
}

sub update_he_with_h2 {
  my $self = shift;
  my @dimms = ();
  my $first_he_cartridge = ($self->get_he_boards())[0];
  my $first_h2_cartridge = ($self->get_h2_boards())[0];
  my $offset = 0;
  # auch hier koennte sowas u.u.vorkommen: he cartridge 0..6, h2 cartridge 1..7
  # ich habs zwar nie gesehen, aber wer weiss...
  if ($first_h2_cartridge != $first_he_cartridge) {
    $offset = $first_h2_cartridge - $first_he_cartridge;
  }
  foreach my $he_dimm (@{$self->{he_dimms}}) {
    if (($he_dimm->{condition} eq 'n/a') || 
        ($he_dimm->{condition} eq 'other')) {
      if (my $h2_dimm = $self->get_h2_module(
          $he_dimm->{cartridge} + $offset, $he_dimm->{module})) {
        # vielleicht hat h2 mehr ahnung
        $he_dimm->{condition} = $h2_dimm->{condition};
        if (1) {
          # ist zwar da, aber irgendwie auskonfiguriert
          $he_dimm->{status} = 'notPresent' if $h2_dimm->{status} eq 'other';
        }
      } else {
        # in dem fall weiss he mehr als h2
      }
    }
    if ($he_dimm->{size} == 0) {
      if (my $h2_dimm = $self->get_h2_module(
          $he_dimm->{cartridge} + $offset, $he_dimm->{module})) {
        $he_dimm->{size} = $h2_dimm->{size};
        # h2 beinhaltet eine size-oid
      }
    }
    push(@dimms,
        HP::Proliant::Component::MemorySubsystem::Dimm->new(
            runtime => $he_dimm->{runtime},
            cartridge => $he_dimm->{cartridge},
            module => $he_dimm->{module},
            size => $he_dimm->{size},
            status => $he_dimm->{status},
            condition => $he_dimm->{condition},
    ));
  }
  return @dimms;
}

sub update_h2_with_h2 {
  my $self = shift;
  my @dimms = ();
  foreach my $h2_dimm (@{$self->{h2_dimms}}) {
    push(@dimms,
        HP::Proliant::Component::MemorySubsystem::Dimm->new(
            runtime => $h2_dimm->{runtime},
            cartridge => $h2_dimm->{cartridge},
            module => $h2_dimm->{module},
            size => $h2_dimm->{size},
            status => $h2_dimm->{status},
            condition => $h2_dimm->{condition},
    ));
  }
  return @dimms;
}

sub is_faulty {
  my $self = shift;
  if (scalar(@{$self->{si_dimms}}) > 0 && 
      scalar(@{$self->{he_dimms}}) > 0) {
    return $self->si_is_faulty() || $self->he_is_faulty();
  } elsif (scalar(@{$self->{si_dimms}}) > 0 &&
        scalar(@{$self->{he_dimms}}) == 0) {
    return $self->si_is_faulty();
  } elsif (scalar(@{$self->{si_dimms}}) == 0 &&
        scalar(@{$self->{he_dimms}}) > 0) {
    return $self->he_is_faulty();
  } else {
    return 0;
  }
}

sub si_is_faulty {
  my $self = shift;
  return ! defined $self->{si_overall_condition} ? 0 :
      $self->{si_overall_condition} eq 'degraded' ? 1 : 0;
}

sub si_is_ok {
  my $self = shift;
  return ! defined $self->{si_overall_condition} ? 1 :
      $self->{si_overall_condition} eq 'ok' ? 1 : 0;
}

sub he_is_faulty {
  my $self = shift;
  return ! defined $self->{he_overall_condition} ? 0 :
      $self->{he_overall_condition} eq 'degraded' ? 1 : 0;
}

sub he_is_ok {
  my $self = shift;
  return ! defined $self->{he_overall_condition} ? 1 :
      $self->{he_overall_condition} eq 'ok' ? 1 : 0;
}

sub get_si_boards {
  my $self = shift;
  my %found = ();
  foreach (@{$self->{si_dimms}}) {
    $found{$_->{cartridge}} = 1;
  }
  return sort { $a <=> $b } keys %found;
}

sub get_si_modules {
  my $self = shift;
  my $board = shift;
  my %found = ();
  foreach (grep { $_->{cartridge} == $board } @{$self->{si_dimms}}) {
    $found{$_->{module}} = 1;
  }
  return sort { $a <=> $b } keys %found;
}

sub get_he_boards {
  my $self = shift;
  my %found = ();
  foreach (@{$self->{he_dimms}}) {
    $found{$_->{cartridge}} = 1;
  }
  return sort { $a <=> $b } keys %found;
}

sub get_he_modules {
  my $self = shift;
  my $board = shift;
  my %found = ();
  foreach (grep { $_->{cartridge} == $board } @{$self->{he_dimms}}) {
    $found{$_->{module}} = 1;
  }
  return sort { $a <=> $b } keys %found;
}

sub get_he_module {
  my $self = shift;
  my $board = shift;
  my $module = shift;
  my $found = (grep { $_->{cartridge} == $board && $_->{module} == $module } 
      @{$self->{he_dimms}})[0];
  return $found;
}

sub get_h2_boards {
  my $self = shift;
  my %found = ();
  # 
  foreach (@{$self->{h2_dimms}}) {
    $found{$_->{cartridge}} = 1;
  }
  return sort { $a <=> $b } keys %found;
}

sub get_h2_modules {
  my $self = shift;
  my $board = shift;
  my %found = ();
  foreach (grep { $_->{cartridge} == $board } @{$self->{h2_dimms}}) {
    $found{$_->{module}} = 1;
  }
  return sort { $a <=> $b } keys %found;
}

sub get_h2_module {
  my $self = shift;
  my $board = shift;
  my $module = shift;
  my $found = (grep { $_->{cartridge} == $board && $_->{module} == $module } 
      @{$self->{h2_dimms}})[0];
  return $found;
}


package HP::Proliant::Component::MemorySubsystem;
our @ISA = qw(HP::Proliant::Component);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    condition => $params{condition},
    status => $params{status},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
    dimms => [],
  };
  bless $self, $class;
  if ($self->{method} eq 'snmp') {
    return HP::Proliant::Component::MemorySubsystem::SNMP->new(%params);
  } elsif ($self->{method} eq 'cli') {
    return HP::Proliant::Component::MemorySubsystem::CLI->new(%params);
  } else {
    die "unknown method";
  }
}

sub check {
  my $self = shift;
  my $errorfound = 0;
  $self->add_info('checking memory');
  foreach (@{$self->{dimms}}) {
    $_->check(); # info ausfuellen
  }
  if ((scalar(grep {
      $_->is_present() && 
      ($_->{condition} ne 'n/a' && $_->{condition} ne 'other' ) 
  } @{$self->{dimms}})) != 0) {
    foreach (@{$self->{dimms}}) {
      if (($_->is_present()) && ($_->{condition} ne 'ok')) {
        $_->add_message(CRITICAL, $_->{info});
        $errorfound++;
      }
    }
  } else {
    if ($self->{runtime}->{options}->{ignore_dimms}) {
      $self->add_message(OK,
          sprintf "ignoring %d dimms with status 'n/a' ",
          scalar(grep { ($_->is_present()) } @{$self->{dimms}}));
    } elsif ($self->{runtime}->{options}->{buggy_firmware}) {
      $self->add_message(OK,
          sprintf "ignoring %d dimms with status 'n/a' because of buggy firmware",
          scalar(grep { ($_->is_present()) } @{$self->{dimms}}));
    } else {
      $self->add_message(WARNING,
        sprintf "status of all %d dimms is n/a (please upgrade firmware)",
        scalar(grep { $_->is_present() } @{$self->{dimms}}));
        $errorfound++;
    }
  }
  foreach (@{$self->{dimms}}) {
    printf "%s\n", $_->{info} if $self->{runtime}->{options}->{verbose} >= 2;
  }
  if (! $errorfound && $self->is_faulty()) {
  #if ($self->is_faulty()) {
    $self->add_message(WARNING,
        sprintf 'overall memory error found');
  }
}

sub dump {
  my $self = shift;
 printf "i dump the memory\n";
  foreach (@{$self->{dimms}}) {
    $_->dump();
  }
}

package HP::Proliant::Component::MemorySubsystem::Dimm;
our @ISA = qw(HP::Proliant::Component::MemorySubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cartridge => $params{cartridge},
    module => $params{module},
    size => $params{size} || 0,
    status => $params{status},
    condition => $params{condition},
    type => $params{type},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->{name} = sprintf '%s:%s',
      $self->{cartridge}, $self->{module};
  $self->{location} = sprintf 'module %s @ cartridge %s',
      $self->{module}, $self->{cartridge};
  return $self;
}  

sub check {
  my $self = shift;
  # check dient nur dazu, info und extended_info zu fuellen
  # die eigentliche bewertung findet eins hoeher statt
  $self->blacklist('d', $self->{name});
  if (($self->{status} eq 'present') || ($self->{status} eq 'good')) {
    if ($self->{condition} eq 'other') {
      $self->add_info(sprintf 'dimm %s (%s) is n/a',
          $self->{name}, $self->{location});
    } elsif ($self->{condition} ne 'ok') {
      $self->add_info(
        sprintf "dimm module %s (%s) needs attention (%s)",
        $self->{name}, $self->{location}, $self->{condition});
    } else {
      $self->add_info(sprintf 'dimm module %s (%s) is %s',
          $self->{name}, $self->{location}, $self->{condition});
    }
  } elsif ($self->{status} eq 'notPresent') {
    $self->add_info(sprintf 'dimm module %s (%s) is not present',
        $self->{name}, $self->{location});
  } else {
    $self->add_info(
      sprintf "dimm module %s (%s) needs attention (%s)",
      $self->{name}, $self->{location}, $self->{condition});
  }
}

sub is_present {
  my $self = shift;
  my @signs_of_presence = (qw(present good add upgraded doesnotmatch 
      notsupported badconfig degraded));
  return scalar(grep { $self->{status} eq $_ } @signs_of_presence);
}


sub dump {
  my $self = shift;
  #printf "[DIMM_%s_%s]\n", $self->{cartridge}, $self->{module};
  #foreach (qw(cartridge module size status condition info)) {
  #  printf "%s: %s\n", $_, $self->{$_};
  #}
  #printf "status: %s\n", $self->{status} if exists $self->{status};
  #printf "\n";
  printf "car %02d  mod %02d  siz %.0f  sta %-12s  con %-10s  typ %s\n",
    $self->{cartridge}, $self->{module}, $self->{size}, 
    $self->{status}, $self->{condition}, defined $self->{type} ? $self->{type} : "";
}


package HP::Proliant::Component::MemorySubsystem::Cartridge;
our @ISA = qw(HP::Proliant::Component::MemorySubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    cpqHeResMemBoardSlotIndex => $params{cpqHeResMemBoardSlotIndex},
    cpqHeResMemBoardOnlineStatus => $params{cpqHeResMemBoardOnlineStatus},
    cpqHeResMemBoardErrorStatus => $params{cpqHeResMemBoardErrorStatus},
    cpqHeResMemBoardNumSockets => $params{cpqHeResMemBoardNumSockets},
    cpqHeResMemBoardOsMemSize => $params{cpqHeResMemBoardOsMemSize},
    cpqHeResMemBoardTotalMemSize => $params{cpqHeResMemBoardTotalMemSize},
    cpqHeResMemBoardCondition => $params{cpqHeResMemBoardCondition},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  return $self;
}  

sub dump {
  my $self = shift;
  #printf "[CARTRIDGE_%s_%s]\n", $self->{cpqHeResMemBoardSlotIndex};
  #foreach (qw(cpqHeResMemBoardSlotIndex cpqHeResMemBoardOnlineStatus
  #    cpqHeResMemBoardErrorStatus cpqHeResMemBoardNumSockets
  #    cpqHeResMemBoardOsMemSize cpqHeResMemBoardTotalMemSize
  #    cpqHeResMemBoardCondition)) {
  #  printf "%s: %s\n", $_, $self->{$_};
  #}
  #printf "\n";
}


package HP::Proliant::Component::NicSubsystem::SNMP;
our @ISA = qw(HP::Proliant::Component::NicSubsystem
    HP::Proliant::Component::SNMP);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
    logical_nics => [],
    physical_nics => [],
  };
  bless $self, $class;
  $self->overall_init(%params);
  $self->init();
  return $self;
}

sub overall_init {
  my $self = shift;
  my %params = @_;
  my $snmpwalk = $self->{rawdata};
  # overall
  my $cpqNicIfLogMapOverallCondition  = '1.3.6.1.4.1.232.18.2.2.2.0';
  my $cpqNicIfLogMapOverallConditionValue = {
    1 => 'other',
    2 => 'ok',
    3 => 'degraded',
    4 => 'failed',
  };
  $self->{lognicstatus} = SNMP::Utils::get_object_value(
      $snmpwalk, $cpqNicIfLogMapOverallCondition,
      $cpqNicIfLogMapOverallConditionValue);
}

sub init {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};
  my $ifconnect = {};
  # CPQNIC-MIB
  my $oids = {
      cpqNicIfLogMapEntry => '1.3.6.1.4.1.232.18.2.2.1.1',
      cpqNicIfLogMapIndex => '1.3.6.1.4.1.232.18.2.2.1.1.1',
      cpqNicIfLogMapIfNumber => '1.3.6.1.4.1.232.18.2.2.1.1.2',
      cpqNicIfLogMapDescription => '1.3.6.1.4.1.232.18.2.2.1.1.3',
      cpqNicIfLogMapGroupType => '1.3.6.1.4.1.232.18.2.2.1.1.4',
      cpqNicIfLogMapAdapterCount => '1.3.6.1.4.1.232.18.2.2.1.1.5',
      cpqNicIfLogMapAdapterOKCount => '1.3.6.1.4.1.232.18.2.2.1.1.6',
      cpqNicIfLogMapPhysicalAdapters => '1.3.6.1.4.1.232.18.2.2.1.1.7',
      cpqNicIfLogMapMACAddress => '1.3.6.1.4.1.232.18.2.2.1.1.8',
      cpqNicIfLogMapSwitchoverMode => '1.3.6.1.4.1.232.18.2.2.1.1.9',
      cpqNicIfLogMapCondition => '1.3.6.1.4.1.232.18.2.2.1.1.10',
      cpqNicIfLogMapStatus => '1.3.6.1.4.1.232.18.2.2.1.1.11',
      cpqNicIfLogMapNumSwitchovers => '1.3.6.1.4.1.232.18.2.2.1.1.12',
      cpqNicIfLogMapHwLocation => '1.3.6.1.4.1.232.18.2.2.1.1.13',
      cpqNicIfLogMapSpeed => '1.3.6.1.4.1.232.18.2.2.1.1.14',
      cpqNicIfLogMapVlanCount => '1.3.6.1.4.1.232.18.2.2.1.1.15',
      cpqNicIfLogMapVlans => '1.3.6.1.4.1.232.18.2.2.1.1.16',

      cpqNicIfLogMapGroupTypeValue => {
          1 => "unknown",
          2 => "none",
          3 => "redundantPair",
          4 => "nft",
          5 => "alb",
          6 => "fec",
          7 => "gec",
          8 => "ad",
          9 => "slb",
          10 => "tlb",
          11 => "redundancySet",
      },
      cpqNicIfLogMapConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
      cpqNicIfLogMapStatusValue => {
          1 => "unknown",
          2 => "ok",
          3 => "primaryFailed",
          4 => "standbyFailed",
          5 => "groupFailed",
          6 => "redundancyReduced",
          7 => "redundancyLost",
      },
      cpqNicIfLogMapSwitchoverModeValue => {
          1 => "unknown",
          2 => "none",
          3 => "manual",
          4 => "switchOnFail",
          5 => "preferredPrimary",
      },
  };

  # INDEX { cpqNicIfLogMapIndex }
  foreach ($self->get_entries($oids, 'cpqNicIfLogMapEntry')) {
    push(@{$self->{logical_nics}}, 
        HP::Proliant::Component::NicSubsystem::LogicalNic->new(%{$_})
    );
  }

  $oids = {
      cpqNicIfPhysAdapterEntry => '1.3.6.1.4.1.232.18.2.3.1.1',
      cpqNicIfPhysAdapterIndex => '1.3.6.1.4.1.232.18.2.3.1.1.1',
      cpqNicIfPhysAdapterIfNumber => '1.3.6.1.4.1.232.18.2.3.1.1.2',
      cpqNicIfPhysAdapterRole => '1.3.6.1.4.1.232.18.2.3.1.1.3',
      cpqNicIfPhysAdapterMACAddress => '1.3.6.1.4.1.232.18.2.3.1.1.4',
      cpqNicIfPhysAdapterSlot => '1.3.6.1.4.1.232.18.2.3.1.1.5',
      cpqNicIfPhysAdapterIoAddr => '1.3.6.1.4.1.232.18.2.3.1.1.6',
      cpqNicIfPhysAdapterIrq => '1.3.6.1.4.1.232.18.2.3.1.1.7',
      cpqNicIfPhysAdapterDma => '1.3.6.1.4.1.232.18.2.3.1.1.8',
      cpqNicIfPhysAdapterMemAddr => '1.3.6.1.4.1.232.18.2.3.1.1.9',
      cpqNicIfPhysAdapterPort => '1.3.6.1.4.1.232.18.2.3.1.1.10',
      cpqNicIfPhysAdapterDuplexState => '1.3.6.1.4.1.232.18.2.3.1.1.11',
      cpqNicIfPhysAdapterCondition => '1.3.6.1.4.1.232.18.2.3.1.1.12',
      cpqNicIfPhysAdapterState => '1.3.6.1.4.1.232.18.2.3.1.1.13',
      cpqNicIfPhysAdapterStatus => '1.3.6.1.4.1.232.18.2.3.1.1.14',
      cpqNicIfPhysAdapterStatsValid => '1.3.6.1.4.1.232.18.2.3.1.1.15',
      cpqNicIfPhysAdapterGoodTransmits => '1.3.6.1.4.1.232.18.2.3.1.1.16',
      cpqNicIfPhysAdapterGoodReceives => '1.3.6.1.4.1.232.18.2.3.1.1.17',
      cpqNicIfPhysAdapterBadTransmits => '1.3.6.1.4.1.232.18.2.3.1.1.18',
      cpqNicIfPhysAdapterBadReceives => '1.3.6.1.4.1.232.18.2.3.1.1.19',
      cpqNicIfPhysAdapterAlignmentErrors => '1.3.6.1.4.1.232.18.2.3.1.1.20',
      cpqNicIfPhysAdapterFCSErrors => '1.3.6.1.4.1.232.18.2.3.1.1.21',
      cpqNicIfPhysAdapterSingleCollisionFrames => '1.3.6.1.4.1.232.18.2.3.1.1.22',
      cpqNicIfPhysAdapterMultipleCollisionFrames => '1.3.6.1.4.1.232.18.2.3.1.1.23',
      cpqNicIfPhysAdapterDeferredTransmissions => '1.3.6.1.4.1.232.18.2.3.1.1.24',
      cpqNicIfPhysAdapterLateCollisions => '1.3.6.1.4.1.232.18.2.3.1.1.25',
      cpqNicIfPhysAdapterExcessiveCollisions => '1.3.6.1.4.1.232.18.2.3.1.1.26',
      cpqNicIfPhysAdapterInternalMacTransmitErrors => '1.3.6.1.4.1.232.18.2.3.1.1.27',
      cpqNicIfPhysAdapterCarrierSenseErrors => '1.3.6.1.4.1.232.18.2.3.1.1.28',
      cpqNicIfPhysAdapterFrameTooLongs => '1.3.6.1.4.1.232.18.2.3.1.1.29',
      cpqNicIfPhysAdapterInternalMacReceiveErrors => '1.3.6.1.4.1.232.18.2.3.1.1.30',
      cpqNicIfPhysAdapterHwLocation => '1.3.6.1.4.1.232.18.2.3.1.1.31',
      cpqNicIfPhysAdapterPartNumber => '1.3.6.1.4.1.232.18.2.3.1.1.32',
      cpqNicIfPhysAdapterRoleValue => {
          1 => "unknown",
          2 => "primary",
          3 => "secondary",
          4 => "member",
          5 => "txRx",
          6 => "tx",
          7 => "standby",
          8 => "none",
          255 => "notApplicable",
      },
      cpqNicIfPhysAdapterDuplexStateValue => {
          1 => "unknown",
          2 => "half",
          3 => "full",
      },
      cpqNicIfPhysAdapterConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
      cpqNicIfPhysAdapterStateValue => {
          1 => "unknown",
          2 => "ok",
          3 => "standby",
          4 => "failed",
      },
      cpqNicIfPhysAdapterStatusValue => {
          1 => "unknown",
          2 => "ok",
          3 => "generalFailure",
          4 => "linkFailure",
      },

  };
  # INDEX { cpqNicIfPhysAdapterIndex }
  foreach ($self->get_entries($oids, 'cpqNicIfPhysAdapterEntry')) {
    push(@{$self->{physical_nics}},
        HP::Proliant::Component::NicSubsystem::PhysicalNic->new(%{$_}));
  }

}


package HP::Proliant::Component::NicSubsystem;
our @ISA = qw(HP::Proliant::Component);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    condition => $params{condition},
    status => $params{status},
    logical_nics => [],
    physical_nics => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  if ($self->{method} eq 'snmp') {
    return HP::Proliant::Component::NicSubsystem::SNMP->new(%params);
  } elsif ($self->{method} eq 'cli') {
    return HP::Proliant::Component::NicSubsystem::CLI->new(%params);
  } else {
    die "unknown method";
  }
  return $self;
}

sub check {
  my $self = shift;
  my $errorfound = 0;
  $self->add_info('checking nic teams');
  if (scalar (@{$self->{logical_nics}}) == 0) {
    $self->add_info('no logical nics found');
    $self->overall_check();
  } else {
    foreach (@{$self->{logical_nics}}) {
      $_->check();
    }
  }
  if (scalar (@{$self->{physical_nics}}) == 0) {
    $self->add_info('no physical nics found. do you connect with slip?');
  } else {
    foreach (@{$self->{physical_nics}}) {
      $_->check();
    }
  }
}

sub num_logical_nics {
  my $self = shift;
  return scalar @{$self->{logical_nics}};
}

sub num_physical_nics {
  my $self = shift;
  return scalar @{$self->{physical_nics}};
}

sub dump {
  my $self = shift;
  foreach (@{$self->{logical_nics}}) {
    $_->dump();
  }
  foreach (@{$self->{physical_nics}}) {
    $_->dump();
  }
}

sub overall_check {
  my $self = shift;
  if ($self->{lognicstatus} ne "ok") {
    $self->add_info(sprintf 'overall logical nic status is %s',
        $self->{lognicstatus});
  }
}


package HP::Proliant::Component::NicSubsystem::LogicalNic;
our @ISA = qw(HP::Proliant::Component::NicSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  foreach (qw(cpqNicIfLogMapIndex cpqNicIfLogMapIfNumber cpqNicIfLogMapDescription cpqNicIfLogMapGroupType cpqNicIfLogMapAdapterCount cpqNicIfLogMapAdapterOKCount cpqNicIfLogMapPhysicalAdapters cpqNicIfLogMapSwitchoverMode cpqNicIfLogMapCondition cpqNicIfLogMapStatus cpqNicIfLogMapNumSwitchovers cpqNicIfLogMapHwLocation cpqNicIfLogMapSpeed cpqNicIfLogMapVlanCount cpqNicIfLogMapVlans)) {
    $self->{$_} = $params{$_};
  }
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('lni', $self->{cpqNicIfLogMapIndex});
  if ($self->{cpqNicIfLogMapAdapterCount} > 0) {
    if ($self->{cpqNicIfLogMapCondition} eq "other") {
      # simply ignore this. if there is a physical nic
      # it is usually unknown/other/scheissegal
      $self->add_info(sprintf "logical nic %d (%s) is %s",
          $self->{cpqNicIfLogMapIndex}, $self->{cpqNicIfLogMapDescription},
          $self->{cpqNicIfLogMapCondition});
    } elsif ($self->{cpqNicIfLogMapCondition} ne "ok") {
      $self->add_info(sprintf "logical nic %d (%s) is %s (%s)",
          $self->{cpqNicIfLogMapIndex}, $self->{cpqNicIfLogMapDescription},
          $self->{cpqNicIfLogMapCondition}, $self->{cpqNicIfLogMapStatus});
      $self->add_message(CRITICAL, $self->{info});
    } else {
      $self->add_info(sprintf "logical nic %d (%s) is %s",
          $self->{cpqNicIfLogMapIndex}, $self->{cpqNicIfLogMapDescription},
          $self->{cpqNicIfLogMapCondition});
    }
  } else {
    $self->add_info(sprintf "logical nic %d (%s) has 0 physical nics",
        $self->{cpqNicIfLogMapIndex}, $self->{cpqNicIfLogMapDescription});
  }
}

sub dump {
  my $self = shift;
  printf "[LNIC_%s]\n", $self->{cpqNicIfLogMapIndex};
  foreach (qw(cpqNicIfLogMapIndex cpqNicIfLogMapIfNumber cpqNicIfLogMapDescription cpqNicIfLogMapAdapterCount cpqNicIfLogMapGroupType cpqNicIfLogMapSwitchoverMode cpqNicIfLogMapCondition cpqNicIfLogMapStatus cpqNicIfLogMapNumSwitchovers cpqNicIfLogMapHwLocation cpqNicIfLogMapSpeed)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "info: %s\n", $self->{info};
  printf "\n";
}


package HP::Proliant::Component::NicSubsystem::PhysicalNic;
our @ISA = qw(HP::Proliant::Component::NicSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  foreach (qw(cpqNicIfPhysAdapterIndex cpqNicIfPhysAdapterIfNumber cpqNicIfPhysAdapterRole cpqNicIfPhysAdapterDuplexState cpqNicIfPhysAdapterCondition cpqNicIfPhysAdapterState cpqNicIfPhysAdapterStatus cpqNicIfPhysAdapterBadTransmits cpqNicIfPhysAdapterBadReceives)) {
    $self->{$_} = $params{$_};
  }
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('pni', $self->{cpqNicIfPhysAdapterIndex});
  if ($self->{cpqNicIfPhysAdapterCondition} eq "other") {
    # hp doesnt output a clear status. i am optimistic, unknown/other
    # means "dont care"
    $self->add_info(sprintf "physical nic %d (%s) is %s",
        $self->{cpqNicIfPhysAdapterIndex}, $self->{cpqNicIfPhysAdapterRole},
        $self->{cpqNicIfPhysAdapterCondition});
  } elsif ($self->{cpqNicIfPhysAdapterCondition} ne "ok") {
    $self->add_info(sprintf "physical nic %d (%s) is %s (%s,%s)",
        $self->{cpqNicIfPhysAdapterIndex}, $self->{cpqNicIfPhysAdapterRole},
        $self->{cpqNicIfPhysAdapterCondition},
        $self->{cpqNicIfPhysAdapterState}, $self->{cpqNicIfPhysAdapterStatus});
    $self->add_message(CRITICAL, $self->{info});
  } else {
    if ($self->{cpqNicIfPhysAdapterDuplexState} ne "full") {
      $self->add_info(sprintf "physical nic %d (%s) is %s duplex",
          $self->{cpqNicIfPhysAdapterIndex}, $self->{cpqNicIfPhysAdapterRole},
          $self->{cpqNicIfPhysAdapterDuplexState});
    } else {
      $self->add_info(sprintf "physical nic %d (%s) is %s",
          $self->{cpqNicIfPhysAdapterIndex}, $self->{cpqNicIfPhysAdapterRole},
          $self->{cpqNicIfPhysAdapterCondition});
    }
  }
}

sub dump {
  my $self = shift;
  printf "[PNIC_%s]\n", $self->{cpqNicIfPhysAdapterIndex};
  foreach (qw(cpqNicIfPhysAdapterIndex cpqNicIfPhysAdapterIfNumber cpqNicIfPhysAdapterRole cpqNicIfPhysAdapterDuplexState cpqNicIfPhysAdapterCondition cpqNicIfPhysAdapterState cpqNicIfPhysAdapterStatus cpqNicIfPhysAdapterBadTransmits cpqNicIfPhysAdapterBadReceives)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "info: %s\n", $self->{info};
  printf "\n";
}


package HP::Proliant::Component::AsrSubsystem::CLI;
our @ISA = qw(HP::Proliant::Component::AsrSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init(%params);
  return $self;
}

sub init {
  my $self = shift;
  my %params = @_;
}

sub overall_check {
  my $self = shift;
  my %params = @_;
}


package HP::Proliant::Component::AsrSubsystem::SNMP;
our @ISA = qw(HP::Proliant::Component::AsrSubsystem
    HP::Proliant::Component::SNMP);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->overall_init(%params);
  return $self;
}

sub overall_init {
  my $self = shift;
  my %params = @_;
  my $snmpwalk = $params{rawdata};
  my $cpqHeAsrStatus = "1.3.6.1.4.1.232.6.2.5.1.0";
  my $cpqHeAsrStatusValue = {
    1 => "other",
    2 => "notAvailable",
    3 => "disabled",
    4 => "enabled",
  };
  my $cpqHeAsrCondition = "1.3.6.1.4.1.232.6.2.5.17.0";
  my $cpqHeAsrConditionValue = {
    1 => "other",
    2 => "ok",
    3 => "degraded",
    4 => "failed",
  };
  $self->{asrcondition} = SNMP::Utils::get_object_value(
      $snmpwalk, $cpqHeAsrCondition,
      $cpqHeAsrConditionValue);
  $self->{asrstatus} = SNMP::Utils::get_object_value(
      $snmpwalk, $cpqHeAsrStatus,
      $cpqHeAsrStatusValue);
  $self->{asrcondition} |= lc $self->{asrcondition};
  $self->{asrstatus} |= lc $self->{asrstatus};
}

sub overall_check {
  my $self = shift;
  my $result = 0;
  $self->blacklist('asr', '');
  if ($self->{asrstatus} and $self->{asrstatus} eq "enabled") {
    my $info = sprintf 'ASR overall condition is %s', $self->{asrcondition};
    if ($self->{asrcondition} eq "degraded") {
      $self->add_message(WARNING, $info);
    } elsif ($self->{asrcondition} eq "failed") {
      $self->add_message(CRITICAL, $info);
    }
    $self->add_info($info);
  } else {
    $self->add_info('This system does not have ASR.');
  }
}


package HP::Proliant::Component::AsrSubsystem;
our @ISA = qw(HP::Proliant::Component);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    condition => $params{condition},
    status => $params{status},
    temperatures => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  if ($self->{method} eq 'snmp') {
    return HP::Proliant::Component::AsrSubsystem::SNMP->new(%params);
  } elsif ($self->{method} eq 'cli') {
    return HP::Proliant::Component::AsrSubsystem::CLI->new(%params);
  } else {
    die "unknown method";
  }
  return $self;
}

sub check {
  my $self = shift;
  my $errorfound = 0;
  $self->add_info('checking ASR');
  $self->overall_check(); 
}

sub dump {
  my $self = shift;
}




package HP::Proliant::Component::SNMP;

sub get_entries {
  my $self = shift;
  my $oids = shift;
  my $entry = shift;
  my $snmpwalk = $self->{rawdata};
  my @params = ();
  my @indices = SNMP::Utils::get_indices($snmpwalk, $oids->{$entry});
  foreach (@indices) {
    my @idx = @{$_};
    my %params = ( 
      runtime => $self->{runtime},
    );
    my $maxdimension = scalar(@idx) - 1;
    foreach my $idxnr (1..scalar(@idx)) {
      $params{'index'.$idxnr} = $_->[$idxnr - 1];
    }
    foreach my $oid (keys %{$oids}) {
      next if $oid =~ /Entry$/;
      next if $oid =~ /Value$/;
      if (exists $oids->{$oid.'Value'}) {
        $params{$oid} = SNMP::Utils::get_object_value(
            $snmpwalk, $oids->{$oid}, $oids->{$oid.'Value'}, @idx);
        if (! defined  $params{$oid}) {
          my $numerical_value = SNMP::Utils::get_object(
              $snmpwalk, $oids->{$oid}, @idx);
          if (! defined $numerical_value) {
            # maschine liefert schrott
            $params{$oid} = 'value_unknown';
          } else {
            $params{$oid} = 'value_'.SNMP::Utils::get_object(
                $snmpwalk, $oids->{$oid}, @idx);
          }
        }
      } else {  
        $params{$oid} = SNMP::Utils::get_object(
            $snmpwalk, $oids->{$oid}, @idx);
      }         
    }     
    push(@params, \%params);
  }
  return @params;
}

sub mib {
  my $self = shift;
  my $mib = shift;
  my $condition = {
      0 => 'other',
      1 => 'ok',
      2 => 'degraded',
      3 => 'failed',
  };
  my $MibRevMajor = $mib.'.1.0';
  my $MibRevMinor = $mib.'.2.0';
  my $MibRevCondition = $mib.'.3.0';
  return (
      $self->SNMP::Utils::get_object($self->{rawdata},
          $MibRevMajor),
      $self->SNMP::Utils::get_object($self->{rawdata},
          $MibRevMinor),
      $self->SNMP::Utils::get_object_value($self->{rawdata},
          $MibRevCondition, $condition));
};


package HP::Proliant::Component::DiskSubsystem::Da::CLI;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Da);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    controllers => [],
    accelerators => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    blacklisted => 0,
  };
  bless $self, $class;
  return $self;
}

sub init {
  my $self = shift;
  my $hpacucli = $self->{rawdata};
  my $slot = 0;
  my $type = "unkn";
  my @lines = ();
  my $thistype = 0;
  my $tmpcntl = {};
  my $tmpaccel = {};
  my $tmpld = {};
  my $tmppd = {};
  my $tmpencl = {};
  my $cntlindex = 0;
  my $enclosureindex = 0;
  my $ldriveindex = 0;
  my $pdriveindex = 0;
  my $incontroller = 0;
  foreach (split(/\n/, $hpacucli)) {
    next unless /^status/;
    next if /^status\s*$/;
    s/^status\s*//;
    if (/(MSA[\s\w]+)\s+in\s+(\w+)/) { 
      $incontroller = 1;
      $slot = $2;
      $cntlindex++;
      $tmpcntl->{$slot}->{cpqDaCntlrIndex} = $cntlindex;
      $tmpcntl->{$slot}->{cpqDaCntlrModel} = $1;
      $tmpcntl->{$slot}->{cpqDaCntlrSlot} = $slot;
    } elsif (/([\s\w]+) in Slot\s+(\d+)/) {
      $incontroller = 1;
      $slot = $2;
      $cntlindex++;
      $tmpcntl->{$slot}->{cpqDaCntlrIndex} = $cntlindex;
      $tmpcntl->{$slot}->{cpqDaCntlrModel} = $1;
      $tmpcntl->{$slot}->{cpqDaCntlrSlot} = $slot;
    } elsif (/Controller Status: (\w+)/) {
      $tmpcntl->{$slot}->{cpqDaCntlrBoardCondition} = lc $1;
      $tmpcntl->{$slot}->{cpqDaCntlrCondition} = lc $1;
    } elsif (/Cache Status: ([\w\s]+?)\s*$/) {
      # Cache Status: OK
      # Cache Status: Not Configured
      # Cache Status: Temporarily Disabled
      $tmpaccel->{$slot}->{cpqDaAccelCntlrIndex} = $cntlindex;
      $tmpaccel->{$slot}->{cpqDaAccelSlot} = $slot;
      #condition: other,ok,degraded,failed
      #status: other,invalid,enabled,tmpDisabled,permDisabled
      $tmpaccel->{$slot}->{cpqDaAccelCondition} = lc $1; 
      if ($tmpaccel->{$slot}->{cpqDaAccelCondition} eq 'ok') {
        $tmpaccel->{$slot}->{cpqDaAccelStatus} = 'enabled';
      } elsif ($tmpaccel->{$slot}->{cpqDaAccelCondition} eq 'not configured') {
        $tmpaccel->{$slot}->{cpqDaAccelCondition} = 'ok';
        $tmpaccel->{$slot}->{cpqDaAccelStatus} = 'enabled';
      } elsif ($tmpaccel->{$slot}->{cpqDaAccelCondition} eq 'temporarily disabled') {
        $tmpaccel->{$slot}->{cpqDaAccelCondition} = 'ok';
        $tmpaccel->{$slot}->{cpqDaAccelStatus} = 'tmpDisabled';
      } elsif ($tmpaccel->{$slot}->{cpqDaAccelCondition} eq 'permanently disabled') {
        $tmpaccel->{$slot}->{cpqDaAccelCondition} = 'ok';
        $tmpaccel->{$slot}->{cpqDaAccelStatus} = 'permDisabled';
      } else {
        $tmpaccel->{$slot}->{cpqDaAccelStatus} = 'enabled';
      }
    } elsif (/Battery.* Status: (\w+)/) {
      # sowas gibts auch Battery/Capacitor Status: OK
      $tmpaccel->{$slot}->{cpqDaAccelBattery} = lc $1;
    } elsif (/^\s*$/) {
    }
  }
  $slot = 0;
  $cntlindex = 0;
  $enclosureindex = 0;
  $ldriveindex = 0;
  $pdriveindex = 0;
  foreach (split(/\n/, $hpacucli)) {
    next unless /^config/;
    next if /^config\s*$/;
    s/^config\s*//;
    if (/(MSA[\s\w]+)\s+in\s+(\w+)/) {
      $slot = $2;
      $cntlindex++;
      $pdriveindex = 1;
    } elsif (/([\s\w]+) in Slot\s+(\d+)/) {
      #if ($slot ne $2 || ! $slot) {
        $cntlindex++;
        # 2012-12-15 das passt nicht zur oberen schleife
        # ich habe keine ahnung, was der hintergrund fuer dieses if ist
      #}
      $slot = $2;
      $pdriveindex = 1;
    } elsif (/([\s\w]+) at Port ([\w]+), Box (\d+), (.*)/) {
      $enclosureindex++;
      $tmpencl->{$slot}->{$enclosureindex}->{cpqDaEnclCntlrIndex} = $cntlindex;
      $tmpencl->{$slot}->{$enclosureindex}->{cpqDaEnclIndex} = $enclosureindex;
      $tmpencl->{$slot}->{$enclosureindex}->{cpqDaEnclPort} = $2;
      $tmpencl->{$slot}->{$enclosureindex}->{cpqDaEnclBox} = $3;
      $tmpencl->{$slot}->{$enclosureindex}->{cpqDaEnclCondition} = $4;
      $tmpencl->{$slot}->{$enclosureindex}->{cpqDaEnclStatus} =
          $tmpencl->{$slot}->{$enclosureindex}->{cpqDaEnclCondition};
      $tmpencl->{$slot}->{$enclosureindex}->{cpqDaLogDrvPhyDrvIDs} = 'unknown';
    } elsif (/logicaldrive\s+(.+?)\s+\((.*)\)/) {
      # logicaldrive 1 (683.5 GB, RAID 5, OK)
      # logicaldrive 1 (683.5 GB, RAID 5, OK)
      # logicaldrive 2 (442 MB, RAID 1+0, OK)
      $ldriveindex = $1;
      $tmpld->{$slot}->{$ldriveindex}->{cpqDaLogDrvCntlrIndex} = $cntlindex;
      $tmpld->{$slot}->{$ldriveindex}->{cpqDaLogDrvIndex} = $ldriveindex;
      ($tmpld->{$slot}->{$ldriveindex}->{cpqDaLogDrvSize},
          $tmpld->{$slot}->{$ldriveindex}->{cpqDaLogDrvFaultTol},
          $tmpld->{$slot}->{$ldriveindex}->{cpqDaLogDrvCondition}) =
          map { lc $_ } split(/,\s*/, $2);
      $tmpld->{$slot}->{$ldriveindex}->{cpqDaLogDrvStatus} =
          $tmpld->{$slot}->{$ldriveindex}->{cpqDaLogDrvCondition};
      $tmpld->{$slot}->{$ldriveindex}->{cpqDaLogDrvPhyDrvIDs} = 'unknown';
    } elsif (/physicaldrive\s+(.+?)\s+\((.*)\)/) {
      # physicaldrive 2:0   (port 2:id 0 , Parallel SCSI, 36.4 GB, OK)
      # physicaldrive 2I:1:6 (port 2I:box 1:bay 6, SAS, 146 GB, OK)
      # physicaldrive 1:1 (box 1:bay 1, Parallel SCSI, 146 GB, OK)
      my $name = $1;
      my($location, $type, $size, $status) = split(/,/, $2);
      $status =~ s/^\s+//g;
      $status =~ s/\s+$//g;
      $status = lc $status;
      my %location = ();
      foreach (split(/:/, $location)) {
        $location{$1} = $2 if /(\w+)\s+(\w+)/;
      }
      $location{box} ||= 0;
      $location{id} ||= $pdriveindex;
      $location{bay} ||= $location{id};
      $location{port} ||= $location{bay};
      $tmppd->{$slot}->{$name}->{name} = lc $name;
      $tmppd->{$slot}->{$name}->{cpqDaPhyDrvCntlrIndex} = $cntlindex;
      $tmppd->{$slot}->{$name}->{cpqDaPhyDrvIndex} = $location{id};
      $tmppd->{$slot}->{$name}->{cpqDaPhyDrvBay} = $location{bay};
      $tmppd->{$slot}->{$name}->{cpqDaPhyDrvBusNumber} = $location{port};
      $tmppd->{$slot}->{$name}->{cpqDaPhyDrvSize} = $size;
      $tmppd->{$slot}->{$name}->{cpqDaPhyDrvStatus} = $status;
      $tmppd->{$slot}->{$name}->{cpqDaPhyDrvCondition} = $status;
      $tmppd->{$slot}->{$name}->{ldriveindex} = $ldriveindex || -1;
      foreach (keys %{$tmppd->{$slot}->{$name}}) {
        $tmppd->{$slot}->{$name}->{$_} =~ s/^\s+//g;
        $tmppd->{$slot}->{$name}->{$_} =~ s/\s+$//g;
        $tmppd->{$slot}->{$name}->{$_} = lc $tmppd->{$slot}->{$name}->{$_};
      }
      $pdriveindex++;
    }
  }

  foreach my $slot (keys %{$tmpcntl}) {
    if (exists $tmpcntl->{$slot}->{cpqDaCntlrModel} &&
        ! $self->identified($tmpcntl->{$slot}->{cpqDaCntlrModel})) {
      delete $tmpcntl->{$slot};
      delete $tmpaccel->{$slot};
      delete $tmpencl->{$slot};
      delete $tmpld->{$slot};
      delete $tmppd->{$slot};
    }
  }

#printf "%s\n", Data::Dumper::Dumper($tmpcntl);
#printf "%s\n", Data::Dumper::Dumper($tmpaccel);
#printf "%s\n", Data::Dumper::Dumper($tmpld);
#printf "%s\n", Data::Dumper::Dumper($tmppd);
  foreach my $slot (sort {
      $tmpcntl->{$a}->{cpqDaCntlrIndex} <=> $tmpcntl->{$b}->{cpqDaCntlrIndex}
      }keys %{$tmpcntl}) {
    $tmpcntl->{$slot}->{runtime} = $self->{runtime};
    push(@{$self->{controllers}},
        HP::Proliant::Component::DiskSubsystem::Da::Controller->new(
            %{$tmpcntl->{$slot}}));
  }
  foreach my $slot (sort {
      $tmpaccel->{$a}->{cpqDaAccelCntlrIndex} <=> $tmpaccel->{$b}->{cpqDaAccelCntlrIndex}
      } keys %{$tmpaccel}) {
    $tmpaccel->{$slot}->{runtime} = $self->{runtime};
    push(@{$self->{accelerators}},
        HP::Proliant::Component::DiskSubsystem::Da::Accelerator->new(
            %{$tmpaccel->{$slot}}));
  }
  foreach my $slot (keys %{$tmpencl}) {
    foreach my $enclosureindex (keys %{$tmpencl->{$slot}}) {
      $tmpencl->{$slot}->{$enclosureindex}->{runtime} = $self->{runtime};
      push(@{$self->{enclosures}},
          HP::Proliant::Component::DiskSubsystem::Da::Enclosure->new(
              %{$tmpencl->{$slot}->{$enclosureindex}}));
    }
  }
  foreach my $slot (keys %{$tmpld}) {
    foreach my $ldriveindex (keys %{$tmpld->{$slot}}) {
      $tmpld->{$slot}->{$ldriveindex}->{runtime} = $self->{runtime};
      push(@{$self->{logical_drives}},
          HP::Proliant::Component::DiskSubsystem::Da::LogicalDrive->new(
              %{$tmpld->{$slot}->{$ldriveindex}}));
    }
    foreach my $pdriveindex (sort {
        (split ':', $a, 2)[0] cmp (split ':', $b, 2)[0] ||
        (split ':', $a, 2)[1] cmp (split ':', $b, 2)[1] ||
        (split ':', $a, 2)[2] <=> (split ':', $b, 2)[2]
        } keys %{$tmppd->{$slot}}) {
      $tmppd->{$slot}->{$pdriveindex}->{runtime} = $self->{runtime};
      push(@{$self->{physical_drives}},
          HP::Proliant::Component::DiskSubsystem::Da::PhysicalDrive->new(
              %{$tmppd->{$slot}->{$pdriveindex}}));
    }
  }
}

sub identified {
  my $self = shift;
  my $info = shift;
  return 1 if $info =~ /Parallel SCSI/;
  return 1 if $info =~ /Smart Array/; # Trond: works fine on E200i, P400, E400
  return 1 if $info =~ /MSA500/;
  #return 1 if $info =~ /Smart Array (5|6)/;
  #return 1 if $info =~ /Smart Array P400i/; # snmp sagt Da, trotz SAS in cli
  #return 1 if $info =~ /Smart Array P410i/; # dto
  return 0;
}
package HP::Proliant::Component::DiskSubsystem::Da::SNMP;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Da
    HP::Proliant::Component::SNMP);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = { 
    controllers => [],
    accelerators => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    blacklisted => 0,
  };
  bless $self, $class;
  return $self;
}

sub init {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};

  # CPQIDA-MIB
  my $oids = {
    cpqDaCntlrEntry => "1.3.6.1.4.1.232.3.2.2.1.1",
    cpqDaCntlrIndex => "1.3.6.1.4.1.232.3.2.2.1.1.1",
    cpqDaCntlrModel => "1.3.6.1.4.1.232.3.2.2.1.1.2",
    cpqDaCntlrSlot => "1.3.6.1.4.1.232.3.2.2.1.1.5",
    cpqDaCntlrCondition => "1.3.6.1.4.1.232.3.2.2.1.1.6",
    cpqDaCntlrBoardCondition => "1.3.6.1.4.1.232.3.2.2.1.1.12",
    cpqDaCntlrModelValue => {
        1 => 'other',
        2 => 'ida',
        3 => 'idaExpansion',
        4 => 'ida-2',
        5 => 'smart',
        6 => 'smart-2e',
        7 => 'smart-2p',
        8 => 'smart-2sl',
        9 => 'smart-3100es',
        10 => 'smart-3200',
        11 => 'smart-2dh',
        12 => 'smart-221',
        13 => 'sa-4250es',
        14 => 'sa-4200',
        15 => 'sa-integrated',
        16 => 'sa-431',
        17 => 'sa-5300',
        18 => 'raidLc2',
        19 => 'sa-5i',
        20 => 'sa-532',
        21 => 'sa-5312',
        22 => 'sa-641',
        23 => 'sa-642',
        24 => 'sa-6400',
        25 => 'sa-6400em',
        26 => 'sa-6i',
    },
    cpqDaCntlrConditionValue => {
        1 => "other",
        2 => "ok",
        3 => "degraded",
        4 => "failed",
    },
    cpqDaCntlrBoardConditionValue => {
        1 => "other",
        2 => "ok",
        3 => "degraded",
        4 => "failed",
    },
  };

  # INDEX { cpqDaCntlrIndex }
  foreach ($self->get_entries($oids, 'cpqDaCntlrEntry')) {
    push(@{$self->{controllers}},
        HP::Proliant::Component::DiskSubsystem::Da::Controller->new(%{$_}));
  }

  $oids = {
      cpqDaAccelEntry => "1.3.6.1.4.1.232.3.2.2.2.1",
      cpqDaAccelCntlrIndex => "1.3.6.1.4.1.232.3.2.2.2.1.1",
      cpqDaAccelStatus => "1.3.6.1.4.1.232.3.2.2.2.1.2",
      cpqDaAccelErrCode  => "1.3.6.1.4.1.232.3.2.2.2.1.5",
      cpqDaAccelBattery  => "1.3.6.1.4.1.232.3.2.2.2.1.6",
      cpqDaAccelCondition  => "1.3.6.1.4.1.232.3.2.2.2.1.9",
      cpqDaAccelBatteryValue => {
          1 => 'other',
          2 => 'ok',
          3 => 'recharging',
          4 => 'failed',
          5 => 'degraded',
          6 => 'notPresent',
      },
      cpqDaAccelConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
      cpqDaAccelStatusValue => {
          1 => "other",
          2 => "invalid",
          3 => "enabled",
          4 => "tmpDisabled",
          5 => "permDisabled",
      },
      cpqDaAccelErrCodeValue => {
          1 => 'other',
          2 => 'invalid',
          3 => 'badConfig',
          4 => 'lowBattery',
          5 => 'disableCmd',
          6 => 'noResources',
          7 => 'notConnected',
          8 => 'badMirrorData',
          9 => 'readErr',
          10 => 'writeErr',
          11 => 'configCmd',
          12 => 'expandInProgress',
          13 => 'snapshotInProgress',
          14 => 'redundantLowBattery',
          15 => 'redundantSizeMismatch',
          16 => 'redundantCacheFailure',
          17 => 'excessiveEccErrors',
          18 => 'adgEnablerMissing',
          19 => 'postEccErrors',
          20 => 'batteryHotRemoved',
          21 => 'capacitorChargeLow',
          22 => 'notEnoughBatteries',
          23 => 'cacheModuleNotSupported',
          24 => 'batteryNotSupported',
          25 => 'noCapacitorAttached',
          26 => 'capBasedBackupFailed',
          27 => 'capBasedRestoreFailed',
          28 => 'capBasedModuleHWFailure',
          29 => 'capacitorFailedToCharge',
          30 => 'capacitorBasedHWMemBeingErased',
          31 => 'incompatibleCacheModule',
          32 => 'fbcmChargerCircuitFailure',
      },
  };
    
  # INDEX { cpqDaAccelCntlrIndex }
  foreach ($self->get_entries($oids, 'cpqDaAccelEntry')) {
    push(@{$self->{accelerators}},
        HP::Proliant::Component::DiskSubsystem::Da::Accelerator->new(%{$_}));
  }

  $oids = {
      cpqDaLogDrvEntry => "1.3.6.1.4.1.232.3.2.3.1.1",
      cpqDaLogDrvCntlrIndex => "1.3.6.1.4.1.232.3.2.3.1.1.1",
      cpqDaLogDrvIndex => "1.3.6.1.4.1.232.3.2.3.1.1.2",
      cpqDaLogDrvFaultTol => "1.3.6.1.4.1.232.3.2.3.1.1.3",
      cpqDaLogDrvStatus => "1.3.6.1.4.1.232.3.2.3.1.1.4",
      cpqDaLogDrvSize => "1.3.6.1.4.1.232.3.2.3.1.1.9",
      cpqDaLogDrvPhyDrvIDs => "1.3.6.1.4.1.232.3.2.3.1.1.10",
      cpqDaLogDrvCondition => "1.3.6.1.4.1.232.3.2.3.1.1.11",
      cpqDaLogDrvPercentRebuild => "1.3.6.1.4.1.232.3.2.3.1.1.12",
      cpqDaLogDrvFaultTolValue => {
          1 => "other",
          2 => "none",
          3 => "mirroring",
          4 => "dataGuard",
          5 => "distribDataGuard",
          7 => "advancedDataGuard",
      },
      cpqDaLogDrvConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
      cpqDaLogDrvStatusValue => {
          1 => "other",
          2 => "ok",
          3 => "failed",
          4 => "unconfigured",
          5 => "recovering",
          6 => "readyForRebuild",
          7 => "rebuilding",
          8 => "wrongDrive",
          9 => "badConnect",
          10 => "overheating",
          11 => "shutdown",
          12 => "expanding",
          13 => "notAvailable",
          14 => "queuedForExpansion",
      },
  };

  # INDEX { cpqDaLogDrvCntlrIndex, cpqDaLogDrvIndex }
  foreach ($self->get_entries($oids, 'cpqDaLogDrvEntry')) {
    $_->{cpqDaLogDrvPhyDrvIDs} ||= 'empty';
    push(@{$self->{logical_drives}},
        HP::Proliant::Component::DiskSubsystem::Da::LogicalDrive->new(%{$_}));
  }

  $oids = {
      cpqDaPhyDrvEntry => "1.3.6.1.4.1.232.3.2.5.1.1",
      cpqDaPhyDrvCntlrIndex => "1.3.6.1.4.1.232.3.2.5.1.1.1",
      cpqDaPhyDrvIndex => "1.3.6.1.4.1.232.3.2.5.1.1.2",
      cpqDaPhyDrvBay => "1.3.6.1.4.1.232.3.2.5.1.1.5",
      cpqDaPhyDrvStatus => "1.3.6.1.4.1.232.3.2.5.1.1.6",
      cpqDaPhyDrvSize => "1.3.6.1.4.1.232.3.2.5.1.1.9",
      cpqDaPhyDrvCondition => "1.3.6.1.4.1.232.3.2.5.1.1.37",
      cpqDaPhyDrvBusNumber => "1.3.6.1.4.1.232.3.2.5.1.1.50",
      cpqDaPhyDrvConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
      cpqDaPhyDrvStatusValue => {
          1 => "other",
          2 => "ok",
          3 => "failed",
          4 => "predictiveFailure",
      },
  };
    
  # INDEX { cpqDaPhyDrvCntlrIndex, cpqDaPhyDrvIndex }
  foreach ($self->get_entries($oids, 'cpqDaPhyDrvEntry')) {
    push(@{$self->{physical_drives}},
        HP::Proliant::Component::DiskSubsystem::Da::PhysicalDrive->new(%{$_}));
  }

}
package HP::Proliant::Component::DiskSubsystem::Da;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    controllers => [],
    accelerators => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    condition => undef,
    blacklisted => 0,
  };
  bless $self, $class;
  if ($self->{method} eq 'snmp') {
    bless $self, 'HP::Proliant::Component::DiskSubsystem::Da::SNMP';
  } else {
    bless $self, 'HP::Proliant::Component::DiskSubsystem::Da::CLI';
  }
  $self->init();
  $self->assemble();
  return $self;
}

sub check {
  my $self = shift;
  foreach (@{$self->{controllers}}) {
    $_->check();
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{controllers}}) {
    $_->dump();
  }
}

package HP::Proliant::Component::DiskSubsystem::Da::Controller;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Da);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqDaCntlrIndex => $params{cpqDaCntlrIndex},
    cpqDaCntlrSlot => $params{cpqDaCntlrSlot},
    cpqDaCntlrModel => $params{cpqDaCntlrModel},
    cpqDaCntlrCondition => $params{cpqDaCntlrCondition},
    cpqDaCntlrBoardCondition => $params{cpqDaCntlrBoardCondition},
    blacklisted => 0,
  };
  $self->{name} = $params{name} || $self->{cpqDaCntlrSlot};
  $self->{controllerindex} = $self->{cpqDaCntlrIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
#$self->dumper($self);
  $self->blacklist('daco', $self->{cpqDaCntlrIndex});
  foreach (@{$self->{accelerators}}) {
    $_->check();
  }
  foreach (@{$self->{enclosures}}) {
    $_->check();
  } 
  foreach (@{$self->{logical_drives}}) {
    $_->check();
  } 
  foreach (@{$self->{physical_drives}}) {
    $_->check();
  } 
  foreach (@{$self->{spare_drives}}) {
    $_->check();
  } 
  if ($self->{cpqDaCntlrCondition} eq 'other') {
    if (scalar(@{$self->{physical_drives}})) {
      $self->add_message(CRITICAL,
          sprintf 'da controller %s in slot %s needs attention', 
              $self->{cpqDaCntlrIndex}, $self->{cpqDaCntlrSlot});
      $self->add_info(sprintf 'da controller %s in slot %s needs attention',
          $self->{cpqDaCntlrIndex}, $self->{cpqDaCntlrSlot});
    } else {
      $self->add_info(sprintf 'da controller %s in slot %s is ok and unused',
          $self->{cpqDaCntlrIndex}, $self->{cpqDaCntlrSlot});
      $self->{blacklisted} = 1;
    }
  } elsif ($self->{cpqDaCntlrCondition} eq 'degraded') {
    # maybe only the battery has failed and is disabled, no problem
    if (scalar(grep {
        $_->has_failed() && $_->is_disabled()
    } @{$self->{accelerators}})) {
      # message was already written in the accel code
    } else {
      $self->add_message(CRITICAL,
          sprintf 'da controller %s in slot %s needs attention', 
              $self->{cpqDaCntlrIndex}, $self->{cpqDaCntlrSlot});
      $self->add_info(sprintf 'da controller %s in slot %s needs attention',
          $self->{cpqDaCntlrIndex}, $self->{cpqDaCntlrSlot});
    }
  } elsif ($self->{cpqDaCntlrCondition} ne 'ok') {
    $self->add_message(CRITICAL,
        sprintf 'da controller %s in slot %s needs attention', 
            $self->{cpqDaCntlrIndex}, $self->{cpqDaCntlrSlot});
    $self->add_info(sprintf 'da controller %s in slot %s needs attention',
        $self->{cpqDaCntlrIndex}, $self->{cpqDaCntlrSlot});
  } else {
    $self->add_info(sprintf 'da controller %s in slot %s is ok', 
        $self->{cpqDaCntlrIndex}, $self->{cpqDaCntlrSlot});
  }
} 

sub dump {
  my $self = shift;
  printf "[DA_CONTROLLER_%s]\n", $self->{name};
  foreach (qw(cpqDaCntlrSlot cpqDaCntlrIndex cpqDaCntlrCondition 
      cpqDaCntlrModel)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
  foreach (@{$self->{accelerators}}) {
    $_->dump();
  }
  foreach (@{$self->{enclosures}}) {
    $_->dump();
  }
  foreach (@{$self->{logical_drives}}) {
    $_->dump();
  }
  foreach (@{$self->{physical_drives}}) {
    $_->dump();
  }
  foreach (@{$self->{spare_drives}}) {
    $_->dump();
  }
}


package HP::Proliant::Component::DiskSubsystem::Da::Accelerator;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Da);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqDaAccelCntlrIndex => $params{cpqDaAccelCntlrIndex},
    cpqDaAccelBattery => $params{cpqDaAccelBattery} || 'notPresent',
    cpqDaAccelCondition => $params{cpqDaAccelCondition},
    cpqDaAccelStatus => $params{cpqDaAccelStatus},
    cpqDaAccelErrCode => $params{cpqDaAccelErrCode},
    blacklisted => 0,
    failed => 0,
  };
  $self->{controllerindex} = $self->{cpqDaAccelCntlrIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('daac', $self->{cpqDaAccelCntlrIndex});
  $self->add_info(sprintf 'controller accelerator is %s',
      $self->{cpqDaAccelCondition});
  if ($self->{cpqDaAccelCondition} eq "failed" || $self->{cpqDaAccelCondition} eq "degraded") {
    $self->add_message(CRITICAL, sprintf
        "controller accelerator is %s (reason: %s) and needs attention",
        $self->{cpqDaAccelCondition}, $self->{cpqDaAccelErrCode});
  }
  $self->blacklist('daacb', $self->{cpqDaAccelCntlrIndex});
  $self->add_info(sprintf 'controller accelerator battery is %s',
      $self->{cpqDaAccelBattery});
  if ($self->{cpqDaAccelBattery} eq "notPresent") {
  } elsif ($self->{cpqDaAccelBattery} eq "recharging") {
    $self->add_message(WARNING, "controller accelerator battery recharging");
  } elsif ($self->{cpqDaAccelBattery} eq "failed" &&
      $self->{cpqDaAccelStatus} eq "tmpDisabled") {
    $self->add_message(WARNING, "controller accelerator battery needs attention");
  } elsif ($self->{cpqDaAccelBattery} ne "ok") {
    # (other) failed degraded
    $self->add_message(CRITICAL, "controller accelerator battery needs attention");
  } 
}

sub has_failed {
  my $self = shift;
  return $self->{cpqDaAccelStatus} =~ /Disabled/ ? 1 : 0;
}

sub is_disabled {
  my $self = shift;
  return $self->{cpqDaAccelStatus} =~ /Disabled/ ? 1 : 0;
}

sub dump {
  my $self = shift;
  printf "[ACCELERATOR]\n";
  foreach (qw(cpqDaAccelCntlrIndex cpqDaAccelBattery
      cpqDaAccelStatus cpqDaAccelCondition cpqDaAccelErrCode)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}

package HP::Proliant::Component::DiskSubsystem::Da::Enclosure;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Da);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    name => $params{name},
    cpqDaEnclCntlrIndex => $params{cpqDaEnclCntlrIndex},
    cpqDaEnclIndex => $params{cpqDaEnclIndex},
    cpqDaEnclPort => $params{cpqDaEnclPort},
    cpqDaEnclBox => $params{cpqDaEnclBox},
    cpqDaEnclCondition => $params{cpqDaEnclCondition},
    blacklisted => 0,
  };
  bless $self, $class;
  $self->{name} = $params{name} ||
      $self->{cpqDaEnclPort}.':'.$self->{cpqDaEnclBox}; ##vorerst
  $self->{controllerindex} = $self->{cpqDaEnclCntlrIndex};
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('dae', $self->{name});
  $self->add_info(
      sprintf "disk enclosure %s is %s",
          $self->{name}, $self->{cpqDaEnclCondition});
  if ($self->{cpqDaEnclCondition} !~ /^OK/) {
    $self->add_message(CRITICAL,
        sprintf "disk enclosure %s is %s",
            $self->{name}, $self->{cpqDaEnclCondition});
  }
}

sub dump {
  my $self = shift;
  printf "[Disk Enclosure]\n";
  foreach (qw(cpqDaEnclCntlrIndex cpqDaEnclIndex cpqDaEnclPort
		   cpqDaEnclBox cpqDaEnclCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}

package HP::Proliant::Component::DiskSubsystem::Da::LogicalDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Da);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqDaLogDrvIndex => $params{cpqDaLogDrvIndex},
    cpqDaLogDrvCntlrIndex => $params{cpqDaLogDrvCntlrIndex},
    cpqDaLogDrvSize => $params{cpqDaLogDrvSize},
    cpqDaLogDrvFaultTol => $params{cpqDaLogDrvFaultTol},
    cpqDaLogDrvPercentRebuild => $params{cpqDaLogDrvPercentRebuild},
    cpqDaLogDrvStatus => $params{cpqDaLogDrvStatus},
    cpqDaLogDrvCondition => $params{cpqDaLogDrvCondition},
    cpqDaLogDrvPhyDrvIDs => $params{cpqDaLogDrvPhyDrvIDs},
    blacklisted => 0,
  };
  bless $self, $class;
  $self->{name} = $params{name} || 
      $self->{cpqDaLogDrvCntlrIndex}.':'.$self->{cpqDaLogDrvIndex}; ##vorerst
  $self->{controllerindex} = $self->{cpqDaLogDrvCntlrIndex};
  if (! $self->{cpqDaLogDrvPercentRebuild} ||
      $self->{cpqDaLogDrvPercentRebuild} == 4294967295) {
    $self->{cpqDaLogDrvPercentRebuild} = 100;
  }
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('dald', $self->{name});
  $self->add_info(sprintf "logical drive %s is %s (%s)",
          $self->{name}, $self->{cpqDaLogDrvStatus},
          $self->{cpqDaLogDrvFaultTol});
  if ($self->{cpqDaLogDrvCondition} ne "ok") {
    if ($self->{cpqDaLogDrvStatus} =~ 
        /rebuild|recovering|recovery|expanding|queued/) {
      $self->add_message(WARNING,
          sprintf "logical drive %s is %s", 
              $self->{name}, $self->{cpqDaLogDrvStatus});
    } else {
      $self->add_message(CRITICAL,
          sprintf "logical drive %s is %s",
              $self->{name}, $self->{cpqDaLogDrvStatus});
    }
  } 
}

sub dump {
  my $self = shift;
  printf "[LOGICAL_DRIVE]\n";
  foreach (qw(cpqDaLogDrvCntlrIndex cpqDaLogDrvIndex cpqDaLogDrvSize
      cpqDaLogDrvFaultTol cpqDaLogDrvStatus cpqDaLogDrvCondition
      cpqDaLogDrvPercentRebuild cpqDaLogDrvPhyDrvIDs)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::Proliant::Component::DiskSubsystem::Da::PhysicalDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Da);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    name => $params{name},
    cpqDaPhyDrvCntlrIndex => $params{cpqDaPhyDrvCntlrIndex},
    cpqDaPhyDrvIndex => $params{cpqDaPhyDrvIndex},
    cpqDaPhyDrvBay => $params{cpqDaPhyDrvBay},
    cpqDaPhyDrvBusNumber => $params{cpqDaPhyDrvBusNumber},
    cpqDaPhyDrvSize => $params{cpqDaPhyDrvSize},
    cpqDaPhyDrvStatus => $params{cpqDaPhyDrvStatus},
    cpqDaPhyDrvCondition => $params{cpqDaPhyDrvCondition},
    blacklisted => 0,
  };
  bless $self, $class;
  $self->{name} = $params{name} ||
      $self->{cpqDaPhyDrvCntlrIndex}.':'.$self->{cpqDaPhyDrvIndex}; ##vorerst
  $self->{controllerindex} = $self->{cpqDaPhyDrvCntlrIndex};
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('dapd', $self->{name});
  $self->add_info(
      sprintf "physical drive %s is %s",
          $self->{name}, $self->{cpqDaPhyDrvCondition});
  if ($self->{cpqDaPhyDrvCondition} ne 'ok') {
    $self->add_message(CRITICAL,
        sprintf "physical drive %s is %s", 
            $self->{name}, $self->{cpqDaPhyDrvCondition});
  }
}

sub dump {
  my $self = shift;
  printf "[PHYSICAL_DRIVE]\n";
  foreach (qw(cpqDaPhyDrvCntlrIndex cpqDaPhyDrvIndex cpqDaPhyDrvBay
      cpqDaPhyDrvBusNumber cpqDaPhyDrvSize cpqDaPhyDrvStatus
      cpqDaPhyDrvCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::Proliant::Component::DiskSubsystem::Da::SpareDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Da);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub dump {
  my $self = shift;
  printf "[SPARE_DRIVE]\n";
  foreach (qw(cpqDaPhyDrvCntlrIndex cpqDaPhyDrvIndex cpqDaPhyDrvBay
      cpqDaPhyDrvBusNumber cpqDaPhyDrvSize cpqDaPhyDrvStatus
      cpqDaPhyDrvCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}



package HP::Proliant::Component::DiskSubsystem::Sas::CLI;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Sas);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    controllers => [],
    accelerators => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    blacklisted => 0,
  };
  bless $self, $class;
  return $self;
}

sub init {
  my $self = shift;
}


package HP::Proliant::Component::DiskSubsystem::Sas::SNMP;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Sas
    HP::Proliant::Component::SNMP);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = { 
    controllers => [],
    accelerators => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    blacklisted => 0,
  };
  bless $self, $class;
  return $self;
}

sub init {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};

  # CPQSCSI-MIB
  my $oids = {
      cpqSasHbaEntry => "1.3.6.1.4.1.232.5.5.1.1.1",
      cpqSasHbaIndex => "1.3.6.1.4.1.232.5.5.1.1.1.1",
      cpqSasHbaLocation => "1.3.6.1.4.1.232.5.5.1.1.1.2",
      cpqSasHbaSlot  => "1.3.6.1.4.1.232.5.5.1.1.1.6",
      cpqSasHbaStatus  => "1.3.6.1.4.1.232.5.5.1.1.1.4",
      cpqSasHbaStatusValue => {
          1 => "other",
          2 => "ok",
          3 => "failed",
      },
      cpqSasHbaCondition  => "1.3.6.1.4.1.232.5.5.1.1.1.5",
      cpqSasHbaConditionValue => {
          1 => "other",
          2 => "ok", 
          3 => "degraded", 
          4 => "failed",
      },
  };

  # INDEX { cpqSasHbaIndex } 
  foreach ($self->get_entries($oids, 'cpqSasHbaEntry')) {
    push(@{$self->{controllers}},
        HP::Proliant::Component::DiskSubsystem::Sas::Controller->new(%{$_}));
  }

  $oids = {
      cpqSasLogDrvEntry => "1.3.6.1.4.1.232.5.5.3.1.1",
      cpqSasLogDrvHbaIndex => "1.3.6.1.4.1.232.5.5.3.1.1.1",
      cpqSasLogDrvIndex => "1.3.6.1.4.1.232.5.5.3.1.1.2",
      cpqSasLogDrvStatus => "1.3.6.1.4.1.232.5.5.3.1.1.4",
      cpqSasLogDrvCondition => "1.3.6.1.4.1.232.5.5.3.1.1.5",
      cpqSasLogDrvRebuildingPercent => "1.3.6.1.4.1.232.5.5.3.1.1.12",
      cpqSasLogDrvRaidLevel => "1.3.6.1.4.1.232.5.5.3.1.1.3",
      cpqSasLogDrvRaidLevelValue => {
          1 => "other",
          2 => "raid0",
          3 => "raid1",
          4 => "raid0plus1",
          5 => "raid5",
          6 => "raid15",
          7 => "volume",
      },
      cpqSasLogDrvConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
      cpqSasLogDrvStatusValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "rebuilding",
          5 => "failed",
          6 => "offline",
      }
  };
  # INDEX { cpqSasLogDrvCntlrIndex, cpqSasLogDrvIndex }
  foreach ($self->get_entries($oids, 'cpqSasLogDrvEntry')) {
    push(@{$self->{logical_drives}},
        HP::Proliant::Component::DiskSubsystem::Sas::LogicalDrive->new(%{$_}));
  }

  $oids = {
      cpqSasPhyDrvEntry => "1.3.6.1.4.1.232.5.5.2.1.1",
      cpqSasPhyDrvHbaIndex => "1.3.6.1.4.1.232.5.5.2.1.1.1",
      cpqSasPhyDrvIndex => "1.3.6.1.4.1.232.5.5.2.1.1.2",
      cpqSasPhyDrvLocationString => "1.3.6.1.4.1.232.5.5.2.1.1.3",
      cpqSasPhyDrvStatus => "1.3.6.1.4.1.232.5.5.2.1.1.5",
      cpqSasPhyDrvSize => "1.3.6.1.4.1.232.5.5.2.1.1.8",
      cpqSasPhyDrvCondition => "1.3.6.1.4.1.232.5.5.2.1.1.6",
      cpqSasPhyDrvConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
      cpqSasPhyDrvStatusValue => {
          1 => "other",
          2 => "ok",
          3 => "predictiveFailure",
          4 => "offline",
          5 => "failed",
          6 => "missingWasOk",
          7 => "missingWasPredictiveFailure",
          8 => "missingWasOffline",
          9 => "missingWasFailed",
      },
  };
    
  # INDEX { cpqPhyLogDrvCntlrIndex, cpqSasPhyDrvIndex }
  foreach ($self->get_entries($oids, 'cpqSasPhyDrvEntry')) {
    push(@{$self->{physical_drives}},
        HP::Proliant::Component::DiskSubsystem::Sas::PhysicalDrive->new(%{$_}));
  }

}
package HP::Proliant::Component::DiskSubsystem::Sas;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    controllers => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    condition => undef,
    blacklisted => 0,
  };
  bless $self, $class;
  if ($self->{method} eq 'snmp') {
    bless $self, 'HP::Proliant::Component::DiskSubsystem::Sas::SNMP';
  } else {
    bless $self, 'HP::Proliant::Component::DiskSubsystem::Sas::CLI';
  }
  $self->init();
  $self->assemble();
  return $self;
}

sub check {
  my $self = shift;
  foreach (@{$self->{controllers}}) {
    $_->check();
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{controllers}}) {
    $_->dump();
  }
}

package HP::Proliant::Component::DiskSubsystem::Sas::Controller;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Sas);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqSasHbaIndex => $params{cpqSasHbaIndex},
    cpqSasHbaLocation => $params{cpqSasHbaLocation},
    cpqSasHbaSlot => $params{cpqSasHbaSlot},
    cpqSasHbaStatus => $params{cpqSasHbaStatus},
    cpqSasHbaCondition => $params{cpqSasHbaCondition},
    blacklisted => 0,
  };
  $self->{name} = $params{name} || $self->{cpqSasHbaSlot};
  $self->{controllerindex} = $self->{cpqSasHbaIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('saco', $self->{cpqSasHbaSlot});
  if ($self->{cpqSasHbaCondition} eq 'other') {
    if (scalar(@{$self->{physical_drives}})) {
      $self->add_message(CRITICAL,
          sprintf 'sas controller in slot %s needs attention',
              $self->{cpqSasHbaSlot});
      $self->add_info(sprintf 'sas controller in slot %s needs attention',
          $self->{cpqSasHbaSlot});
    } else {
      $self->add_info(sprintf 'sas controller in slot %s is ok and unused',
          $self->{cpqSasHbaSlot});
      $self->{blacklisted} = 1;
    }
  } elsif ($self->{cpqSasHbaCondition} ne 'ok') {
    $self->add_message(CRITICAL,
        sprintf 'sas controller in slot %s needs attention',
            $self->{cpqSasHbaSlot});
    $self->add_info(sprintf 'sas controller in slot %s needs attention',
        $self->{cpqSasHbaSlot});
  } else {
    $self->add_info(sprintf 'sas controller in slot %s is ok',
        $self->{cpqSasHbaSlot});
  }
  foreach (@{$self->{logical_drives}}) {
    $_->check();
  } 
  foreach (@{$self->{physical_drives}}) {
    $_->check();
  } 
  foreach (@{$self->{spare_drives}}) {
    $_->check();
  } 
} 

sub dump {
  my $self = shift;
  printf "[SAS_HBA%s]\n", $self->{name};
  foreach (qw(cpqSasHbaSlot cpqSasHbaIndex cpqSasHbaCondition
      cpqSasHbaStatus cpqSasHbaLocation)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
  foreach (@{$self->{logical_drives}}) {
    $_->dump();
  }
  foreach (@{$self->{physical_drives}}) {
    $_->dump();
  }
  foreach (@{$self->{spare_drives}}) {
    $_->dump();
  }
}


package HP::Proliant::Component::DiskSubsystem::Sas::LogicalDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Sas);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqSasLogDrvHbaIndex => $params{cpqSasLogDrvHbaIndex},
    cpqSasLogDrvIndex => $params{cpqSasLogDrvIndex},
    cpqSasLogDrvStatus => $params{cpqSasLogDrvStatus},
    cpqSasLogDrvCondition => $params{cpqSasLogDrvCondition},
    cpqSasLogDrvRebuildingPercent => $params{cpqSasLogDrvRebuildingPercent},
    cpqSasLogDrvRaidLevel => $params{cpqSasLogDrvRaidLevel},
    blacklisted => 0,
  };
  bless $self, $class;
  $self->{name} = $params{name} || 
      $self->{cpqSasLogDrvHbaIndex}.':'.$self->{cpqSasLogDrvIndex}; ####vorerst
  $self->{controllerindex} = $self->{cpqSasLogDrvHbaIndex};
  if (! $self->{cpqSasLogDrvRebuildingPercent} ||
      $self->{cpqSasLogDrvRebuildingPercent} == 4294967295) {
    $self->{cpqSasLogDrvRebuildingPercent} = 100;
  }
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('sald', $self->{name});
  if ($self->{cpqSasLogDrvCondition} ne "ok") {
    if ($self->{cpqSasLogDrvStatus} =~ 
        /rebuild|recovering|expanding|queued/) {
      $self->add_message(WARNING,
          sprintf "logical drive %s is %s", 
              $self->{name}, $self->{cpqSasLogDrvStatus});
    } else {
      $self->add_message(CRITICAL,
          sprintf "logical drive %s is %s",
              $self->{name}, $self->{cpqSasLogDrvStatus});
    }
  } 
  $self->add_info(
      sprintf "logical drive %s is %s (%s)", $self->{name},
          $self->{cpqSasLogDrvStatus}, $self->{cpqSasLogDrvRaidLevel});
}

sub dump {
  my $self = shift;
  printf "[LOGICAL_DRIVE]\n";
  foreach (qw(cpqSasLogDrvHbaIndex cpqSasLogDrvIndex cpqSasLogDrvRaidLevel
      cpqSasLogDrvStatus cpqSasLogDrvCondition
      cpqSasLogDrvRebuildingPercent)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::Proliant::Component::DiskSubsystem::Sas::PhysicalDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Sas);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqSasPhyDrvHbaIndex => $params{cpqSasPhyDrvHbaIndex},
    cpqSasPhyDrvIndex => $params{cpqSasPhyDrvIndex},
    cpqSasPhyDrvLocationString => $params{cpqSasPhyDrvLocationString},
    cpqSasPhyDrvStatus => $params{cpqSasPhyDrvStatus},
    cpqSasPhyDrvSize => $params{cpqSasPhyDrvSize},
    cpqSasPhyDrvCondition => $params{cpqSasPhyDrvCondition},
    blacklisted => 0,
  };
  $self->{name} = $params{name} || 
      $self->{cpqSasPhyDrvHbaIndex}.':'.$self->{cpqSasPhyDrvIndex}; ####vorerst
  $self->{controllerindex} = $self->{cpqSasPhyDrvHbaIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('sapd', $self->{name});
  if ($self->{cpqSasPhyDrvCondition} ne 'ok') {
    $self->add_message(CRITICAL,
        sprintf "physical drive %s is %s", 
            $self->{name}, $self->{cpqSasPhyDrvCondition});
  }
  $self->add_info(
      sprintf "physical drive %s is %s", 
          $self->{name}, $self->{cpqSasPhyDrvCondition});
}

sub dump {
  my $self = shift;
  printf "[PHYSICAL_DRIVE]\n";
  foreach (qw(cpqSasPhyDrvHbaIndex cpqSasPhyDrvIndex cpqSasPhyDrvLocationString
      cpqSasPhyDrvSize cpqSasPhyDrvStatus cpqSasPhyDrvCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::Proliant::Component::DiskSubsystem::Sas::SpareDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Sas);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub dump {
  my $self = shift;
  printf "[SPARE_DRIVE]\n";
}



package HP::Proliant::Component::DiskSubsystem::Scsi::CLI;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Scsi);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    controllers => [],
    accelerators => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    blacklisted => 0,
  };
  bless $self, $class;
  return $self;
}

sub init {
  my $self = shift;
}


package HP::Proliant::Component::DiskSubsystem::Scsi::SNMP;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Scsi
    HP::Proliant::Component::SNMP);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = { 
    controllers => [],
    accelerators => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    blacklisted => 0,
  };
  bless $self, $class;
  return $self;
}

sub init {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};

  # CPQSCSI-MIB
  my $oids = {
      cpqScsiCntlrEntry => '1.3.6.1.4.1.232.5.2.2.1.1',
      cpqScsiCntlrIndex => '1.3.6.1.4.1.232.5.2.2.1.1.1',
      cpqScsiCntlrBusIndex => '1.3.6.1.4.1.232.5.2.2.1.1.2',
      cpqScsiCntlrSlot => '1.3.6.1.4.1.232.5.2.2.1.1.6',
      cpqScsiCntlrStatus => '1.3.6.1.4.1.232.5.2.2.1.1.7',
      cpqScsiCntlrCondition => '1.3.6.1.4.1.232.5.2.2.1.1.12',
      cpqScsiCntlrHwLocation => '1.3.6.1.4.1.232.5.2.2.1.1.16',
      cpqScsiCntlrStatusValue => {
          1 => "other",
          2 => "ok",
          3 => "failed",
      },
      cpqScsiCntlrConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      }
  };

  # INDEX { cpqScsiCntlrIndex, cpqScsiCntlrBusIndex }
  foreach ($self->get_entries($oids, 'cpqScsiCntlrEntry')) {
    push(@{$self->{controllers}},
        HP::Proliant::Component::DiskSubsystem::Scsi::Controller->new(%{$_}));
  }

  $oids = {
      cpqScsiLogDrvEntry => '1.3.6.1.4.1.232.5.2.3.1.1',
      cpqScsiLogDrvCntlrIndex => '1.3.6.1.4.1.232.5.2.3.1.1.1',
      cpqScsiLogDrvBusIndex => '1.3.6.1.4.1.232.5.2.3.1.1.2',
      cpqScsiLogDrvIndex => '1.3.6.1.4.1.232.5.2.3.1.1.3',
      cpqScsiLogDrvFaultTol => '1.3.6.1.4.1.232.5.2.3.1.1.4',
      cpqScsiLogDrvStatus => '1.3.6.1.4.1.232.5.2.3.1.1.5',
      cpqScsiLogDrvSize => '1.3.6.1.4.1.232.5.2.3.1.1.6',
      cpqScsiLogDrvPhyDrvIDs => '1.3.6.1.4.1.232.5.2.3.1.1.7',
      cpqScsiLogDrvCondition => '1.3.6.1.4.1.232.5.2.3.1.1.8',
      cpqScsiLogDrvStatusValue => {
          1 => "other",
          2 => "ok",
          3 => "failed",
          4 => "unconfigured",
          5 => "recovering",
          6 => "readyForRebuild",
          7 => "rebuilding",
          8 => "wrongDrive",
          9 => "badConnect",
      },
      cpqScsiLogDrvConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
      cpqScsiLogDrvFaultTolValue => {
          1 => "other",
          2 => "none",
          3 => "mirroring",
          4 => "dataGuard",
          5 => "distribDataGuard",
      },

  };
  # INDEX { cpqScsiLogDrvCntlrIndex, cpqScsiLogDrvBusIndex, cpqScsiLogDrvIndex }
  foreach ($self->get_entries($oids, 'cpqScsiLogDrvEntry')) {
    push(@{$self->{logical_drives}},
        HP::Proliant::Component::DiskSubsystem::Scsi::LogicalDrive->new(%{$_}));
  }

  $oids = {
      cpqScsiPhyDrvEntry => '1.3.6.1.4.1.232.5.2.4.1.1',
      cpqScsiPhyDrvCntlrIndex => '1.3.6.1.4.1.232.5.2.4.1.1.1',
      cpqScsiPhyDrvBusIndex => '1.3.6.1.4.1.232.5.2.4.1.1.2',
      cpqScsiPhyDrvIndex => '1.3.6.1.4.1.232.5.2.4.1.1.3',
      cpqScsiPhyDrvStatus => '1.3.6.1.4.1.232.5.2.4.1.1.9',
      cpqScsiPhyDrvSize => '1.3.6.1.4.1.232.5.2.4.1.1.7',
      cpqScsiPhyDrvCondition => '1.3.6.1.4.1.232.5.2.4.1.1.26',
      cpqScsiPhyDrvConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
      cpqScsiPhyDrvStatusValue => {
          1 => "other",
          2 => "ok",
          3 => "failed",
          4 => "notConfigured",
          5 => "badCable",
          6 => "missingWasOk",
          7 => "missingWasFailed",
          8 => "predictiveFailure",
          9 => "missingWasPredictiveFailure",
          10 => "offline",
          11 => "missingWasOffline",
          12 => "hardError",
      },
  };
    
  # INDEX { cpqScsiPhyDrvCntlrIndex, cpqScsiPhyDrvBusIndex, cpqScsiPhyDrvIndex }
  foreach ($self->get_entries($oids, 'cpqScsiPhyDrvEntry')) {
    push(@{$self->{physical_drives}},
        HP::Proliant::Component::DiskSubsystem::Scsi::PhysicalDrive->new(%{$_}));
  }

}
package HP::Proliant::Component::DiskSubsystem::Scsi;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    controllers => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    condition => undef,
    blacklisted => 0,
  };
  bless $self, $class;
  if ($self->{method} eq 'snmp') {
    bless $self, 'HP::Proliant::Component::DiskSubsystem::Scsi::SNMP';
  } else {
    bless $self, 'HP::Proliant::Component::DiskSubsystem::Scsi::CLI';
  }
  $self->init();
  $self->assemble();
  return $self;
}

sub check {
  my $self = shift;
  foreach (@{$self->{controllers}}) {
    $_->check();
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{controllers}}) {
    $_->dump();
  }
}

package HP::Proliant::Component::DiskSubsystem::Scsi::Controller;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Scsi);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  map { $self->{$_} = $params{$_} } grep /cpqScsiCntlr/, keys %params;
  $self->{name} = $params{name} || $params{cpqScsiCntlrIndex}.':'.$params{cpqScsiCntlrBusIndex};
  $self->{controllerindex} = $self->{cpqScsiCntlrIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('scco', $self->{name});
  my $info = sprintf 'scsi controller %s in slot %s is %s',
      $self->{name}, $self->{cpqScsiCntlrSlot}, $self->{cpqScsiCntlrCondition};
  if ($self->{cpqScsiCntlrCondition} eq 'other') {
    if (scalar(@{$self->{physical_drives}})) {
      $info .= ' and needs attention';
      $self->add_message(CRITICAL, $info);
      $self->add_info($info);
    } else {
      $info .= ' and unused';
      $self->add_info($info);
      $self->{blacklisted} = 1;
    }
  } elsif ($self->{cpqScsiCntlrCondition} ne 'ok') {
    $info .= ' and needs attention';
    $self->add_message(CRITICAL, $info);
    $self->add_info($info);
  } else {
    $self->add_info($info);
  }
  foreach (@{$self->{logical_drives}}) {
    $_->check();
  } 
  foreach (@{$self->{physical_drives}}) {
    $_->check();
  } 
  foreach (@{$self->{spare_drives}}) {
    $_->check();
  } 
} 

sub dump {
  my $self = shift;
  printf "[SCSI_CONTROLLER_%s]\n", $self->{name};
  foreach (qw(cpqScsiCntlrIndex cpqScsiCntlrBusIndex cpqScsiCntlrSlot
      cpqScsiCntlrStatus cpqScsiCntlrCondition cpqScsiCntlrHwLocation)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
  foreach (@{$self->{logical_drives}}) {
    $_->dump();
  }
  foreach (@{$self->{physical_drives}}) {
    $_->dump();
  }
  foreach (@{$self->{spare_drives}}) {
    $_->dump();
  }
}


package HP::Proliant::Component::DiskSubsystem::Scsi::LogicalDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Scsi);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  map { $self->{$_} = $params{$_} } grep /cpqScsiLogDrv/, keys %params;
  $self->{name} = $params{name} || $params{cpqScsiLogDrvCntlrIndex}.':'.$params{cpqScsiLogDrvBusIndex}.':'.$params{cpqScsiLogDrvIndex};
  bless $self, $class;
  $self->{controllerindex} = $self->{cpqScsiLogDrvCntlrIndex};
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('scld', $self->{name});
  my $info = sprintf 'logical drive %s is %s', $self->{name}, $self->{cpqScsiLogDrvStatus};
  if ($self->{cpqScsiLogDrvCondition} ne "ok") {
    if ($self->{cpqScsiLogDrvStatus} =~ 
        /rebuild|recovering/) {
      $self->add_message(WARNING, $info);
    } else {
      $self->add_message(CRITICAL, $info);
    }
  } 
  $self->add_info($info);
}

sub dump {
  my $self = shift;
  printf "[LOGICAL_DRIVE_%s]\n", $self->{name};
  foreach (qw(cpqScsiLogDrvCntlrIndex cpqScsiLogDrvBusIndex cpqScsiLogDrvIndex
      cpqScsiLogDrvFaultTol cpqScsiLogDrvStatus cpqScsiLogDrvSize 
      cpqScsiLogDrvPhyDrvIDs cpqScsiLogDrvCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::Proliant::Component::DiskSubsystem::Scsi::PhysicalDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Scsi);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  map { $self->{$_} = $params{$_} } grep /cpqScsiPhyDrv/, keys %params;
  $self->{name} = $params{name} || 
      $self->{cpqScsiPhyDrvCntlrIndex}.':'.$self->{cpqScsiPhyDrvBusIndex}.':'.$self->{cpqScsiPhyDrvIndex}; 
  $self->{controllerindex} = $self->{cpqScsiPhyDrvCntlrIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('scpd', $self->{name});
  my $info = sprintf 'physical drive %s is %s', $self->{name}, $self->{cpqScsiPhyDrvCondition};
  if ($self->{cpqScsiPhyDrvCondition} ne 'ok') {
    $self->add_message(CRITICAL, $info);
  }
  $self->add_info($info);
}

sub dump {
  my $self = shift;
  printf "[PHYSICAL_DRIVE_%s]\n", $self->{name};
  foreach (qw(cpqScsiPhyDrvCntlrIndex cpqScsiPhyDrvBusIndex cpqScsiPhyDrvIndex
      cpqScsiPhyDrvStatus cpqScsiPhyDrvSize cpqScsiPhyDrvCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::Proliant::Component::DiskSubsystem::Scsi::SpareDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Scsi);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub dump {
  my $self = shift;
  printf "[SPARE_DRIVE]\n";
}



package HP::Proliant::Component::DiskSubsystem::Ide::CLI;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Ide);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    controllers => [],
    accelerators => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    blacklisted => 0,
  };
  bless $self, $class;
  return $self;
}

sub init {
  my $self = shift;
}


package HP::Proliant::Component::DiskSubsystem::Ide::SNMP;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Ide
    HP::Proliant::Component::SNMP);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = { 
    controllers => [],
    accelerators => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    blacklisted => 0,
  };
  bless $self, $class;
  return $self;
}

sub init {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};

  # CPQIDE-MIB
  my $oids = {
      cpqIdeControllerEntry => '1.3.6.1.4.1.232.14.2.3.1.1',
      cpqIdeControllerIndex => '1.3.6.1.4.1.232.14.2.3.1.1.1',
      cpqIdeControllerOverallCondition => '1.3.6.1.4.1.232.14.2.3.1.1.2',
      cpqIdeControllerModel => '1.3.6.1.4.1.232.14.2.3.1.1.3',
      cpqIdeControllerSlot => '1.3.6.1.4.1.232.14.2.3.1.1.5',
      cpqIdeControllerOverallConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
  };

  # INDEX { cpqIdeControllerIndex }
  foreach ($self->get_entries($oids, 'cpqIdeControllerEntry')) {
    push(@{$self->{controllers}},
        HP::Proliant::Component::DiskSubsystem::Ide::Controller->new(%{$_}));
  }

  $oids = {
      cpqIdeLogicalDriveEntry => '1.3.6.1.4.1.232.14.2.6.1.1',
      cpqIdeLogicalDriveControllerIndex => '1.3.6.1.4.1.232.14.2.6.1.1.1',
      cpqIdeLogicalDriveIndex => '1.3.6.1.4.1.232.14.2.6.1.1.2',
      cpqIdeLogicalDriveRaidLevel => '1.3.6.1.4.1.232.14.2.6.1.1.3',
      cpqIdeLogicalDriveCapacity => '1.3.6.1.4.1.232.14.2.6.1.1.4',
      cpqIdeLogicalDriveStatus => '1.3.6.1.4.1.232.14.2.6.1.1.5',
      cpqIdeLogicalDriveCondition => '1.3.6.1.4.1.232.14.2.6.1.1.6',
      cpqIdeLogicalDriveDiskIds => '1.3.6.1.4.1.232.14.2.6.1.1.7',
      cpqIdeLogicalDriveSpareIds => '1.3.6.1.4.1.232.14.2.6.1.1.9',
      cpqIdeLogicalDriveRebuildingDisk => '1.3.6.1.4.1.232.14.2.6.1.1.10',
      cpqIdeLogicalDriveRaidLevelValue => {
          1 => "other",
          2 => "raid0",
          3 => "raid1",
          4 => "raid0plus1",
      },
      cpqIdeLogicalDriveStatusValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "rebuilding",
          5 => "failed",
      },
      cpqIdeLogicalDriveConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
  };
  # INDEX { cpqIdeLogicalDriveControllerIndex, cpqIdeLogicalDriveIndex }
  foreach ($self->get_entries($oids, 'cpqIdeLogicalDriveEntry')) {
    push(@{$self->{logical_drives}},
        HP::Proliant::Component::DiskSubsystem::Ide::LogicalDrive->new(%{$_}));
  }

  $oids = {
      cpqIdeAtaDiskEntry => '1.3.6.1.4.1.232.14.2.4.1.1',
      cpqIdeAtaDiskControllerIndex => '1.3.6.1.4.1.232.14.2.4.1.1.1',
      cpqIdeAtaDiskIndex => '1.3.6.1.4.1.232.14.2.4.1.1.2',
      cpqIdeAtaDiskModel => '1.3.6.1.4.1.232.14.2.4.1.1.3',
      cpqIdeAtaDiskStatus => '1.3.6.1.4.1.232.14.2.4.1.1.6',
      cpqIdeAtaDiskCondition => '1.3.6.1.4.1.232.14.2.4.1.1.7',
      cpqIdeAtaDiskCapacity => '1.3.6.1.4.1.232.14.2.4.1.1.8',
      cpqIdeAtaDiskLogicalDriveMember => '1.3.6.1.4.1.232.14.2.4.1.1.13',
      cpqIdeAtaDiskIsSpare => '1.3.6.1.4.1.232.14.2.4.1.1.14',
      cpqIdeAtaDiskStatusValue => {
          1 => "other",
          2 => "ok",
          3 => "smartError",
          4 => "failed",
      },
      cpqIdeAtaDiskConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
  };
  # INDEX { cpqIdeAtaDiskControllerIndex, cpqIdeAtaDiskIndex }
  foreach ($self->get_entries($oids, 'cpqIdeAtaDiskEntry')) {
    push(@{$self->{physical_drives}},
        HP::Proliant::Component::DiskSubsystem::Ide::PhysicalDrive->new(%{$_}));
  }

}
package HP::Proliant::Component::DiskSubsystem::Ide;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    controllers => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    condition => undef,
    blacklisted => 0,
  };
  bless $self, $class;
  if ($self->{method} eq 'snmp') {
    bless $self, 'HP::Proliant::Component::DiskSubsystem::Ide::SNMP';
  } else {
    bless $self, 'HP::Proliant::Component::DiskSubsystem::Ide::CLI';
  }
  $self->init();
  $self->assemble();
  return $self;
}

sub check {
  my $self = shift;
  foreach (@{$self->{controllers}}) {
    $_->check();
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{controllers}}) {
    $_->dump();
  }
}

package HP::Proliant::Component::DiskSubsystem::Ide::Controller;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Ide);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqIdeControllerIndex => $params{cpqIdeControllerIndex},
    cpqIdeControllerOverallCondition => $params{cpqIdeControllerOverallCondition},
    cpqIdeControllerModel => $params{cpqIdeControllerModel},
    cpqIdeControllerSlot => $params{cpqIdeControllerSlot}, # -1 ist sysboard?
    blacklisted => 0,
  };
  $self->{name} = $params{name} || $self->{cpqIdeControllerIndex};
  $self->{controllerindex} = $self->{cpqIdeControllerIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('ideco', $self->{name});
  if ($self->{cpqIdeControllerOverallCondition} eq 'other') {
    if (scalar(@{$self->{physical_drives}})) {
      $self->add_message(CRITICAL,
          sprintf 'ide controller %s in slot %s needs attention',
              $self->{cpqIdeControllerIndex}, $self->{cpqIdeControllerSlot});
      $self->add_info(sprintf 'ide controller %s in slot %s needs attention',
          $self->{cpqIdeControllerIndex}, $self->{cpqIdeControllerSlot});
    } else {
      $self->add_info(sprintf 'ide controller %s in slot %s is ok and unused',
          $self->{cpqIdeControllerIndex}, $self->{cpqIdeControllerSlot});
      $self->{blacklisted} = 1;
    }
  } elsif ($self->{cpqIdeControllerOverallCondition} ne 'ok') {
    $self->add_message(CRITICAL,
        sprintf 'ide controller %s in slot %s needs attention',
            $self->{cpqIdeControllerIndex}, $self->{cpqIdeControllerSlot});
    $self->add_info(sprintf 'ide controller %s in slot %s needs attention',
        $self->{cpqIdeControllerIndex}, $self->{cpqIdeControllerSlot});
  } else {
    $self->add_info(sprintf 'ide controller %s in slot %s is ok',
        $self->{cpqIdeControllerIndex}, $self->{cpqIdeControllerSlot});
  }
  foreach (@{$self->{logical_drives}}) {
    $_->check();
  } 
  foreach (@{$self->{physical_drives}}) {
    $_->check();
  } 
  foreach (@{$self->{spare_drives}}) {
    $_->check();
  } 
} 

sub dump {
  my $self = shift;
  printf "[IDE_CONTROLLER_%s]\n", $self->{name};
  foreach (qw(cpqIdeControllerIndex cpqIdeControllerSlot
      cpqIdeControllerModel cpqIdeControllerOverallCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
  foreach (@{$self->{logical_drives}}) {
    $_->dump();
  }
  foreach (@{$self->{physical_drives}}) {
    $_->dump();
  }
  foreach (@{$self->{spare_drives}}) {
    $_->dump();
  }
}


package HP::Proliant::Component::DiskSubsystem::Ide::LogicalDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Ide);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqIdeLogicalDriveControllerIndex => $params{cpqIdeLogicalDriveControllerIndex},
    cpqIdeLogicalDriveIndex => $params{cpqIdeLogicalDriveIndex},
    cpqIdeLogicalDriveRaidLevel => $params{cpqIdeLogicalDriveRaidLevel},
    cpqIdeLogicalDriveCapacity => $params{cpqIdeLogicalDriveCapacity},
    cpqIdeLogicalDriveStatus => $params{cpqIdeLogicalDriveStatus},
    cpqIdeLogicalDriveCondition => $params{cpqIdeLogicalDriveCondition},
    cpqIdeLogicalDriveDiskIds => $params{cpqIdeLogicalDriveDiskIds},
    cpqIdeLogicalDriveSpareIds => $params{cpqIdeLogicalDriveSpareIds},
    cpqIdeLogicalDriveRebuildingDisk => $params{cpqIdeLogicalDriveRebuildingDisk},
    blacklisted => 0,
  };
  bless $self, $class;
  $self->{name} = $params{name} || 
      $self->{cpqIdeLogicalDriveControllerIndex}.':'.
      $self->{cpqIdeLogicalDriveIndex};
  $self->{controllerindex} = $self->{cpqIdeLogicalDriveControllerIndex};
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('ideld', $self->{name});
  if ($self->{cpqIdeLogicalDriveCondition} ne "ok") {
    if ($self->{cpqIdeLogicalDriveStatus} =~ 
        /rebuild/) {
      $self->add_message(WARNING,
          sprintf "logical drive %s is %s", 
              $self->{name}, $self->{cpqIdeLogicalDriveStatus});
    } else {
      $self->add_message(CRITICAL,
          sprintf "logical drive %s is %s",
              $self->{name}, $self->{cpqIdeLogicalDriveStatus});
    }
  } 
  $self->add_info(
      sprintf "logical drive %s is %s", $self->{name},
          $self->{cpqIdeLogicalDriveStatus});
}

sub dump {
  my $self = shift;
  printf "[LOGICAL_DRIVE]\n";
  foreach (qw(cpqIdeLogicalDriveControllerIndex cpqIdeLogicalDriveIndex
      cpqIdeLogicalDriveRaidLevel cpqIdeLogicalDriveCapacity 
      cpqIdeLogicalDriveDiskIds cpqIdeLogicalDriveSpareIds 
      cpqIdeLogicalDriveRebuildingDisk cpqIdeLogicalDriveStatus 
      cpqIdeLogicalDriveCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::Proliant::Component::DiskSubsystem::Ide::PhysicalDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Ide);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqIdeAtaDiskControllerIndex => $params{cpqIdeAtaDiskControllerIndex},
    cpqIdeAtaDiskIndex => $params{cpqIdeAtaDiskIndex},
    cpqIdeAtaDiskModel => $params{cpqIdeAtaDiskModel},
    cpqIdeAtaDiskStatus => $params{cpqIdeAtaDiskStatus},
    cpqIdeAtaDiskCondition => $params{cpqIdeAtaDiskCondition},
    cpqIdeAtaDiskCapacity => $params{cpqIdeAtaDiskCapacity},
    cpqIdeAtaDiskLogicalDriveMember => $params{cpqIdeAtaDiskLogicalDriveMember},
    cpqIdeAtaDiskIsSpare => $params{cpqIdeAtaDiskIsSpare},
    blacklisted => 0,
  };
  $self->{name} = $params{name} || 
      $self->{cpqIdeAtaDiskControllerIndex}.':'.
      $self->{cpqIdeAtaDiskIndex}; ####vorerst
  $self->{controllerindex} = $self->{cpqIdeAtaDiskControllerIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('idepd', $self->{name});
  if ($self->{cpqIdeAtaDiskCondition} ne 'ok') {
    $self->add_message(CRITICAL,
        sprintf "physical drive %s is %s", 
            $self->{name}, $self->{cpqIdeAtaDiskCondition});
  }
  $self->add_info(
      sprintf "physical drive %s is %s", 
          $self->{name}, $self->{cpqIdeAtaDiskCondition});
}

sub dump {
  my $self = shift;
  printf "[PHYSICAL_DRIVE]\n";
  foreach (qw(cpqIdeAtaDiskControllerIndex cpqIdeAtaDiskIndex
      cpqIdeAtaDiskModel cpqIdeAtaDiskCapacity cpqIdeAtaDiskLogicalDriveMember 
      cpqIdeAtaDiskStatus cpqIdeAtaDiskCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::Proliant::Component::DiskSubsystem::Ide::SpareDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Ide);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub dump {
  my $self = shift;
  printf "[SPARE_DRIVE]\n";
}



package HP::Proliant::Component::DiskSubsystem::Fca::CLI;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Fca);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    controllers => [],
    accelerators => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    blacklisted => 0,
  };
  bless $self, $class;
  return $self;
}

sub init {
  my $self = shift;
  # .....
  $self->{global_status} =
      HP::Proliant::Component::DiskSubsystem::Fca::GlobalStatus->new(
          runtime => $self->{runtime},
          cpqFcaMibCondition => 'n/a',
      );

}


package HP::Proliant::Component::DiskSubsystem::Fca::SNMP;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Fca
    HP::Proliant::Component::SNMP);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = { 
    controllers => [],
    accelerators => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    blacklisted => 0,
  };
  bless $self, $class;
  return $self;
}

sub init {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};

  # CPQFCA-MIB
  my $oids = {
      cpqFcaHostCntlrEntry => '1.3.6.1.4.1.232.16.2.7.1.1',
      cpqFcaHostCntlrIndex => '1.3.6.1.4.1.232.16.2.7.1.1.1',
      cpqFcaHostCntlrSlot => '1.3.6.1.4.1.232.16.2.7.1.1.2',
      cpqFcaHostCntlrModel => '1.3.6.1.4.1.232.16.2.7.1.1.3',
      cpqFcaHostCntlrStatus => '1.3.6.1.4.1.232.16.2.7.1.1.4',
      cpqFcaHostCntlrCondition => '1.3.6.1.4.1.232.16.2.7.1.1.5',
      cpqFcaHostCntlrOverallCondition => '1.3.6.1.4.1.232.16.2.7.1.1.8',
      cpqFcaHostCntlrModelValue => {
          1 => "other",
          # You may need to upgrade your driver software and\or instrument
          # agent(s).  You have a drive array controller in the system
          # that the instrument agent does not recognize.  (other according to CPQFCAL-MIB)
          2 => "fchc-p",
          3 => "fchc-e",
          4 => "fchc64",
          5 => "sa-sam",
          6 => "fca-2101",
          7 => "sw64-33",
          8 => "fca-221x",
          9 => "dpfcmc",
          10 => 'fca-2404',
          11 => 'fca-2214',
          12 => 'a7298a',
          13 => 'fca-2214dc',
          14 => 'a6826a',
          15 => 'fcmcG3',
          16 => 'fcmcG4',
          17 => 'ab46xa',
          18 => 'fc-generic',
          19 => 'fca-1143',
          20 => 'fca-1243',
          21 => 'fca-2143',
          22 => 'fca-2243',
          23 => 'fca-1050',
          24 => 'fca-lpe1105',
          25 => 'fca-qmh2462',
          26 => 'fca-1142sr',
          27 => 'fca-1242sr',
          28 => 'fca-2142sr',
          29 => 'fca-2242sr',
          30 => 'fcmc20pe',
          31 => 'fca-81q',
          32 => 'fca-82q',
          33 => 'fca-qmh2562',
          34 => 'fca-81e',
          35 => 'fca-82e',
          36 => 'fca-1205',
      },
      cpqFcaHostCntlrStatusValue => {
          1 => "other",
          2 => "ok",
          3 => "failed",
          4 => "shutdown",
          5 => "loopDegraded",
          6 => "loopFailed",
          7 => "notConnected",
      },
      cpqFcaHostCntlrConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
      cpqFcaHostCntlrOverallConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      }, # cntrl + alle associated storage boxes
  };

  # INDEX { cpqFcaHostCntlrIndex }
  foreach ($self->get_entries($oids, 'cpqFcaHostCntlrEntry')) {
    push(@{$self->{host_controllers}},
        HP::Proliant::Component::DiskSubsystem::Fca::HostController->new(%{$_}));
  }

  $oids = {
      cpqFcaCntlrEntry => '1.3.6.1.4.1.232.16.2.2.1.1',
      cpqFcaCntlrBoxIndex => '1.3.6.1.4.1.232.16.2.2.1.1.1',
      cpqFcaCntlrBoxIoSlot => '1.3.6.1.4.1.232.16.2.2.1.1.2',
      cpqFcaCntlrModel => '1.3.6.1.4.1.232.16.2.2.1.1.3',
      cpqFcaCntlrStatus => '1.3.6.1.4.1.232.16.2.2.1.1.5',
      cpqFcaCntlrCondition => '1.3.6.1.4.1.232.16.2.2.1.1.6',
      cpqFcaCntlrModelValue => {
        1 => "other",
        2 => "fibreArray",
        3 => "msa1000",
        4 => "smartArrayClusterStorage",
        5 => "hsg80",
        6 => "hsv110", 
        7 => "msa500g2", 
        8 => "msa20",
        8 => "msa1510i",
      },
      cpqFcaCntlrStatusValue => {
          1 => "other",
          2 => "ok",
          3 => "failed",
          4 => "offline",
          5 => "redundantPathOffline",
          6 => "notConnected",
      },
      cpqFcaCntlrConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
  };

  # INDEX { cpqFcaCntlrBoxIndex, cpqFcaCntlrBoxIoSlot }
  foreach ($self->get_entries($oids, 'cpqFcaCntlrEntry')) {
    push(@{$self->{controllers}},
        HP::Proliant::Component::DiskSubsystem::Fca::Controller->new(%{$_}));
  }

  $oids = {
      cpqFcaAccelEntry => '1.3.6.1.4.1.232.16.2.2.2.1',
      cpqFcaAccelBoxIndex => '1.3.6.1.4.1.232.16.2.2.2.1.1',
      cpqFcaAccelBoxIoSlot => '1.3.6.1.4.1.232.16.2.2.2.1.2',
      cpqFcaAccelStatus => '1.3.6.1.4.1.232.16.2.2.2.1.3',
      cpqFcaAccelErrCode => '1.3.6.1.4.1.232.16.2.2.2.1.5',
      cpqFcaAccelBatteryStatus => '1.3.6.1.4.1.232.16.2.2.2.1.6',
      cpqFcaAccelCondition => '1.3.6.1.4.1.232.16.2.2.2.1.9',
      cpqFcaAccelStatusValue => {
          1 => "other",
          2 => "invalid",
          3 => "enabled",
          4 => "tmpDisabled",
          5 => "permDisabled",
      },
      cpqFcaAccelErrCodeValue => {
          1 => 'other',
          2 => 'invalid',
          3 => 'badConfig',
          4 => 'lowBattery',
          5 => 'disableCmd',
          6 => 'noResources',
          7 => 'notConnected',
          8 => 'badMirrorData',
          9 => 'readErr',
          10 => 'writeErr',
          11 => 'configCmd',
          12 => 'expandInProgress',
          13 => 'snapshotInProgress',
          14 => 'redundantLowBattery',
          15 => 'redundantSizeMismatch',
          16 => 'redundantCacheFailure',
          17 => 'excessiveEccErrors',
          19 => 'postEccErrors',
      },
      cpqFcaAccelBatteryStatusValue => {
          1 => 'other',
          2 => 'ok',
          3 => 'recharging',
          4 => 'failed',
          5 => 'degraded',
          6 => 'notPresent',
      },
      cpqFcaAccelConditionValue => {
          1 => "other",
          2 => "ok",
          3 => "degraded",
          4 => "failed",
      },
  };

  # INDEX { cpqFcaAccelBoxIndex, cpqFcaAccelBoxIoSlot }
  foreach ($self->get_entries($oids, 'cpqFcaAccelEntry')) {
    push(@{$self->{accelerators}},
        HP::Proliant::Component::DiskSubsystem::Fca::Accelerator->new(%{$_}));
  }

  $oids = {
      cpqFcaLogDrvEntry => '1.3.6.1.4.1.232.16.2.3.1.1',
      cpqFcaLogDrvBoxIndex => '1.3.6.1.4.1.232.16.2.3.1.1.1',
      cpqFcaLogDrvIndex => '1.3.6.1.4.1.232.16.2.3.1.1.2',
      cpqFcaLogDrvFaultTol => '1.3.6.1.4.1.232.16.2.3.1.1.3',
      cpqFcaLogDrvStatus => '1.3.6.1.4.1.232.16.2.3.1.1.4',
      cpqFcaLogDrvPercentRebuild => '1.3.6.1.4.1.232.16.2.3.1.1.6',
      cpqFcaLogDrvSize => '1.3.6.1.4.1.232.16.2.3.1.1.9',
      cpqFcaLogDrvPhyDrvIDs => '1.3.6.1.4.1.232.16.2.3.1.1.10',
      cpqFcaLogDrvCondition => '1.3.6.1.4.1.232.16.2.3.1.1.11',
      cpqFcaLogDrvFaultTolValue => {
          1 => 'other',
          2 => 'none',
          3 => 'mirroring',
          4 => 'dataGuard',
          5 => 'distribDataGuard',
          7 => 'advancedDataGuard',
      },
      cpqFcaLogDrvStatusValue => {
          1 => 'other',
          2 => 'ok',
          3 => 'failed',
          4 => 'unconfigured',
          5 => 'recovering',
          6 => 'readyForRebuild',
          7 => 'rebuilding',
          8 => 'wrongDrive',
          9 => 'badConnect',
          10 => 'overheating',
          11 => 'shutdown',
          12 => 'expanding',
          13 => 'notAvailable',
          14 => 'queuedForExpansion',
          15 => 'hardError',
      },
      cpqFcaLogDrvConditionValue => {
          1 => 'other',
          2 => 'ok',
          3 => 'degraded',
          4 => 'failed',
      },
  };

  # INDEX { cpqFcaLogDrvBoxIndex, cpqFcaLogDrvIndex }
  foreach ($self->get_entries($oids, 'cpqFcaLogDrvEntry')) {
    push(@{$self->{logical_drives}},
        HP::Proliant::Component::DiskSubsystem::Fca::LogicalDrive->new(%{$_}));
  }

  $oids = {
      cpqFcaPhyDrvEntry => '1.3.6.1.4.1.232.16.2.5.1.1',
      cpqFcaPhyDrvBoxIndex => '1.3.6.1.4.1.232.16.2.5.1.1.1',
      cpqFcaPhyDrvIndex => '1.3.6.1.4.1.232.16.2.5.1.1.2',
      cpqFcaPhyDrvModel => '1.3.6.1.4.1.232.16.2.5.1.1.3',
      cpqFcaPhyDrvBay => '1.3.6.1.4.1.232.16.2.5.1.1.5',
      cpqFcaPhyDrvStatus => '1.3.6.1.4.1.232.16.2.5.1.1.6',
      cpqFcaPhyDrvCondition => '1.3.6.1.4.1.232.16.2.5.1.1.31',
      cpqFcaPhyDrvSize => '1.3.6.1.4.1.232.16.2.5.1.1.38',
      cpqFcaPhyDrvBusNumber => '1.3.6.1.4.1.232.16.2.5.1.1.42',
      cpqFcaPhyDrvStatusValue => {
          1 => 'other',
          2 => 'unconfigured',
          3 => 'ok',
          4 => 'threshExceeded',
          5 => 'predictiveFailure',
          6 => 'failed',
      },
      cpqFcaPhyDrvConditionValue => {
          1 => 'other',
          2 => 'ok',
          3 => 'degraded',
          4 => 'failed',
      },
  };

  # INDEX { cpqFcaPhyDrvBoxIndex, cpqFcaPhyDrvIndex }
  foreach ($self->get_entries($oids, 'cpqFcaPhyDrvEntry')) {
    push(@{$self->{physical_drives}},
        HP::Proliant::Component::DiskSubsystem::Fca::PhysicalDrive->new(%{$_}));
  }

  $oids = {
      cpqFcaMibRevMajor => '1.3.6.1.4.1.232.16.1.1.0',
      cpqFcaMibRevMinor => '1.3.6.1.4.1.232.16.1.2.0',
      cpqFcaMibCondition => '1.3.6.1.4.1.232.16.1.3.0',
      cpqFcaMibConditionValue => {
          1 => 'other',
          2 => 'ok',
          3 => 'degraded',
          4 => 'failed',
      },
  };
  $self->{global_status} =
      HP::Proliant::Component::DiskSubsystem::Fca::GlobalStatus->new(
          runtime => $self->{runtime},
          cpqFcaMibCondition => 
            SNMP::Utils::get_object_value($snmpwalk,
                $oids->{cpqFcaMibCondition}, $oids->{cpqFcaMibConditionValue})
      );
}
package HP::Proliant::Component::DiskSubsystem::Fca;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    host_controllers => [],
    controllers => [],
    accelerators => [],
    enclosures => [],
    physical_drives => [],
    logical_drives => [],
    spare_drives => [],
    global_status => undef,
    blacklisted => 0,
  };
  bless $self, $class;
  if ($self->{method} eq 'snmp') {
    bless $self, 'HP::Proliant::Component::DiskSubsystem::Fca::SNMP';
  } else {
    bless $self, 'HP::Proliant::Component::DiskSubsystem::Fca::CLI';
  }
  $self->init();
  $self->assemble();
  return $self;
}

sub assemble {
  my $self = shift;
  $self->trace(3, sprintf "%s controllers und platten zusammenfuehren",
      ref($self));
  $self->trace(3, sprintf "has %d host controllers", 
      scalar(@{$self->{host_controllers}}));
  $self->trace(3, sprintf "has %d controllers",
      scalar(@{$self->{controllers}}));
  $self->trace(3, sprintf "has %d physical_drives",
      scalar(@{$self->{physical_drives}}));
  $self->trace(3, sprintf "has %d logical_drives",
      scalar(@{$self->{logical_drives}}));
  $self->trace(3, sprintf "has %d spare_drives",
      scalar(@{$self->{spare_drives}}));
}

sub check {
  my $self = shift;
  foreach (@{$self->{host_controllers}}) {
    $_->check();
  }
  foreach (@{$self->{controllers}}) {
    $_->check();
  }
  foreach (@{$self->{accelerators}}) {
    $_->check();
  }
  foreach (@{$self->{logical_drives}}) {
    $_->check();
  }
  foreach (@{$self->{physical_drives}}) {
    $_->check();
  }
  # wozu eigentlich?
  #if (! $self->has_controllers()) {
    #$self->{global_status}->check();
  #}
}

sub dump {
  my $self = shift;
  foreach (@{$self->{host_controllers}}) {
    $_->dump();
  }
  foreach (@{$self->{controllers}}) {
    $_->dump();
  }
  foreach (@{$self->{accelerators}}) {
    $_->dump();
  }
  foreach (@{$self->{logical_drives}}) {
    $_->dump();
  }
  foreach (@{$self->{physical_drives}}) {
    $_->dump();
  }
  #$self->{global_status}->dump();
}


package HP::Proliant::Component::DiskSubsystem::Fca::GlobalStatus;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Fca);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    cpqFcaMibCondition => $params{cpqFcaMibCondition},
  };
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  if ($self->{cpqFcaMibCondition} eq 'other') {
    $self->add_message(OK,
        sprintf 'fcal overall condition is other, update your drivers, please');
  } elsif ($self->{cpqFcaMibCondition} ne 'ok') {
    $self->add_message(CRITICAL, 
        sprintf 'fcal overall condition is %s', $self->{cpqFcaMibCondition});
  }
  $self->{info} = 
      sprintf 'fcal overall condition is %s', $self->{cpqFcaMibCondition};
}

sub dump {
  my $self = shift;
  printf "[FCAL]\n";
  foreach (qw(cpqFcaMibCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::Proliant::Component::DiskSubsystem::Fca::HostController;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Fca);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef, 
  }; 
  map { $self->{$_} = $params{$_} } grep /cpqFcaHostCntlr/, keys %params;
  $self->{name} = $params{name} || $self->{cpqFcaHostCntlrIndex};
  $self->{controllerindex} = $self->{cpqFcaHostCntlrIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('fcahc', $self->{name});
  my $info = sprintf 'fcal host controller %s in slot %s is %s',
      $self->{name}, $self->{cpqFcaHostCntlrSlot}, $self->{cpqFcaHostCntlrCondition};
  if ($self->{cpqFcaHostCntlrCondition} eq 'other') {
    #$info .= sprintf ' and needs attention (%s)', $self->{cpqFcaHostCntlrStatus};
    # let's assume other=ok
    $self->add_message(OK, $info);
    $self->add_info($info);
  } elsif ($self->{cpqFcaHostCntlrCondition} ne 'ok') {
    $self->add_message(CRITICAL, $info);
    $self->add_info($info);
  } else { 
    $self->add_info($info);
  }
  $self->blacklist('fcahco', $self->{name});
  $info = sprintf 'fcal host controller %s overall condition is %s',
      $self->{name}, $self->{cpqFcaHostCntlrOverallCondition};
  if ($self->{cpqFcaHostCntlrOverallCondition} eq 'other') {
    $self->add_message(OK, $info);
  } elsif ($self->{cpqFcaHostCntlrOverallCondition} ne 'ok') {
    $self->add_message(WARNING, $info);
  }
  $self->add_info($info);
}   

sub dump { 
  my $self = shift;
  printf "[FCAL_HOST_CONTROLLER_%s]\n", $self->{name};
  foreach (qw(cpqFcaHostCntlrIndex cpqFcaHostCntlrSlot
      cpqFcaHostCntlrModel cpqFcaHostCntlrStatus cpqFcaHostCntlrCondition
      cpqFcaHostCntlrOverallCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::Proliant::Component::DiskSubsystem::Fca::Controller;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Fca);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef, 
  }; 
  map { $self->{$_} = $params{$_} } grep /cpqFcaCntlr/, keys %params;
  $self->{name} = $params{name} || 
      $self->{cpqFcaCntlrBoxIndex}.':'.$self->{cpqFcaCntlrBoxIoSlot};
  $self->{controllerindex} = $self->{cpqFcaCntlrBoxIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('fcaco', $self->{name});
  my $info = sprintf 'fcal controller %s in box %s/slot %s is %s',
      $self->{name}, $self->{cpqFcaCntlrBoxIndex}, $self->{cpqFcaCntlrBoxIoSlot},
      $self->{cpqFcaCntlrCondition};
  if ($self->{cpqFcaCntlrCondition} eq 'other') {
    if (1) { # was ist mit phys. drives?
      $info .= ' needs attention';
      $info .= sprintf(' (%s)', $self->{cpqFcaCntlrStatus}) unless $self->{cpqFcaCntlrStatus} eq 'ok';
      $self->add_message(CRITICAL, $info);
      $self->add_info($info);
    } else {
      $self->add_info($info);
      $self->{blacklisted} = 1;
    }
  } elsif ($self->{cpqFcaCntlrCondition} ne 'ok') {
    $info .= ' needs attention';
    $info .= sprintf(' (%s)', $self->{cpqFcaCntlrStatus}) unless $self->{cpqFcaCntlrStatus} eq 'ok';
    $self->add_message(CRITICAL, $info);
    $self->add_info($info);
  } else {
    $self->add_info($info);
  }
} 

sub dump {
  my $self = shift;
  printf "[FCAL_CONTROLLER_%s]\n", $self->{name};
  foreach (qw(cpqFcaCntlrBoxIndex cpqFcaCntlrBoxIoSlot cpqFcaCntlrModel
      cpqFcaCntlrStatus cpqFcaCntlrCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::Proliant::Component::DiskSubsystem::Fca::Accelerator;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Fca);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef, 
  }; 
  map { $self->{$_} = $params{$_} } grep /cpqFcaAccel/, keys %params;
  $self->{name} = $params{name} ||
      $self->{cpqFcaAccelBoxIndex}.':'.$self->{cpqFcaAccelBoxIoSlot};
  $self->{controllerindex} = $self->{cpqFcaAccelBoxIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  # !!! cpqFcaAccelStatus
  $self->blacklist('fcaac', $self->{name});
  my $info = sprintf 'fcal accelerator %s in box %s/slot %s is ',
      $self->{name}, $self->{cpqFcaAccelBoxIndex}, $self->{cpqFcaAccelBoxIoSlot};
  if ($self->{cpqFcaAccelStatus} eq 'invalid') {
    $info .= 'not installed';
    $self->add_info($info);
  } elsif ($self->{cpqFcaAccelStatus} eq 'tmpDisabled') {
    $info .= 'temp disabled';
    $self->add_info($info);
  } elsif ($self->{cpqFcaAccelCondition} eq 'other') {
    $info .= sprintf '%s and needs attention (%s)',
        $self->{cpqFcaAccelCondition}, $self->{cpqFcaAccelErrCode};
    $self->add_message(CRITICAL, $info);
    $self->add_info($info);
  } elsif ($self->{cpqFcaAccelCondition} ne 'ok') {
    $info .= sprintf '%s and needs attention (%s)',
        $self->{cpqFcaAccelCondition}, $self->{cpqFcaAccelErrCode};
    $self->add_message(CRITICAL, $info);
    $self->add_info($info);
  } else {
    $info .= sprintf '%s', $self->{cpqFcaAccelCondition};
    $self->add_info($info);
  }
}

sub dump {
  my $self = shift;
  printf "[FCAL_ACCELERATOR_%s]\n", $self->{name};
  foreach (qw(cpqFcaAccelBoxIndex cpqFcaAccelBoxIoSlot cpqFcaAccelStatus
      cpqFcaAccelErrCode cpqFcaAccelBatteryStatus cpqFcaAccelCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::Proliant::Component::DiskSubsystem::Fca::LogicalDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Fca);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef, 
  }; 
  map { $self->{$_} = $params{$_} } grep /cpqFcaLogDrv/, keys %params;
  bless $self, $class;
  $self->{name} = $params{name} || 
      $self->{cpqFcaLogDrvBoxIndex}.':'.
      $self->{cpqFcaLogDrvIndex};
  $self->{controllerindex} = $self->{cpqFcaLogDrvBoxIndex};
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('fcald', $self->{name});
  my $info = sprintf 'logical drive %s (%s) is %s',
      $self->{name}, $self->{cpqFcaLogDrvFaultTol}, $self->{cpqFcaLogDrvCondition};
  if ($self->{cpqFcaLogDrvCondition} ne "ok") {
    $info .= sprintf ' (%s)', $self->{cpqFcaLogDrvStatus};
    if ($self->{cpqFcaLogDrvStatus} =~ 
        /rebuild|recovering|expand/) {
      $info .= sprintf ' (%s)', $self->{cpqFcaLogDrvStatus};
      $self->add_message(WARNING, $info);
    } else {
      $self->add_message(CRITICAL, $info);
    }
  } 
  $self->add_info($info);
}

sub dump {
  my $self = shift;
  printf "[LOGICAL_DRIVE_%s]\n", $self->{name};
  foreach (qw(cpqFcaLogDrvBoxIndex cpqFcaLogDrvIndex cpqFcaLogDrvFaultTol
      cpqFcaLogDrvStatus cpqFcaLogDrvPercentRebuild cpqFcaLogDrvSize 
      cpqFcaLogDrvPhyDrvIDs cpqFcaLogDrvCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::Proliant::Component::DiskSubsystem::Fca::PhysicalDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Fca);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef, 
  }; 
  map { $self->{$_} = $params{$_} } grep /cpqFcaPhyDrv/, keys %params;
  $self->{name} = $params{name} || 
      $self->{cpqFcaPhyDrvBoxIndex}.':'.$self->{cpqFcaPhyDrvIndex}; ####vorerst
  $self->{controllerindex} = $self->{cpqScsiPhyDrvCntlrIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('fcapd', $self->{name});
  my $info = sprintf "physical drive %s is %s", 
      $self->{name}, $self->{cpqFcaPhyDrvStatus};
  if ($self->{cpqFcaPhyDrvStatus} eq 'unconfigured') {
    # not part of a logical drive
    # condition will surely be other
  } elsif ($self->{cpqFcaPhyDrvCondition} ne 'ok') {
    $self->add_message(CRITICAL, $info);
  }
  $self->add_info($info);
}

sub dump {
  my $self = shift;
  printf "[PHYSICAL_DRIVE_%s]\n", $self->{name};
  foreach (qw(cpqFcaPhyDrvBoxIndex cpqFcaPhyDrvIndex cpqFcaPhyDrvModel
      cpqFcaPhyDrvBay cpqFcaPhyDrvStatus cpqFcaPhyDrvCondition
      cpqFcaPhyDrvSize cpqFcaPhyDrvBusNumber)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::Proliant::Component::DiskSubsystem::Fca::SpareDrive;
our @ISA = qw(HP::Proliant::Component::DiskSubsystem::Fca);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub dump {
  my $self = shift;
  printf "[SPARE_DRIVE]\n";
}



package HP::Proliant::Component::DiskSubsystem;
our @ISA = qw(HP::Proliant::Component);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    da_subsystem => undef,
    sas_da_subsystem => undef,
    ide_da_subsystem => undef,
    fca_da_subsystem => undef,
    scsi_da_subsystem => undef,
    condition => $params{condition},
    blacklisted => 0,
  };
  bless $self, $class;
  $self->init();
  return $self;
}

sub init {
  my $self = shift;
  $self->{da_subsystem} = HP::Proliant::Component::DiskSubsystem::Da->new(
    runtime => $self->{runtime},
    rawdata => $self->{rawdata},
    method => $self->{method},
  );
  $self->{sas_subsystem} = HP::Proliant::Component::DiskSubsystem::Sas->new(
    runtime => $self->{runtime},
    rawdata => $self->{rawdata},
    method => $self->{method},
  );
  $self->{scsi_subsystem} = HP::Proliant::Component::DiskSubsystem::Scsi->new(
    runtime => $self->{runtime},
    rawdata => $self->{rawdata},
    method => $self->{method},
  );
  $self->{ide_subsystem} = HP::Proliant::Component::DiskSubsystem::Ide->new(
    runtime => $self->{runtime},
    rawdata => $self->{rawdata},
    method => $self->{method},
  );
  $self->{fca_subsystem} = HP::Proliant::Component::DiskSubsystem::Fca->new(
    runtime => $self->{runtime},
    rawdata => $self->{rawdata},
    method => $self->{method},
  );
}

sub check {
  my $self = shift;
  $self->add_info('checking disk subsystem');
  $self->{da_subsystem}->check();
  $self->{sas_subsystem}->check();
  $self->{scsi_subsystem}->check();
  $self->{ide_subsystem}->check();
  $self->{fca_subsystem}->check();
  $self->disk_summary();
}

sub dump {
  my $self = shift;
  $self->{da_subsystem}->dump();
  $self->{sas_subsystem}->dump();
  $self->{scsi_subsystem}->dump();
  $self->{ide_subsystem}->dump();
  $self->{fca_subsystem}->dump();
}

sub disk_summary {
  my $self = shift;
  foreach my $subsys (qw(da sas scsi ide fca)) {
    if (my $pd = $self->{$subsys.'_subsystem'}->has_physical_drives()) {
      my $ld = $self->{$subsys.'_subsystem'}->has_logical_drives();
      $self->add_summary(sprintf '%s: %d logical drives, %d physical drives',
          $subsys, $ld, $pd);
    }
  }
}

sub assemble {
  my $self = shift;
  $self->trace(3, sprintf "%s controllers und platten zusammenfuehren",
      ref($self));
  $self->trace(3, sprintf "has %d controllers",
      scalar(@{$self->{controllers}}));
  $self->trace(3, sprintf "has %d accelerators",
      scalar(@{$self->{accelerators}})) if exists $self->{accelerators};
  $self->trace(3, sprintf "has %d enclosures",
      scalar(@{$self->{enclosures}}));
  $self->trace(3, sprintf "has %d physical_drives",
      scalar(@{$self->{physical_drives}}));
  $self->trace(3, sprintf "has %d logical_drives",
      scalar(@{$self->{logical_drives}}));
  $self->trace(3, sprintf "has %d spare_drives",
      scalar(@{$self->{spare_drives}}));
  my $found = {
      accelerators => {},
      enclosures => {},
      logical_drives => {},
      physical_drives => {},
      spare_drives => {},
  };
  # found->{komponente}->{controllerindex} ist ein array
  # von teilen, die zu einem controller gehoeren
  foreach my $item (qw(accelerators enclosures logical_drives physical_drives spare_drives)) {
    next if ($item eq "enclosures" && ! exists $self->{$item});
    foreach (@{$self->{$item}}) {
      $found->{item}->{$_->{controllerindex}} = []
          unless exists $found->{$item}->{$_->{controllerindex}};
      push(@{$found->{$item}->{$_->{controllerindex}}}, $_);
    }
  }
  foreach my $item (qw(accelerators enclosures logical_drives physical_drives spare_drives)) {
    foreach (@{$self->{controllers}}) {
      if (exists $found->{$item}->{$_->{controllerindex}}) {
        $_->{$item} = $found->{$item}->{$_->{controllerindex}};
        delete $found->{$item}->{$_->{controllerindex}};
      } else {
        $_->{$item} = []; # z.b. ein leerer controller: physical_drives = []
      }
    }
  }
  # was jetzt noch in $found uebrig ist, gehoert zu keinem controller
  # d.h. komponenten mit ungueltigen cnrtlindex wurden gefunden
}

sub has_controllers {
  my $self = shift;
  return scalar(@{$self->{controllers}});
}

sub has_accelerators {
  my $self = shift;
  return exists $self->{accelerators} ? scalar(@{$self->{accelerators}}) : 0;
}

sub has_physical_drives {
  my $self = shift;
  return scalar(@{$self->{physical_drives}});
}

sub has_logical_drives {
  my $self = shift;
  return scalar(@{$self->{logical_drives}});
}

sub has_enclosures {
  my $self = shift;
  return scalar(@{$self->{enclosures}});
}


package HP::Proliant::Component;
our @ISA = qw(HP::Proliant);



package HP::Proliant;

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };
use Data::Dumper;

our @ISA = qw(HP::Server);

sub init {
  my $self = shift;
  $self->{components} = {
      powersupply_subsystem => undef,
      fan_subsystem => undef,
      temperature_subsystem => undef,
      cpu_subsystem => undef,
      memory_subsystem => undef,
      nic_subsystem => undef,
      disk_subsystem => undef,
      asr_subsystem => undef,
      event_subsystem => undef,
  };
  $self->{serial} = 'unknown';
  $self->{product} = 'unknown';
  $self->{romversion} = 'unknown';
  $self->collect();
  if (! $self->{runtime}->{plugin}->check_messages() && 
      ! exists $self->{noinst_hint}) {
    $self->set_serial();
    $self->check_for_buggy_firmware();
    $self->analyze_cpus();
    $self->analyze_powersupplies();
    $self->analyze_fan_subsystem();
    $self->analyze_temperatures();
    $self->analyze_memory_subsystem();
    $self->analyze_nic_subsystem();
    $self->analyze_disk_subsystem();
    $self->analyze_asr_subsystem();
    $self->analyze_event_subsystem();
    $self->auto_blacklist();
    $self->check_cpus();
    $self->check_powersupplies();
    $self->check_fan_subsystem();
    $self->check_temperatures();
    $self->check_memory_subsystem();
    $self->check_nic_subsystem();
    $self->check_disk_subsystem();
    $self->check_asr_subsystem();
    $self->check_event_subsystem();
  }
}

sub identify {
  my $self = shift;
  foreach (qw(product serial romversion)) {
    $self->{$_} =~ s/^\s+//;
    $self->{$_} =~ s/\s+$//;
  }
  return sprintf "System: '%s', S/N: '%s', ROM: '%s'", 
      $self->{product}, $self->{serial}, $self->{romversion};
}

sub check_for_buggy_firmware {
  my $self = shift;
  my @buggyfirmwares = (
      "P24 12/11/2001",
      "P24 11/15/2002",
      "D13 06/03/2003",
      "D13 09/15/2004",
      "P20 12/17/2002"
  );
  if ($self->{romversion} =~ /^\w+ \d+\/\d+\/\d+$/) {
    $self->{runtime}->{options}->{buggy_firmware} =
        grep /^$self->{romversion}/, @buggyfirmwares;
  } else {
    # nicht parsbarer schrott in cpqSeSysRomVer, gesehen bei Gen9
    $self->{runtime}->{options}->{buggy_firmware} = undef;
  }
}

sub dump {
  my $self = shift;
  printf STDERR "serial %s\n", $self->{serial};
  printf STDERR "product %s\n", $self->{product};
  printf STDERR "romversion %s\n", $self->{romversion};
  printf STDERR "%s\n", Data::Dumper::Dumper($self->{components});
}

sub analyze_powersupplies {
  my $self = shift;
  $self->{components}->{powersupply_subsystem} =
      HP::Proliant::Component::PowersupplySubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_fan_subsystem {
  my $self = shift;
  $self->{components}->{fan_subsystem} = 
      HP::Proliant::Component::FanSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_temperatures {
  my $self = shift;
  $self->{components}->{temperature_subsystem} = 
      HP::Proliant::Component::TemperatureSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_cpus {
  my $self = shift;
  $self->{components}->{cpu_subsystem} =
      HP::Proliant::Component::CpuSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_memory_subsystem {
  my $self = shift;
  $self->{components}->{memory_subsystem} = 
      HP::Proliant::Component::MemorySubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_nic_subsystem {
  my $self = shift;
  return if $self->{method} ne "snmp";
  $self->{components}->{nic_subsystem} = 
      HP::Proliant::Component::NicSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_disk_subsystem {
  my $self = shift;
  $self->{components}->{disk_subsystem} =
      HP::Proliant::Component::DiskSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_asr_subsystem {
  my $self = shift;
  $self->{components}->{asr_subsystem} =
      HP::Proliant::Component::AsrSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_event_subsystem {
  my $self = shift;
  $self->{components}->{event_subsystem} =
      HP::Proliant::Component::EventSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub check_cpus {
  my $self = shift;
  $self->{components}->{cpu_subsystem}->check();
  $self->{components}->{cpu_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_powersupplies {
  my $self = shift;
  $self->{components}->{powersupply_subsystem}->check();
  $self->{components}->{powersupply_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_fan_subsystem {
  my $self = shift;
  $self->{components}->{fan_subsystem}->check();
  $self->{components}->{fan_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_temperatures {
  my $self = shift;
  $self->{components}->{temperature_subsystem}->check();
  $self->{components}->{temperature_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_memory_subsystem {
  my $self = shift;
  $self->{components}->{memory_subsystem}->check();
  $self->{components}->{memory_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_nic_subsystem {
  my $self = shift;
  return if $self->{method} ne "snmp";
  if ($self->{runtime}->{plugin}->{opts}->get('eval-nics')) {
    $self->{components}->{nic_subsystem}->check();
    $self->{components}->{nic_subsystem}->dump()
        if $self->{runtime}->{options}->{verbose} >= 2;
  }
}
sub check_disk_subsystem {
  my $self = shift;
  $self->{components}->{disk_subsystem}->check();
  $self->{components}->{disk_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
  # zum anhaengen an die normale ausgabe... da: 2 logical drives, 5 physical...
  $self->{runtime}->{plugin}->add_message(OK,
      $self->{components}->{disk_subsystem}->{summary})
      if $self->{components}->{disk_subsystem}->{summary};
}

sub check_asr_subsystem {
  my $self = shift;
  $self->{components}->{asr_subsystem}->check();
  $self->{components}->{asr_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_event_subsystem {
  my $self = shift;
  $self->{components}->{event_subsystem}->check();
  $self->{components}->{event_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub auto_blacklist() {
  my $self = shift;
  if ($self->{product} =~ /380 g6/) {
    # http://bizsupport1.austin.hp.com/bc/docs/support/SupportManual/c01723408/c01723408.pdf seite 19
    if ($self->{components}->{cpu_subsystem}->num_cpus() == 1) {
      $self->add_blacklist('ff/f:5,6');
    }
  } elsif ($self->{product} =~ /380 g6/) {
    # http://bizsupport1.austin.hp.com/bc/docs/support/SupportManual/c01704762/c01704762.pdf Fan 2 is only required when processor 2 is installed in the server.
  }
}


package HP::Proliant::CLI;

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

our @ISA = qw(HP::Proliant);

sub collect {
  my $self = shift;
  my $hpasmcli = undef;
  if (($self->{runtime}->{plugin}->opts->hpasmcli) &&
      (-f $self->{runtime}->{plugin}->opts->hpasmcli) &&
      (! -x $self->{runtime}->{plugin}->opts->hpasmcli)) {
    no strict 'refs';
    open(BIRK, $self->{runtime}->{plugin}->opts->hpasmcli);
    # all output in one file prefixed with server|powersupply|fans|temp|dimm
    while(<BIRK>) {
      chomp;
      $self->{rawdata} .= $_."\n";
    }
    close BIRK;
    # If you run this script and redirect it's output to a file
    # you can use it for testing purposes with
    # --hpasmcli <output>
    # It must not be executable. (chmod 644)
    my $diag = <<'EOEO';
    hpasmcli=$(which hpasmcli)
    hpacucli=$(which hpacucli)
    for i in server powersupply fans temp dimm
    do
      $hpasmcli -s "show $i" | while read line
      do
        printf "%s %s\n" $i "$line"
      done
    done 
    if [ -x "$hpacucli" ]; then
      for i in config status
      do
        $hpacucli ctrl all show $i | while read line
        do
          printf "%s %s\n" $i "$line"
        done
      done
    fi
EOEO
  } else {
    #die "exec hpasmcli";
    # alles einsammeln und in rawdata stecken
    my $hpasmcli = undef;
    $hpasmcli = $self->{runtime}->{plugin}->opts->hpasmcli ?
        $self->{runtime}->{plugin}->opts->hpasmcli : '/sbin/hpasmcli';
# check if this exists at all
# descend the directory
    if ($self->{runtime}->{plugin}->opts->hpasmcli &&
        -e $self->{runtime}->{plugin}->opts->hpasmcli) {
      $hpasmcli = $self->{runtime}->{plugin}->opts->hpasmcli;
    } elsif (-e '/sbin/hpasmcli') {
      $hpasmcli = '/sbin/hpasmcli';
    } else {
      $hpasmcli = undef;
    }
    if ($hpasmcli) {
      if ($< != 0) {
        close STDIN;
        $hpasmcli = "sudo -S ".$hpasmcli;
      }
      $self->trace(2, sprintf "calling %s\n", $hpasmcli);
      $self->check_daemon();
      if (! $self->{runtime}->{plugin}->check_messages()) {
        $self->check_hpasm_client($hpasmcli);
        if (! $self->{runtime}->{plugin}->check_messages()) {
          foreach my $component (qw(server fans temp dimm powersupply iml)) {
            if (open HPASMCLI, "$hpasmcli -s \"show $component\" </dev/null |") {
              my @output = <HPASMCLI>;
              close HPASMCLI;
              $self->{rawdata} .= join("\n", map {
                  $component.' '.$_;
              } @output);
            }
          }
          if ($self->{runtime}->{options}->{hpacucli}) {
            #1 oder 0. pfad selber finden
            my $hpacucli = undef;
            if (-e '/usr/sbin/hpssacli') {
              $hpacucli = '/usr/sbin/hpssacli';
            } elsif (-e '/usr/local/sbin/hpssacli') {
              $hpacucli = '/usr/local/sbin/hpssacli';
            } elsif (-e '/usr/sbin/hpacucli') {
              $hpacucli = '/usr/sbin/hpacucli';
            } elsif (-e '/usr/local/sbin/hpacucli') {
              $hpacucli = '/usr/local/sbin/hpacucli';
            } elsif (-e '/usr/sbin/hpssacli') {
              $hpacucli = '/usr/sbin/hpssacli';
            } elsif (-e '/usr/local/sbin/hpssacli') {
              $hpacucli = '/usr/local/sbin/hpssacli';
            } else {
              $hpacucli = $hpasmcli;
              $hpacucli =~ s/^sudo\s*//;
              $hpacucli =~ s/hpasmcli/hpacucli/;
              $hpacucli = -e $hpacucli ? $hpacucli : undef;
              if (! $hpacucli) {
                $hpacucli = $hpasmcli;
                $hpacucli =~ s/^sudo\s*//;
                $hpacucli =~ s/hpasmcli/hpssacli/;
                $hpacucli = -e $hpacucli ? $hpacucli : undef;
              }
            }
            if ($hpacucli) {
              if ($< != 0) {
                close STDIN;
                $hpacucli = "sudo -S ".$hpacucli;
              }
              $self->trace(2, sprintf "calling %s\n", $hpacucli);
              $self->check_hpacu_client($hpacucli);
              if (! $self->{runtime}->{plugin}->check_messages()) {
                if (open HPACUCLI, "$hpacucli ctrl all show status 2>&1|") {
                  my @output = <HPACUCLI>;
                  close HPACUCLI;
                  $self->{rawdata} .= join("\n", map {
                      'status '.$_;
                  } @output);
                }
                if (open HPACUCLI, "$hpacucli ctrl all show config 2>&1|") {
                  my @output = <HPACUCLI>;
                  close HPACUCLI;
                  $self->{rawdata} .= join("\n", map {
                      'config '.$_;
                  } @output);
                  if (grep /Syntax error at "config"/, @output) {
                    # older version of hpacucli CLI 7.50.18.0
                    foreach my $slot (0..10) {
                      if (open HPACUCLI, "$hpacucli ctrl slot=$slot logicaldrive all show 2>&1|") {
                        my @output = <HPACUCLI>;
                        close HPACUCLI;
                        $self->{rawdata} .= join("\n", map {
                            'config '.$_;
                        } @output);
                      }
                      if (open HPACUCLI, "$hpacucli ctrl slot=$slot physicaldrive all show 2>&1|") {
                        my @output = <HPACUCLI>;
                        close HPACUCLI;
                        $self->{rawdata} .= join("\n", map {
                            'config '.$_;
                        } @output);
                      }
                    }
                  }
                }
              } elsif ($self->{runtime}->{options}->{hpacucli} == 2) {
                # we probably don't have sudo-privileges, but we were compiled with
                # --enable-hpacucli=maybe
                # so we cover it up in silence
                $self->remove_message(UNKNOWN);
                $self->trace(2, sprintf "calling %s seems to have failed, but nobody cares\n", $hpacucli);
              }
            } else {
              if ($self->{runtime}->{options}->{noinstlevel} eq 'ok') {
                $self->add_message(OK,
                    'hpacucli is not installed. let\'s hope the best...');
              } else {
                $self->add_message(
                    uc $self->{runtime}->{options}->{noinstlevel},
                    'hpacucli is not installed.');
              }
            }
          }
        }
      }
    } else {
      if ($self->{runtime}->{options}->{noinstlevel} eq 'ok') {
        $self->add_message(OK,
            'hpasm is not installed, i can only guess');
        $self->{noinst_hint} = 1;
      } else {
        $self->add_message(
            uc $self->{runtime}->{options}->{noinstlevel},
            'hpasmcli is not installed.');
      }
    }
  }
}


sub check_daemon {
  my $self = shift;
  my $multiproc_os_signatures_files = {
      '/etc/SuSE-release' => 'VERSION\s*=\s*8',
      '/etc/trustix-release' => '.*',
      '/etc/redhat-release' => '.*Pensacola.*',
      '/etc/debian_version' => '3\.1',
      '/etc/issue' => '.*Kernel 2\.4\.9-vmnix2.*', # VMware ESX Server 2.5.4
  };
  if (open PS, "/bin/ps -e -ocmd|") {
    my $numprocs = 0;
    my $numcliprocs = 0;
    my @procs = <PS>;
    close PS;
    $numprocs = grep /hpasm.*d$/, map { (split /\s+/, $_)[0] } @procs;
    $numcliprocs = grep /hpasmcli/, grep !/check_hpasm/, @procs;
    if (! $numprocs ) {
      $self->add_message(CRITICAL, 'hpasmd needs to be restarted');
    } elsif ($numprocs > 1) {
      my $known = 0;
      foreach my $osfile (keys %{$multiproc_os_signatures_files}) {
        if (-f $osfile) {
          open OSSIG, $osfile;
          if (grep /$multiproc_os_signatures_files->{$osfile}/, <OSSIG>) {
            $known = 1;
          }
          close OSSIG;
        }
      }
      if (! $known) {
        $self->add_message(UNKNOWN, 'multiple hpasmd procs');
      }
    }
    if ($numcliprocs == 1) {
      $self->add_message(UNKNOWN, 'another hpasmcli is running');
    } elsif ($numcliprocs > 1) {
      $self->add_message(UNKNOWN, 'hanging hpasmcli processes');
    }
  }
}

sub check_hpasm_client {
  my $self = shift;
  my $hpasmcli = shift;
  if (open HPASMCLI, "$hpasmcli -s help 2>&1 |") {
    my @output = <HPASMCLI>;
    close HPASMCLI;
    if (grep /Could not communicate with hpasmd/, @output) {
      $self->add_message(CRITICAL, 'hpasmd needs to be restarted');
    } elsif (grep /(asswor[dt]:)|(You must be root)/, @output) {
      $self->add_message(UNKNOWN,
          sprintf "insufficient rights to call %s", $hpasmcli);
    } elsif (grep /must have a tty/, @output) {
      $self->add_message(CRITICAL,
          'sudo must be configured with requiretty=no (man sudo)');
    } elsif (grep /ERROR: hpasmcli only runs on HPE Proliant Servers/, @output) {
      $self->add_message(UNKNOWN, "hpasmcli detected incompatible hardware");
    } elsif (! grep /CLEAR/, @output) {
      $self->add_message(UNKNOWN,
          sprintf "insufficient rights to call %s", $hpasmcli);
    }
  } else {
    $self->add_message(UNKNOWN,
        sprintf "insufficient rights to call %s", $hpasmcli);
  }
}

sub check_hpacu_client {
  my $self = shift;
  my $hpacucli = shift;
  if (open HPACUCLI, "$hpacucli help 2>&1 |") {
    my @output = <HPACUCLI>;
    close HPACUCLI;
    if (grep /Another instance of hpacucli is running/, @output) {
      $self->add_message(UNKNOWN, 'another hpacucli is running');
    } elsif (grep /You need to have administrator rights/, @output) {
      $self->add_message(UNKNOWN,
          sprintf "insufficient rights to call %s", $hpacucli);
    } elsif (grep /(asswor[dt]:)|(You must be root)/, @output) {
      $self->add_message(UNKNOWN,
          sprintf "insufficient rights to call %s", $hpacucli);
    } elsif (! grep /(CLI Syntax)|(ACU CLI)/, @output) {
      $self->add_message(UNKNOWN,
          sprintf "insufficient rights to call %s", $hpacucli);
    }
  } else {
    $self->add_message(UNKNOWN,
        sprintf "insufficient rights to call %s", $hpacucli);
  }
}

sub set_serial {
  my $self = shift;
  foreach (grep(/^server/, split(/\n/, $self->{rawdata}))) {
    if (/System\s+:\s+(.*[^\s])/) {
      $self->{product} = lc $1;
    } elsif (/Serial No\.\s+:\s+(\w+)/) {
      $self->{serial} = $1;
    } elsif (/ROM version\s+:\s+(.*[^\s])/) {
      $self->{romversion} = $1;
    }
  }
  $self->{serial} = $self->{serial};
  $self->{product} = lc $self->{product};
  $self->{romversion} = $self->{romversion};
  foreach (qw(serial product romversion)) {
    $self->{$_} =~ s/\s+$//g;
  }
}


package HP::Proliant::SNMP;

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

our @ISA = qw(HP::Proliant);

sub collect {
  my $self = shift;
  my %oidtables = (
      system =>            "1.3.6.1.2.1.1",
      cpqSeProcessor =>    "1.3.6.1.4.1.232.1.2.2",
      cpqHePWSComponent => "1.3.6.1.4.1.232.6.2.9",
      cpqHeThermal =>      "1.3.6.1.4.1.232.6.2.6",
      cpqHeMComponent =>   "1.3.6.1.4.1.232.6.2.14",
      cpqDaComponent =>    "1.3.6.1.4.1.232.3.2",
      cpqSiComponent =>    "1.3.6.1.4.1.232.2.2",
      cpqSeRom =>          "1.3.6.1.4.1.232.1.2.6",
      cpqSasComponent =>   "1.3.6.1.4.1.232.5",
      cpqIdeComponent =>   "1.3.6.1.4.1.232.14",
      cpqFcaComponent =>   "1.3.6.1.4.1.232.16.2",
      cpqHeAsr =>          "1.3.6.1.4.1.232.6.2.5",
      cpqNic =>            "1.3.6.1.4.1.232.18.2",
      cpqHeEventLog =>     "1.3.6.1.4.1.232.6.2.11",

      #    cpqHeComponent =>  "1.3.6.1.4.1.232.6.2",
      #    cpqHeFComponent => "1.3.6.1.4.1.232.6.2.6.7",
      #    cpqHeTComponent => "1.3.6.1.4.1.232.6.2.6.8",
  );
  my %oidvalues = (
      cpqHeEventLogSupported => "1.3.6.1.4.1.232.6.2.11.1.0",
      cpqHeEventLogCondition => "1.3.6.1.4.1.232.6.2.11.2.0",
      cpqNicIfLogMapOverallCondition => "1.3.6.1.4.1.232.18.2.2.2.0",
      cpqHeThermalTempStatus => "1.3.6.1.4.1.232.6.2.6.3.0",
      cpqHeThermalSystemFanStatus => "1.3.6.1.4.1.232.6.2.6.4.0",
      cpqHeThermalCpuFanStatus => "1.3.6.1.4.1.232.6.2.6.5.0",
      cpqHeAsrStatus => "1.3.6.1.4.1.232.6.2.5.1.0",
      cpqHeAsrCondition => "1.3.6.1.4.1.232.6.2.5.17.0",
  );
  if ($self->{runtime}->{plugin}->opts->snmpwalk) {
    my $cpqSeMibCondition = '1.3.6.1.4.1.232.1.1.3.0'; # 2=ok
    my $cpqHeMibCondition = '1.3.6.1.4.1.232.6.1.3.0'; # hat nicht jeder
    if ($self->{productname} =~ /4LEE/) {
      # rindsarsch!
      $self->{rawdata}->{$cpqHeMibCondition} = 0;
    }
    if (! exists $self->{rawdata}->{$cpqHeMibCondition} &&
        ! exists $self->{rawdata}->{$cpqSeMibCondition}) { # vlt. geht doch was
        $self->add_message(CRITICAL,
            'snmpwalk returns no health data (cpqhlth-mib)');
    }
    $self->{fullrawdata} = {};
    %{$self->{fullrawdata}} = %{$self->{rawdata}};
    $self->{rawdata} = {};
    if (! $self->{runtime}->{plugin}->check_messages()) {
      # for a better simulation, only put those oids into 
      # rawdata which would also be put by a real snmp agent.
      foreach my $table (keys %oidtables) {
        my $oid = $oidtables{$table};
        $oid =~ s/\./\\./g;
        my $tmpoids = {};
        my $tic = time;
        map { $tmpoids->{$_} = $self->{fullrawdata}->{$_} }
            grep /^$oid/, %{$self->{fullrawdata}};
        my $tac = time;
        $self->trace(2, sprintf "%03d seconds for walk %s (%d oids)",
            $tac - $tic, $table, scalar(keys %{$tmpoids}));
        map { $self->{rawdata}->{$_} = $tmpoids->{$_} } keys %{$tmpoids};
      }
      my @oids = values %oidvalues;
      map { $self->{rawdata}->{$_} = $self->{fullrawdata}->{$_} } @oids;
    }
  } else {
    my $net_snmp_version = Net::SNMP->VERSION(); # 5.002000 or 6.000000
    #$params{'-translate'} = [
    #  -all => 0x0
    #];
    my ($session, $error) = 
        Net::SNMP->session(%{$self->{runtime}->{snmpparams}});
    if (! defined $session) {
      $self->{plugin}->add_message(CRITICAL, 'cannot create session object');
      $self->trace(1, Data::Dumper::Dumper($self->{runtime}->{snmpparams}));
    } else {
      $session->translate(['-timeticks' => 0]);
      # revMajor is often used for discovery of hp devices
      my $cpqHeMibRev = '1.3.6.1.4.1.232.6.1';
      my $cpqHeMibRevMajor = '1.3.6.1.4.1.232.6.1.1.0';
      my $cpqHeMibCondition = '1.3.6.1.4.1.232.6.1.3.0';
      my $result = $session->get_request(
          -varbindlist => [$cpqHeMibCondition]
      );
      if ($self->{productname} =~ /4LEE/) {
        # rindsarsch!
        $result->{$cpqHeMibCondition} = 0;
      }
      if (!defined($result) || 
          $result->{$cpqHeMibCondition} eq 'noSuchInstance' ||
          $result->{$cpqHeMibCondition} eq 'noSuchObject' ||
          $result->{$cpqHeMibCondition} eq 'endOfMibView') {
        $self->add_message(CRITICAL,
            'snmpwalk returns no health data (cpqhlth-mib)');
        $session->close;
      } else {
        # this is not reliable. many agents return 4=failed
        #if ($result->{$cpqHeMibCondition} != 2) {
        #  $obstacle = "cmapeerstart";
        #}
      }
    }
    if (! $self->{runtime}->{plugin}->check_messages()) {
      # snmp peer is alive
      $self->trace(2, sprintf "Protocol is %s", 
          $self->{runtime}->{snmpparams}->{'-version'});
      $session->translate;
      my $response = {}; #break the walk up in smaller pieces
      foreach my $table (keys %oidtables) {
        my $oid = $oidtables{$table};
        my $tic = time;
        my $tmpresponse = $session->get_table(
            -baseoid => $oid);
        if (scalar (keys %{$tmpresponse}) == 0) {
          $self->trace(2, sprintf "maxrepetitions failed. fallback");
          $tmpresponse = $session->get_table(
              -maxrepetitions => 1,
              -baseoid => $oid);
        }
        my $tac = time;
        $self->trace(2, sprintf "%03d seconds for walk %s (%d oids)",
            $tac - $tic, $table, scalar(keys %{$tmpresponse}));
        map { $response->{$_} = $tmpresponse->{$_} } keys %{$tmpresponse};
      }
      my @oids = values %oidvalues;
      my $tic = time;
      my $tmpresponse = $session->get_request(
          -varbindlist => \@oids,
      );
      my $tac = time;
      $self->trace(2, sprintf "%03d seconds for get various (%d oids)",
          $tac - $tic, scalar(keys %{$tmpresponse}));
      map { $response->{$_} = $tmpresponse->{$_} } keys %{$tmpresponse};
      $session->close();
      $self->{rawdata} = $response;
    }
  }
  return $self->{runtime}->{plugin}->check_messages();
}

sub set_serial {
  my $self = shift;

  my $cpqSiSysSerialNum = "1.3.6.1.4.1.232.2.2.2.1.0";
  my $cpqSiProductName = "1.3.6.1.4.1.232.2.2.4.2.0";
  my $cpqSeSysRomVer = "1.3.6.1.4.1.232.1.2.6.1.0";
  my $cpqSeRedundantSysRomVer = "1.3.6.1.4.1.232.1.2.6.4.0";

  $self->{serial} = 
      SNMP::Utils::get_object($self->{rawdata}, $cpqSiSysSerialNum);
  $self->{product} =
      SNMP::Utils::get_object($self->{rawdata}, $cpqSiProductName);
  $self->{romversion} =
      SNMP::Utils::get_object($self->{rawdata}, $cpqSeSysRomVer);
  $self->{redundantromversion} =
      SNMP::Utils::get_object($self->{rawdata}, $cpqSeRedundantSysRomVer);
  if ($self->{romversion} && $self->{romversion} =~
      #/(\d{2}\/\d{2}\/\d{4}).*?([ADP]{1}\d{2}).*/) {
      /(\d{2}\/\d{2}\/\d{4}).*?Family.*?([A-Z]{1})(\d+).*/) {
    $self->{romversion} = sprintf("%s%02d %s", $2, $3, $1);
  } elsif ($self->{romversion} && $self->{romversion} =~
      /([ADP]{1}\d{2})\-(\d{2}\/\d{2}\/\d{4})/) {
    $self->{romversion} = sprintf("%s %s", $1, $2);
  } else {
    # fallback if romversion is broken, redundantromversion not
    #.1.3.6.1.4.1.232.1.2.6.1.0 = STRING: "4), Family "
    #.1.3.6.1.4.1.232.1.2.6.3.0 = ""
    #.1.3.6.1.4.1.232.1.2.6.4.0 = STRING: "v1.20 (08/26/2014), Family "
    if ($self->{redundantromversion} && $self->{redundantromversion} =~
        /(\d{2}\/\d{2}\/\d{4}).*?Family.*?([A-Z]{1})(\d+).*/) {
      $self->{romversion} = sprintf("%s%02d %s", $2, $3, $1);
    } elsif ($self->{redundantromversion} && $self->{redundantromversion} =~
        /([ADP]{1}\d{2})\-(\d{2}\/\d{2}\/\d{4})/) {
      $self->{romversion} = sprintf("%s %s", $1, $2);
    }
  }
  if (!$self->{serial} && $self->{romversion}) {
    # this probably is a very, very old server.
    $self->{serial} = "METHUSALEM";
    $self->{runtime}->{scrapiron} = 1;
  }
  $self->{serial} = $self->{serial};
  $self->{product} = lc $self->{product};
  $self->{romversion} = $self->{romversion};
  $self->{runtime}->{product} = $self->{product};
}



package HP::BladeSystem::Component::CommonEnclosureSubsystem;
our @ISA = qw(HP::BladeSystem::Component);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    common_enclosures => [],
    common_enclosure_temp_subsys => undef,
    common_enclosure_fan_subsys => undef,
    common_enclosure_fuse_subsys => undef,
    common_enclosure_manager_subsys => undef,
    common_enclosure_frus => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init();
  return $self;
}

sub init {
  my $self = shift;
  # jeweils ein block fuer
  # enclosures, temps, fans, fuses
  # loop ueber oids und entspr. new
  my $oids = {
      cpqRackCommonEnclosureEntry => '1.3.6.1.4.1.232.22.2.3.1.1.1',
      cpqRackCommonEnclosureRack => '1.3.6.1.4.1.232.22.2.3.1.1.1.1',
      cpqRackCommonEnclosureIndex => '1.3.6.1.4.1.232.22.2.3.1.1.1.2',
      cpqRackCommonEnclosureModel => '1.3.6.1.4.1.232.22.2.3.1.1.1.3',
      cpqRackCommonEnclosureSparePartNumber => '1.3.6.1.4.1.232.22.2.3.1.1.1.6',
      cpqRackCommonEnclosureSerialNum => '1.3.6.1.4.1.232.22.2.3.1.1.1.7',
      cpqRackCommonEnclosureFWRev => '1.3.6.1.4.1.232.22.2.3.1.1.1.8',
      cpqRackCommonEnclosureName => '1.3.6.1.4.1.232.22.2.3.1.1.1.9',
      cpqRackCommonEnclosureCondition => '1.3.6.1.4.1.232.22.2.3.1.1.1.16',
      cpqRackCommonEnclosureHasServerBlades => '1.3.6.1.4.1.232.22.2.3.1.1.1.17',
      cpqRackCommonEnclosureHasPowerBlades => '1.3.6.1.4.1.232.22.2.3.1.1.1.18',
      cpqRackCommonEnclosureHasNetConnectors => '1.3.6.1.4.1.232.22.2.3.1.1.1.19',
      cpqRackCommonEnclosureHasTempSensors => '1.3.6.1.4.1.232.22.2.3.1.1.1.20',
      cpqRackCommonEnclosureHasFans => '1.3.6.1.4.1.232.22.2.3.1.1.1.21',
      cpqRackCommonEnclosureHasFuses => '1.3.6.1.4.1.232.22.2.3.1.1.1.22',
      cpqRackCommonEnclosureConditionValue => {
          1 => 'other',
          2 => 'ok',
          3 => 'degraded',
          4 => 'failed',
      },
      cpqRackCommonEnclosureHasServerBladesValue => {
          1 => 'false',
          2 => 'true',
      },
  };
  $oids->{cpqRackCommonEnclosureHasPowerBladesValue} =
    $oids->{cpqRackCommonEnclosureHasServerBladesValue};
  $oids->{cpqRackCommonEnclosureHasNetConnectorsValue} =
    $oids->{cpqRackCommonEnclosureHasServerBladesValue};
  $oids->{cpqRackCommonEnclosureHasTempSensorsValue} =
    $oids->{cpqRackCommonEnclosureHasServerBladesValue};
  $oids->{cpqRackCommonEnclosureHasFansValue} =
    $oids->{cpqRackCommonEnclosureHasServerBladesValue};
  $oids->{cpqRackCommonEnclosureHasServerBladesValue} =
    $oids->{cpqRackCommonEnclosureHasServerBladesValue};
  # INDEX { cpqRackCommonEnclosureRack cpqRackCommonEnclosureIndex }
  foreach ($self->get_entries($oids, 'cpqRackCommonEnclosureEntry')) {
    push(@{$self->{common_enclosures}},
        HP::BladeSystem::Component::CommonEnclosureSubsystem::CommonEnclosure->new(%{$_}));
  }

  $self->{common_enclosure_fan_subsys} = HP::BladeSystem::Component::CommonEnclosureSubsystem::FanSubsystem->new(
      rawdata => $self->{rawdata},
      method => $self->{method},
      runtime => $self->{runtime},
  );
  $self->{common_enclosure_temp_subsys} = HP::BladeSystem::Component::CommonEnclosureSubsystem::TempSubsystem->new(
      rawdata => $self->{rawdata},
      method => $self->{method},
      runtime => $self->{runtime},
  );
  $self->{common_enclosure_fuse_subsys} = HP::BladeSystem::Component::CommonEnclosureSubsystem::FuseSubsystem->new(
      rawdata => $self->{rawdata},
      method => $self->{method},
      runtime => $self->{runtime},
  );
  $self->{common_enclosure_manager_subsys} = HP::BladeSystem::Component::CommonEnclosureSubsystem::ManagerSubsystem->new(
      rawdata => $self->{rawdata},
      method => $self->{method},
      runtime => $self->{runtime},
  );
}

sub check {
  my $self = shift;
  foreach (@{$self->{common_enclosures}}) {
    $_->check();
  }
  $self->{common_enclosure_fan_subsys}->check();
  $self->{common_enclosure_temp_subsys}->check();
  $self->{common_enclosure_fuse_subsys}->check();
  $self->{common_enclosure_manager_subsys}->check();
}

sub dump {
  my $self = shift;
  foreach (@{$self->{common_enclosures}}) {
    $_->dump();
  }
  $self->{common_enclosure_fan_subsys}->dump();
  $self->{common_enclosure_temp_subsys}->dump();
  $self->{common_enclosure_fuse_subsys}->dump();
  $self->{common_enclosure_manager_subsys}->dump();
}


package HP::BladeSystem::Component::CommonEnclosureSubsystem::CommonEnclosure;
our @ISA = qw(HP::BladeSystem::Component::CommonEnclosureSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift; 
  my %params = @_;
  my $self = { 
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    blacklisted => 0,
    info => undef, 
    extendedinfo => undef,
  };
  map { $self->{$_} = $params{$_} } grep /cpqRackCommonEnclosure/, keys %params;
  $self->{name} = $self->{cpqRackCommonEnclosureRack}.':'.$self->{cpqRackCommonEnclosureIndex};
  $self->{serfw} = sprintf "Ser: %s, FW: %s", $self->{cpqRackCommonEnclosureSerialNum},
      $self->{cpqRackCommonEnclosureFWRev};
  bless $self, $class;
  return $self;
}


sub check {
  my $self = shift;
  $self->blacklist('ce', $self->{cpqRackCommonEnclosureName});
  my $info = sprintf 'common enclosure %s condition is %s (%s)',
      $self->{cpqRackCommonEnclosureName}, $self->{cpqRackCommonEnclosureCondition}, $self->{serfw};
  $self->add_info($info);
  if ($self->{cpqRackCommonEnclosureCondition} eq 'failed') {
    $info .= sprintf " (SparePartNum %s)", $self->{cpqRackCommonEnclosureSparePartNumber};
    $self->add_message(CRITICAL, $info);
  } elsif ($self->{cpqRackCommonEnclosureCondition} eq 'degraded') {
    $info .= sprintf " (SparePartNum %s)", $self->{cpqRackCommonEnclosureSparePartNumber};
    $self->add_message(WARNING, $info);
  } 
}

sub dump {
  my $self = shift;
    printf "[COMMON_ENCLOSURE_%s]\n", $self->{cpqRackCommonEnclosureName};
  foreach (qw(cpqRackCommonEnclosureRack cpqRackCommonEnclosureIndex cpqRackCommonEnclosureModel
      cpqRackCommonEnclosureSerialNum cpqRackCommonEnclosureFWRev cpqRackCommonEnclosureFWRev
      cpqRackCommonEnclosureName
      cpqRackCommonEnclosureCondition cpqRackCommonEnclosureHasServerBlades 
      cpqRackCommonEnclosureHasPowerBlades cpqRackCommonEnclosureHasNetConnectors 
      cpqRackCommonEnclosureHasTempSensors cpqRackCommonEnclosureHasFans cpqRackCommonEnclosureHasFuses)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}


package HP::BladeSystem::Component::CommonEnclosureSubsystem::FanSubsystem;
our @ISA = qw(HP::BladeSystem::Component::CommonEnclosureSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    blacklisted => 0,
    fans => [],
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init();
  return $self;
}

sub init {
  my $self = shift;
  my $oids = {
      cpqRackCommonEnclosureFanEntry => '1.3.6.1.4.1.232.22.2.3.1.3.1',
      cpqRackCommonEnclosureFanRack => '1.3.6.1.4.1.232.22.2.3.1.3.1.1',
      cpqRackCommonEnclosureFanChassis => '1.3.6.1.4.1.232.22.2.3.1.3.1.2',
      cpqRackCommonEnclosureFanIndex => '1.3.6.1.4.1.232.22.2.3.1.3.1.3',
      cpqRackCommonEnclosureFanEnclosureName => '1.3.6.1.4.1.232.22.2.3.1.3.1.4',
      cpqRackCommonEnclosureFanLocation => '1.3.6.1.4.1.232.22.2.3.1.3.1.5',
      cpqRackCommonEnclosureFanPartNumber => '1.3.6.1.4.1.232.22.2.3.1.3.1.6',
      cpqRackCommonEnclosureFanSparePartNumber => '1.3.6.1.4.1.232.22.2.3.1.3.1.7',
      cpqRackCommonEnclosureFanPresent => '1.3.6.1.4.1.232.22.2.3.1.3.1.8',
      cpqRackCommonEnclosureFanRedundant => '1.3.6.1.4.1.232.22.2.3.1.3.1.9',
      cpqRackCommonEnclosureFanRedundantGroupId => '1.3.6.1.4.1.232.22.2.3.1.3.1.10',
      cpqRackCommonEnclosureFanCondition => '1.3.6.1.4.1.232.22.2.3.1.3.1.11',
      cpqRackCommonEnclosureFanEnclosureSerialNum => '1.3.6.1.4.1.232.22.2.3.1.3.1.12',
      cpqRackCommonEnclosureFanPresentValue => {
          1 => 'other',
          2 => 'absent',
          3 => 'present',
      },
      cpqRackCommonEnclosureFanRedundantValue => {
          0 => 'other', # meiner phantasie entsprungen, da sich hp nicht aeussert
          1 => 'other',
          2 => 'notRedundant',
          3 => 'redundant',
      },
      cpqRackCommonEnclosureFanConditionValue => {
          1 => 'other',
          2 => 'ok',
          3 => 'degraded',
          4 => 'failed',
      }
  };
  # INDEX { cpqRackCommonEnclosureFanRack, cpqRackCommonEnclosureFanChassis, cpqRackCommonEnclosureFanIndex }
  foreach ($self->get_entries($oids, 'cpqRackCommonEnclosureFanEntry')) {
    push(@{$self->{fans}},
        HP::BladeSystem::Component::CommonEnclosureSubsystem::FanSubsystem::Fan->new(%{$_}));
  }

}

sub check {
  my $self = shift;
  foreach (@{$self->{fans}}) {
    $_->check() if $_->{cpqRackCommonEnclosureFanPresent} eq 'present' ||
        $self->{runtime}->{options}->{verbose} >= 3; # absent nur bei -vvv
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{fans}}) {
    $_->dump() if $_->{cpqRackCommonEnclosureFanPresent} eq 'present' ||
        $self->{runtime}->{options}->{verbose} >= 3; # absent nur bei -vvv
  }
}


package HP::BladeSystem::Component::CommonEnclosureSubsystem::FanSubsystem::Fan;

our @ISA = qw(HP::BladeSystem::Component::CommonEnclosureSubsystem::FanSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  map { $self->{$_} = $params{$_} } grep /cpqRackCommonEnclosureFan/, keys %params;
  $self->{name} = $self->{cpqRackCommonEnclosureFanRack}.':'.$self->{cpqRackCommonEnclosureFanChassis}.':'.$self->{cpqRackCommonEnclosureFanIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('f', $self->{name});
  $self->add_info(sprintf 'fan %s is %s, location is %s, redundance is %s, condition is %s',
      $self->{name}, $self->{cpqRackCommonEnclosureFanPresent},
      $self->{cpqRackCommonEnclosureFanLocation},
      $self->{cpqRackCommonEnclosureFanRedundant},
      $self->{cpqRackCommonEnclosureFanCondition});
  if ($self->{cpqRackCommonEnclosureFanCondition} eq 'degraded') {
    $self->{info} .= sprintf ", (SparePartNum: %s)", $self->{cpqRackCommonEnclosureFanSparePartNumber};
    $self->add_message(WARNING, $self->{info});
  } elsif ($self->{cpqRackCommonEnclosureFanCondition} eq 'failed') {
    $self->{info} .= sprintf ", (SparePartNum: %s)", $self->{cpqRackCommonEnclosureFanSparePartNumber};
    $self->add_message(CRITICAL, $self->{info});
  }
}

sub dump {
  my $self = shift;
  printf "[FAN_%s]\n", $self->{name};
  foreach (qw(cpqRackCommonEnclosureFanRack cpqRackCommonEnclosureFanChassis 
      cpqRackCommonEnclosureFanIndex cpqRackCommonEnclosureFanEnclosureName 
      cpqRackCommonEnclosureFanLocation cpqRackCommonEnclosureFanPartNumber 
      cpqRackCommonEnclosureFanSparePartNumber cpqRackCommonEnclosureFanPresent 
      cpqRackCommonEnclosureFanRedundant cpqRackCommonEnclosureFanRedundantGroupId
      cpqRackCommonEnclosureFanCondition cpqRackCommonEnclosureFanEnclosureSerialNum)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "info: %s\n", $self->{info};
  printf "\n";
}


package HP::BladeSystem::Component::CommonEnclosureSubsystem::TempSubsystem;
our @ISA = qw(HP::BladeSystem::Component::CommonEnclosureSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    condition => $params{condition},
    status => $params{status},
    temperatures => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  if ($params{runtime}->{options}->{customthresholds}) {
    if (-f $params{runtime}->{options}->{customthresholds}) {
      open CT, $params{runtime}->{options}->{customthresholds};
      $params{runtime}->{options}->{customthresholds} = <CT>;
      close CT;
    }
    foreach my $ct_items
        (split(/\//, $params{runtime}->{options}->{customthresholds})) {
      if ($ct_items =~ /^(\d+):(\d+)$/) {
        my $temp = $2;
        $params{runtime}->{options}->{thresholds}->{$1} = $temp;
      } else {
        die sprintf "invalid threshold %s", $ct_items;
      }
    }
  }
  $self->init();
  return $self;
}

sub init {
  my $self = shift;
  my %params = @_;
  my $snmpwalk = $self->{rawdata};
  my $oids = {
      cpqRackCommonEnclosureTempEntry => '1.3.6.1.4.1.232.22.2.3.1.2.1',
      cpqRackCommonEnclosureTempRack => '1.3.6.1.4.1.232.22.2.3.1.2.1.1',
      cpqRackCommonEnclosureTempChassis => '1.3.6.1.4.1.232.22.2.3.1.2.1.2',
      cpqRackCommonEnclosureTempSensorIndex => '1.3.6.1.4.1.232.22.2.3.1.2.1.3',
      cpqRackCommonEnclosureTempSensorEnclosureName => '1.3.6.1.4.1.232.22.2.3.1.2.1.4',
      cpqRackCommonEnclosureTempLocation => '1.3.6.1.4.1.232.22.2.3.1.2.1.5',
      cpqRackCommonEnclosureTempCurrent => '1.3.6.1.4.1.232.22.2.3.1.2.1.6',
      cpqRackCommonEnclosureTempThreshold => '1.3.6.1.4.1.232.22.2.3.1.2.1.7',
      cpqRackCommonEnclosureTempCondition => '1.3.6.1.4.1.232.22.2.3.1.2.1.8',
      cpqRackCommonEnclosureTempType => '1.3.6.1.4.1.232.22.2.3.1.2.1.9',
      cpqRackCommonEnclosureTempConditionValue => {
          1 => 'other',
          2 => 'ok',
          3 => 'degraded',
          4 => 'failed',
      },
      cpqRackCommonEnclosureTempTypeValue => {
          1 => 'other',
          5 => 'blowout',
          9 => 'caution',
          15 => 'critical',
      },
  };
  # INDEX { cpqRackCommonEnclosureTempRack cpqRackCommonEnclosureTempChassis
  #         cpqRackCommonEnclosureTempSensorIndex }
  foreach ($self->get_entries($oids, 'cpqRackCommonEnclosureTempEntry')) {
    push(@{$self->{temperatures}},
       HP::BladeSystem::Component::CommonEnclosureSubsystem::TempSubsystem::Temp->new(%{$_}))  if (($_->{cpqRackCommonEnclosureTempCurrent} != -1 && $_->{cpqRackCommonEnclosureTempThreshold} != -1) && ($_->{cpqRackCommonEnclosureTempThreshold} != 0));
  }

}


sub check {
  my $self = shift;
  my $errorfound = 0;
  if (scalar (@{$self->{temperatures}}) == 0) {
    #$self->overall_check();
  } else {
    foreach (@{$self->{temperatures}}) {
      $_->check();
    }
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{temperatures}}) {
    $_->dump();
  }
}


package HP::BladeSystem::Component::CommonEnclosureSubsystem::TempSubsystem::Temp;
our @ISA = qw(HP::BladeSystem::Component::CommonEnclosureSubsystem::TempSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  map { $self->{$_} = $params{$_} } grep /cpqRackCommonEnclosureTemp/, keys %params;
  $self->{name} = $params{cpqRackCommonEnclosureTempRack}.':'.
      $params{cpqRackCommonEnclosureTempChassis}.':'.
      $params{cpqRackCommonEnclosureTempSensorIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('t', $self->{name});
  if ($self->{cpqRackCommonEnclosureTempCurrent} > $self->{cpqRackCommonEnclosureTempThreshold}) {
    $self->add_info(sprintf "%s temperature too high (%d%s)",
        $self->{cpqRackCommonEnclosureTempLocation},
        $self->{cpqRackCommonEnclosureTempCurrent},
        $self->{runtime}->{options}->{celsius} ? "C" : "F");
    $self->add_message(CRITICAL, $self->{info});
  } else {
    $self->add_info(sprintf "%s temperature is %d%s (%d max)",
        $self->{cpqRackCommonEnclosureTempLocation},
        $self->{cpqRackCommonEnclosureTempCurrent},
        $self->{runtime}->{options}->{celsius} ? "C" : "F",
        $self->{cpqRackCommonEnclosureTempThreshold});
  }
  if ($self->{runtime}->{options}->{perfdata} == 2) {
    $self->{runtime}->{plugin}->add_perfdata(
        label => sprintf('temp_%s', $self->{name}),
        value => $self->{cpqRackCommonEnclosureTempCurrent},
        warning => $self->{cpqRackCommonEnclosureTempThreshold},
        critical => $self->{cpqRackCommonEnclosureTempThreshold}
    );
  } elsif ($self->{runtime}->{options}->{perfdata} == 1) {
    $self->{runtime}->{plugin}->add_perfdata(
        label => sprintf('temp_%s_%s', $self->{name},
            $self->{cpqRackCommonEnclosureTempLocation}),
        value => $self->{cpqRackCommonEnclosureTempCurrent},
        warning => $self->{cpqRackCommonEnclosureTempThreshold},
        critical => $self->{cpqRackCommonEnclosureTempThreshold}
    );
  }
  $self->add_extendedinfo(sprintf "temp_%s=%d", 
      $self->{name}, $self->{cpqRackCommonEnclosureTempCurrent});

}


sub dump {
  my $self = shift;
  printf "[TEMP_%s]\n", $self->{name};
  foreach (qw(cpqRackCommonEnclosureTempRack cpqRackCommonEnclosureTempChassis
      cpqRackCommonEnclosureTempSensorIndex cpqRackCommonEnclosureTempSensorEnclosureName
      cpqRackCommonEnclosureTempLocation
      cpqRackCommonEnclosureTempCurrent cpqRackCommonEnclosureTempThreshold 
      cpqRackCommonEnclosureTempCondition cpqRackCommonEnclosureTempType)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "info: %s\n\n", $self->{info};
}



package HP::BladeSystem::Component::CommonEnclosureSubsystem::FuseSubsystem;
our @ISA = qw(HP::BladeSystem::Component::CommonEnclosureSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    blacklisted => 0,
    fuses => [],
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init();
  return $self;
}

sub init {
  my $self = shift;
  my $oids = {
      cpqRackCommonEnclosureFuseEntry => '1.3.6.1.4.1.232.22.2.3.1.4.1',
      cpqRackCommonEnclosureFuseRack => '1.3.6.1.4.1.232.22.2.3.1.4.1.1',
      cpqRackCommonEnclosureFuseChassis => '1.3.6.1.4.1.232.22.2.3.1.4.1.2',
      cpqRackCommonEnclosureFuseIndex => '1.3.6.1.4.1.232.22.2.3.1.4.1.3',
      cpqRackCommonEnclosureFuseEnclosureName => '1.3.6.1.4.1.232.22.2.3.1.4.1.4',
      cpqRackCommonEnclosureFuseLocation => '1.3.6.1.4.1.232.22.2.3.1.4.1.5',
      cpqRackCommonEnclosureFusePresent => '1.3.6.1.4.1.232.22.2.3.1.4.1.8',
      cpqRackCommonEnclosureFuseCondition => '1.3.6.1.4.1.232.22.2.3.1.4.1.11',
      cpqRackCommonEnclosureFusePresentValue => {
          1 => 'other',
          2 => 'absent',
          3 => 'present',
      },
      cpqRackCommonEnclosureFuseConditionValue => {
          1 => 'other',
          2 => 'ok',
          4 => 'failed',
      }
  };
  # INDEX { cpqRackCommonEnclosureFuseRack, cpqRackCommonEnclosureFuseChassis, cpqRackCommonEnclosureFuseIndex }
  foreach ($self->get_entries($oids, 'cpqRackCommonEnclosureFuseEntry')) {
    push(@{$self->{fuses}},
        HP::BladeSystem::Component::CommonEnclosureSubsystem::FuseSubsystem::Fuse->new(%{$_}));
  }

}

sub check {
  my $self = shift;
  foreach (@{$self->{fuses}}) {
    $_->check() if $_->{cpqRackCommonEnclosureFusePresent} eq 'present' ||
        $self->{runtime}->{options}->{verbose} >= 3; # absent nur bei -vvv
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{fuses}}) {
    $_->dump() if $_->{cpqRackCommonEnclosureFusePresent} eq 'present' ||
        $self->{runtime}->{options}->{verbose} >= 3; # absent nur bei -vvv
  }
}


package HP::BladeSystem::Component::CommonEnclosureSubsystem::FuseSubsystem::Fuse;

our @ISA = qw(HP::BladeSystem::Component::CommonEnclosureSubsystem::FuseSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  map { $self->{$_} = $params{$_} } grep /cpqRackCommonEnclosureFuse/, keys %params;
  $self->{name} = $self->{cpqRackCommonEnclosureFuseRack}.':'.$self->{cpqRackCommonEnclosureFuseChassis}.':'.$self->{cpqRackCommonEnclosureFuseIndex};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('fu', $self->{name});
  $self->add_info(sprintf 'fuse %s is %s, location is %s, condition is %s',
      $self->{name}, $self->{cpqRackCommonEnclosureFusePresent},
      $self->{cpqRackCommonEnclosureFuseLocation}, $self->{cpqRackCommonEnclosureFuseCondition});
  if ($self->{cpqRackCommonEnclosureFuseCondition} eq 'failed') {
    $self->add_message(CRITICAL, $self->{info});
  } elsif ($self->{cpqRackCommonEnclosureFuseCondition} ne 'ok') {
    $self->add_message(WARNING, $self->{info});
  }
}

sub dump {
  my $self = shift;
  printf "[FUSE_%s]\n", $self->{name};
  foreach (qw(cpqRackCommonEnclosureFuseRack cpqRackCommonEnclosureFuseChassis 
      cpqRackCommonEnclosureFuseIndex cpqRackCommonEnclosureFuseEnclosureName 
      cpqRackCommonEnclosureFuseLocation cpqRackCommonEnclosureFusePresent 
      cpqRackCommonEnclosureFuseCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "info: %s\n", $self->{info};
  printf "\n";
}


package HP::BladeSystem::Component::CommonEnclosureSubsystem::ManagerSubsystem;
our @ISA = qw(HP::BladeSystem::Component::CommonEnclosureSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    blacklisted => 0,
    managers => [],
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init();
  return $self;
}

sub init {
  my $self = shift;
  my $oids = {
      cpqRackCommonEnclosureManagerEntry => '1.3.6.1.4.1.232.22.2.3.1.6.1',
      cpqRackCommonEnclosureManagerRack => '1.3.6.1.4.1.232.22.2.3.1.6.1.1',
      cpqRackCommonEnclosureManagerChassis => '1.3.6.1.4.1.232.22.2.3.1.6.1.2',
      cpqRackCommonEnclosureManagerIndex => '1.3.6.1.4.1.232.22.2.3.1.6.1.3',
      cpqRackCommonEnclosureManagerEnclosureName => '1.3.6.1.4.1.232.22.2.3.1.6.1.4',
      cpqRackCommonEnclosureManagerLocation => '1.3.6.1.4.1.232.22.2.3.1.6.1.5',
      cpqRackCommonEnclosureManagerPartNumber => '1.3.6.1.4.1.232.22.2.3.1.6.1.6',
      cpqRackCommonEnclosureManagerSparePartNumber => '1.3.6.1.4.1.232.22.2.3.1.6.1.7',
      cpqRackCommonEnclosureManagerSerialNum => '1.3.6.1.4.1.232.22.2.3.1.6.1.8',
      cpqRackCommonEnclosureManagerRole => '1.3.6.1.4.1.232.22.2.3.1.6.1.9',
      cpqRackCommonEnclosureManagerPresent => '1.3.6.1.4.1.232.22.2.3.1.6.1.10',
      cpqRackCommonEnclosureManagerRedundant => '1.3.6.1.4.1.232.22.2.3.1.6.1.11',
      cpqRackCommonEnclosureManagerCondition => '1.3.6.1.4.1.232.22.2.3.1.6.1.12',
      cpqRackCommonEnclosureManagerFWRev => '1.3.6.1.4.1.232.22.2.3.1.6.1.15',
      cpqRackCommonEnclosureManagerRoleValue => {
          1 => 'standby',
          2 => 'active',
      },
      cpqRackCommonEnclosureManagerPresentValue => {
          1 => 'other',
          2 => 'absent', # mit vorsicht zu geniessen!
          3 => 'present',
      },
      cpqRackCommonEnclosureManagerRedundantValue => {
          0 => 'other', # meiner phantasie entsprungen, da sich hp nicht aeussert
          1 => 'other',
          2 => 'notRedundant',
          3 => 'redundant',
      },
      cpqRackCommonEnclosureManagerConditionValue => {
          1 => 'other',
          2 => 'ok',
          3 => 'degraded',
          4 => 'failed',
      }
  };
  # INDEX { cpqRackCommonEnclosureManagerRack, cpqRackCommonEnclosureManagerChassis, cpqRackCommonEnclosureManagerIndex }
  foreach ($self->get_entries($oids, 'cpqRackCommonEnclosureManagerEntry')) {
    push(@{$self->{managers}},
        HP::BladeSystem::Component::CommonEnclosureSubsystem::ManagerSubsystem::Manager->new(%{$_}));
  }
}

sub check {
  my $self = shift;
  foreach (@{$self->{managers}}) {
    $_->check() if $_->{cpqRackCommonEnclosureManagerPresent} eq 'present' ||
        $self->{runtime}->{options}->{verbose} >= 3; # absent nur bei -vvv
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{managers}}) {
    $_->dump() if $_->{cpqRackCommonEnclosureManagerPresent} eq 'present' ||
        $self->{runtime}->{options}->{verbose} >= 3; # absent nur bei -vvv
  }
}


package HP::BladeSystem::Component::CommonEnclosureSubsystem::ManagerSubsystem::Manager;

our @ISA = qw(HP::BladeSystem::Component::CommonEnclosureSubsystem::ManagerSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  map { $self->{$_} = $params{$_} } grep /cpqRackCommonEnclosureManager/, keys %params;
  $self->{name} = $self->{cpqRackCommonEnclosureManagerRack}.
      ':'.$self->{cpqRackCommonEnclosureManagerChassis}.
      ':'.$self->{cpqRackCommonEnclosureManagerIndex};
  if ($self->{cpqRackCommonEnclosureManagerPresent} eq "absent" &&
      defined $self->{cpqRackCommonEnclosureManagerEnclosureName}) {
    $self->{cpqRackCommonEnclosureManagerPresent} = "present";
  }
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('em', $self->{name});
  my $info = sprintf 'manager %s is %s, location is %s, redundance is %s, condition is %s, role is %s',
      $self->{name}, $self->{cpqRackCommonEnclosureManagerPresent},
      $self->{cpqRackCommonEnclosureManagerLocation},
      $self->{cpqRackCommonEnclosureManagerRedundant},
      $self->{cpqRackCommonEnclosureManagerCondition},
      $self->{cpqRackCommonEnclosureManagerRole};
  $self->add_info($info) if $self->{cpqRackCommonEnclosureManagerPresent} eq 'present' ||
      $self->{runtime}->{options}->{verbose} >= 3; # absent managers nur bei -vvv
  if ($self->{cpqRackCommonEnclosureManagerCondition} eq 'degraded') {
    $self->{info} .= sprintf ' (SparePartNum: %s)',
        $self->{cpqRackCommonEnclosureManagerSparePartNumber};
    $self->add_message(WARNING, $self->{info});
  } elsif ($self->{cpqRackCommonEnclosureManagerCondition} eq 'failed') {
    $self->{info} .= sprintf ' (SparePartNum: %s)',
        $self->{cpqRackCommonEnclosureManagerSparePartNumber};
    $self->add_message(CRITICAL, $self->{info});
  }
}

sub dump {
  my $self = shift;
  printf "[ENCLOSURE_MANAGER_%s]\n", $self->{name};
  foreach (qw(cpqRackCommonEnclosureManagerRack cpqRackCommonEnclosureManagerChassis 
      cpqRackCommonEnclosureManagerIndex cpqRackCommonEnclosureManagerEnclosureName 
      cpqRackCommonEnclosureManagerLocation cpqRackCommonEnclosureManagerPartNumber 
      cpqRackCommonEnclosureManagerSparePartNumber cpqRackCommonEnclosureManagerPresent 
      cpqRackCommonEnclosureManagerRedundant 
      cpqRackCommonEnclosureManagerCondition cpqRackCommonEnclosureManagerFWRev)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "info: %s\n", $self->{info};
  printf "\n";
}


package HP::BladeSystem::Component::PowerEnclosureSubsystem;
our @ISA = qw(HP::BladeSystem::Component);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    power_enclosures => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init();
  return $self;
}

sub init {
  my $self = shift;

# cpqRackPowerEnclosureTable
  my $oids = {
      cpqRackPowerEnclosureEntry => '1.3.6.1.4.1.232.22.2.3.3.1.1',
      cpqRackPowerEnclosureRack => '1.3.6.1.4.1.232.22.2.3.3.1.1.1',
      cpqRackPowerEnclosureIndex => '1.3.6.1.4.1.232.22.2.3.3.1.1.2',
      cpqRackPowerEnclosureName => '1.3.6.1.4.1.232.22.2.3.3.1.1.3',
      cpqRackPowerEnclosureMgmgtBoardSerialNum => '1.3.6.1.4.1.232.22.2.3.3.1.1.4',
      cpqRackPowerEnclosureRedundant => '1.3.6.1.4.1.232.22.2.3.3.1.1.5',
      cpqRackPowerEnclosureLoadBalanced => '1.3.6.1.4.1.232.22.2.3.3.1.1.6',
      cpqRackPowerEnclosureInputPwrType => '1.3.6.1.4.1.232.22.2.3.3.1.1.7',
      cpqRackPowerEnclosurePwrFeedMax => '1.3.6.1.4.1.232.22.2.3.3.1.1.8',
      cpqRackPowerEnclosureCondition => '1.3.6.1.4.1.232.22.2.3.3.1.1.9',
      cpqRackPowerEnclosureRedundantValue => {
          1 => 'other',
          2 => 'notRedundant',
          3 => 'redundant',
      },
      cpqRackPowerEnclosureLoadBalancedValue => {
          0 => 'aechz',
          1 => 'other',
          2 => 'notLoadBalanced',
          3 => 'loadBalanced',
      },
      cpqRackPowerEnclosureInputPwrTypeValue => {
          1 => 'other',
          2 => 'singlePhase',
          3 => 'threePhase',
          4 => 'directCurrent',
      },
      cpqRackPowerEnclosureConditionValue => {
          1 => 'other',
          2 => 'ok',
          3 => 'degraded',
      },
  };
 
 
  # INDEX { cpqRackPowerEnclosureRack, cpqRackPowerEnclosureIndex }
  # dreckada dreck, dreckada
  foreach ($self->get_entries($oids, 'cpqRackPowerEnclosureEntry')) {
    push(@{$self->{power_enclosures}},
        HP::BladeSystem::Component::PowerEnclosureSubsystem::PowerEnclosure->new(%{$_}));
  }
}

sub check {
  my $self = shift;
  foreach (@{$self->{power_enclosures}}) {
    $_->check();
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{power_enclosures}}) {
    $_->dump();
  }
}


package HP::BladeSystem::Component::PowerEnclosureSubsystem::PowerEnclosure;
our @ISA = qw(HP::BladeSystem::Component::PowerEnclosureSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  map { $self->{$_} = $params{$_} } grep /cpqRackPowerEnclosure/, keys %params;
  $self->{name} = $self->{cpqRackPowerEnclosureRack}.':'.$self->{cpqRackPowerEnclosureIndex};
  bless $self, $class;
  $self->init();
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('pe', $self->{name});
  my $info = sprintf 'power enclosure %s \'%s\' condition is %s',
      $self->{name}, $self->{cpqRackPowerEnclosureName}, $self->{cpqRackPowerEnclosureCondition};
  $self->add_info($info);
  if ($self->{cpqRackPowerEnclosureCondition} eq 'degraded') {
    $self->add_message(WARNING, $info);
  } 
} 
  
sub dump {
  my $self = shift;
    printf "[POWER_ENCLOSURE_%s]\n", $self->{cpqRackPowerEnclosureName};
  foreach (qw(cpqRackPowerEnclosureRack cpqRackPowerEnclosureIndex 
      cpqRackPowerEnclosureName cpqRackPowerEnclosureMgmgtBoardSerialNum
      cpqRackPowerEnclosureRedundant cpqRackPowerEnclosureLoadBalanced
      cpqRackPowerEnclosureInputPwrType cpqRackPowerEnclosurePwrFeedMax
      cpqRackPowerEnclosureCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}



package HP::BladeSystem::Component::PowerSupplySubsystem;
our @ISA = qw(HP::BladeSystem::Component);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    power_supplies => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init();
  return $self;
}

sub init {
  my $self = shift;
  my $oids = {
      cpqRackPowerSupplyEntry => '1.3.6.1.4.1.232.22.2.5.1.1.1',
      cpqRackPowerSupplyRack => '1.3.6.1.4.1.232.22.2.5.1.1.1.1',
      cpqRackPowerSupplyChassis => '1.3.6.1.4.1.232.22.2.5.1.1.1.2',
      cpqRackPowerSupplyIndex => '1.3.6.1.4.1.232.22.2.5.1.1.1.3',
      cpqRackPowerSupplyEnclosureName => '1.3.6.1.4.1.232.22.2.5.1.1.1.4',
      cpqRackPowerSupplySerialNum => '1.3.6.1.4.1.232.22.2.5.1.1.1.5',
      cpqRackPowerSupplySparePartNumber => '1.3.6.1.4.1.232.22.2.5.1.1.1.7',
      cpqRackPowerSupplyFWRev => '1.3.6.1.4.1.232.22.2.5.1.1.1.8',
      cpqRackPowerSupplyMaxPwrOutput => '1.3.6.1.4.1.232.22.2.5.1.1.1.9',
      cpqRackPowerSupplyCurPwrOutput => '1.3.6.1.4.1.232.22.2.5.1.1.1.10',
      cpqRackPowerSupplyIntakeTemp => '1.3.6.1.4.1.232.22.2.5.1.1.1.12',
      cpqRackPowerSupplyExhaustTemp => '1.3.6.1.4.1.232.22.2.5.1.1.1.13',
      cpqRackPowerSupplyStatus => '1.3.6.1.4.1.232.22.2.5.1.1.1.14',
      cpqRackPowerSupplySupplyInputLineStatus => '1.3.6.1.4.1.232.22.2.5.1.1.1.15',
      cpqRackPowerSupplyPresent => '1.3.6.1.4.1.232.22.2.5.1.1.1.16',
      cpqRackPowerSupplyCondition => '1.3.6.1.4.1.232.22.2.5.1.1.1.17',
      cpqRackPowerSupplySupplyInputLineStatusValue => {
          1 => 'noError',
          2 => 'lineOverVoltage',
          3 => 'lineUnderVoltage',
          4 => 'lineHit',
          5 => 'brownOut',
          6 => 'linePowerLoss',
      },
      cpqRackPowerSupplyStatusValue => {
          1 => 'noError',
          2 => 'generalFailure',
          3 => 'bistFailure',
          4 => 'fanFailure',
          5 => 'tempFailure',
          6 => 'interlockOpen',
          7 => 'epromFailed',
          8 => 'vrefFailed',
          9 => 'dacFailed',
          10 => 'ramTestFailed',
          11 => 'voltageChannelFailed',
          12 => 'orringdiodeFailed',
          13 => 'brownOut',
          14 => 'giveupOnStartup',
          15 => 'nvramInvalid',
          16 => 'calibrationTableInvalid',
      },
      cpqRackPowerSupplyPresentValue => {
          1 => 'other',
          2 => 'absent',
          3 => 'present',
      },
      cpqRackPowerSupplyConditionValue => {
          1 => 'other',
          2 => 'ok',
          3 => 'degraded',
          4 => 'failed',
      },
  };
 
 
  # INDEX { cpqRackPowerSupplyRack, cpqRackPowerSupplyChassis, cpqRackPowerSupplyIndex }
  foreach ($self->get_entries($oids, 'cpqRackPowerSupplyEntry')) {
    push(@{$self->{power_supplies}},
        HP::BladeSystem::Component::PowerSupplySubsystem::PowerSupply->new(%{$_}));
  }
}

sub check {
  my $self = shift;
  my $total_current_watt = 0;
  my $total_max_watt = 0;
  my $total_in_temp = 0;
  my $total_out_temp = 0;
  my $num_ps = 0;
  foreach (@{$self->{power_supplies}}) {
    $_->check() if $_->{cpqRackPowerSupplyPresent} eq 'present' ||
        $self->{runtime}->{options}->{verbose} >= 3; # absent nur bei -vvv
    if ($_->{cpqRackPowerSupplyPresent} eq 'present') {
      $total_max_watt += $_->{cpqRackPowerSupplyMaxPwrOutput};
      $total_current_watt += $_->{cpqRackPowerSupplyCurPwrOutput};
      $total_in_temp += $_->{cpqRackPowerSupplyIntakeTemp} 
          if $_->{cpqRackPowerSupplyIntakeTemp} != -1;
      $total_out_temp += $_->{cpqRackPowerSupplyExhaustTemp}
          if $_->{cpqRackPowerSupplyExhaustTemp} != -1;
      $num_ps++;
    }
  }
  $self->{runtime}->{plugin}->add_perfdata(
      label => 'watt_total',
      value => $total_current_watt,
      warning => $total_max_watt,
      critical => $total_max_watt,
  );
  #$self->{runtime}->{plugin}->add_perfdata(
  #    label => 'watt_total_pct',
  #    value => ($total_current_watt == 0 ? 0 :
  #        sprintf("%.2f",
  #        ($total_current_watt / $total_max_watt * 100))),
  #    warning => 100,
  #    critical => 100,
  #    uom => '%',
  #);
  if ($total_in_temp) {
    $self->{runtime}->{plugin}->add_perfdata(
        label => 'in_temp',
        value => $total_in_temp / $num_ps,
    );
  }
  if ($total_out_temp) {
    $self->{runtime}->{plugin}->add_perfdata(
        label => 'out_temp',
        value => $total_out_temp / $num_ps,
    );
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{power_supplies}}) {
    $_->dump() if $_->{cpqRackPowerSupplyPresent} eq 'present' ||
        $self->{runtime}->{options}->{verbose} >= 3; # absent nur bei -vvv
  }
}


package HP::BladeSystem::Component::PowerSupplySubsystem::PowerSupply;
our @ISA = qw(HP::BladeSystem::Component::PowerSupplySubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  map { $self->{$_} = $params{$_} } grep /cpqRackPowerSupply/, keys %params;
  $self->{name} = $params{cpqRackPowerSupplyRack}.
      ':'.$params{cpqRackPowerSupplyChassis}.
      ':'.$params{cpqRackPowerSupplyIndex};
  $self->{serfw} = sprintf "Ser: %s, FW: %s", $self->{cpqRackPowerSupplySerialNum}, $self->{cpqRackPowerSupplyFWRev};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('ps', $self->{name});
  my $info = sprintf 'power supply %s is %s, condition is %s (%s)', 
      $self->{name}, $self->{cpqRackPowerSupplyPresent},
      $self->{cpqRackPowerSupplyCondition}, $self->{serfw};
  $self->add_info($info);
  if ($self->{cpqRackPowerSupplyPresent} eq 'present') {
    if ($self->{cpqRackPowerSupplyCondition} eq 'degraded') {
      $info .= sprintf " (SparePartNum %s)", $self->{cpqRackPowerSupplySparePartNumber};
      $self->add_message(WARNING, $info);
      $self->add_info(sprintf 'power supply %s status is %s, inp.line status is %s',
          $self->{name}, $self->{cpqRackPowerSupplyStatus},
          $self->{cpqRackPowerSupplySupplyInputLineStatus});
    } elsif ($self->{cpqRackPowerSupplyCondition} eq 'failed') {
      $info .= sprintf " (SparePartNum %s)", $self->{cpqRackPowerSupplySparePartNumber};
      $self->add_message(CRITICAL, $info);
      $self->add_info(sprintf 'power supply %s status is %s, inp.line status is %s',
          $self->{name}, $self->{cpqRackPowerSupplyStatus},
          $self->{cpqRackPowerSupplySupplyInputLineStatus});
    } 
    if ($self->{runtime}->{options}->{perfdata} != 2) {
      $self->{runtime}->{plugin}->add_perfdata(
          label => sprintf('watt_%s', $self->{name}),
          value => $self->{cpqRackPowerSupplyCurPwrOutput},
          warning => $self->{cpqRackPowerSupplyMaxPwrOutput},
          critical => $self->{cpqRackPowerSupplyMaxPwrOutput}
      );
      #$self->{runtime}->{plugin}->add_perfdata(
      #    label => sprintf('watt_pct_%s', $self->{name}),
      #    value => ($self->{cpqRackPowerSupplyCurPwrOutput} == 0 ? 0 :
      #        sprintf ("%.2f",
      #        ($self->{cpqRackPowerSupplyCurPwrOutput} /
      #        $self->{cpqRackPowerSupplyMaxPwrOutput} * 100))),
      #    warning => 100,
      #    critical => 100,
      #    uom => '%',
      #);
      if ($self->{cpqRackPowerSupplyIntakeTemp} != -1) {
        $self->{runtime}->{plugin}->add_perfdata(
            label => sprintf('in_temp_%s', $self->{name}),
            value => $self->{cpqRackPowerSupplyIntakeTemp},
        );
      }
      if ($self->{cpqRackPowerSupplyExhaustTemp} != -1) {
        $self->{runtime}->{plugin}->add_perfdata(
            label => sprintf('out_temp_%s', $self->{name}),
            value => $self->{cpqRackPowerSupplyExhaustTemp},
        );
      }
    }
  }
} 
  
sub dump {
  my $self = shift;
    printf "[POWER_SUPPLY%s]\n", $self->{name};
  foreach (qw(cpqRackPowerSupplyRack cpqRackPowerSupplyChassis cpqRackPowerSupplyIndex cpqRackPowerSupplyEnclosureName cpqRackPowerSupplySerialNum cpqRackPowerSupplySparePartNumber cpqRackPowerSupplyFWRev cpqRackPowerSupplyMaxPwrOutput cpqRackPowerSupplyCurPwrOutput cpqRackPowerSupplyIntakeTemp cpqRackPowerSupplyExhaustTemp cpqRackPowerSupplyStatus cpqRackPowerSupplySupplyInputLineStatus cpqRackPowerSupplyPresent cpqRackPowerSupplyCondition)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}



package HP::BladeSystem::Component::NetConnectorSubsystem;
our @ISA = qw(HP::BladeSystem::Component);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    net_connectors => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init();
  return $self;
}

sub init {
  my $self = shift;
  my $oids = {
      cpqRackNetConnectorEntry => '1.3.6.1.4.1.232.22.2.6.1.1.1',
      cpqRackNetConnectorRack => '1.3.6.1.4.1.232.22.2.6.1.1.1.1',
      cpqRackNetConnectorChassis => '1.3.6.1.4.1.232.22.2.6.1.1.1.2',
      cpqRackNetConnectorIndex => '1.3.6.1.4.1.232.22.2.6.1.1.1.3',
      cpqRackNetConnectorEnclosureName => '1.3.6.1.4.1.232.22.2.6.1.1.1.4',
      cpqRackNetConnectorName => '1.3.6.1.4.1.232.22.2.6.1.1.1.5',
      cpqRackNetConnectorModel => '1.3.6.1.4.1.232.22.2.6.1.1.1.6',
      cpqRackNetConnectorSerialNum => '1.3.6.1.4.1.232.22.2.6.1.1.1.7',
      cpqRackNetConnectorPartNumber => '1.3.6.1.4.1.232.22.2.6.1.1.1.8',
      cpqRackNetConnectorSparePartNumber => '1.3.6.1.4.1.232.22.2.6.1.1.1.9',
      cpqRackNetConnectorFWRev => '1.3.6.1.4.1.232.22.2.6.1.1.1.10',
      cpqRackNetConnectorType => '1.3.6.1.4.1.232.22.2.6.1.1.1.11',
      cpqRackNetConnectorLocation => '1.3.6.1.4.1.232.22.2.6.1.1.1.12',
      cpqRackNetConnectorPresent => '1.3.6.1.4.1.232.22.2.6.1.1.1.13',
      cpqRackNetConnectorHasFuses => '1.3.6.1.4.1.232.22.2.6.1.1.1.14',
      cpqRackNetConnectorEnclosureSerialNum => '1.3.6.1.4.1.232.22.2.6.1.1.1.15',
      cpqRackNetConnectorTypeValue => {
          0 => 'other', # undefined
          1 => 'other',
          2 => 'active',
          3 => 'passive',
      },
      cpqRackNetConnectorPresentValue => {
          1 => 'other',
          2 => 'absent',
          3 => 'present',
      },
      cpqRackNetConnectorHasFusesValue => {
          -1 => 'false', # wird geliefert, also vermute ich false
          1 => 'false',
          2 => 'true',
      },
  };
 
 
  # INDEX { cpqRackNetConnectorRack, cpqRackNetConnectorChassis, cpqRackNetConnectorIndex }
  # dreckada dreck, dreckada
  foreach ($self->get_entries($oids, 'cpqRackNetConnectorEntry')) {
    push(@{$self->{net_connectors}},
        HP::BladeSystem::Component::NetConnectorSubsystem::NetConnector->new(%{$_}));
  }
}

sub check {
  my $self = shift;
  foreach (@{$self->{net_connectors}}) {
    $_->check() if $_->{cpqRackNetConnectorPresent} eq 'present' ||
        $self->{runtime}->{options}->{verbose} >= 3; # absent nur bei -vvv
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{net_connectors}}) {
    $_->dump() if $_->{cpqRackNetConnectorPresent} eq 'present' ||
        $self->{runtime}->{options}->{verbose} >= 3; # absent nur bei -vvv
  }
}


package HP::BladeSystem::Component::NetConnectorSubsystem::NetConnector;
our @ISA = qw(HP::BladeSystem::Component::NetConnectorSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  map { $self->{$_} = $params{$_} } grep /cpqRackNetConnector/, keys %params;
  $self->{name} = $params{cpqRackNetConnectorRack}.
      ':'.$params{cpqRackNetConnectorChassis}.
      ':'.$params{cpqRackNetConnectorIndex};
  $self->{serfw} = sprintf "Ser: %s, FW: %s", $self->{cpqRackNetConnectorSerialNum}, $self->{cpqRackNetConnectorFWRev};
  bless $self, $class;
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('nc', $self->{name});
  my $info = sprintf 'net connector %s is %s, model is %s (%s)', 
      $self->{name}.($self->{cpqRackNetConnectorName} ? ' \''.$self->{cpqRackNetConnectorName}.'\'' : ''),
      $self->{cpqRackNetConnectorPresent}, $self->{cpqRackNetConnectorModel}, $self->{serfw};
  $self->add_info($info);
  # hat weder status noch condition, vielleicht spaeter mal
  $info .= sprintf " (SparePartNum %s)", $self->{cpqRackNetConnectorSparePartNumber};
} 
  
sub dump {
  my $self = shift;
    printf "[NET_CONNECTOR_%s]\n", $self->{cpqRackNetConnectorName};
  foreach (qw(cpqRackNetConnectorRack cpqRackNetConnectorChassis cpqRackNetConnectorIndex cpqRackNetConnectorEnclosureName cpqRackNetConnectorName cpqRackNetConnectorModel cpqRackNetConnectorSerialNum cpqRackNetConnectorPartNumber cpqRackNetConnectorSparePartNumber cpqRackNetConnectorFWRev cpqRackNetConnectorType cpqRackNetConnectorLocation cpqRackNetConnectorPresent cpqRackNetConnectorHasFuses cpqRackNetConnectorEnclosureSerialNum)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}



package HP::BladeSystem::Component::ServerBladeSubsystem;
our @ISA = qw(HP::BladeSystem::Component);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    server_blades => [],
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  bless $self, $class;
  $self->init();
  return $self;
}

sub init {
  my $self = shift;
  my $oids = {
      cpqRackServerBladeEntry => '1.3.6.1.4.1.232.22.2.4.1.1.1',
      cpqRackServerBladeRack => '1.3.6.1.4.1.232.22.2.4.1.1.1.1',
      cpqRackServerBladeChassis => '1.3.6.1.4.1.232.22.2.4.1.1.1.2',
      cpqRackServerBladeIndex => '1.3.6.1.4.1.232.22.2.4.1.1.1.3',
      cpqRackServerBladeName => '1.3.6.1.4.1.232.22.2.4.1.1.1.4',
      cpqRackServerBladeEnclosureName => '1.3.6.1.4.1.232.22.2.4.1.1.1.5',
      cpqRackServerBladePartNumber => '1.3.6.1.4.1.232.22.2.4.1.1.1.6',
      cpqRackServerBladeSparePartNumber => '1.3.6.1.4.1.232.22.2.4.1.1.1.7',
      cpqRackServerBladePosition => '1.3.6.1.4.1.232.22.2.4.1.1.1.8',
      cpqRackServerBladeHeight => '1.3.6.1.4.1.232.22.2.4.1.1.1.9',
      cpqRackServerBladeWidth => '1.3.6.1.4.1.232.22.2.4.1.1.1.10',
      cpqRackServerBladeDepth => '1.3.6.1.4.1.232.22.2.4.1.1.1.11',
      cpqRackServerBladePresent => '1.3.6.1.4.1.232.22.2.4.1.1.1.12',
      cpqRackServerBladeHasFuses => '1.3.6.1.4.1.232.22.2.4.1.1.1.13',
      cpqRackServerBladeEnclosureSerialNum => '1.3.6.1.4.1.232.22.2.4.1.1.1.14',
      cpqRackServerBladeSlotsUsed => '1.3.6.1.4.1.232.22.2.4.1.1.1.15',
      cpqRackServerBladeStatus => '1.3.6.1.4.1.232.22.2.4.1.1.1.21',
      cpqRackServerBladeDiagnosticString => '1.3.6.1.4.1.232.22.2.4.1.1.1.24',
      cpqRackServerBladePowered => '1.3.6.1.4.1.232.22.2.4.1.1.1.25',
      cpqRackServerBladePOSTStatus => '1.3.6.1.4.1.232.22.2.4.1.1.1.35',
      cpqRackServerBladePresentValue => {
          1 => 'other',
          2 => 'absent',
          3 => 'present',
      },
      cpqRackServerBladeStatusValue => {
          1 => 'other',
          2 => 'ok',
          3 => 'degraded',
          4 => 'failed',
      },
      cpqRackServerBladePoweredValue => {
          0 => 'aechz',
          1 => 'other',
          2 => 'on',
          3 => 'off',
          4 => 'powerStagedOff',
          5 => 'reboot',
      },
      cpqRackServerBladePOSTStatusValue => {
          1 => 'other',
          2 => 'started',
          3 => 'completed',
          4 => 'failed',
      },
  };
 
 
  # INDEX { cpqRackServerBladeRack, cpqRackServerBladeChassis, cpqRackServerBladeIndex }
  # dreckada dreck, dreckada
  foreach ($self->get_entries($oids, 'cpqRackServerBladeEntry')) {
    push(@{$self->{server_blades}},
        HP::BladeSystem::Component::ServerBladeSubsystem::ServerBlade->new(%{$_}));
  }
}

sub check {
  my $self = shift;
  foreach (@{$self->{server_blades}}) {
    $_->check() if $_->{cpqRackServerBladePresent} eq 'present' ||
        $self->{runtime}->{options}->{verbose} >= 3; # absent blades nur bei -vvv
  }
}

sub dump {
  my $self = shift;
  foreach (@{$self->{server_blades}}) {
    $_->dump() if $_->{cpqRackServerBladePresent} eq 'present' ||
        $self->{runtime}->{options}->{verbose} >= 3; # absent blades nur bei -vvv
  }
}


package HP::BladeSystem::Component::ServerBladeSubsystem::ServerBlade;
our @ISA = qw(HP::BladeSystem::Component::ServerBladeSubsystem);

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    rawdata => $params{rawdata},
    method => $params{method},
    blacklisted => 0,
    info => undef,
    extendedinfo => undef,
  };
  map { $self->{$_} = $params{$_} } grep /cpqRackServerBlade/, keys %params;
  $self->{cpqRackServerBladeDiagnosticString} ||= '';
  $self->{name} = $self->{cpqRackServerBladeRack}.
      ':'.$self->{cpqRackServerBladeChassis}.
      ':'.$self->{cpqRackServerBladeIndex};
  bless $self, $class;
  $self->init();
#printf "%s\n", Data::Dumper::Dumper(\%params);
  return $self;
}

sub check {
  my $self = shift;
  $self->blacklist('sb', $self->{name});
  my $info = sprintf 'server blade %s \'%s\' is %s, status is %s, powered is %s',
      $self->{name}, $self->{cpqRackServerBladeName}, $self->{cpqRackServerBladePresent},
      $self->{cpqRackServerBladeStatus}, $self->{cpqRackServerBladePowered};
  $self->add_info($info);
  if ($self->{cpqRackServerBladePowered} eq 'on') {
    if ($self->{cpqRackServerBladeStatus} eq 'degraded') {
      $self->add_message(WARNING, sprintf 'server blade %s diag is \'%s\', post status is %s',
          $self->{cpqRackServerBladeName}, $self->{cpqRackServerBladeDiagnosticString},
          $self->{cpqRackServerBladePOSTStatus});
    } elsif ($self->{cpqRackServerBladeStatus} eq 'failed') {
      $self->add_message(CRITICAL, sprintf 'server blade %s diag is \'%s\', post status is %s',
          $self->{cpqRackServerBladeName}, $self->{cpqRackServerBladeDiagnosticString},
          $self->{cpqRackServerBladePOSTStatus});
    } 
  }
} 
  
sub dump {
  my $self = shift;
    printf "[SERVER_BLADE_%s]\n", $self->{cpqRackServerBladeName};
  foreach (qw(cpqRackServerBladeRack cpqRackServerBladeChassis cpqRackServerBladeIndex cpqRackServerBladeName cpqRackServerBladeEnclosureName cpqRackServerBladePartNumber cpqRackServerBladeSparePartNumber cpqRackServerBladePosition cpqRackServerBladeHeight cpqRackServerBladeWidth cpqRackServerBladeDepth cpqRackServerBladePresent cpqRackServerBladeHasFuses cpqRackServerBladeEnclosureSerialNum cpqRackServerBladeSlotsUsed cpqRackServerBladeStatus cpqRackServerBladeDiagnosticString cpqRackServerBladePowered cpqRackServerBladePOSTStatus)) {
    printf "%s: %s\n", $_, $self->{$_};
  }
  printf "\n";
}



package HP::BladeSystem::Component;

use strict;

our @ISA = qw(HP::BladeSystem);


package HP::BladeSystem;

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };
use Data::Dumper;

our @ISA = qw(HP::Server HP::Proliant::Component::SNMP);

sub init {
  my $self = shift;
  $self->{components} = {
      common_enclosure_subsystem => undef,
      power_enclosure_subsystem => undef,
      power_supply_subsystem => undef,
      net_connector_subsystem => undef,
      server_blade_subsystem => undef,
  };
  $self->{serial} = 'unknown';
  $self->{product} = 'unknown';
  $self->{romversion} = 'unknown';
  $self->trace(3, 'BladeSystem identified');
  $self->collect();
  if (! $self->{runtime}->{plugin}->check_messages()) {
    $self->set_serial();
    $self->analyze_common_enclosures();
    $self->analyze_power_enclosures();
    $self->analyze_power_supplies();
    $self->analyze_net_connectors();
    $self->analyze_server_blades();
    $self->check_common_enclosures();
    $self->check_power_enclosures();
    $self->check_power_supplies();
    $self->check_net_connectors();
    $self->check_server_blades();
  }
}

sub identify {
  my $self = shift;
  return sprintf "System: '%s', S/N: '%s'",
      $self->{product}, $self->{serial};
}

sub dump {
  my $self = shift;
  printf STDERR "serial %s\n", $self->{serial};
  printf STDERR "product %s\n", $self->{product};
  printf STDERR "romversion %s\n", $self->{romversion};
  printf STDERR "%s\n", Data::Dumper::Dumper($self->{enclosures});
}

sub analyze_common_enclosures {
  my $self = shift;
  $self->{components}->{common_enclosure_subsystem} =
      HP::BladeSystem::Component::CommonEnclosureSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_power_enclosures {
  my $self = shift;
  $self->{components}->{power_enclosure_subsystem} =
      HP::BladeSystem::Component::PowerEnclosureSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_power_supplies {
  my $self = shift;
  $self->{components}->{power_supply_subsystem} =
      HP::BladeSystem::Component::PowerSupplySubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_net_connectors {
  my $self = shift;
  $self->{components}->{net_connector_subsystem} =
      HP::BladeSystem::Component::NetConnectorSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_server_blades {
  my $self = shift;
  $self->{components}->{server_blade_subsystem} =
      HP::BladeSystem::Component::ServerBladeSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub check_common_enclosures {
  my $self = shift;
  $self->{components}->{common_enclosure_subsystem}->check();
  $self->{components}->{common_enclosure_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_power_enclosures {
  my $self = shift;
  $self->{components}->{power_enclosure_subsystem}->check();
  $self->{components}->{power_enclosure_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_power_supplies {
  my $self = shift;
  $self->{components}->{power_supply_subsystem}->check();
  $self->{components}->{power_supply_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_net_connectors {
  my $self = shift;
  $self->{components}->{net_connector_subsystem}->check();
  $self->{components}->{net_connector_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_server_blades {
  my $self = shift;
  $self->{components}->{server_blade_subsystem}->check();
  $self->{components}->{server_blade_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub collect {
  my $self = shift;
  if ($self->{runtime}->{plugin}->opts->snmpwalk) {
    my $cpqRackMibCondition = '1.3.6.1.4.1.232.22.1.3.0';
    $self->trace(3, 'getting cpqRackMibCondition');
    if (! exists $self->{rawdata}->{$cpqRackMibCondition}) {
        $self->add_message(CRITICAL,
            'snmpwalk returns no health data (cpqrack-mib)');
    }
  } else {
    my $net_snmp_version = Net::SNMP->VERSION(); # 5.002000 or 6.000000
    #$params{'-translate'} = [
    #  -all => 0x0
    #];
    my ($session, $error) =
        Net::SNMP->session(%{$self->{runtime}->{snmpparams}});
    if (! defined $session) {
      $self->{plugin}->add_message(CRITICAL, 'cannot create session object');
      $self->trace(1, Data::Dumper::Dumper($self->{runtime}->{snmpparams}));
    } else {
      # revMajor is often used for discovery of hp devices
      my $cpqSeMibRev = '1.3.6.1.4.1.232.22.1';
      my $cpqSeMibRevMajor = '1.3.6.1.4.1.232.22.1.1.0';
      my $cpqRackMibCondition = '1.3.6.1.4.1.232.22.1.3.0';
      $self->trace(3, 'getting cpqRackMibCondition');
      my $result = $session->get_request(
          -varbindlist => [$cpqRackMibCondition]
      );
      if (!defined($result) ||
          $result->{$cpqRackMibCondition} eq 'noSuchInstance' ||
          $result->{$cpqRackMibCondition} eq 'noSuchObject' ||
          $result->{$cpqRackMibCondition} eq 'endOfMibView') {
        $self->add_message(CRITICAL,
            'snmpwalk returns no health data (cpqrack-mib)');
        $session->close;
      } else {
        $self->trace(3, 'getting cpqRackMibCondition done');
      }
    }
    if (! $self->{runtime}->{plugin}->check_messages()) {
      # snmp peer is alive
      $self->trace(2, sprintf "Protocol is %s",
          $self->{runtime}->{snmpparams}->{'-version'});
      my $oidtrees = [
          ["cpqSiComponent", "1.3.6.1.4.1.232.2.2"],
          ["cpqSiAsset", "1.3.6.1.4.1.232.2.2.2"],
          #["cpqRackInfo", "1.3.6.1.4.1.232.22"],
          ['cpqRackCommonEnclosureEntry', '1.3.6.1.4.1.232.22.2.3.1.1.1'],
          ['cpqRackCommonEnclosureTempEntry', '1.3.6.1.4.1.232.22.2.3.1.2.1'],
          ['cpqRackCommonEnclosureFanEntry', '1.3.6.1.4.1.232.22.2.3.1.3.1'],
          ['cpqRackCommonEnclosureFuseEntry', '1.3.6.1.4.1.232.22.2.3.1.4.1'],
          ['cpqRackCommonEnclosureManagerEntry', '1.3.6.1.4.1.232.22.2.3.1.6.1'],
          ['cpqRackPowerEnclosureEntry', '1.3.6.1.4.1.232.22.2.3.3.1.1'],
          ['cpqRackServerBladeEntry', '1.3.6.1.4.1.232.22.2.4.1.1.1'],
          ['cpqRackPowerSupplyEntry', '1.3.6.1.4.1.232.22.2.5.1.1.1'],
          ['cpqRackNetConnectorEntry', '1.3.6.1.4.1.232.22.2.6.1.1.1'],
          ['cpqRackMibCondition', '1.3.6.1.4.1.232.22.1.3.0'],
      ];
      my $cpqSiComponent = "1.3.6.1.4.1.232.2.2";
      my $cpqSiAsset = "1.3.6.1.4.1.232.2.2.2";
      my $cpqRackInfo = "1.3.6.1.4.1.232.22";
      $session->translate;
      my $response = {}; #break the walk up in smaller pieces
      foreach my $subtree (@{$oidtrees}) {
          my $tic = time; my $tac = $tic;
          my $response0 = $session->get_table(
              -baseoid => $subtree->[1]);
          if (scalar (keys %{$response0}) == 0) {
            $self->trace(2, sprintf "maxrepetitions failed. fallback");
            $response0 = $session->get_table(
                -maxrepetitions => 1,
                -baseoid => $subtree->[1]);
          }
          $tac = time;
          $self->trace(2, sprintf "%03d seconds for walk %s (%d oids)",
              $tac - $tic, $subtree->[0], scalar(keys %{$response0}));
          map { $response->{$_} = $response0->{$_} } keys %{$response0};
      }
      $session->close;
      map { $response->{$_} =~ s/^\s+//; $response->{$_} =~ s/\s+$//; }
          keys %$response;
      $self->{rawdata} = $response;
    }
  }
  return $self->{runtime}->{plugin}->check_messages();
}

sub set_serial {
  my $self = shift;

  my $cpqSiSysSerialNum = "1.3.6.1.4.1.232.2.2.2.1.0";
  my $cpqSiProductName = "1.3.6.1.4.1.232.2.2.4.2.0";

  $self->{serial} =
      SNMP::Utils::get_object($self->{rawdata}, $cpqSiSysSerialNum);
  $self->{product} =
      SNMP::Utils::get_object($self->{rawdata}, $cpqSiProductName);
  $self->{serial} = $self->{serial};
  $self->{product} = lc $self->{product};
  $self->{romversion} = 'unknown';
#####################################################################
$self->{runtime}->{product} = $self->{product};
}

package HP::Storage;

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };
use Data::Dumper;

our @ISA = qw(HP::Server);

sub init {
  my $self = shift;
  $self->{components} = {
      powersupply_subsystem => undef,
      fan_subsystem => undef,
      temperature_subsystem => undef,
      cpu_subsystem => undef,
      memory_subsystem => undef,
      disk_subsystem => undef,
      sensor_subsystem => undef,
  };
  $self->{serial} = 'unknown';
  $self->{product} = 'unknown';
  $self->{romversion} = 'unknown';
  $self->collect();
  if (! $self->{runtime}->{plugin}->check_messages()) {
    $self->set_serial();
#    $self->check_for_buggy_firmware();
#    $self->analyze_cpus();
#    $self->analyze_powersupplies();
#    $self->analyze_fan_subsystem();
#    $self->analyze_temperatures();
#    $self->analyze_memory_subsystem();
    $self->analyze_disk_subsystem();
##    $self->analyze_sensor_subsystem();
#    $self->check_cpus();
#    $self->check_powersupplies();
#    $self->check_fan_subsystem();
#    $self->check_temperatures();
#    $self->check_memory_subsystem();
    $self->check_disk_subsystem();
##    $self->check_sensor_subsystem();
  }
}

sub identify {
  my $self = shift;
  return sprintf "System: '%s', S/N: '%s', ROM: '%s'", 
      $self->{product}, $self->{serial}, $self->{romversion};
}

sub check_for_buggy_firmware {
  my $self = shift;
  my @buggyfirmwares = (
      "P24 12/11/2001",
      "P24 11/15/2002",
      "D13 06/03/2003",
      "D13 09/15/2004",
      "P20 12/17/2002"
  );
  $self->{runtime}->{options}->{buggy_firmware} =
      grep /^$self->{romversion}/, @buggyfirmwares;
}

sub dump {
  my $self = shift;
  printf STDERR "serial %s\n", $self->{serial};
  printf STDERR "product %s\n", $self->{product};
  printf STDERR "romversion %s\n", $self->{romversion};
  printf STDERR "%s\n", Data::Dumper::Dumper($self->{components});
}

sub analyze_powersupplies {
  my $self = shift;
  $self->{components}->{powersupply_subsystem} =
      HP::Storage::Component::PowersupplySubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_fan_subsystem {
  my $self = shift;
  $self->{components}->{fan_subsystem} = 
      HP::Storage::Component::FanSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_temperatures {
  my $self = shift;
  $self->{components}->{temperature_subsystem} = 
      HP::Storage::Component::TemperatureSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_cpus {
  my $self = shift;
  $self->{components}->{cpu_subsystem} =
      HP::Storage::Component::CpuSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_memory_subsystem {
  my $self = shift;
  $self->{components}->{memory_subsystem} = 
      HP::Storage::Component::MemorySubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_disk_subsystem {
  my $self = shift;
  $self->{components}->{disk_subsystem} =
      HP::Proliant::Component::DiskSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub analyze_sensor_subsystem {
  my $self = shift;
  $self->{components}->{sensor_subsystem} =
      HP::FCMGMT::Component::SensorSubsystem->new(
    rawdata => $self->{rawdata},
    method => $self->{method},
    runtime => $self->{runtime},
  );
}

sub check_cpus {
  my $self = shift;
  $self->{components}->{cpu_subsystem}->check();
  $self->{components}->{cpu_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_powersupplies {
  my $self = shift;
  $self->{components}->{powersupply_subsystem}->check();
  $self->{components}->{powersupply_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_fan_subsystem {
  my $self = shift;
  $self->{components}->{fan_subsystem}->check();
  $self->{components}->{fan_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_temperatures {
  my $self = shift;
  $self->{components}->{temperature_subsystem}->check();
  $self->{components}->{temperature_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_memory_subsystem {
  my $self = shift;
  $self->{components}->{memory_subsystem}->check();
  $self->{components}->{memory_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 2;
}

sub check_disk_subsystem {
  my $self = shift;
  $self->{components}->{disk_subsystem}->check();
  $self->{components}->{disk_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 1;
}

sub check_sensor_subsystem {
  my $self = shift;
  $self->{components}->{isensor_subsystem}->check();
  $self->{components}->{sensor_subsystem}->dump()
      if $self->{runtime}->{options}->{verbose} >= 1;
}


sub collect {
  my $self = shift;
  if ($self->{runtime}->{plugin}->opts->snmpwalk) {
    my $cpqSeMibCondition = '1.3.6.1.4.1.232.6.1.3.0';
    # rindsarsch!
    $self->{rawdata}->{$cpqSeMibCondition} = 0;
    if (! exists $self->{rawdata}->{$cpqSeMibCondition}) {
        $self->add_message(CRITICAL,
            'snmpwalk returns no health data (cpqhlth-mib)');
    }
  } else {
    my $net_snmp_version = Net::SNMP->VERSION(); # 5.002000 or 6.000000
    #$params{'-translate'} = [
    #  -all => 0x0
    #];
    my ($session, $error) = 
        Net::SNMP->session(%{$self->{runtime}->{snmpparams}});
    if (! defined $session) {
      $self->{plugin}->add_message(CRITICAL, 'cannot create session object');
      $self->trace(1, Data::Dumper::Dumper($self->{runtime}->{snmpparams}));
    } else {
      # revMajor is often used for discovery of hp devices
      my $cpqSeMibRev = '1.3.6.1.4.1.232.6.1';
      my $cpqSeMibRevMajor = '1.3.6.1.4.1.232.6.1.1.0';
      my $cpqSeMibCondition = '1.3.6.1.4.1.232.6.1.3.0';
      my $result = $session->get_request(
          -varbindlist => [$cpqSeMibCondition]
      );
      # rindsarsch!
      $result->{$cpqSeMibCondition} = 0;
      if (!defined($result) || 
          $result->{$cpqSeMibCondition} eq 'noSuchInstance' ||
          $result->{$cpqSeMibCondition} eq 'noSuchObject' ||
          $result->{$cpqSeMibCondition} eq 'endOfMibView') {
        $self->add_message(CRITICAL,
            'snmpwalk returns no health data (cpqhlth-mib)');
        $session->close;
      } else {
        # this is not reliable. many agents return 4=failed
        #if ($result->{$cpqSeMibCondition} != 2) {
        #  $obstacle = "cmapeerstart";
        #}
      }
    }
    if (! $self->{runtime}->{plugin}->check_messages()) {
      # snmp peer is alive
      $self->trace(2, sprintf "Protocol is %s", 
          $self->{runtime}->{snmpparams}->{'-version'});
      my $cpqSsSys =  "1.3.6.1.4.1.232.8";
      $session->translate;
      my $response = {}; #break the walk up in smaller pieces
      my $tic = time; my $tac = $tic;
      my $response1 = $session->get_table(
          -baseoid => $cpqSsSys);
      $tac = time;
      $self->trace(2, sprintf "%03d seconds for walk cpqSsSys (%d oids)",
          $tac - $tic, scalar(keys %{$response1}));
      $session->close;
      map { $response->{$_} = $response1->{$_} } keys %{$response1};
      map { $response->{$_} =~ s/^\s+//; $response->{$_} =~ s/\s+$//; }
          keys %$response;
      $self->{rawdata} = $response;
    }
  }
  return $self->{runtime}->{plugin}->check_messages();
}

sub set_serial {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};
  my @serials = ();
  my @models = ();
  my @fws = ();
  my $cpqSsBackplaneEntry = '1.3.6.1.4.1.232.8.2.2.6.1';
  my $cpqSsBackplaneFWRev = '1.3.6.1.4.1.232.8.2.2.6.1.3';
  my $cpqSsBackplaneModel = '1.3.6.1.4.1.232.8.2.2.6.1.9';
  my $cpqSsBackplaneSerialNumber = '1.3.6.1.4.1.232.8.2.2.6.1.13';
  # INDEX { cpqSsBackplaneChassisIndex, cpqSsBackplaneIndex }
  my @indexes = SNMP::Utils::get_indices($snmpwalk,
      $cpqSsBackplaneEntry);
  foreach (@indexes) {
    my($idx1, $idx2) = ($_->[0], $_->[1]);
    my $fw = SNMP::Utils::get_object($snmpwalk,
        $cpqSsBackplaneFWRev, $idx1, $idx2);
    my $model = SNMP::Utils::get_object($snmpwalk,
        $cpqSsBackplaneModel, $idx1, $idx2);
    my $serial = SNMP::Utils::get_object($snmpwalk,
        $cpqSsBackplaneSerialNumber, $idx1, $idx2);
    push(@serials, $serial);
    push(@models, $model);
    push(@fws, $fw);
  }
  
  $self->{serial} = join('/', @serials);
  $self->{product} = join('/', @models);
  $self->{romversion} = join('/', @fws);
  $self->{runtime}->{product} = $self->{product};
}




















package HP::StorageWorks;

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };
use Data::Dumper;

our @ISA = qw(HP::Server);

sub init {
  my $self = shift;
  $self->{serial} = 'unknown';
  $self->{product} = 'unknown';
  $self->{romversion} = 'unknown';
  $self->collect();
  if (! $self->{runtime}->{plugin}->check_messages()) {
    $self->set_serial();
    $self->overall_init();
    $self->overall_check();
  }
}

sub overall_init {
  my $self = shift;
  my %params = @_;
  my $snmpwalk = $self->{rawdata};
  my $cpqHoMibStatusArray = '1.3.6.1.4.1.232.11.2.10.1.0';
  $self->{cpqHoMibStatusArray} = SNMP::Utils::get_object(
      $snmpwalk, $cpqHoMibStatusArray);
  if ($self->{cpqHoMibStatusArray} =~ /^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/) {
    $self->{cpqHoMibStatusArray} = 1 * $2;
  } elsif ($self->{cpqHoMibStatusArray} =~ /^0x.*(\d\d)(\d\d)(\d\d)(\d\d)$/) {
    $self->{cpqHoMibStatusArray} = 1 * $2;
  }
}

sub overall_check {
  my $self = shift;
  if ($self->{cpqHoMibStatusArray} == 4) {
    $self->add_info('overall status is failed');
    $self->add_message(CRITICAL, 'overall status is failed');
  } elsif ($self->{cpqHoMibStatusArray} == 3) {
    $self->add_info('overall status is degraded');
    $self->add_message(WARNING, 'overall status is degraded');
  } elsif ($self->{cpqHoMibStatusArray} == 2) {
    $self->add_info('overall status is ok');
    $self->add_message(OK, 'overall status is ok');
  } elsif ($self->{cpqHoMibStatusArray} == 1) {
    $self->add_info('overall status is other');
    $self->add_message(UNKNOWN, 'overall status is other');
  }
}

sub identify {
  my $self = shift;
  return $self->{productname};
}

sub dump {
  my $self = shift;
  printf STDERR "serial %s\n", $self->{serial};
  printf STDERR "product %s\n", $self->{product};
  printf STDERR "romversion %s\n", $self->{romversion};
  printf STDERR "%s\n", Data::Dumper::Dumper($self->{components});
}

sub collect {
  my $self = shift;
  if ($self->{runtime}->{plugin}->opts->snmpwalk) {
    my $cpqHoMibStatusArray = '1.3.6.1.4.1.232.11.2.10.1.0';
    if (! exists $self->{rawdata}->{$cpqHoMibStatusArray}) {
        $self->add_message(CRITICAL,
            'snmpwalk returns no health data (cpqhost-mib)');
    }
  } else {
    my $net_snmp_version = Net::SNMP->VERSION(); # 5.002000 or 6.000000
    #$params{'-translate'} = [
    #  -all => 0x0
    #];
    my ($session, $error) = 
        Net::SNMP->session(%{$self->{runtime}->{snmpparams}});
    if (! defined $session) {
      $self->{plugin}->add_message(CRITICAL, 'cannot create session object');
      $self->trace(1, Data::Dumper::Dumper($self->{runtime}->{snmpparams}));
    }
    if (! $self->{runtime}->{plugin}->check_messages()) {
      # snmp peer is alive
      $self->trace(2, sprintf "Protocol is %s", 
          $self->{runtime}->{snmpparams}->{'-version'});
      my $cpqHoMibStatusArray = '1.3.6.1.4.1.232.11.2.10.1.0';
      $session->translate;
      my $tic = time;
      my $response = $session->get_request(
          -varbindlist => [$cpqHoMibStatusArray]
      );
      my $tac = time;
      $self->trace(2, sprintf "%03d seconds for walk cpqHoMibStatusArray (%d oids)",
          $tac - $tic, scalar(keys %{$response}));
      $session->close;
      map { $response->{$_} =~ s/^\s+//; $response->{$_} =~ s/\s+$//; }
          keys %$response;
      $self->{rawdata} = $response;
    }
  }
  return $self->{runtime}->{plugin}->check_messages();
}

sub set_serial {
  my $self = shift;
  my $snmpwalk = $self->{rawdata};
  my @serials = ();
  my @models = ();
  my @fws = ();
  my $cpqSsBackplaneEntry = '1.3.6.1.4.1.232.8.2.2.6.1';
  my $cpqSsBackplaneFWRev = '1.3.6.1.4.1.232.8.2.2.6.1.3';
  my $cpqSsBackplaneModel = '1.3.6.1.4.1.232.8.2.2.6.1.9';
  my $cpqSsBackplaneSerialNumber = '1.3.6.1.4.1.232.8.2.2.6.1.13';
  # INDEX { cpqSsBackplaneChassisIndex, cpqSsBackplaneIndex }
  my @indexes = SNMP::Utils::get_indices($snmpwalk,
      $cpqSsBackplaneEntry);
  foreach (@indexes) {
    my($idx1, $idx2) = ($_->[0], $_->[1]);
    my $fw = SNMP::Utils::get_object($snmpwalk,
        $cpqSsBackplaneFWRev, $idx1, $idx2);
    my $model = SNMP::Utils::get_object($snmpwalk,
        $cpqSsBackplaneModel, $idx1, $idx2);
    my $serial = SNMP::Utils::get_object($snmpwalk,
        $cpqSsBackplaneSerialNumber, $idx1, $idx2);
    push(@serials, $serial);
    push(@models, $model);
    push(@fws, $fw);
  }
  
  $self->{serial} = join('/', @serials);
  $self->{product} = join('/', @models);
  $self->{romversion} = join('/', @fws);
  $self->{runtime}->{product} = $self->{product};
}




















package HP::Server;

use strict;
use constant { OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 };

sub new {
  my $class = shift;
  my %params = @_;
  my $self = {
    runtime => $params{runtime},
    productname => 'unknown',
  };
  bless $self, $class;
  if (! ($self->{runtime}->{plugin}->opts->hostname ||
      $self->{runtime}->{plugin}->opts->snmpwalk)) {
    bless $self, 'HP::Proliant::CLI';
    $self->{method} = 'cli';
  } else {
    $self->check_snmp_and_model();
    if ($self->{runtime}->{options}->{servertype}) {
      $self->{productname} = 'ProLiant' if 
          $self->{runtime}->{options}->{servertype} eq 'proliant';
      $self->{productname} = 'BladeSystem' if 
          $self->{runtime}->{options}->{servertype} eq 'bladesystem';
      $self->{productname} = 'Storage' if 
          $self->{runtime}->{options}->{servertype} eq 'storage';
    }
    if (! $self->{runtime}->{plugin}->check_messages()) {
      if ($self->{productname} =~ /ProLiant/) {
        bless $self, 'HP::Proliant::SNMP';
        $self->trace(3, 'using HP::Proliant::SNMP');
      } elsif ($self->{productname} =~ /^DL\d+\s*G\d+/) {
        bless $self, 'HP::Proliant::SNMP';
        $self->trace(3, 'using HP::Proliant::SNMP');
      } elsif ($self->{productname} =~ /OpenView .* appliance/) {
        bless $self, 'HP::Proliant::SNMP';
        $self->trace(3, 'using HP::Proliant::SNMP');
      } elsif ($self->{productname} =~ /Synergy.*Gen/i) {
        bless $self, 'HP::Proliant::SNMP';
        $self->trace(3, 'using HP::Proliant::SNMP');
      } elsif ($self->{productname} =~ /Superdome.*Enclosure/i) {
        bless $self, 'HP::BladeSystem';
        $self->trace(3, 'using HP::BladeSystem');
      } elsif ($self->{productname} =~ /BladeSystem/) {
        bless $self, 'HP::BladeSystem';
        $self->trace(3, 'using HP::BladeSystem');
      } elsif ($self->{productname} =~ /PROLIANT 4LEE/) {
        bless $self, 'HP::Storage';
        $self->trace(3, 'using HP::Storage');
      } elsif ($self->{productname} =~ /X\d+[\s\w]* Network Storage/) {
        # HP X1600 Network Storage System
        # HP X1600 G2 Network Storage System
        bless $self, 'HP::Proliant::SNMP';
        $self->trace(3, 'using HP::Proliant::SNMP');
      } elsif ($self->{productname} =~ /StorageWorks/i) {
        bless $self, 'HP::StorageWorks';
        $self->trace(3, 'using HP::StorageWorks');
      } elsif ($self->{productname} =~ /MSA/i) {
        bless $self, 'HP::StorageWorks';
        $self->trace(3, 'using HP::StorageWorks');
      } elsif ($self->{productname} =~ /StoreEasy/i) {
        bless $self, 'HP::Proliant::SNMP';
        $self->trace(3, 'using HP::Proliant::SNMP');
      } elsif ($self->{productname} =~ /Storage/) { # fake
        bless $self, 'HP::Storage';
        $self->trace(3, 'using HP::Storage');
      } else {
        $self->add_message(CRITICAL,
            sprintf('unknown device%s', $self->{productname} eq 'unknown' ?
                '' : '('.$self->{productname}.')'));
      }
      $self->{method} = 'snmp';
    }
  }
  if ($self->{runtime}->{options}->{blacklist} &&
      -f $self->{runtime}->{options}->{blacklist}) {
    $self->{runtime}->{options}->{blacklist} = do {
        local (@ARGV, $/) = $self->{runtime}->{options}->{blacklist}; <> };
  }
  return $self;
}

sub check_snmp_and_model {
# uptime pruefen
# dann whoami
  my $self = shift;
  if ($self->{runtime}->{plugin}->opts->snmpwalk) {
    my $response = {};
    if (! -f $self->{runtime}->{plugin}->opts->snmpwalk) {
      $self->{runtime}->{plugin}->add_message(CRITICAL, 
          sprintf 'file %s not found',
          $self->{runtime}->{plugin}->opts->snmpwalk);
    } elsif (-x $self->{runtime}->{plugin}->opts->snmpwalk) {
      my $cmd = sprintf "%s -On -v%s -c%s %s:%s 1.3.6.1.4.1.232 2>&1",
          $self->{runtime}->{plugin}->opts->snmpwalk,
          $self->{runtime}->{plugin}->opts->protocol,
          $self->{runtime}->{plugin}->opts->community,
          $self->{runtime}->{plugin}->opts->domain,
          $self->{runtime}->{plugin}->opts->hostname;
      open(WALK, "$cmd |");
      while (<WALK>) {
        if (/^.*?\.(232\.[\d\.]+) = .*?: (\-*\d+)/) {
          $response->{'1.3.6.1.4.1.'.$1} = $2;
        } elsif (/^.*?\.(232\.[\d\.]+) = .*?: "(.*?)"/) {
          $response->{'1.3.6.1.4.1.'.$1} = $2;
          $response->{'1.3.6.1.4.1.'.$1} =~ s/\s+$//;
        }
      }
      close WALK;
    } else {
      open(MESS, $self->{runtime}->{plugin}->opts->snmpwalk);
      my $in_string = 0;
      my $in_hex_string = 0;
      my $hex_oid = 0;
      while(<MESS>) {
        chomp;
        if ($in_hex_string && /^(([0-9a-fA-F]{2})( [0-9a-fA-F]{2})*)\s*$/) {
          $response->{$hex_oid} .= " ".$1;
        } elsif ($in_string) {
          if (/(.*)"$/) {
            $response->{$hex_oid} .= $1;
            $in_string = 0;
          } else {
            $response->{$hex_oid} .= $_;
          }
        } elsif (/^.*?\.(232\.[\d\.]+) = .*?: (\-*\d+)\s*$/) {
          # SNMPv2-SMI::enterprises.232.6.2.6.7.1.3.1.4 = INTEGER: 6
          $response->{'1.3.6.1.4.1.'.$1} = $2;
          $in_hex_string = 0;
        } elsif (/^.*?\.(232\.[\d\.]+) = .*?: "(.*?)"/) {
          $response->{'1.3.6.1.4.1.'.$1} = $2;
          $response->{'1.3.6.1.4.1.'.$1} =~ s/\s+$//;
          $in_hex_string = 0;
        } elsif (/^.*?\.(232\.[\d\.]+) = (\-*\d+)/) {
          $response->{'1.3.6.1.4.1.'.$1} = $2;
          $in_hex_string = 0;
        } elsif (/^.*?\.(232\.[\d\.]+) = "(.*?)"/) {
          $response->{'1.3.6.1.4.1.'.$1} = $2;
          $response->{'1.3.6.1.4.1.'.$1} =~ s/\s+$//;
          $in_hex_string = 0;
        } elsif (/^.*?\.(232\.[\d\.]+) = STRING: "(.*?[^"])$/) {
          $response->{'1.3.6.1.4.1.'.$1} = $2;
          $response->{'1.3.6.1.4.1.'.$1} =~ s/\s+$//;
          $in_string = 0;
          $in_hex_string = 0;
        } elsif (/^.*?\.(232\.[\d\.]+) = Hex-STRING: (.*)/) {
          $response->{'1.3.6.1.4.1.'.$1} = $2;
          $in_hex_string = 1;
          $hex_oid = '1.3.6.1.4.1.'.$1;
        } elsif (/^.*?\.(232\.[\d\.]+) =[ ]{1,2}Hex: (.*)/) {
          $response->{'1.3.6.1.4.1.'.$1} = $2;
          $in_hex_string = 1;
          $hex_oid = '1.3.6.1.4.1.'.$1;
        }
      }
      close MESS;
    }
    map { $response->{$_} =~ s/^\s+//; $response->{$_} =~ s/\s+$//; }
        keys %$response;
    $self->{rawdata} = $response;
    $self->whoami();
  } else {
    if (eval "require Net::SNMP") {
      my %params = ();
      my $net_snmp_version = Net::SNMP->VERSION(); # 5.002000 or 6.000000
      $params{'-translate'} = [
        -timeticks => 0x0
      ];
      $params{'-hostname'} = $self->{runtime}->{plugin}->opts->hostname;
      $params{'-domain'} = $self->{runtime}->{plugin}->opts->domain;
      $params{'-version'} = $self->{runtime}->{plugin}->opts->protocol;
      if ($self->{runtime}->{plugin}->opts->port) {
        $params{'-port'} = $self->{runtime}->{plugin}->opts->port;
      }
      if ($self->{runtime}->{plugin}->opts->protocol eq '3') {
        $params{'-username'} = $self->{runtime}->{plugin}->opts->username;
        if ($self->{runtime}->{plugin}->opts->authpassword) {
          $params{'-authpassword'} = $self->decode_password($self->{runtime}->{plugin}->opts->authpassword);
        }
        if ($self->{runtime}->{plugin}->opts->authprotocol) {
          $params{'-authprotocol'} = $self->{runtime}->{plugin}->opts->authprotocol;
        }
        if ($self->{runtime}->{plugin}->opts->privpassword) {
          $params{'-privpassword'} = $self->decode_password($self->{runtime}->{plugin}->opts->privpassword);
        }
        if ($self->{runtime}->{plugin}->opts->privprotocol) {
          $params{'-privprotocol'} = $self->{runtime}->{plugin}->opts->privprotocol;
        }
      } elsif ($self->decode_password($self->{runtime}->{plugin}->opts->community) =~ /^snmpv3(.)(.+)/) {
        my $separator = $1;
        my ($authprotocol, $authpassword, $privprotocol, $privpassword,
          $username, $contextengineid, $contextname) = split(/$separator/, $2);
        $params{'-version'} = 3;
        $params{'-username'} = $self->decode_password($username) if $username;
        $params{'-authprotocol'} = $self->decode_password($authprotocol) if $authprotocol;
        $params{'-authpassword'} = $self->decode_password($authpassword) if $authpassword;
        $params{'-privprotocol'} = $self->decode_password($privprotocol) if $privprotocol;
        $params{'-privpassword'} = $self->decode_password($privpassword) if $privpassword;
      } else {
        $params{'-community'} = $self->decode_password($self->{runtime}->{plugin}->opts->community);
      }
      $self->{runtime}->{snmpparams} = \%params;
      my ($session, $error) = Net::SNMP->session(%params);
      $self->{session} = $session;
      if (! defined $session) {
        $self->add_message(CRITICAL, 'cannot create session object (maybe wrong hostname)');
        $self->trace(1, Data::Dumper::Dumper(\%params));
      } else {
        my $sysUpTime = '1.3.6.1.2.1.1.3.0';
        my $result = $session->get_request(
            -varbindlist => [$sysUpTime]
        );
        if (!defined($result)) {
          $self->add_message(CRITICAL,
              'could not contact snmp agent');
          $session->close;
        } else {
          $self->trace(3, 'snmp agent answered');
          $self->whoami();
        }
      }
    } else {
      $self->add_message(CRITICAL,
          'could not find Net::SNMP module');
    }
  }
}

sub whoami {
  my $self = shift;
  my $productname = undef;
  if ($self->{runtime}->{plugin}->opts->snmpwalk) {
    my $cpqSiProductName = '1.3.6.1.4.1.232.2.2.4.2.0';
    my $cpqSsMibRevMajor = '1.3.6.1.4.1.232.8.1.1.0';
    my $cpqSsBackplaneModel = '1.3.6.1.4.1.232.8.2.2.6.1.9'.'.1.1';
    my $cpqHoMibStatusArray = '1.3.6.1.4.1.232.11.2.10.1.0';
    if ($productname = $self->{rawdata}->{$cpqSiProductName}) {
      if (! $productname) {
        $self->{productname} = 'ProLiant';
      } else {
        $self->{productname} = $self->{rawdata}->{$cpqSiProductName};
      }
    } elsif (exists $self->{rawdata}->{$cpqSsBackplaneModel}) {
      $self->{productname} = $self->{rawdata}->{$cpqSsBackplaneModel};
    } elsif (exists $self->{rawdata}->{$cpqSsMibRevMajor}) {
      # at least there is a CPQSTSYS-MIB
      $self->{productname} = 'Storage'
    } else {
      $self->add_message(CRITICAL,
          'snmpwalk returns no product name (cpqsinfo-mib)');
    }
  } else {
    my $cpqSiProductName = '1.3.6.1.4.1.232.2.2.4.2.0';
    my $cpqSsMibRevMajor = '1.3.6.1.4.1.232.8.1.1.0';
    my $cpqSsBackplaneModel = '1.3.6.1.4.1.232.8.2.2.6.1.9'.'.1.1';
    my $cpqHoMibStatusArray = '1.3.6.1.4.1.232.11.2.10.1.0';
    my $dummy = '1.3.6.1.2.1.1.5.0';
    if ($productname = $self->valid_response($cpqSiProductName)) {
      if ($productname =~ /HP P\d+ .*SAS/ && $self->valid_response($cpqHoMibStatusArray)) {
        $self->{productname} = 'StorageWorks'
      } elsif ($productname eq '') {
        $self->{productname} = 'ProLiant';
      } else {
        $self->{productname} = $productname;
      }
    } elsif ($productname = $self->valid_response($cpqSsBackplaneModel)) {
      $self->{productname} = $productname;
    } elsif ($self->valid_response($cpqSsMibRevMajor)) {
      # at least there is a CPQSTSYS-MIB
      $self->{productname} = 'Storage'
    } elsif ($self->valid_response($cpqHoMibStatusArray)) {
      $self->{productname} = 'StorageWorks'
    } else {
      $self->add_message(CRITICAL,
          'snmpwalk returns no product name (cpqsinfo-mib)');
      $self->{session}->close;
    }
    $self->trace(3, 'whoami: '.$self->{productname});
  }
}

sub valid_response {
  my $self = shift;
  my $oid = shift;
  my $result = $self->{session}->get_request(
      -varbindlist => [$oid]
  );
  if (!defined($result) ||
      ! defined $result->{$oid} ||
      $result->{$oid} eq 'noSuchInstance' ||
      $result->{$oid} eq 'noSuchObject' ||
      $result->{$oid} eq 'endOfMibView') {
    return undef;
  } else {
    return $result->{$oid};
  }
}

sub trace {
  my $self = shift;
  my $level = shift;
  my $message = shift;
  if ($self->{runtime}->{options}->{verbose} >= $level) {
    printf "%s\n", $message;
  }
}

sub blacklist {
  my $self = shift;
  my $type = shift;
  my $name = shift;
  $self->{blacklisted} = $self->is_blacklisted($type, $name);
}

sub add_blacklist {
  my $self = shift;
  my $list = shift;
  $self->{runtime}->{options}->{blacklist} = join('/',
      (split('/', $self->{runtime}->{options}->{blacklist}), $list));
}

sub is_blacklisted {
  my $self = shift;
  my $type = shift;
  my $name = shift;
  my $blacklisted = 0;
#  $name =~ s/\:/-/g;
  foreach my $bl_items (split(/\//, $self->{runtime}->{options}->{blacklist})) {
    if ($bl_items =~ /^(\w+):([\:\w\-,]+)$/) {
      my $bl_type = $1;
      my $bl_names = $2;
      foreach my $bl_name (split(/,/, $bl_names)) {
        if ($bl_type eq $type && $bl_name eq $name) {
          $blacklisted = 1;
        }
      }
    } elsif ($bl_items =~ /^(\w+)$/) {
      my $bl_type = $1;
      if ($bl_type eq $type) {
        $blacklisted = 1;
      }
    }
  }
  return $blacklisted;
}

sub add_message {
  my $self = shift;
  my $level = shift;
  my $message = shift;
  $self->{runtime}->{plugin}->add_message($level, $message) 
      unless $self->{blacklisted};
  if (exists $self->{failed}) {
    if ($level == UNKNOWN && $self->{failed} == OK) {
      $self->{failed} = $level;
    } elsif ($level > $self->{failed}) {
      $self->{failed} = $level;
    }
  }
}

sub remove_message {
  my $self = shift;
  my $level = shift;
  my $message = shift;
  $self->{runtime}->{plugin}->remove_message($level) ;
}

sub has_failed {
  my $self = shift;
  return $self->{failed};
}

sub add_info {
  my $self = shift;
  my $info = shift;
  $info = $self->{blacklisted} ? $info.' (blacklisted)' : $info;
  $self->{info} = $info;
  if (! exists $self->{runtime}->{plugin}->{info}) {
    $self->{runtime}->{plugin}->{info} = [];
  }
  push(@{$self->{runtime}->{plugin}->{info}}, $info);
}

sub annotate_info {
  my $self = shift;
  my $annotation = shift;
  my $lastinfo = pop(@{$self->{runtime}->{plugin}->{info}});
  $lastinfo .= sprintf ' (%s)', $annotation;
  push(@{$self->{runtime}->{plugin}->{info}}, $lastinfo);
}

sub add_extendedinfo {
  my $self = shift;
  my $info = shift;
  $self->{extendedinfo} = $info;
  return if ! $self->{runtime}->{options}->{extendedinfo};
  if (! exists $self->{runtime}->{plugin}->{extendedinfo}) {
    $self->{runtime}->{plugin}->{extendedinfo} = [];
  }
  push(@{$self->{runtime}->{plugin}->{extendedinfo}}, $info);
}

sub get_extendedinfo {
  my $self = shift;
  if (! exists $self->{runtime}->{plugin}->{extendedinfo}) {
    $self->{runtime}->{plugin}->{extendedinfo} = [];
  }
  return join(' ', @{$self->{runtime}->{plugin}->{extendedinfo}});
}

sub add_summary {
  my $self = shift;
  my $summary = shift;
  if (! exists $self->{runtime}->{plugin}->{summary}) {
    $self->{runtime}->{plugin}->{summary} = [];
  }
  push(@{$self->{runtime}->{plugin}->{summary}}, $summary);
}

sub get_summary {
  my $self = shift;
  if (! exists $self->{runtime}->{plugin}->{summary}) {
    $self->{runtime}->{plugin}->{summary} = [];
  }
  return join(', ', @{$self->{runtime}->{plugin}->{summary}});
}

sub dumper {
  my $self = shift;
  my $object = shift;
  my $run = $object->{runtime};
  delete $object->{runtime};
  printf STDERR "%s\n", Data::Dumper::Dumper($object);
  $object->{runtime} = $run;
}

sub decode_password {
  my $self = shift;
  my $password = shift;
  if ($password && $password =~ /^rfc3986:\/\/(.*)/) {
    $password = $1;
    $password =~ s/\%([A-Fa-f0-9]{2})/pack('C', hex($1))/seg;
  }
  return $password;
}

package main;
#! /usr/local/bin/perl

use strict;

my $CELSIUS = 1;
my $PERFDATA = 1;
my $EXTENDEDINFO = 1;
my $HWINFO = 1;
my $HPACUCLI = 1;
my $NOINSTLEVEL = 'unknown';

use constant OK         => 0;
use constant WARNING    => 1;
use constant CRITICAL   => 2;
use constant UNKNOWN    => 3;
use constant DEPENDENT  => 4;

my $plugin = Nagios::MiniPlugin->new(
    shortname => '',
    usage => 'Usage: %s [ -v|--verbose ] [ -t <timeout> ] '.
        '--hostname <proliant> --community <snmp-community>'.
        '  ...]',
    version => '4.7.5.5',
    blurb => 'This plugin checks the hardware of hp/compaq proliant servers',
    url => 'http://labs.consol.de/nagios/check_hpasm',
    timeout => 60,
    shortname => '',
);
$plugin->add_arg(
    spec => 'blacklist|b=s',
    help => '--blacklist
   Blacklist some (missing/failed) components',
    required => 0,
    default => '',
);
$plugin->add_arg(
    spec => 'ignore-dimms|i',
    help => '--ignore-dimms
   Ignore "N/A"-DIMM status on misc. servers (e.g. older DL320)',
    required => 0,
);
$plugin->add_arg(
    spec => 'ignore-fan-redundancy',
    help => '--ignore-fan-redundancy
   Ignore missing redundancy partners',
    required => 0,
);
$plugin->add_arg(
    spec => 'customthresholds|c=s',
    help => '--customthresholds
   Use custom thresholds for certain temperatures',
    required => 0,
);
$plugin->add_arg(
    spec => 'eventrange=s',
    help => '--eventrange=<warningrange>/<criticalrange>
   Period of time before critical IML events respecively become warnings or vanish
   A range is descibed as a number and a unit (s, m, h, d), e.g. --eventrange 1h/20m',
    required => 0,
);
$plugin->add_arg(
    spec => 'perfdata=s',
    help => '--perfdata=[short]
   Output performance data. If your performance data string becomes
   too long and is truncated by Nagios, then you can use --perfdata=short
   instead. This will output temperature tags without location information',
    required => 0,
);
$plugin->add_arg(
    spec => 'hostname|H=s',
    help => '--hostname
   Hostname or IP-address of the server (SNMP mode only)',
    required => 0,
);
$plugin->add_arg(
    spec => 'port=i',
    help => '--port
   The SNMP port to use (default: 161)',
    required => 0,
    default => 161,
);
$plugin->add_arg(
    spec => 'protocol|P=s',
    help => '--protocol
   The SNMP protocol to use (default: 2c, other possibilities: 1,3)',
    required => 0,
    default => '2c',
);
$plugin->add_arg(
    spec => 'domain|d=s',
    help => '--domain
   The transport protocol to use (default: udp, other possibilities: tcp)',
    required => 0,
    default => 'udp',
);
$plugin->add_arg(
    spec => 'community|C=s',
    help => '--community
   SNMP community of the server (SNMP v1/2 only)',
    required => 0,
    default => 'public',
);
$plugin->add_arg(
    spec => 'username=s',
    help => '--username
   The securityName for the USM security model (SNMPv3 only)',
    required => 0,
);
$plugin->add_arg(
    spec => 'authpassword=s',
    help => '--authpassword
   The authentication password for SNMPv3',
    required => 0,
);
$plugin->add_arg(
    spec => 'authprotocol=s',
    help => '--authprotocol
   The authentication protocol for SNMPv3 (md5|sha)',
    required => 0,
);
$plugin->add_arg(
    spec => 'privpassword=s',
    help => '--privpassword
   The password for authPriv security level',
    required => 0,
);
$plugin->add_arg(
    spec => 'privprotocol=s',
    help => '--privprotocol
   The private protocol for SNMPv3 (des|aes|aes128|3des|3desde)',
    required => 0,
);
$plugin->add_arg(
    spec => 'snmpwalk=s',
    help => '--snmpwalk
   A file with the output of snmpwalk 1.3.6.1.4.1.232',
    required => 0,
);
$plugin->add_arg(
    spec => 'hpasmcli=s',
    help => '--hpasmcli
   A file with the output of hpasmcli',
    required => 0,
);
$plugin->add_arg(
    spec => 'servertype=s',
    help => '--servertype
   The type of the server: proliant (default) or bladesystem',
    required => 0,
);
$plugin->add_arg(
    spec => 'eval-nics',
    help => '--eval-nics
   Check network interfaces (and groups). Try it and report me whyt you think about it. I need to build up some know how on this subject. If get an error and you think, it is not justified for your configuration, please tell me about it. (alwasy send the output of "snmpwalk -On .... 1.3.6.1.4.1.232" and a description how you setup your nics and why it is correct opposed to the plugins error message',
    required => 0,
);

$plugin->getopts();
if (! $PERFDATA && $plugin->opts->get('perfdata')) {
  $PERFDATA = 1;
}
if ($PERFDATA && $plugin->opts->get('perfdata') &&
    ($plugin->opts->get('perfdata') eq 'short')) {
  $PERFDATA = 2;
}
$plugin->{messages}->{unknown} = []; # wg. add_message(UNKNOWN,...)

$plugin->{info} = []; # gefrickel

$SIG{'ALRM'} = sub {
  printf "UNKNOWN - check_hpasm timed out after %d seconds\n", 
      $plugin->opts->get('timeout');
  exit $ERRORS{UNKNOWN};
};
alarm($plugin->opts->get('timeout'));

my $server = HP::Server->new( runtime => {
    plugin => $plugin,
    options => {
        servertype => $plugin->opts->get('servertype'),
        verbose => $plugin->opts->get('verbose'),
        scrapiron => 0,
        ignore_fan_redundancy => $plugin->opts->get('ignore-fan-redundancy'),
        ignore_dimms => $plugin->opts->get('ignore-dimms'),
        customthresholds => $plugin->opts->get('customthresholds'),
        eventrange => $plugin->opts->get('eventrange'),
        blacklist => $plugin->opts->get('blacklist'),
        celsius => $CELSIUS,
        perfdata => $PERFDATA,
        extendedinfo => $EXTENDEDINFO,
        hwinfo => $HWINFO,
        hpacucli => $HPACUCLI,
        noinstlevel => $NOINSTLEVEL,
    },
},);
if (! $plugin->check_messages()) {
  $server->init();
  $plugin->add_message(OK, $server->identify()) if $HWINFO;
  if (! $plugin->check_messages()) {
    $plugin->add_message(OK, 'hardware working fine');
    $plugin->add_message(OK, $server->get_summary()) 
        if $server->get_summary();
    $plugin->add_message(OK, $server->get_extendedinfo()) 
        if $server->get_extendedinfo();
  } 
} else {
  $plugin->add_message(CRITICAL, 'wrong device');
}

my ($code, $message) = $plugin->check_messages(join => ', ', join_all => ', ');
$message .= sprintf "\n%s\n", join("\n", @{$plugin->{info}})
    if $plugin->opts->get('verbose') >= 1;
#printf "%s\n", Data::Dumper::Dumper($plugin->{info});
$plugin->nagios_exit($code, $message);

