????

Your IP : 216.73.216.174


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

use strict;
use warnings;

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

use JSON::Tiny qw(decode_json encode_json);

=head1 NAME

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

=head1 DESCRIPTION

MegaraidSAS specific methods to gather information required for populating the .info 
files.

=head1 USAGE

use Raider::Info::MegaraidSAS;
my $megaraidsasInfo = Raider::Info::MegaraidSAS->new();

=head1 METHODS

=head2 get_info()

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

=head2 get_disk_info(\%args)

Return the disk info.

=head2 extract_LD_info(\%)

Return information on the given LD.

=head2 get_controller_info(\%)

Return information on the given controller.

=head2 host_supports_info()

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

Returns:

BOOL (1 or 0)

=cut

use Raider::Jobs::MegaraidSAS;

sub get_info {
  my $self = shift;
  my $opts = shift;

  my $jobsMegaraidSAS = Raider::Jobs::MegaraidSAS->new();
  my $icmd = $jobsMegaraidSAS->get_icmd();  
  $jobsMegaraidSAS->icmd_in_path({ icmd => "$icmd" });
  my $controller_list_ref = $jobsMegaraidSAS->get_controller_list({ icmd => "$icmd" });
  my @controller_list = @$controller_list_ref;

  my $d_struct_disks = { };
  my $d_struct_controllers = { };
  my $storm_d_struct = { controllers => { }, };

  unless ($self->host_supports_info()) {
    $self->logger({cat => 'w', msg => "this host doesnt support get-info collection for MegaraidSAS 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 ) {
    my ($cntrl_numarrays,$cntrl_numdrives,$cntrl_firmware,$cntrl_model,$cntrl_serial,$bbu_present,$bbu_state,$cntrl_memory,
      $cntrl_mem_correctable_errors,$cntrl_mem_uncorrectable_errors) = $self->get_controller_info({ 
                                                                                                    controller => $controller, 
                                                                                                    icmd => $icmd 
                                                                                                  });
    my $cntrl_numarrays_end = $cntrl_numarrays;

    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'identifier'} = "$cntrl_model\:::$cntrl_serial";
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'make'} = 'LSI';
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'model'} = $cntrl_model;
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'serial'} = $cntrl_serial;
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'firmware'} = $cntrl_firmware;
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'numdrives'} = $cntrl_numdrives;
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'numlds'} = $cntrl_numarrays;
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'bbu_present'} = $bbu_present;
    if ( $bbu_present ) {
      $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'bbu_state'} = $bbu_state;
    }
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'memory'} = $cntrl_memory;
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'memory_correctable_errors'} = $cntrl_mem_correctable_errors;
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'memory_uncorrectable_errors'} = $cntrl_mem_uncorrectable_errors;

    $storm_d_struct->{'controllers'}->{ $controller } = { disks => { } };
    $storm_d_struct->{'controllers'}->{ $controller }->{'make'} = 'LSI';
    $storm_d_struct->{'controllers'}->{ $controller }->{'model'} = $cntrl_model;
    $storm_d_struct->{'controllers'}->{ $controller }->{'firmware'} = $cntrl_firmware;
    $storm_d_struct->{'controllers'}->{ $controller }->{'numdrives'} = $cntrl_numdrives;
    $storm_d_struct->{'controllers'}->{ $controller }->{'numarrays'} = $cntrl_numarrays;
    
    $cntrl_numarrays--;

    ######################
    ##  PD Level        ##
    ######################

    my $dnum = 0;
    my $dcount = 0;
    my $d_total;
    for my $line ( split /^/, `$icmd -AdpAllInfo -a$controller` ) {
      if ( $line =~ /^\s+Disks(\s+)?:\s+(\d+)/i ) {
        $d_total = $2;
      }
    }
    my $device_id_to_model_serial_map = {};
    my $unidentifiable_disks_cntrl_total_counter = 0;
    while ( $dcount < $d_total ) {
      my ($disk_type,$size_mb,$port,$channel,$model,$firmware,$serial,$state,$smart_attribs,$block_device,$device_id,$size_unparsed,
        $vendor_tool_serial,$vendor_tool_model,$vendor_tool_firmware) = $self->get_disk_info({ 
                                                                                              controller => $controller, 
                                                                                              controllers => \@controller_list, 
                                                                                              phys_disk => $dnum, 
                                                                                              icmd => $icmd, 
                                                                                              cntrl_firmware => $cntrl_firmware
                                                                                            });
      if ( $disk_type ) {

        if ( $model =~ /^\[No|unknown/i || $serial =~ /^\[No|unknown/i ) {
          $self->logger({ cat => 'i', msg => "PD [$dnum] not responding to SMART inquiries." });

          my $map_file_success = 0;
          my $vendor_tool_success = 0;

          # MAP file fallback.
          if ( $self->map_file_exists({ device => 'megaraidsas_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'}/megaraidsas_device_map.info";
            open FILE, "<$device_map_file";
            my $device_map_file_text = do { local $/; <FILE> };
            close(FILE);

            # Decode.
            my $d_struct_map_file;
            eval {
              $d_struct_map_file = decode_json($device_map_file_text);
            };
            if ( $@ ) {
              $self->logger({ cat => 'w', msg => "Invalid JSON: [$device_map_file] Cannot decode MAP file!" });
            }
            else {
              # Check and see if this device is identified in our decoded MAP file.
              if ( defined($d_struct_map_file->{ "$cntrl_model\:::$cntrl_serial" }->{ "$channel\:::$port" }) ) {
                $model = $d_struct_map_file->{ "$cntrl_model\:::$cntrl_serial" }->{ "$channel\:::$port" }->{model};
                $serial = $d_struct_map_file->{ "$cntrl_model\:::$cntrl_serial" }->{ "$channel\:::$port" }->{serial};
                if ( defined($d_struct_map_file->{ "$cntrl_model\:::$cntrl_serial" }->{ "$channel\:::$port" }->{disk_type}) ) {
                  $disk_type = $d_struct_map_file->{ "$cntrl_model\:::$cntrl_serial" }->{ "$channel\:::$port" }->{disk_type};
                }
                $map_file_success = 1;
                $self->logger({ cat => 'i', msg => "MegaraidSAS: Got model+serial from MAP file." });
              }
              else {
                $self->logger({  cat => 'w', msg => "No device entry found in MAP file for [$cntrl_model\:::$cntrl_serial -> $channel\:::$port]." });
              } 
            }
          }

          # Vendor tool fallback.
          if ( ! $map_file_success && $vendor_tool_serial && $vendor_tool_model) {
            $model = $vendor_tool_model;
            $serial = $vendor_tool_serial;
            $vendor_tool_success = 1;
            $self->logger({ cat => 'i', msg => "Discovered model+serial from vendor tool." });
          }

          # No MAP file, and cannot get info from Vendor tool.
          if ( ! $map_file_success && ! $vendor_tool_success ) {
            $self->logger({ 
                            cat => 'w', 
                            msg => "PD [$dnum] not responding to SMART inquiries. No RAIDER MAP entry, and cannot get model+serial from vendor tool. Disk will not identify properly!" 
                          });
          }
        }

        if ( $firmware =~ /^\[No|unknown/i ) {
          $firmware = $vendor_tool_firmware;
        }

        # Only add to MAP file and disks key if model-serial is valid.
        if ( $model !~ /^\[No|unknown/i && $serial !~ /^\[No|unknown/i ) {
          $d_struct_map_file_new->{ "$cntrl_model\:::$cntrl_serial" }{ "0\:::$port" } = {
            model => "$model",
            serial => "$serial",
            disk_type => "$disk_type"
          };

          $d_struct_disks->{ $opts->{disk_cnt} } = {
            identifier => "$model\:::$serial",
            type => $disk_type,
            size_mb => $size_mb,
            size_unparsed => $size_unparsed,
            port => $port,
            channel => $channel,
            model => $model,
            firmware => $firmware,
            serial => $serial,
            state => $state,
            block_device => $block_device,
            device_id => $device_id
          };
          if ( $smart_attribs ne 'unsupported' ) {
            $d_struct_disks->{ $opts->{disk_cnt} }->{'smart_attributes'} = $smart_attribs;
          }
          # Increment our passed disks counter.
          $opts->{disk_cnt}++;

          $device_id_to_model_serial_map->{ $device_id } = "$model\:::$serial";
        }
        else {
          # Increment unidentifiable_disks counter.
          $unidentifiable_disks_cntrl_total_counter++;
        }

        $storm_d_struct->{'controllers'}->{ $controller }->{'disks'}->{ $dcount } = { type => $disk_type };
        $dcount++;
      }
      $dnum++;
    };

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

    ######################
    ## Unit/array level ##
    ######################

    my @unit_list = map { 0 + $_ } 0..$cntrl_numarrays;
    my $ldnum_serial_map = {};
    for my $each_unit ( @unit_list ) {
      my $unidentifiable_disks_unit = 0;
      # Parse -CfgDsply -a$controller and determine what disks belong to $each_unit
      my $in_group = 0;
      my @phys_disks_in_unit = ();
      my $model_serial_key;
      for my $line ( split /^/, `$icmd -CfgDsply -a$controller` ) {
      
        if ( $line =~ /\(Target\s+Id(\s+)?:\s+(\d+)\)/i ) {
        
          if ( $2 == $each_unit ) {
            # This is our disk group, enable.
            $in_group = 1;
            next;
          }
          else {
            # This is not our disk group, disable.
            $in_group = 0;
            next;
          }
        }
        
        # Disk is in our unit. Record this.
        if ( $in_group ) {
          if ( $line =~ /^Device\s+Id(\s+)?:\s+(\d+)/i ) {
            my $device_id = $2;
            if ( $device_id_to_model_serial_map->{ $device_id } ){
              # Any PD inside this LD will suffice for checking which block device this LD corresponds with.
              $model_serial_key = $device_id_to_model_serial_map->{ $device_id };
              push(@phys_disks_in_unit, $device_id_to_model_serial_map->{ $device_id });
            }
            else {
              $unidentifiable_disks_unit++;
              $self->logger({ cat => 'w', msg => "Failed to map device id [$device_id] to model-serial!" });
            }
          }
        }

      }

      my $health_check = `$icmd -LDInfo -L$each_unit -a$controller`;
      my $state = $jobsMegaraidSAS->array_is_ok({ health_check => $health_check });
      my ($raid_level,$numdrives,$size,$ld_serial,$raid_state,$ld_type,$ld_has_cachecade_enabled,$stripe_size,$size_raw,
        $ld_cachecade_backed_by_id,$ld_block_device,$disk_cache,$current_cache_policy,$default_cache_policy) = $self->extract_LD_info({
                                                                                icmd => $icmd, 
                                                                                controller => $controller,
                                                                                controllers => \@controller_list,
                                                                                unit =>  $each_unit,
                                                                                units => \@unit_list
                                                                              });

      $ldnum_serial_map->{ $each_unit } = {
        serial => $ld_serial,
        ld_type => $ld_type
      };

      # LSI controllers do not export a block device for CacheCade LD's. As a result, 
      # we must identify CacheCade LD's with a different formula.
      #
      #   --ssullivan May 08, 2013
      if ( $ld_type =~ /CacheCade/i ) {
        $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $each_unit } = {
          identifier => "CacheCade\:::$each_unit",
          ld_num => $each_unit,
          type => $ld_type,
          size_mb => $size,
          size_unparsed => $size_raw,
          state => $raid_state,
          raidlevel => $raid_level,
          unidentifiable_disks => $unidentifiable_disks_unit,
          physical_disks => \@phys_disks_in_unit,
          cachecade_backing_ldnum => $ld_cachecade_backed_by_id,
          cachecade_backing_ld =>  "$cntrl_model\:::$cntrl_serial\:::$ldnum_serial_map->{ $ld_cachecade_backed_by_id }->{serial}"
        };
      }
      else {
        if ( ! defined($d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $each_unit }) ) {
          $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $each_unit } = {
            identifier => "$cntrl_model\:::$cntrl_serial\:::$ld_serial",
            ld_num => $each_unit,
            numdrives => $numdrives,
            raidlevel => $raid_level,
            state => $raid_state,
            size_mb => $size,
            size_unparsed => $size_raw,
            block_device => $ld_block_device,
            serial => $ld_serial,
            type => $ld_type,
            stripe_size => $stripe_size,
            cachecade_backed => $ld_has_cachecade_enabled,
            unidentifiable_disks => $unidentifiable_disks_unit,
            physical_disks => \@phys_disks_in_unit,
            disk_cache => $disk_cache,
            current_cache_policy => $current_cache_policy,
            default_cache_policy => $default_cache_policy
          };
        }
        else {
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'identifier' } = "$cntrl_model\:::$cntrl_serial\:::$ld_serial";
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'ld_num' } = $each_unit;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'numdrives' } = $numdrives;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'raidlevel' } = $raid_level;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'state' } = $raid_state;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'size_mb' } = $size;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'size_unparsed' } = $size_raw;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'block_device' } = $ld_block_device;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'serial' } = $ld_serial;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'type' } = $ld_type;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'stripe_size' } = $stripe_size;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'cachecade_backed' } = $ld_has_cachecade_enabled;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'unidentifiable_disks' } = $unidentifiable_disks_unit;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'physical_disks' } = \@phys_disks_in_unit;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'disk_cache' } = $disk_cache;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'current_cache_policy' } = $current_cache_policy;
          $d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'default_cache_policy' } = $default_cache_policy;
        }
      }
      
      # Provisioning cannot currently handle CacheCade LD (or more than one LD
      # period it would appear). Solve this for now by fudging the data
      # structure it looks at to remove CacheCade LDs, and decrement the
      # numarrays counter, per CacheCade LD. 
      #
      # TODO: We probably want to fix this someday.
      #
      #   --ssullivan Jan, 22nd, 2014
      if ( $raid_level =~ /CacheCade/i  ) {
        $storm_d_struct->{'controllers'}->{ $controller }->{ 'numarrays' }--;
      }
      else {
        $storm_d_struct->{'controllers'}->{ $controller }->{'arrays'}->{ $each_unit } = { numdrives => $numdrives, raidlevel => $raid_level, state => $state };
      }
    };
    # Increment our passed controller count.
    $opts->{cntrl_cnt}++;
  };

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

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

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

