????

Your IP : 216.73.216.174


Current Path : /usr/lib/raider/Raider/Info/
Upload File :
Current File : //usr/lib/raider/Raider/Info/StorCli.pm

use strict;
use warnings;

package Raider::Info::StorCli;
use base qw( Raider::Info );

use Raider::Jobs::StorCli;
use JSON::Tiny qw(decode_json encode_json);

=head1 NAME

Raider::Info::StorCli - StorCli specific instructions for get-info

=head1 DESCRIPTION

StorCli specific methods to gather information required for populating the .info file.

=head1 USAGE

use Raider::Info::StorCli;
my $info = Raider::Info::StorCli->new();

=head1 METHODS

=head2 get_info({ cntrl_cnt => int, disk_cnt => int })

Initiates all needed steps to generate a finished StorCli info file.

Return tuple:

HASH, HASH, int, int

=head2 get_disk_info(jobsObj, int, int, string)

Given a jobs object, controller, did, and block device, return the info of the disk.

Returns:

{
  dtype         => string,
  model         => string,
  port          => int,
  firmware      => string,
  serial        => string,
  state         => string,
  size_unparsed => string,
  size_mb       => int,
  smart_attribs => HASH,
}

=head2 extract_LD_info(jobsObj, int, int)

Given a jobs object, controller, and array, return info on the array (LD, logical disk).

Retruns:

{
  num_drives    => int,
  raid_level    => int,
  block_device  => string,
  raid_state    => string,
  stripe_size   => string,
  size_unparsed => string,
  size_mb       => int,
  pds           => [ "model::serial" ],
  unidentifiable_disks => int,
}

=head2 get_controller_attributes(jobsObject, int)

Given a jobsObj, and a controller, return attributes of the controller.

Returns:

{
  serial      => string,
  model       => string,
  firmware    => string,
  numdrives   => int,
  numarrays   => int,
  bbu_present => int,
  memory      => string,
}

=head2 get_cntrl_phys_disk_ids(jobsObj, int)

Given a Jobs object and a controller, return the DID's (disk identifiers) attached to the controller.

Returns:

[
  int,
]

=head2 get_phys_disk_serial_firmware(int, string)

Given a did and a block device, return disk firmware and serial

Returns:

{
  firmware => string,
  serial => string,
}

=head2 host_supports_info()

Given no args, return whether or not this physical host supports get-info collection.

Returns:

BOOL (1 or 0)

=cut

