????

Your IP : 216.73.216.152


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

use strict;
use warnings;

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

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

=head1 NAME

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

=head1 DESCRIPTION

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

=head1 USAGE

use Raider::Info::FusionMPTSAS2;
my $infoFusionMPTSAS2 = Raider::Info::FusionMPTSAS2->new();

=head1 METHODS

=head2 get_info()

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

=head2 get_disk_block_device(\%)

Return given serials block device.

=head2 extract_LD_info(\%)

Return info on a LD.

=head2 get_base_cntrl_info(\%args)

Return base controller info.

=head2 host_supports_info()

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

Returns:

BOOL (1 or 0)

=cut


my $icmd = '/usr/bin/sas2ircu';

sub get_info {
  my $self = shift;
  my $opts = shift;
  my $jobsFusionMPTSAS2 = Raider::Jobs::FusionMPTSAS2->new();
  $jobsFusionMPTSAS2->icmd_in_path({ icmd => $icmd });
  my $controller_list_ref = $jobsFusionMPTSAS2->get_controller_list();
  my @controller_list = @$controller_list_ref;

  my $d_struct_disks = { };
  my $d_struct_controllers = { };

  unless ($self->host_supports_info()) {
    $self->logger({cat => 'w', msg => "this host doesnt support get-info collection for FusionMPTSAS2 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_info = `$icmd $controller display`;
    
    my $base_cntrl_info = $self->get_base_cntrl_info({ c => $controller, cntrl_info => $cntrl_info });
    my $cntrl_model = $base_cntrl_info->{'cntrl_model'};
    my $cntrl_serial = $base_cntrl_info->{'cntrl_serial'};

    $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} }->{'firmware'} = $base_cntrl_info->{'cntrl_firmware'};
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'numdrives'} = $base_cntrl_info->{'cntrl_numdrives'};
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'numlds'} = $base_cntrl_info->{'cntrl_numarrays'};
    $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'serial'} = $cntrl_serial;

    if ( ! defined($d_struct_map_file_new->{ "$cntrl_model\:::$cntrl_serial" }) ) {
      $d_struct_map_file_new = { "$cntrl_model\:::$cntrl_serial" => {} };
    }

    # Populate physical disks array.
    my $pd_list = $self->get_pds({ cntrl_info => $cntrl_info });

    # Populate logical disks array.
    my $unit_list = $self->get_lds({ cntrl_info => $cntrl_info });

    ##########################
    ##  Physical Disks      ##
    ##########################

    my $pd_info_store_ld = {};
    for my $each_pd ( @{ $pd_list } ) {

      # Get detailed info on pd
      my $pd_info = $self->get_pd_info({ 
          cntrl_info => $cntrl_info, 
          pd => $each_pd,
          cntrl_model => $cntrl_model,
          cntrl_serial => $cntrl_serial
        });

      # If we got back valid PD data, update our MAP file.
      if ( defined($pd_info->{'model'}) && defined($pd_info->{'serial'}) && defined($pd_info->{'type'}) ) {
        $d_struct_map_file_new->{ "$cntrl_model\:::$cntrl_serial" }{ "$pd_info->{'channel'}\:::$pd_info->{'port'}" } = {
          model => $pd_info->{'model'},
          serial => $pd_info->{'serial'},
          disk_type => $pd_info->{'type'}
        };
      }


      $d_struct_disks->{ $opts->{disk_cnt} } = {
        identifier => "$pd_info->{'model'}\:::$pd_info->{'serial'}",
        type => $pd_info->{'type'},
        size_mb => $pd_info->{'size_mb'},
        size_unparsed => $pd_info->{'size_unparsed'},
        port => $pd_info->{'port'},
        channel => $pd_info->{'channel'},
        model => $pd_info->{'model'},
        firmware => $pd_info->{'firmware'},
        serial => $pd_info->{'serial'},
        state => $pd_info->{'state'},
        block_device => $pd_info->{'block_device'}
      };

      # Attempt to get the PD's SMART attributes.
      my $smart_attribs = 'unsupported';
      $smart_attribs = $self->get_smart_attribs({
        device => $pd_info->{'block_device'},
      });
      $smart_attribs = 'unsupported' if ( defined($smart_attribs->{error}) );
      
      # Record PD's SMART attributes if supported.
      if ( $smart_attribs ne 'unsupported' ) {
        $d_struct_disks->{ $opts->{disk_cnt} }->{'smart_attributes'} = $smart_attribs;
      }

      $pd_info_store_ld->{ $each_pd } = $d_struct_disks->{ $opts->{disk_cnt} };

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


    ##########################
    ##  Logical Disks       ##
    ##########################

    # Make sure pd_list and unit_list are same size. If not, make unit_list
    # equal pd_list. This is a "temporaryish" thing, full explanation look at
    # comments in $self->get_lds().
    #     --ssullivan Oct 29th, 2013
    if ( scalar @{ $pd_list } != scalar @{ $unit_list } ) {
      my $pd_list_size = @{ $pd_list };
      $pd_list_size--; # start at 0
      $unit_list = [ 0..$pd_list_size ];
    }

    my $unit_pass = 0;
    for my $each_unit ( @{ $unit_list } ) {
      $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $unit_pass  }->{physical_disks} = [ "$pd_info_store_ld->{ @{ $pd_list }[$unit_pass] }->{model}\:::$pd_info_store_ld->{ @{ $pd_list}[$unit_pass] }->{serial}" ];
      $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $unit_pass  }->{size_mb} = $pd_info_store_ld->{ @{ $pd_list }[$unit_pass] }->{size_mb};
      $d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $unit_pass  }->{block_device} = $pd_info_store_ld->{ @{ $pd_list }[$unit_pass] }->{block_device};
      $unit_pass++;
    }



    # Increment our passed controller counter.
    $opts->{cntrl_cnt}++;
  }
  
  if ( defined($d_struct_map_file_new) ) {
    $self->write_json({data => $d_struct_map_file_new, device => 'FusionMPTSAS2_device_map'});
  }

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

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

  if ( ! defined($opts->{pd}) || ! defined($opts->{cntrl_info}) || ! defined($opts->{cntrl_model}) || ! defined($opts->{cntrl_serial}) ) {
    $self->logger({ cat => 'c', msg => "get_pd_info() missing required args!" });
  }

  my ($enc, $slot) = split(/:/,$opts->{pd});

  my $pd_store = {};
  my $pd_store_tmp = {};
  my $pd_section = 0;
  my $my_pd_section = 0;

  # Based on our given controller output, attempt to collect the dataz..
  for my $line ( split /^/, $opts->{cntrl_info} ) {
    
    # Each PD section starts with this
    if ( $line =~ /Device\s+is\s+a\s+Hard\s+disk/i ) {
      $pd_section = 1;
      # Clear out tmp data, this is a new PD.
      $pd_store_tmp = {};
    }  

    # Don't even bother checking $line unless we're in PD section of output.
    if ( $pd_section ) {

      if ( $line =~ /^\s+Enclosure\s+#(\s+)?:\s+(\d+)/i ) {
        $pd_store_tmp->{'enc'} = $2;
      }
      elsif ( $line =~ /^\s+Slot\s+#(\s+)?:\s+(\d+)/i ) {
        $pd_store_tmp->{'slot'} = $2;
      }

      # If we have both enc and slot for this PD...
      if ( defined($pd_store_tmp->{'enc'}) && defined($pd_store_tmp->{'slot'}) ) {
        # if enc and slot match our given query PD..
        if ( $pd_store_tmp->{'enc'} == $enc && $pd_store_tmp->{'slot'} == $slot ) {
          # this is our given PDs section
          $my_pd_section = 1;
          # Update our channel/port data.
          $pd_store->{'port'} = $pd_store_tmp->{'slot'};
          $pd_store->{'channel'} = $pd_store_tmp->{'enc'};
        }
        else {
          # this is not our given PDs section
          $my_pd_section = 0;
        }
      }

      # If this is our given PDs section..
      if ( $my_pd_section ) {

        if ( $line =~ /^\s+Drive\s+Type(\s+)?:\s+(\S+)/i ) {
          $pd_store->{'type'} = $2;
        }
        elsif ( $line =~ /^\s+Size\s+\S+\s+MB.+:\s(\d+)\/\d+/i ) {
          $pd_store->{'size_mb'} = $1;
        }
        elsif ( $line =~ /^\s+Size\s+.+:(.+)/i ) {
          $pd_store->{'size_unparsed'} = $1;
        }
        elsif ( $line =~ /^\s+Model\s+Number(\s+)?:\s+(.+)/i ) {
          my $model_stripped = $2;
          $model_stripped =~ tr/ //ds;
          $pd_store->{'model'} = $model_stripped;
        }
        elsif ( $line =~ /^\s+Firmware\s+Revision(\s+)?:\s+(\S+)/i ) {
          $pd_store->{'firmware'} = $2;
        }
        elsif ( $line =~ /^\s+Serial\s+No(\s+)?:\s+(\S+)/i ) {
          $pd_store->{'serial'} = $2;
        }
        elsif ( $line =~ /^\s+State(\s+)?:\s+(\S+)/i ) {
          $pd_store->{'state'} = $2;
        }

        # If we haven't yet gotten the block_device..
        if ( ! defined($pd_store->{'block_device'}) ) {
          # But we have our serial, retrieve our block_device
          if ( defined($pd_store->{'serial'}) ) {
            $pd_store->{'block_device'} = $self->get_disk_block_device({ serial => $pd_store->{'serial'} });
          }
        }

      }

    }
  }

  # If our given data wasn't nice, and we couldn't determine the disks model,
  # serial, or type, let's check our MAP file. Yes, sometimes this happens.
  # Usually you see this when the given PD is dead, the vendor tool will often
  # no longer display its model or serial, or type. We attempt to get this
  # data in this case from the MAP file, so the JSON data collected looks more
  # sane. This is especially true for Mr. RADAR..
  #   --ssullivan Oct 29th, 2013
  if ( ! defined($pd_store->{'model'}) || ! defined($pd_store->{'serial'}) || ! defined($pd_store->{'type'}) ) {
    $self->logger({ cat => 'i', msg => "Unable to extract model-serial-type from PD [$pd_store->{'channel'}:$pd_store->{'port'}]" });
  
    # MAP file fallback
    if ( $self->map_file_exists({ device => 'FusionMPTSAS2_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'}/FusionMPTSAS2_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->{ "$opts->{'cntrl_model'}\:::$opts->{'cntrl_serial'}" }->{ "$pd_store->{'channel'}\:::$pd_store->{'port'}" }) ) {
          $pd_store->{'model'} = $d_struct_map_file->{ "$opts->{'cntrl_model'}\:::$opts->{'cntrl_serial'}" }->{ "$pd_store->{'channel'}\:::$pd_store->{'port'}" }->{model};
          $pd_store->{'serial'} = $d_struct_map_file->{ "$opts->{'cntrl_model'}\:::$opts->{'cntrl_serial'}" }->{ "$pd_store->{'channel'}\:::$pd_store->{'port'}" }->{serial};
          $pd_store->{'type'} = $d_struct_map_file->{ "$opts->{'cntrl_model'}\:::$opts->{'cntrl_serial'}" }->{ "$pd_store->{'channel'}\:::$pd_store->{'port'}" }->{type};
          $self->logger({ cat => 'i', msg => "FusionMPTSAS2: Got model+serial from MAP file." });
        }
        else {
          $self->logger({ 
                          cat => 'w', 
                          msg => "No device entry found in MAP file for [$opts->{'cntrl_model'}\:::$opts->{'cntrl_serial'} -> $pd_store->{'channel'}\:::$pd_store->{'port'}].  Disk will not identify properly!" 
                        });
        }
      }
    }
  }

  return $pd_store;
}

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

  my @pd_list = ();
  my $pd_store = {};
  my $pd_section = 0;
  my $first_pass = 1;

  for my $line ( split /^/, $opts->{cntrl_info} ) {

    # Each PD section starts with this
    if ( $line =~ /Device\s+is\s+a\s+Hard\s+disk/i ) {
 
      # If this is not the first pass..
      if ( ! $first_pass ) {
        
        # consider this an error if we don't have enc and slot recorded.
        if ( ! defined($pd_store->{'enc'}) || ! defined($pd_store->{'slot'}) ) {
          $self->logger({ cat => 'c', msg => "Unable to find either enclosure [$pd_store->{'enc'}] or slot [$pd_store->{'slot'}]" });
        }
        else {
          # We have enc and slot. Add to pd_list.
          push(@pd_list, "$pd_store->{'enc'}:$pd_store->{'slot'}");
        }
      }
      else {
        $first_pass = 0;
      }
      # Since this is the start of a new PD section, clear out collected enc
      # and slot info.
      $pd_store = {};
      $pd_section = 1;
    }

    if ( $pd_section ) {
      if ( $line =~ /^\s+Enclosure\s+#(\s+)?:\s+(\d+)/i ) {
        $pd_store->{'enc'} = $2;
        $pd_store->{'last'}->{'enc'} = $2;
      }
      elsif ( $line =~ /^\s+Slot\s+#(\s+)?:\s+(\d+)/i ) {
        $pd_store->{'slot'} = $2;
        $pd_store->{'last'}->{'slot'} = $2;
      } 
    }
  }
  # Don't leave out the last discovered PD
  push(@pd_list, "$pd_store->{'last'}->{'enc'}:$pd_store->{'last'}->{'slot'}");

  return \@pd_list;
}

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

  # Right now, we just show every PD as a LD. This isn't really right, since
  # some FusionMPTSAS2 cards can support RAID (healthchecks are supported in
  # Jobs/FusionMPTSAS2). However, that setup we are not currently using anywhere 
  # within LiquidWeb, so I have no test hardware available. If we start
  # offering such setups, we will probably want to update this method to list
  # RAID LD's, and the disks they contain. However doing this with no test
  # hardware available is far to error prone, so for now every PD will show as
  # its own LD (like JBOD, which is typically what these HBA cards are used
  # for anyways).
  #     --ssullivan Oct 29th, 2013
  my @unit_list = ();
  my $ld_num = 0;
  for my $line ( split /^/, $opts->{'cntrl_info'} ) {
    if ( $line =~ /Device is a Hard disk/i ) {
      push(@unit_list, $ld_num);
      $ld_num++;
    }
  }

  return \@unit_list;
}

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

  my $block_devices = $self->get_block_devices();

  for my $b_device ( @{ $block_devices } ) {
    for my $line ( split /^/, `smartctl -i /dev/$b_device` ) {
      if ( $line =~ /^Serial\s+Number(\s+)?:\s+(\S+)/i ) {
        my $smartctl_serial = $2;
        # Turns out, sas2ircu removes the - character from a serial. This
        # causes problems when you are comparing serials as returned by
        # sas2ircu and smartctl. Remedy this situation by removing the -
        # character from the serial returned by smartctl. Clearly, one is left
        # wondering if other characters are filtered out by sas2ircu.. add
        # here if this turns out to be the case.
        #     --ssullivan Oct 29th, 2013
        $smartctl_serial =~ tr/-//ds;
        if ( $smartctl_serial eq $opts->{serial} ) {
          return "/dev/$b_device";
        }
      }
    }
  } 
  $self->logger({ cat => 'w', msg => "Failed to find corresponding block device for discovered disk serial [$opts->{serial}]" });
  
  return 'unknown';
}

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

  my $cntrl_info = {};

  my $pd_counter = 0;
  my $ld_counter = 0;
  for my $line ( split /^/, $opts->{cntrl_info} ) {
    # Controller model
    if ( $line =~ /^\s+Controller\s+type(\s+)?:\s+(.+)/i ) {
      $cntrl_info->{'cntrl_model'} = $2;
    }
    # Controller firmware version
    elsif ( $line =~ /^\s+Firmware\s+version(\s+)?:\s+(.+)/i ) {
      $cntrl_info->{'cntrl_firmware'} = $2;
    }
    # Controller numdrives
    elsif ( $line =~ /^\s+Slot\s+#(\s+)?:\s+\d+/i ) {
      $pd_counter++;
    }
    # Controller numlds
    elsif ( $line =~ /^Device\s+is\s+a\s+Hard\s+disk/i ) {
    #elsif ( $line =~ /^IR\s+volume\s+\d+/i ) {
      $ld_counter++;
    }
  }
  $cntrl_info->{'cntrl_numdrives'} = $pd_counter;
  $cntrl_info->{'cntrl_numarrays'} = $ld_counter;
  # sas2ircu doesn't provide a way to get the serial of the controller. So
  # just use PCI address for now.
  for my $line ( split /^/, `$icmd list` ) {
    if ( $line =~ /^\s+$opts->{c}\s+\S+\s+\S+\s+\S+\s+(\S+)/i ) {
      $cntrl_info->{'cntrl_serial'} = $1;
    }
  }


  return $cntrl_info;
}

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

  return 1;
}


1;