sub extract_LD_info {
  my $self = shift;
  my $opts = shift;

  my ($d,$primary,$size_unit);
  my $ld_state = 'unknown';
  my $ld_type = 'unknown';
  my $stripe_size = 'unknown';
  my $ld_size_raw = 'unknown';
  my $ld_cachecade_backed_by_ld_num = 'unknown';
  my $ld_block_device = 'unknown';
  my $disk_cache = 'unknown';
  my $current_cache_policy = 'unknown';
  my $default_cache_policy = 'unknown';

  my $ld_has_cachecade_enabled = 0;
  my $size_mb = 0;
  my $size_value = 0;

  for my $line ( split /^/, `$opts->{icmd} -LDInfo -L$opts->{unit} -a$opts->{controller}` ) {
    if ( $line =~ /^Disk\s+Cache\s+Policy(\s+)?:(\s+)(.+)/i ) {
      $disk_cache = $3;
    }
    if ( $line =~ /^Current\s+Cache\s+Policy(\s+)?:(\s+)(.+)/i ) {
      $current_cache_policy = $3;
    }
    if ( $line =~ /^Default\s+Cache\s+Policy(\s+)?:(\s+)(.+)/i ) {
      $default_cache_policy = $3;
    }
    if ( $line =~ /^Number\s+Of\s+Drives(\s+)?:(\s+)?(\d+)/i || $line =~ /^Number\s+Of\s+Drives\s+per\s+span(\s+)?:(\s+)?(\d+)/i ) {
      $d = $3;
    }
    if ( $line =~ /^RAID\s+Level(\s+)?:\s+Primary-(\d+)/i ) {
      $primary = $2;
    }
    # value and unit have a space between them.
    if ( $line =~ /^Size(\s+)?:(\s+)?(\S+)\s+(\S+)/i ) {
      $size_value = $3;
      $size_unit = $4;
    }
    # value and unit have no space between them.
    elsif ( $line =~ /^Size(\s+)?:(\s+)?(\S+)/i ) {
      my $size_dump = $3;
      if ( $size_dump && $size_dump =~ /([[:alpha:]]+)/i ) {
        $size_unit = $1;
        $size_dump =~ s/$size_unit//;
        $size_value = $size_dump;
      }
    }

    if ( $line =~ /^Size(\s+)?:(\s+)?(.+)/i ) {
      $ld_size_raw = $3;
    }

    if ( $line =~ /^State(\s+)?:\s+(.+)/i ) {
      $ld_state = $2;
    }

    if ( $line =~ /^Virtual\s+Drive\s+Type(\s+)?:\s+(\S+)/i ) {
      $ld_type = $2;
    }

    if ( $line =~ /^Cache\s+Cade\s+Type(\s+)?:\s+Read/i ) {
      $ld_has_cachecade_enabled = 1;
    }

    if ( $line =~ /^Strip(\S)?\s+Size(\s+)?:\s+(.+)/i ) {
      $stripe_size = $3;
    }

    # Could be more than one associated LD.
    if ( $line =~ /^Target\s+Id\s+of\s+the\s+Associated\s+LDs(\s+)?:(\s+)?(.+)/i ) {
      $ld_cachecade_backed_by_ld_num = $3;
    }

  }
  
  if ( $ld_type !~ /CacheCade/i ) {
    $ld_type = 'RAID';
  }

  if ( $opts->{ld_type_only} ) {
    return $ld_type;  
  }

  my $num_spans_array;
  my $ld_found = 0;
  for my $line ( split /^/, `$opts->{icmd} -LdPdInfo -a$opts->{controller}` ) {
    if ( $line =~ /Virtual\s+Drive:\s+(\d+)/i || $line =~ /Virtual\s+Disk:\s+(\d+)/i ) {
      if ( $1 == $opts->{unit} ) {
        $ld_found = 1;
      }
      else {
        $ld_found = 0;
      }
    }

    if ( $ld_found ) {
      if ( $line =~ /^Number\s+of\s+Spans(\s+)?:\s+(\d+)/i ) {
        $num_spans_array = $2;
      }
    }
  }

  # Drives in array
  my $num_drives; 
  if ( $d ) {
    $num_drives = $d * $num_spans_array;
  }

  # Raid level
  if ( $num_spans_array > 1 ) {
    $primary .= 0;
  }

  $size_mb = $self->convert_to_mb({ unit => $size_unit, value => $size_value});

  # CacheCade LD's do not have block devices.
  my $udev_info;
  if ( $ld_type !~ /CacheCade/i ) {  
    $ld_block_device = $self->find_ld_block_device({ 
        ld_num => $opts->{unit}, 
        device => 'megaraidsas', 
        lds_array => $opts->{units}, 
        icmd => $opts->{icmd}, 
        controller => $opts->{controller},
        controllers => $opts->{controllers}
      });

    if ( $ld_block_device !~ /^\/dev\//i ) {
      $ld_block_device = "/dev/$ld_block_device";
    }
    my $block_device_name;
    if ( $ld_block_device =~ /^\/dev\/(\S+)/i ) {
      $block_device_name = $1;
    }

    # Get serial of passed LD block device.
    $udev_info = $self->get_udev_block_device_info({ block_device_name => $block_device_name });
  }

  # MegaCli-4 doesnt support -ShowSummary. But -ShowSummary on megacli-8 and newer always shows CacheCade raid 
  # level as 'CacheCade', so fudge it if not found (where megacli-4 is installed).

  my $our_ld = 0;
  for my $line ( split /^/, `$opts->{icmd} -ShowSummary -a$opts->{controller}` ) {

    if ( $line =~ /^\s+Virtual\s+drive(\s+)?:\s+Target\s+Id\s+(\d+)/i ) {
      if ( $2 == $opts->{unit} ) {
        # This is LD we should inspect
        $our_ld = 1;
        next;
      }
      else {
        $our_ld = 0;
      }
    }

    if ( $our_ld ) {
      if ( $line =~ /^\s+State(\s+)?:\s+(.+)/i ) {
        $ld_state = $2;
      }
      if ( $line =~ /^\s+RAID\s+Level(\s+)?:\s+(\S+)/i ) {
        $primary = $2;
      }
    }

  };
  if ( ref($udev_info) eq 'HASH' && $ld_type =~ /CacheCade/i ) {
    $primary = 'CacheCade';
    $udev_info->{id_serial} = 'cachecade_faked';
  }

  my $id_serial = 'unknown';
  $id_serial = $udev_info->{id_serial} if ( ref($udev_info) eq 'HASH' );
  return ($primary, $num_drives, $size_mb, $id_serial, $ld_state, $ld_type, $ld_has_cachecade_enabled, $stripe_size, $ld_size_raw, $ld_cachecade_backed_by_ld_num, $ld_block_device, $disk_cache, $current_cache_policy, $default_cache_policy);
}