sub get_info {
  my $self = shift;
  my $opts = shift;
  my $jobs = Raider::Jobs::StorCli->new();

  my @controller_list = @{ $jobs->get_controller_list() };

  my $d_struct_disks = { }; # sonar consumer
  my $d_struct_controllers = { }; # sonar consumer
  my $storm_d_struct = { controllers => { }, }; # storm consumer

  unless ($self->host_supports_info()) {
    $self->logger({cat => 'w', msg => "this host doesnt support get-info collection for StorCli devices; disabling"});
    return ($d_struct_controllers,$d_struct_disks,$opts->{cntrl_cnt},$opts->{disk_cnt});
  }

  my $d_struct_map_file_new;

  for my $controller ( @controller_list ) {
    ###########################
    # Controller data
    ###########################

    my $cntrl_attribs = $self->get_controller_attributes($jobs, $controller);

    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'identifier'} = "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}";
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'make'} = 'LSI';
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'model'} = $cntrl_attribs->{model};
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'firmware'} = $cntrl_attribs->{firmware};
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'numdrives'} = $cntrl_attribs->{numdrives};
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'numlds'} = $cntrl_attribs->{numarrays};
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'serial'} = $cntrl_attribs->{serial};
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'bbu_present'} = $cntrl_attribs->{bbu_present};
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'memory'} = $cntrl_attribs->{memory};

    $storm_d_struct->{'controllers'}->{ $controller } = { disks => { } };
    $storm_d_struct->{'controllers'}->{ $controller }->{'make'} = 'LSI';
    $storm_d_struct->{'controllers'}->{ $controller }->{'model'} = $cntrl_attribs->{model};
    $storm_d_struct->{'controllers'}->{ $controller }->{'firmware'} = $cntrl_attribs->{firmware};
    $storm_d_struct->{'controllers'}->{ $controller }->{'numdrives'} = $cntrl_attribs->{numdrives};
    $storm_d_struct->{'controllers'}->{ $controller }->{'numarrays'} = $cntrl_attribs->{numarrays};

    if ( ! defined($d_struct_map_file_new->{ "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" }) ) {
      $d_struct_map_file_new = { "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" => {} };
    }

    ###########################
    # Logical disk data
    ###########################

    my $unidentifiable_disks_total_controller = 0;
    my $ld_info;
    for my $array ( @{ $jobs->get_array_list($controller) } ) {
      $ld_info = $self->extract_LD_info($jobs, $controller, $array);

      $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $array } = {
        identifier    => "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}\:::$array",
        numdrives     => $ld_info->{num_drives},
        ld_num        => $array,
        type          => 'RAID', # TODO: support cachecade etc?
        stripe_size   => $ld_info->{stripe_size},
        raidlevel     => $ld_info->{raid_level},
        state         => $ld_info->{raid_state},
        size_mb       => $ld_info->{size_mb},
        size_unparsed => $ld_info->{size_unparsed},
        block_device  => $ld_info->{block_device},
      };

      $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $array }->{'unidentifiable_disks'} = $ld_info->{unidentifiable_disks};
      $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $array }->{physical_disks} = $ld_info->{pds};

      $storm_d_struct->{'controllers'}->{ $controller }->{'arrays'}->{ $array } = {
        numdrives => $ld_info->{num_drives},
        raidlevel => $ld_info->{raid_level},
        state     => $ld_info->{raid_state} =~ /^Optl/i ? 1 : 0, # prov wants this "bool"
      };

      $unidentifiable_disks_total_controller = $unidentifiable_disks_total_controller + $ld_info->{unidentifiable_disks};
    }

    # Populate unidentifiable_disks.
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'unidentifiable_disks'} = $unidentifiable_disks_total_controller;

    ###########################
    # Physical disk data
    ###########################

    for my $did ( @{ $self->get_cntrl_phys_disk_ids($jobs, $controller) } ) {
      my $disk_info = $self->get_disk_info($jobs, $controller, $did, $ld_info->{block_device});

      # when a disk fails, it typically isn't visible to the system anymore. Which means of course, we can't do
      # things like fetch its serial from smartctl. Because we don't want failed disks to disappear yet from the
      # server until its actually removed, this map stuff is below. This stems from a really old desire from two
      # now ex employees.. as a part of the original raider+sonar integration (see: sonar.info). The idea at the
      # time, was that this information would be used for aiding tracking inventory. That didn't really ever
      # happen tbh.. but lets play along and not create a martyr at least for now.
      #    ssullivan - Oct 2019
      if ( $disk_info->{model} =~ /^\[No|unknown/i || $disk_info->{serial} =~ /^\[No|unknown/i ) {
        $self->logger({ cat => 'i', msg => "PD [$did] on StorCli controller [$controller] not responding to SMART inquiries." });

        if ( $self->map_file_exists({ device => 'StorCli_device_map' }) ) {
          $self->logger({ cat => 'i', msg => "RAIDER MAP entry file exists." });

          # Open file, read contents.
          my $device_map_file = "$Raider::Base::base_conf{'data_path'}/StorCli_device_map.info";
          open FILE, "<$device_map_file" or $self->logger({cat => 'c', msg => "failed opening [$Raider::Base::base_conf{'data_path'}/StorCli_device_map.info]: $!"});
          my $device_map_file_text = do { local $/; <FILE> };
          close(FILE) or $self->logger({cat => 'c', msg => "failed closing [$Raider::Base::base_conf{'data_path'}/StorCli_device_map.info]: $!"});

          # Decode.
          my $d_struct_map_file = eval { decode_json($device_map_file_text) };
          if ( my $e = $@ ) {
            $self->logger({ cat => 'w', msg => "failed json decoding [$device_map_file]: $e" });
          }
          else {
            # Check and see if this device is identified in our decoded MAP file.
            if ( defined($d_struct_map_file->{ "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" }->{$did}) ) {
              $disk_info->{model} = $d_struct_map_file->{ "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" }->{$did}->{model};
              $disk_info->{serial} = $d_struct_map_file->{ "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" }->{$did}->{serial};
              if ( defined($d_struct_map_file->{ "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" }->{$did}->{dtype}) ) {
                $disk_info->{dtype} = $d_struct_map_file->{ "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" }->{$did}->{dtype};
              }
              $self->logger({ cat => 'i', msg => "StorCli: Got model+serial from MAP file." });
            }
            else {
              $self->logger({ cat => 'w', msg => "No device entry found in MAP file for [$did] on StorCli controller [$controller]" });
            }
          }
        }
        else {
          $self->logger({ cat => 'w', msg => "PD [$did] on StorCli controller [$controller] not responding to SMART inquiries, and no RAIDER MAP entry. Disk will not identify properly!" });
        }
      }
      # Only add to MAP file and sonar disks key if model+serial is valid.
      else {
        $d_struct_map_file_new->{ "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" }{$did} = {
          model => $disk_info->{model},
          serial => $disk_info->{serial},
          dtype => $disk_info->{dtype},
        };

        $d_struct_disks->{ $opts->{disk_cnt} } = {
          identifier => "$disk_info->{model}\:::$disk_info->{serial}",
          type => $disk_info->{dtype},
          size_mb => $disk_info->{size_mb},
          size_unparsed => $disk_info->{size_unparsed},
          port => $did,
          model => $disk_info->{model},
          firmware => $disk_info->{firmware},
          serial => $disk_info->{serial},
          state => $disk_info->{state},
          block_device => $ld_info->{block_device},
          device_id => $did,
        };

        $d_struct_disks->{ $opts->{disk_cnt} }->{'smart_attributes'} = $disk_info->{smart_attribs};
      }

      $storm_d_struct->{'controllers'}->{ $controller }->{'disks'}->{ $did } = { type => $disk_info->{dtype} };

      # Increment our passed disk counter.
      $opts->{disk_cnt}++;
    };

    # Increment our passed controller counter.
    $opts->{cntrl_cnt}++;
  };

  $self->write_json({data => $storm_d_struct, device => 'StorCli'});

  if ( defined($d_struct_map_file_new) ) {
    $self->write_json({data => $d_struct_map_file_new, device => 'StorCli_device_map'});
  }

  return ($d_struct_controllers, $d_struct_disks, $opts->{cntrl_cnt}, $opts->{disk_cnt});
}