sub get_disk_info {
  my $self = shift;
  my $opts = shift;

  my $enclosure_id;
  my $size_unparsed = 'unknown';

  if ( ! defined($opts->{cntrl_firmware}) ) {
    $self->logger({ 
        cat => 'c', 
        msg => "Info/MegaraidSAS: get_disk_info() called with no cntrl_firmware given!" 
      })
  }

  for my $line ( split /^/, `$opts->{icmd} -EncInfo -a$opts->{controller}` ) {
    if ( $line =~ /^\s+Device\s+ID(\s+)?:\s+(\d+)/i ) {
      $enclosure_id = $2;
    }
  }
 
  # If controllers firmware is 2.130.393-2551 or newer, it doesn't like a
  # space after PhysDrv. If a space is there, it will error. This causes a
  # infinite loop where we continously look for the phys_disk by incrementing
  # it over and over. That said, without the space is how its been since the
  # dawn of RAIDER, so I felt safer only taking the space out if its the
  # confirmed firmware version I know its broken on with the space or newer.
  # If its found that the space needs to go away with a firmware older than
  # the one specified in $base_firmware, modify that variable below to equal
  # the lowest known firmware version to require the space to not be there.
  #
  #   --ssullivan Feb 5th, 2014
  my $base_firmware = '2.130.393-2551';
  $base_firmware =~ s/\D//g;
  $opts->{cntrl_firmware} =~ s/\D//g; 
  
  my $pdInfoOut;
  if ( $enclosure_id ) {
    if ( $opts->{cntrl_firmware} >= $base_firmware ) {
      $pdInfoOut = `$opts->{icmd} -PDInfo -PhysDrv[$enclosure_id:$opts->{phys_disk}] -a$opts->{controller}`;
    }
    else {
      $pdInfoOut = `$opts->{icmd} -PDInfo -PhysDrv [$enclosure_id:$opts->{phys_disk}] -a$opts->{controller}`;
    }
  }
  else {
    if ( $opts->{cntrl_firmware} >= $base_firmware ) {
      $pdInfoOut = `$opts->{icmd} -PDInfo -PhysDrv[:$opts->{phys_disk}] -a$opts->{controller}`;
    }
    else {
      $pdInfoOut = `$opts->{icmd} -PDInfo -PhysDrv [:$opts->{phys_disk}] -a$opts->{controller}`;
    }
  }
 

  # We need to initialize dtype as null here. This way we don't increment dnum counter 
  # if there is no physical disk found.
  my $dtype;
  my $port = 'unknown';
  my $channel = 'unknown';
  # Serial, firmware, and model of a disk in a LD can only be found in
  # the inquiry data section from megacli (or of course smartctl).
  my $model = 'unknown';
  my $firmware = 'unknown';
  my $serial = 'unknown';
  my $state = 'unknown';
  my $ld_block_device = 'unknown';

  my $device_id;

  my $size_value = 0;
  my $size_unit;
  my $inq_data_line;
  for my $line ( split /^/, $pdInfoOut ) {
    if ( $line =~ /^Device\s+Id(\s+)?:\s+(\d+)/i ) {
      $device_id = $2;
    }
    if ( $line =~ /^PD\s+Type(\s+)?:\s+(\S+)/i ) {
      $dtype = $2;
    }
    if ( $line =~ /^Inquiry\s+Data(\s+)?:\s+(.+)/i ) {
      $inq_data_line = $line;
      if ( $line =~ /INTEL|KINGSTON|CRUCIAL/i ) {
        $dtype = 'SSD';
      }
    }
    if ( $line =~ /^Raw\s+Size(\s+)?:\s+(\S+)\s+(\S+)/i ) {
      $size_value = $2;
      $size_unit = $3;
    }
    # Older megacli (version 1.x at least) is hateful and does this:
    #  Raw Size: 572325MB [0x45dd2fb0 Sectors] 
    # So we get to try and catch this as well.
    if ( $line =~ /^Raw\s+Size(\s+)?:(\s+)?\s+(\S+)\s+\[/i ) {
      my $size_dump = $3;
      if ( $size_dump && $size_dump =~ /([[:alpha:]]+)/i ) {
        $size_unit = $1;
        $size_dump =~ s/$size_unit//;
        $size_value = $size_dump;
      }
      else {
        $self->logger({ cat => 'c', msg => "Failed to extract size_dump [$size_dump] from string [$line]" });
      }
    }
    if ( $line =~ /^Raw\s+Size(\s+)?:(\s+)?(.+)/i ) {
      $size_unparsed = $3;
    }
    if ( $line =~ /^Connected\s+Port\s+Number(\s+)?:\s+(\d+)\(path(\d+)\)/i ) {
      $port = $2;
      $channel = $3;
    }
    if ( $line =~ /^Firmware\s+state(\s+)?:\s+(.+)/i ) {
      $state = $2;
    }
  }
  
  # If device_id is undef, or not >= 0, not a valid device; return.
  return unless ( defined($device_id) && $device_id >= 0 );

  # MegaCli-1.01.39 at least with a MegaRAID SAS 8704ELP doesn't show the PD Type line we
  # are looking for above. The result is in such a setup we would loop till we met timeout
  # since $dtype wasn't getting populated. So if $dtype is presently empty, check if we are
  # on MegaCli less than version 3. We know version 4 shows the output we expect.
  unless ( $dtype ) {
    if ( $self->get_megacli_ver({icmd => $opts->{icmd}}) <= 3 ) {
      # FIXME: I dont see anywhere to get the actual physical disk type if PD Type isn't returned.
      # Realistically, this only comes up on old MegaCli versions so for now just assume SATA in
      # this specific case.
      $dtype = 'SATA';
    }
    else {
      $self->logger({ cat => 'c', msg => "Info/MegaraidSAS: Unable to detect dtype and MegaCli version is >= 4!" });
    }
  }

  # -ShowSummary isn't in MegaCli-4; we won't ever get vendor or product ID on MegaCli-4...
  my $vendor_tool_model;
  my $vendor_tool_vendor_id;

  my $is_our_search_disk = 0;
  for my $line ( split /^/, `$opts->{icmd} -ShowSummary -a$opts->{controller}` ) {
    
    if ( $line =~ /\s+Connector(\s+)?:\s+Port\s+$channel\s+-\s+\S+\s+Slot\s+$port/i ) {
      $is_our_search_disk = 1;
    }
    # If we come across this line, but $channel-$port do not match, this isn't our disk.
    elsif ( $line =~ /\s+Connector(\s+)?:\s+Port\s+\d+\s+-\s+\S+\s+Slot\s+\d+/i ) {
      $is_our_search_disk = 0;
    }
    
    if ( $is_our_search_disk ) {  
      if ( $line =~ /^\s+Product\s+Id(\s+)?:(.+)/i ) {
        $vendor_tool_model = $2;
      }

      if ( $line =~ /^\s+Vendor\s+Id(\s+)?:(.+)/i ) {
        $vendor_tool_vendor_id = $2;
      }
    }

  }

  # Filter out MegaCli-4...
  my ($vendor_tool_firmware,$vendor_tool_serial);
  if ( $vendor_tool_model && $vendor_tool_vendor_id ) {
    my $is_our_search_disk = 0;
    for my $line ( split /^/, `$opts->{icmd} -PDList -a$opts->{controller}` ) {

      if ( $line =~ /^Slot\s+Number(\s+)?:\s+$port/i ) {
        $is_our_search_disk = 1;
      }
      elsif ( $line =~ /^Slot\s+Number(\s+)?:\s+\d+/i ) {
        $is_our_search_disk = 0;
      }

      if ( $is_our_search_disk ) {
        if ( $line =~ /^Device\s+Firmware\s+Level(\s+)?:(.+)/i ) {
          $vendor_tool_firmware = $2;
        }
      }

    }

    # The inquiry data line is unreliable. It appears to just be a raw dump from the device, unparsed. 
    # This means field order can and does change from device to device, and some devices dont even have
    # fields that others do (such as firmware). This logic attempts to account for this.
    #
    # Assumption: 
    #   vendor_tool_serial = inq_data_line - vendor_tool_vendor_id - vendor_tool_model - vendor_tool_firmware

    $vendor_tool_serial = $inq_data_line;
    if ( $vendor_tool_serial =~ /Inquiry\s+Data(\s+)?:(.+)/i ) {
      $vendor_tool_serial = $2;
    }

    $vendor_tool_vendor_id =~ tr/ //ds;
    $vendor_tool_model =~ tr/ //ds;
    $vendor_tool_firmware =~ tr/ //ds;

    $vendor_tool_serial =~ s/$vendor_tool_vendor_id//;
    $vendor_tool_serial =~ s/$vendor_tool_model//;
    $vendor_tool_serial =~ s/$vendor_tool_firmware//;  
  
    $vendor_tool_serial =~ tr/ //ds;    
    $vendor_tool_model =~ tr/ //ds;
  }



  if ( $pdInfoOut =~ /Media\s+Type(\s+)?:\s+Solid\s+State\s+Device/i ) {
    $dtype = 'SSD';
  }

  my $size_mb = $self->convert_to_mb({ unit => $size_unit, value => $size_value });

  # Determine a compatible block device to query. 
  my $disc_block_devices_ref = $self->get_block_devices();
  my $smart_attribs = 'unsupported';
  my $failed_to_get_smart_info = 1;
  my @block_device_candidates = ();
  for my $block_device ( @{ $disc_block_devices_ref } ) {
    last unless ( $failed_to_get_smart_info );
    $failed_to_get_smart_info = 1;
    for my $line ( split /^/, `smartctl -i /dev/$block_device` ) {
      if ( $line =~ /Vendor(\s+)?:\s+LSI\s+|megaraid|AVAGO|Device(\s+)?:\s+LSI\s+/i ) {
        push(@block_device_candidates, "/dev/$block_device");
      }
    }
  }  

  my $candidates_num = scalar(@block_device_candidates);
  if ( $candidates_num == 1 ) {
    $ld_block_device = $block_device_candidates[0];
  }
  # More than one LD.  
  else {
    # Find a block device that belongs to our given controller.
    $ld_block_device = $self->find_suitable_block_device({ 
                                                          candidate_block_devs => \@block_device_candidates, 
                                                          controller =>  $opts->{controller}, 
                                                          controllers => $opts->{controllers}
                                                        });
  }  

  if ( $ld_block_device && $ld_block_device !~ /^\/dev\//i ) {
    $ld_block_device = "/dev/$ld_block_device";
  }

  # Get SMART data, now that we have the block device.
  $smart_attribs = $self->get_smart_attribs({
    device => $ld_block_device,
    d_flag => "sat+megaraid,$device_id",
  });
  $smart_attribs = 'unsupported' if ( defined($smart_attribs->{error}) );

  my $smart_info = $self->get_smart_info({
    device => $ld_block_device,
    d_flag => "sat+megaraid,$device_id",
  });
  
  for my $line ( split /^/, $smart_info ) {
    if ( $line =~ /Device\s+Model(\s+)?:(.+)/i ) {
      $model = $2;
      $model =~ tr/ //ds;
      $failed_to_get_smart_info = 0;
    }
    if ( $line =~ /Serial\s+Number(\s+)?:(.+)/i ) {
      $serial = $2;
      $serial =~ tr/ //ds;
      $failed_to_get_smart_info = 0;
    }
    if ( $line =~ /Firmware\s+Version(\s+)?:(.+)/i ) {
      $firmware = $2;
      $firmware =~ tr/ //ds;
      $failed_to_get_smart_info = 0;
    }
  }

  # If we dont have the smart info we are looking for still, attempt without
  # SATA specific args.
  if ( $failed_to_get_smart_info || $model =~ /NoInformationFound/i || $serial =~ /NoInformationFound/i ) {
    my $smart_info_sas = $self->get_smart_info({
      device => $ld_block_device,
      d_flag => "megaraid,$device_id",
    });

    # $smart_info_sas is going to look like this:
    #
    # Vendor:               FUJITSU 
    # Product:              MBA3147RC       
    # Revision:             0103
    # User Capacity:        147,086,327,808 bytes [147 GB]
    # Logical block size:   512 bytes
    # Logical Unit id:      0x500000e1171561f0
    # Serial number:        BJA0PB10GT6W
    # Device type:          disk
    # Transport protocol:   SAS
    # Local Time is:        Wed Apr  1 16:18:07 2015 CEST
    # Device supports SMART and is Enabled
    # Temperature Warning Enabled
    # Informational Exceptions (SMART) enabled
    # Temperature warning enabled
    for my $line ( split /^/, $smart_info_sas ) {
      if ( $line =~ /^Product(\s+)?:(.+)/i ) {
        $model = $2;
        $model =~ tr/ //ds;
        $failed_to_get_smart_info = 1;
      }
      if ( $line =~ /^Serial\s+number(\s+)?:(.+)/i ) {
        $serial = $2;
        $serial =~ tr/ //ds;
        $failed_to_get_smart_info = 1;
      }
      # Firmware version is not shown in this output..
      $firmware = 'unknown';
    }
  }

  if ( $failed_to_get_smart_info ) {
    $self->logger({ cat => 'w', msg => "failed to get SMART info for device id [$device_id] block device [$ld_block_device]." });
    $failed_to_get_smart_info = 1;
  }

  return ($dtype,$size_mb,$port,$channel,$model,$firmware,$serial,$state,$smart_attribs,$ld_block_device,$device_id,$size_unparsed,$vendor_tool_serial,$vendor_tool_model,$vendor_tool_firmware);
}

sub get_megacli_ver {
  my $self = shift;
  my $opts = shift;
  for my $line ( split/^/, `$opts->{icmd} -v` ) {
    if ( $line =~ /\s+Ver\s+(\d+)\.\d+/ ) {
      return $1;
    }
  }
  $self->logger({ cat => 'c', msg => 'get_megacli_ver(); failed to detect MegaCli version' });
}

sub get_controller_info {
  my $self = shift;
  my $opts = shift;

  my $array_count;
  my $disk_count;
  my $cntrl_firmware = 'unknown';
  my $cntrl_model = 'unknown';
  my $cntrl_serial = 'unknown';
  my $bbu_present = 0;
  my $bbu_state = 'unknown';
  my $cntrl_memory = 'unknown';
  my $cntrl_memory_correctable_errors = 0;
  my $cntrl_memory_uncorrectable_errors = 0;

  for my $line ( split /^/, `$opts->{icmd} -AdpAllInfo -a$opts->{controller}` ) {
    if ( $line =~ /^Virtual\s+Drives(\s+)?:\s+(\d+)/i ) {
      $array_count = $2;
    }
    elsif ( $line =~ /^\s+Disks(\s+)?:\s+(\d+)/i ) {
      $disk_count = $2;
    }
    elsif ( $line =~ /^FW\s+Version(\s+)?:\s+(\S+)/i ) {
      $cntrl_firmware = $2;
    }
    elsif ( $line =~ /^Product\s+Name(\s+)?:\s+(.+)/i ) {
      $cntrl_model = $2;
      $cntrl_model =~ tr/ //ds;
    }
    elsif ( $line =~ /^Serial\s+No(\s+)?:\s+(\S+)/i ) {
      $cntrl_serial = $2;
    }
    elsif ( $line =~ /^BBU(\s+)?:\s+Present/i ) {
      $bbu_present = 1;
      my $bbu_check_cmd = `$opts->{icmd} -AdpBbuCmd -GetBbuStatus -a$opts->{controller}`;
      if ( $bbu_check_cmd =~ /Battery\s+State(\s+)?:\s+(.+)/i ) {
        $bbu_state = $2;      
      }
    }
    elsif ( $line =~ /^Memory\s+Size(\s+)?:\s+(\S+)/i ) {
      $cntrl_memory = $2;
    }
    elsif ( $line =~ /^Memory\s+Correctable\s+Errors(\s+)?:\s+(.+)/i ) {
      $cntrl_memory_correctable_errors = $2;
    }
    elsif ( $line =~ /^Memory\s+Uncorrectable\s+Errors(\s+)?:\s+(.+)/i ) {
      $cntrl_memory_uncorrectable_errors = $2;
    }
  }
  $cntrl_memory_correctable_errors = $self->strip_whitespace({ string => $cntrl_memory_correctable_errors });
  $cntrl_memory_uncorrectable_errors = $self->strip_whitespace({ string => $cntrl_memory_uncorrectable_errors });

  # If empty, set to error.
  $cntrl_model = 'error' if ( $cntrl_model !~ /\S/ );
  $cntrl_serial = 'error' if ( $cntrl_serial !~ /\S/ );

  return ($array_count,$disk_count,$cntrl_firmware,$cntrl_model,$cntrl_serial,$bbu_present,$bbu_state,$cntrl_memory,$cntrl_memory_correctable_errors,$cntrl_memory_uncorrectable_errors);
}

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

  return 1;
}


1;