sub extract_LD_info {
  my ($self, $jobs, $controller, $array) = @_;

  my $data = $jobs->runStorCliCmdJsonSingleton("/c$controller/v$array show all")->{'Response Data'};
  my ($raid_level) = $data->{"/c$controller/v$array"}[0]->{TYPE} =~ /^RAID(\d+)/i;
  my $block_device = $data->{"VD$array Properties"}{'OS Drive Name'};

  my @phys_disks_in_ld;
  my $unidentifiable_disks = 0;
  if ($block_device) {
    my $disks = $jobs->runStorCliCmdJsonSingleton("/c$controller/v$array show all")->{'Response Data'}{"PDs for VD $array"};
    for my $disk (@{$disks}) {
      my $model = $disk->{Model};
      $model =~ tr/ //ds;
      my $did = $disk->{DID};
      unless ($did || $model) {
        $unidentifiable_disks++;
        next;
      }

      my $serial = $self->get_phys_disk_serial_firmware($did, $block_device)->{serial};
      unless ($serial) {
        $unidentifiable_disks++;
        next;
      }
      push(@phys_disks_in_ld, "$model\:::$serial");
    }
  }
  else {
    $self->logger({cat => 'w', msg => "failed discovering block device of array [$array] on StorCli controller [$controller]. Some disk information will not be available."});
  }

  my %info = (
    num_drives    => scalar @{$data->{"PDs for VD $array"}},
    raid_level    => $raid_level //= 'unknown',
    block_device  => $block_device //= 'unknown',
    raid_state    => $data->{"/c$controller/v$array"}[0]->{State} //= 'unknown',
    stripe_size   => $data->{"VD$array Properties"}{'Strip Size'} //= 'unknown',
    size_unparsed => $data->{"/c$controller/v$array"}[0]->{Size} //= 'unknown',
    size_mb       => 'unknown',
    pds           => \@phys_disks_in_ld,
    unidentifiable_disks => $unidentifiable_disks,
  );

  if ($data->{"VD$array Properties"}{'Number of Blocks'}) {
    $info{size_mb} = $self->convert_to_mb({ unit => 'block', value => $data->{"VD$array Properties"}{'Number of Blocks'} });
  }

  return \%info;
}

sub get_cntrl_phys_disk_ids {
  my ($self, $jobs, $controller) = @_;

  my @phys_disk_ids;

  my $topology = $jobs->runStorCliCmdJsonSingleton("/c$controller/dall show")->{'Response Data'}{'Response Data'}{TOPOLOGY};

  # "DID" is the disk identifier. We need this to fetch information from the physical disk.

  for my $ld ( @{ $topology } ) {
    next unless $ld->{Type} =~ /^DRIVE/i; # raid lds will show up here, go away
    next unless defined $ld->{DID}; # shouldnt happen..

    push @phys_disk_ids, $ld->{DID};
  }

  return \@phys_disk_ids;
}

sub get_disk_info {
  my ($self, $jobs, $controller, $did, $block_device) = @_;

  my %info = (
    dtype         => 'unknown',
    model         => 'unknown',
    port          => 'unknown',
    firmware      => 'unknown',
    serial        => 'unknown',
    state         => 'unknown',
    size_unparsed => 'unknown',
    size_mb       => 'unknown',
    smart_attribs => 'unsupported',
  );

  my $drives = $jobs->runStorCliCmdJsonSingleton("/c$controller/dall show all")->{'Response Data'}{'Response Data'}{'DG Drive LIST'};

  for my $drive (@{ $drives } ) {
    next unless defined $drive->{DID}; # 0 is valid

    next if $drive->{DID} != $did;

    $info{dtype} = $drive->{Intf};
    $info{model} = $drive->{Model};
    $info{model} =~ tr/ //ds;
    $info{port} = $drive->{DID};
    $info{size_unparsed} = $drive->{Size};
    $info{state} = $drive->{State};
    my ($size_value, $size_unit) = $drive->{Size} =~ /(\S+)\s+(\S+)/;
    if ($size_value && $size_unit) {
      $info{size_mb} = $self->convert_to_mb({ value => $size_value, unit => $size_unit });
    }
  }

  my $extra_disk_data = $self->get_phys_disk_serial_firmware($did, $block_device);
  $info{firmware} = $extra_disk_data->{firmware};
  $info{serial} = $extra_disk_data->{serial};

  my $smart_attribs = $self->get_smart_attribs({
    device => $block_device,
    d_flag => "megaraid,$did"
  });
  unless ($smart_attribs->{error}) {
    $info{smart_attribs} = $smart_attribs;
  }

  return \%info;
}

sub get_phys_disk_serial_firmware {
  my ($self, $did, $block_device) = @_;

  # smartctl provides integrated support for megaraid powered controllers. Access is obtained as such:
  #   smartctl -i -d megaraid,N $block_device
  # where <N> stands for the device id on the controller (DID in storcli).

  my %info;
  my $smartctl_info = `smartctl -i -d megaraid,$did $block_device -T permissive`;
  for my $line (split /^/, $smartctl_info) {
    if ( $line =~ /^Serial\s+number(\s+)?:(.+)/i ) {
      $info{serial} = $2;
      $info{serial} =~ tr/ //ds;
    }
    elsif ( $line =~ /^Firmware\s+Version(\s+)?:(.+)/i ) {
      $info{firmware} = $2;
      $info{firmware} =~ tr/ //ds;
    }
    elsif ( $line =~ /^Revision(\s+)?:(.+)/i ) {
      $info{firmware} = $2;
      $info{firmware} =~ tr/ //ds;
    }
  }

  return \%info;
}

sub get_controller_attributes {
  my ($self, $jobs, $controller) = @_;

  my $data = $jobs->runStorCliCmdJsonSingleton("/c$controller show all")->{'Response Data'};

  my %attribs = (
    serial      => $data->{'Basics'}{'Serial Number'} //= 'unknown',
    model       => $data->{'Basics'}{Model} //= 'unknown',
    firmware    => $data->{'Version'}{'Firmware Version'} //= 'unknown',
    numdrives   => $data->{'Physical Drives'} //= 'unknown',
    numarrays   => scalar @{ $jobs->get_array_list($controller) },
    bbu_present => (ref($jobs->getBbuStatus($controller)) eq 'HASH') ? 1 : 0,
    memory      => $data->{HwCfg}{'On Board Memory Size'} //= 'unknown',
  );

  return \%attribs;
}

sub host_supports_info {
  my ($self) = @_;

  # in testing, the 1000:0014 device (AVAGO MegaRAID SAS 9460-16i) wouldn't fetch full details on the controller
  # and backing disks (/opt/MegaRAID/storcli/storcli64 /c0 show) without hanging until I updated megaraid_sas
  # kernel module to version 07.711.04.00 (MR_LINUX_DRIVER_7.11-07.711.04.00-1.tgz). All things required for jobs
  # would work on the older module, but full controller details required for Info would only work with the newer
  # version.
  #   kmod-megaraid_sas-07.711.04.00_el7.6-1.x86_64
  my $min_ver_numeric = '077110400';

  if (`lsmod` !~ /megaraid_sas/) {
    $self->logger({cat => 'w', msg => "StorCli is registered, but megaraid_sas kernel module isn't loaded. Info collection will be disabled."});
    return 0;
  }

  my $numeric_ver_running;
  for my $line (split /^/, `modinfo megaraid_sas`) {
    if ($line =~ /^version:\s+(\S+)/i) {
      my $raw_version = $1;

      $numeric_ver_running = $raw_version;
      $numeric_ver_running =~ s/\D+//g;
    }
  }

  if ($numeric_ver_running) {
    if ($numeric_ver_running < $min_ver_numeric) {
      return 0;
    }
  }

  return 1;
}


1;