From 86956ac4e1ad7596efaa1608bfc5f5781996cf16 Mon Sep 17 00:00:00 2001 From: Armin Kunaschik Date: Sat, 7 Nov 2020 03:02:49 +0100 Subject: [PATCH] initial commit --- luninfo | 1475 ++++++++++++++++++++++++++++++++++++++++ luninfo.cfg | 20 + show_vio_disk_mappings | 365 ++++++++++ show_vio_host_mappings | 455 +++++++++++++ 4 files changed, 2315 insertions(+) create mode 100755 luninfo create mode 100644 luninfo.cfg create mode 100755 show_vio_disk_mappings create mode 100755 show_vio_host_mappings diff --git a/luninfo b/luninfo new file mode 100755 index 0000000..6772da6 --- /dev/null +++ b/luninfo @@ -0,0 +1,1475 @@ +#!/usr/bin/perl +# +# Show lun information and HBA related info in AIX +# +# +# Copyright Armin Kunaschik +# Version 1.0 complete rewrite +# Version 1.01 added FC connection and speed to -f +# Version 1.02 added effective queue depth from kernel to -d -l +# Version 1.03 10TB disk size "support" :-) +# Version 1.04 display permanent and temporary system firmware +# Version 1.05 moved new firmware into long output +# added GiB display with -g in info view +# added virtual fibre channel support for -f, -v +# changed serials hash to hold separate location info for systems +# dynamic column width for some columns in -i +# Version 1.06 get virtual WWPNs from fcstat instead from lscfg +# Version 1.07 pcmpath adapter states added, more dynamic columns and cosmetics +# basic generic AIX mpio disk (mpioosdisk) detection +# Version 1.08 more sanity checking for fcs adapters in edge cases +# Version 1.09 added 2 new p770 +# Version 1.10 moved storage and ms serials out into XML cfg file +# Version 1.11 ignore sasarray disks, support maybe added later +# Version 1.12 added -L option to dump cfg information +# added support for Hitachi VSP devices +# Version 1.13 worked around a fcstat hang when fc link is down +# Version 1.14 bug IV79359 -> vfc host detection in -v needs a workaround +# Version 1.15 emfscsi detection added. Newer servers come with this new type +# of FC adapters +# added PowerHA 7 cluster repository disk detection (backup disks) +# Version 1.16 IBM moved pcmpath to /usr/sbin, added detection to find both +# Version 1.17 Fixed scsi id detection for mpioosdisks on 2145 + +use strict; +use IPC::Open2; +use Getopt::Long; +use List::Util qw( max ); +use XML::Simple; # for config file +#use Data::Dumper; + +my $luninfo_version="1.17"; + +my $cfgfile="/etc/luninfo.cfg"; + + +my %disks; +my %paths; +my %path_defaults; + +sub usage { + print STDERR "Usage: $0 [-h] [-V] [-L] [-H | [ -d [ -l ] | -f [ -l ] | -v | -p | -m | -i [ -l ] [ -g ] ] ]\n"; + print STDERR " -h display this help\n"; + print STDERR " -V display version\n"; + print STDERR " -d list disk detail\n"; + print STDERR " -f list fibre channel hba detail\n"; + print STDERR " -g display disk size in GiB\n"; + print STDERR " -H don't display header line, only useful in combination with other options\n"; + print STDERR " -i display disk info summary (default when there is no -i, -d, -f, -m, -p or -v)\n"; + print STDERR " -l long list; display more information like uuid, mirror pool detail, locations, firmware versions\n"; + print STDERR " -m show managed system info\n";; + print STDERR " -p list disk paths and priority\n"; + print STDERR " -v list virtual scsi and fibre channel hba detail\n"; + print STDERR " -L list all known dc locations and serial numbers\n"; + exit 1; + +} + +my %options = ( 'ms' => 0, + 'disks' => 0, + 'fscsi' => 0, + 'gigabyte' => 0, + 'info' => 0, # default option used when no other option is present + 'virtual' => 0, + 'paths' => 0, + 'long' => 0, + 'noheader' => 0, + 'version' => 0, + 'list' => 0, + 'help' => 0 +); + +my %option_constraints = ( + 'mutual_exclusive' => [ 'info', 'disks','fscsi','virtual','paths','ms','version','list'] , + # put here only valid combinations of the mutual exclusive options... at least one!!! + 'valid_combinations' => { 'info' => [ 'long', 'noheader', 'gigabyte'], + 'disks' => [ 'long','noheader' ], + 'fscsi' => [ 'long', 'noheader' ], + 'virtual' => [ 'noheader' ], + 'paths' => [ 'noheader' ], + 'version' => [ 'none' ], # none is pseudo option, TODO: improve argument checking :-) + 'list' => [ 'none' ], # none is pseudo option, TODO: improve argument checking :-) + 'ms' => [ 'noheader', 'long' ] + + } +); +#print Data::Dumper->Dump([\%option_constraints]); + +Getopt::Long::Configure ("bundling"); # enable bundling of options like -lp same as -l -p +GetOptions( "m" => \$options{'ms'}, + "d" => \$options{'disks'}, + "f" => \$options{'fscsi'}, + "g" => \$options{'gigabyte'}, + "i" => \$options{'info'}, + "H" => \$options{'noheader'}, + "v" => \$options{'virtual'}, + "p" => \$options{'paths'}, + "l" => \$options{'long'}, + "V" => \$options{'version'}, + "h" => \$options{'help'}, + "L" => \$options{'list'}, + ) or usage; + +# any option list containing -h will display usage +if ( $options{'help'} ) { usage; }; + +#print Data::Dumper->Dump([\%options]); + +my $sum_opts=0; +foreach my $option ( @{$option_constraints{'mutual_exclusive'}} ) { + $sum_opts += $options{$option}; +} +# no exclusive option found? -> set 'info' as default +if ( $sum_opts == 0 ) { $sum_opts+=1; $options{'info'} = 1; } +if ( $sum_opts > 1 ) { # there can only be one of this options left, bail out if not + print STDERR "Invalid combination of arguments!\n"; + usage; +} + +# check for valid combinations of the 1 exclusive option we found earlier +foreach my $option ( @{$option_constraints{'mutual_exclusive'}} ) { + next if ( ! $options{$option} ); # check only active option + + my $regex=join("|",@{$option_constraints{'valid_combinations'}->{$option}}); + #print ">>>$option valid combinations: $regex\n"; + my @leftover=grep(!/$regex|$option/,keys %options); + #my $tmp=join(":",@leftover); + #print "### leftover to check: $tmp\n"; + my $sum_opts=0; + foreach my $leftover_option ( @leftover ) { + $sum_opts+= $options{$leftover_option}; + } + if ( $sum_opts > 0 ) { + print STDERR "Invalid combination of arguments!\n"; + usage; + } +} + +# show one line with version information +if ( $options{'version'} ) { + print "luninfo version $luninfo_version\n"; + exit 0; +} + +my %locations; # storage box locations +my %serials; # serials, names and locations of managed systems +my $cfg; #configuration hash + +# read locations and serials from config file only when necessary +if ( $options{'ms'} || $options{'info'} || $options{'list'} ) { + + + if ( -e $cfgfile ) { + $cfg=XMLin($cfgfile, ForceArray => 1); + } + + # XML config file example + # IMPORTANT!!!! There are NO # comments allowed in the config file!!!!! + # Also important: locations, storage and ms serial numbers (that is "name"-elements) need to be unique!!! + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # will generate this cfg hash: + # + # $cfg = { + # 'location' => { + # 'DC1' => { + # 'comment' => "Data Center 1" + # 'storage' => { + # '12345' => { + # comment => 'SVC1' + # } + # }, + # 'ms' => { + # '6512346' => { + # 'ms-name' => 'p770dc11', + # 'rack => 'A1-B4' + # 'comment => '' + # }, + # '6523457' => { + # 'ms-name' = 'p770dc12' + # 'rack => 'A2-B2' + # 'comment' => '', + # } + # } + # }, + # 'DC2' => { + # 'comment' => "Data Center 2" + # 'storage' => { + # '12346' => { + # comment => 'SVC2' + # } + # }, + # 'ms' => { + # '0612348' => { + # 'ms-name' => 'p770dc21', + # 'comment => '' + # 'rack => 'A5-B6' + # }, + # '0612349' => { + # 'ms-name' = 'p770dc22', + # 'comment' => '' + # 'rack => 'A5-B7' + # } + # } + # } + # } + # } + # + # comment elements are currently not used for anything except printing the device dump with -L! + + # some sanity check for the config file + # build storage location (%location) and serials hash (%serials) + # duplicate serials are only recognized when they appear in different locations + foreach my $location ( keys %{$cfg->{'location'}} ) { + foreach my $element ( keys %{$cfg->{'location'}->{$location}} ) { + for ( $element) { + if ( /storage/ ) { + foreach my $box ( keys %{$cfg->{'location'}->{$location}->{$element}} ) { + if ( defined $locations{$box} ) { + print STDERR "ERROR in $cfgfile: Duplicate use of storage serial $box! Using first definition.\n"; + next; + } + $locations{$box}=$location; + } + } elsif ( /ms/ ) { + foreach my $ms ( keys %{$cfg->{'location'}->{$location}->{$element}} ) { + if ( defined $serials{$ms} ) { + print STDERR "ERROR in $cfgfile: Duplicate use of managed system serial $ms! Using first definition.\n"; + next; + } + if ( defined $cfg->{'location'}->{$location}->{$element}->{$ms}->{'rack'} ) { + $serials{$ms}->{'rack'}=$cfg->{'location'}->{$location}->{$element}->{$ms}->{'rack'}; + } + # only add if ms-name is defined, otherwise + if ( defined $cfg->{'location'}->{$location}->{$element}->{$ms}->{'ms-name'} ) { + $serials{$ms}->{'name'}=$cfg->{'location'}->{$location}->{$element}->{$ms}->{'ms-name'}; + $serials{$ms}->{'location'}=$location; + } else { + print STDERR "ERROR: Incomplete entry in $cfgfile! Ignoring location $location serial $ms: ms-name missing!\n" + } + } + } # add more elements here if wanted + } + } + } +} + +# IBM changed the path of pcmpath from /usr/bin to /usr/sbin some time, shortly before dumping sddpcm... sigh +my $pcmpath=""; +if ( -x "/usr/bin/pcmpath" ) { + $pcmpath="/usr/bin/pcmpath" +} elsif ( -x "/usr/sbin/pcmpath" ) { + $pcmpath="/usr/sbin/pcmpath" +} + +# subroutine to sort the disks/devices (like hdiskXY/fscsiXX) numerically rather than alphabetically +sub device_numerically { + $a=~ /\D+(\d+)/; + my $aa=$1; + $b=~ /\D+(\d+)/; + my $bb=$1; + $aa <=> $bb; +} + +# option -i, -d, -p +# read the disk types and availability of all known disks and put it into %disks +sub get_all_disk_devs { + open(CMD,"/usr/sbin/lsdev -c disk -F'name subclass type status'|") or die "Error running lsdev -c disk -F'name subclass type'!"; + # vSCSI disks have the type vdisk here, regardless of the storage system.. find more about it later + while() { + chomp; + if ( m/(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/ ) { + $disks{$1}->{'name'}=$1; # will also display defined disks + $disks{$1}->{'subclass'}=$2; + $disks{$1}->{'type'}=$3; + $disks{$1}->{'status'}=$4; + } + } + close(CMD); +} + +# option -i +# read all PVs and volume group names, get disk sizes +sub get_all_pv_vg_sizes { + open(CMD,"/usr/sbin/lspv|") or die "Error running /usr/sbin/lspv!"; + while() { + chomp; + if ( m/(\S+)\s+(\S+)\s+(\S+)\s+(\S+){0,1}/ ) { + #$disks{$1}->{'name'}=$1; + $disks{$1}->{'pvid'}=$2; + $disks{$1}->{'vg_name'}=$3; + $disks{$1}->{'vg_state'}=defined $4?$4:"-"; # just in case $4 didn't match + my $disk=$1; + # get size of each available disk... this is the most time consuming part + if ( $disks{$disk}->{'status'} eq "Available" ) { + open(GETCONF,"/usr/bin/getconf DISK_SIZE /dev/$disk 2>/dev/null|") or die "Error getting disk size for disk $disk !\n"; + while() { + chomp; + # if size is requested in GiB + if ( $options{'gigabyte'} ) { + # size/1024 -> GiB + # if less than 1GiB, reduce decimal precision to 2 + # same applies to disks out of GiB boundaries, like 1.5 GiB + $disks{$disk}->{'size'}=( $_ < 1024 || $_%1024 != 0 ) ? sprintf("%.2f",$_/1024) : $_/1024; + } else { + $disks{$disk}->{'size'}=$_; + } + } + close(GETCONF); + # check return code of finished process (shift right by 8) + my $rc=$? >> 8; + if ( $rc != 0 ) { + # disksize -> none if getconf gives error + $disks{$disk}->{'size'}="none"; + } + } else { + # disksize -> none for defined disks + $disks{$disk}->{'size'}="none"; + } + } + } + close(CMD); + + # check for PowerHA 7 repository disks and mark backup repositories as caavg_backup + # for more detailed information use /usr/es/sbin/cluster/utilities/clmgr -v query repository + if ( -x "/usr/es/sbin/cluster/utilities/clmgr" ) { # are we running on a PowerHA 7.x cluster? + open(CMD,"/usr/es/sbin/cluster/utilities/clmgr view repository|") or die "Error running clmgr view repository.\n"; + # hdisk0 (00cf3f460e15d3c1) + # hdisk1 (00cf3f460e15ecf0) + while() { + chomp; + if ( m/(\S+)\s+\S+/ ) { + if ( $disks{$1}->{'vg_name'} eq "None" ) { + # backup repository has no name and is not active -> give it a fake name + $disks{$1}->{'vg_name'}="caavg_backup"; + } + } + + } + } + #print Data::Dumper->Dump([\%disks]); +} + +# option -i +# get all unique id's from odm +sub get_unique_ids { + my @odmdata; + { + local $/ = "CuAt:"; + @odmdata = `/usr/bin/odmget -q "attribute=unique_id" CuAt`; # ODMDIR is not set explicitly, so take care... as always! :-) + } + for my $odmrecord (@odmdata){ + my @odmstanza = split "\n", $odmrecord; # list of lines in each stanza, including "CuAt" and empty lines + my $name=""; my $value=""; + for my $line (@odmstanza){ + if ($line =~ /\s+name\s+=\s+"(.*)"/) { $name = $1; } + if ($line =~ /\s+value\s+=\s+"(.*)"/) { $value = $1; } + } + if ( defined $disks{$name} ) { # only set unique_id if the disk is already known + $disks{$name}->{'unique_id'}=$value; + } + } + #print Data::Dumper->Dump([\%disks]); +} + + +# option -i, -d, -p (only because of PCM) +# read disk parameters of all disks, mostly -d, because of lun_id -> also -c +sub get_all_lun_params { + foreach my $disk ( keys %disks ) { + open(CMD,"/usr/sbin/lsattr -El $disk|") or die "Error running /usr/sbin/lsattr -El $disk !\n"; + while() { + if ( /^PCM\s+(\S+)/) { $disks{$disk}->{'PCM'}=$1; } # needed to get default path prio later + if ( /^hcheck_interval\s+(\S+)/ ) { $disks{$disk}->{'hcheck_interval'}=$1; } + if ( /^hcheck_mode\s+(\S+)/ ) { $disks{$disk}->{'hcheck_mode'}=$1; } + if ( /^hcheck_cmd\s+(\S+)/ ) { $disks{$disk}->{'hcheck_cmd'}=$1; } # only with vscsi + if ( /^queue_depth\s+(\S+)/ ) { $disks{$disk}->{'queue_depth'}=$1; } + if ( /^lun_id\s+(\S+)/ ) { $disks{$disk}->{'lun_id'}=$1; } # only for some physical disks (e.g. 2145, 1818..) + if ( /^rw_timeout\s+(\S+)/ ) { $disks{$disk}->{'rw_timeout'}=$1; } # not on vscsi + if ( /^reserve_policy\s+(\S+)/ ) { $disks{$disk}->{'reserve_policy'}=$1; } + if ( /^PR_key_value\s+(\S+)/ ) { $disks{$disk}->{'PR_key_value'}=$1; } + if ( /^algorithm\s+(\S+)/ ) { $disks{$disk}->{'algorithm'}=$1; } + } + close(CMD); + + # get/match uuid, box, location etc from collected data + for ( $disks{$disk}{'type'} ) { + #2105 - 2105 models (ESS) + #2145 - 2145 models (SVC) + #2147 - 2147 models (SVC) + #2810XIV - XIV (maybe 2812 too) ??? + #1750 - 1750 devices (DS 6000) + #2107 - 2107 devices (DS 8000) + #1724 - 1724 devices (DS 4100) + #1814 - 1814 devices (DS 4200 and DS 4700) + #1722 - 1722 devices (DS 4300) + #1742 - 1742 devices (DS 4400 and DS 4500) + #1815 - 1815 devices (DS 4800) + #1818 - 1818 devices (DS 5000) + + # or vscsi, scsd + if (/2145/) { + # SVC disks + # 3321360050768019081CFD800000000000A8404214503IBMfcp + # IBM storage WWN always start with 6005076 + if ( defined $disks{$disk}->{'unique_id'} && $disks{$disk}->{'unique_id'} =~ /^\S+(6005076......(.....)..........(..)(..))..2145..IBMfcp/ ) { + $disks{$disk}->{'uuid'}=$1; + $disks{$disk}->{'box'}=$2; + $disks{$disk}->{'suid'}="$3:$4"; + $disks{$disk}->{'box_type'}="2145"; + } + # This should work from AIX 5.3 upwards for 2145 + # This might work for DS5000 (type 1818) too, although I don't have a machine to test with + if ( defined $disks{$disk}->{'lun_id'} ) { + if ( $disks{$disk}->{'lun_id'} =~ m/0x(?:([\da-f]+)0{12}|0)\b/ ) { # either 0 or value without 12 zeros + $disks{$disk}->{'scsi_id'}= defined $1 ? hex($1):0; + # m/0x([\da-f]+)\b/; scsi_id = hex ($1) >> 48 works only with 64 bit perl! sigh + } + } + } + elsif (/2107/) { + # DS8000 family LUN + # 200B75G6831015607210790003IBMfcp + $disks{$disk}->{'unique_id'}=~m/^....(.......)((..)(..))..2107900..IBMfcp/; + $disks{$disk}->{'box'}=$1; + $disks{$disk}->{'uuid'}="$1_$2"; # this is normally $2 ONLY, but we don't print the box + $disks{$disk}->{'suid'}="$3:$4"; + $disks{$disk}->{'box_type'}="2107"; + # There is a lun_id for DS8000 too, but this contains the same info as $3$4 (0x40XX40XX00000000) + $disks{$disk}->{'scsi_id'}="-" + } + #elsif (/1814/) { + # there is no unique_id on DS4800 type boxes, but a ieee_volname (e.g. 600A0B800011FA78000035D648C5DBA1) + # don't know how to get the box serial from ieee_volname + # lun_id is the same as with 2145 => 0x0004000000000000 (remove last 12 "0" and leading "0" to get lun_id) + # on vscsi lpars, the ieee_volname is inside uniqe_id: 3520600A0B800011FA78000035D648C5DBA105VDASD03AIXvscsi + #} + elsif (/vdisk/) { # vSCSI disks + # SVC LUNs visible through VIO + # 48333321360050768019081CFD8000000000009D804214503IBMfcp05VDASD03AIXvscsi + if ( defined $disks{$disk}->{'unique_id'} && $disks{$disk}->{'unique_id'}=~m/^\S+(6005076......(.....)..........(..)(..))..2145..IBMfcp..VDASD..AIXvscsi/ ) { + $disks{$disk}->{'uuid'}=$1; + $disks{$disk}->{'box'}=$2; + $disks{$disk}->{'suid'}="$3:$4"; + $disks{$disk}->{'box_type'}="2145"; + } + # DS8000 family LUN visible through VIO + # 3520200B75CF371014A07210790003IBMfcp05VDASD03AIXvscsi" + elsif ( defined $disks{$disk}->{'unique_id'} && $disks{$disk}->{'unique_id'}=~m/........(.......)((..)(..))..2107900..IBMfcp..VDASD..AIXvscsi/ ) { + $disks{$disk}->{'box'}=$1; + $disks{$disk}->{'uuid'}="$1_$2"; + $disks{$disk}->{'suid'}="$3:$4"; + $disks{$disk}->{'box_type'}="2107"; + } + # we don't know a method to get the real scsi-id of vscsi devices, set it to "-" + $disks{$disk}{'scsi_id'}="-" + + } + elsif (/mpioosdisk/) { # generic AIX MPIO + # SVC LUNs visible directly via FC or NPIV but without SDDPCM + # 33213600507680180867968000000000006E804214503IBMfcp + if ( defined $disks{$disk}->{'unique_id'} && $disks{$disk}->{'unique_id'}=~m/^\S+(6005076......(.....)..........(..)(..))..2145..IBMfcp/ ) { + $disks{$disk}->{'uuid'}=$1; + $disks{$disk}->{'box'}=$2; + $disks{$disk}->{'suid'}="$3:$4"; + $disks{$disk}->{'box_type'}="2145"; + + if ( defined $disks{$disk}->{'lun_id'} ) { + if ( $disks{$disk}->{'lun_id'} =~ m/0x(?:([\da-f]+)0{12}|0)\b/ ) { # either 0 or value without 12 zeros + $disks{$disk}->{'scsi_id'}= defined $1 ? hex($1):0; + # m/0x([\da-f]+)\b/; scsi_id = hex ($1) >> 48 works only with 64 bit perl! sigh + } + } + } + # DS8000 family LUN directly via FC or NPIV but without SDDPCM + # 3520200B75CF371014A07210790003IBMfcp + elsif ( defined $disks{$disk}->{'unique_id'} && $disks{$disk}->{'unique_id'}=~m/........(.......)((..)(..))..2107900..IBMfcp/ ) { + $disks{$disk}->{'box'}=$1; + $disks{$disk}->{'uuid'}="$1_$2"; + $disks{$disk}->{'suid'}="$3:$4"; + $disks{$disk}->{'box_type'}="2107"; + + # we don't know a method to get the real scsi-id here too, so set it to "-" + $disks{$disk}{'scsi_id'}="-" + } + + } + elsif (/htcvspgx00mpio/) { # Hitachi VSP GX00 MPIO + # may also work with Hitachi boxes that use those disk types: + # htc9900mpio + # htchusvmmpio + # htcuspmpio + # htcuspvmpio + # htcvspg1000mpio + # htcvspmpio + + # tested with Hitachi VSP G200 without HDLM, just AIX MPIO + # 240C504138FE013006OPEN-V07HITACHIfcp + if ( defined $disks{$disk}->{'unique_id'} && $disks{$disk}->{'unique_id'}=~m/........(....)((..)(..))..OPEN-...HITACHIfcp/ ) { + $disks{$disk}->{'box'}=$1; + $disks{$disk}->{'uuid'}="$1_$2"; + $disks{$disk}->{'suid'}="$3:$4"; + $disks{$disk}->{'box_type'}="HTC"; + } + if ( defined $disks{$disk}->{'lun_id'} ) { + if ( $disks{$disk}->{'lun_id'} =~ m/0x(?:([\da-f]+)0{12}|0)\b/ ) { # either 0 or value without 12 zeros + $disks{$disk}->{'scsi_id'}= defined $1 ? hex($1):0; + # m/0x([\da-f]+)\b/; scsi_id = hex ($1) >> 48 works only with 64 bit perl! sigh + } + } + } + elsif (/scsd/) { # parallel or serial SCSI disk (subclass would be scsi and sas) + delete $disks{$disk}; # we are not interested in those -> remove from hash + } + elsif (/sisarray/) { # SAS RAID arrays + delete $disks{$disk}; # we are (currently) not interested in those -> remove from hash + } else { + print STDERR "Unknown/unsupported disk type $disks{$disk}->{'type'} for disk $disk found!\n"; + delete $disks{$disk}; + } + } + } + #print Data::Dumper->Dump([\%disks]); +} + +# option -d +# get effective queue_depth value for each disk from kernel +# old function, replaced with a faster one. Problems: See below! +#sub get_queuedepth_from_kernel { +# foreach my $disk ( sort keys %disks ) { +# local $/ = undef; +# my $scsidisk_info=qx{ /usr/bin/echo "scsidisk $disk" | /usr/sbin/kdb -script }; # or use open2 +# my $line_no = 0; +# foreach my $entry ( split ( /^NAME\s+ADDRESS\s+STATE\s+CMDS_OUT\s+CURRBUF\s+LOW\n/m, $scsidisk_info ) ) { +# next if not $line_no++; # remove first block of junk +# #print ">>>$entry\n"; +# my $disk="";; +# if ( $entry =~ m/(\S+)/ ) { # first word is disk name +# $disk=$1; +# } +# my $queuedepth; +# # ushort queue_depth = 0x14; +# if ( $entry =~ m/\s+ushort\s+queue_depth\s+=\s+0x([\da-f]+);/m ) { +# $queuedepth= hex( $1); +# $disks{$disk}->{'queue_depth_kernel'}=$queuedepth; +# } +# #print "$disk $disks{$disk}->{'queue_depth_kernel'}\n"; +# } +# } +#} + +# option -d +# get effective queue_depth value for each disk from kernel +# TODO: Remove completly when there is no older AIX 7.1 anymore, queue_depth can be changed online in >7.1 TL3 +# so there is no need anymore to find out whether or not it is changed but not yet active +sub get_queuedepth_from_kernel { + my $parallel_kdb_queries=10; # reduce if you see empty queuedepth values! + my $kdb_count=0; + my $number_of_disks= scalar(keys %disks); + my $initial_kdb_command=""; # no additional initial kdb command necessary + my $kdb_command=$initial_kdb_command; + foreach my $disk ( keys %disks ) { + $kdb_count++; $number_of_disks--; + $kdb_command .= "scsidisk $disk; "; + if ( $kdb_count == $parallel_kdb_queries || $number_of_disks == 0 ) { + my $disk_name=""; my $queuedepth=""; + my $kdb_output=qx{ /usr/bin/echo "$kdb_command" | /usr/sbin/kdb -script }; + my @paragraphs=split(/Executing\s+scsidisk\s+command/,$kdb_output); + foreach my $paragraph ( @paragraphs ) { + if ( $paragraph =~ m/^NAME\s+ADDRESS\s+STATE\s+CMDS_OUT\s+CURRBUF\s+LOW\n(\S+)/m ) { + $disk_name=$1; + } + if ( $paragraph =~ m/\s+ushort\s+queue_depth\s+=\s+0x([\da-f]+);/m) { + $queuedepth= hex( $1); + } + if ( $queuedepth ne "" && $disk_name ne "" ) { + $disks{$disk_name}->{'queue_depth_kernel'}=$queuedepth; + $queuedepth=""; $disk_name=""; + } + } + $kdb_count=0; $kdb_command=$initial_kdb_command; + } + } +} + +# option -i, -d, -p +# get default path prio for each unique disk type + +sub get_default_path_prio { + my @odmdata; + { + local $/ = "PdPathAt:"; + @odmdata = `/usr/bin/odmget -q "attribute=priority" PdPathAt`; + } + for my $odmrecord (@odmdata){ + my @odmstanza = split "\n", $odmrecord; + my $default=''; my $uniquetype=''; + for my $line (@odmstanza){ + if ($line =~ /\s+uniquetype\s+=\s+"(.*)"/) { $uniquetype = $1; } + if ($line =~ /\s+deflt\s+=\s+"(.*)"/) { $default = $1; } + } + if ( $uniquetype ne '' ) { $path_defaults{$uniquetype}=$default; } + } + #print Data::Dumper->Dump([\%path_defaults]); +} + + +# option -i, -d, -p +# parse the lspath output to get all possible path information +sub get_all_path_info { + open(CMD,'/usr/sbin/lspath -F "status name parent connection path_status path_id"|') or die "Error running /usr/sbin/lspath !\n"; + while () { + chomp; + # Enabled hdisk2 vscsi0 810000000000 Available 0 + my ($state, $disk, $adapter, $connection, $path_status, $path_id)=m/(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/; + next if ($disk =~m/^ses/); # ignore SAS enclosures + + $paths{$disk}->{'id'}->{$path_id}->{'path_status'}=$path_status; + $paths{$disk}->{'id'}->{$path_id}->{'connection'}=$connection; # currently unused, contains SCSI-ID for each path + # might be useful later + + # set defaults to 0 + if ( not defined $paths{$disk}->{'count'} ) { $paths{$disk}->{'count'}=0; } + if ( not defined $paths{$disk}->{'enabled'} ) { $paths{$disk}->{'enabled'}=0; } + if ( not defined $paths{$disk}->{'not_enabled'} ) { $paths{$disk}->{'not_enabled'}=0; } + $paths{$disk}->{'count'}+=1; # count paths + + if ( $state ne "Enabled" ) { + $paths{$disk}->{'not_enabled'}+=1; # count "not enabled" paths + } else { + $paths{$disk}->{'enabled'}+=1; # count enabled paths + } + $paths{$disk}->{'id'}->{$path_id}->{'adapter'}=$adapter; # save state of every path/adapter + $paths{$disk}->{'id'}->{$path_id}->{'state'}=$state; # save state of every path/adapter + $paths{$disk}->{'id'}->{$path_id}->{'select'}="-"; # defaults, to be overridden by sddpcm + $paths{$disk}->{'id'}->{$path_id}->{'errors'}="-"; # defaults, to be overridden by sddpcm + if ( defined $disks{$disk} && defined $disks{$disk}->{'PCM'} && defined $path_defaults{$disks{$disk}->{'PCM'}} ) { # because of autovivification + # set default prio of every disk when available + $paths{$disk}->{'id'}->{$path_id}->{'prio'}=$path_defaults{$disks{$disk}->{'PCM'}}; + } + } + close(CMD); + #print Data::Dumper->Dump([\%paths]); +} + + +# option -p +# override default path prio with actual prio (if set) +# this applies to MPIO disks only. sddpcm disks don't put priorities here! +sub get_all_path_prio { + my @odmdata; + { + local $/ = "CuPathAt:"; + @odmdata = `/usr/bin/odmget -q "attribute=priority" CuPathAt`; + } + for my $odmrecord (@odmdata){ + my @odmstanza = split "\n", $odmrecord; + my $disk=""; my $path_id=""; my $value=""; + for my $line (@odmstanza){ + if ($line =~ /\s+name\s+=\s+"(.*)"/) { $disk = $1; } + if ($line =~ /\s+path_id\s+=\s+(.*)/) { $path_id = $1; } + if ($line =~ /\s+value\s+=\s+"(.*)"/) { $value = $1; } + } + if ( defined $paths{$disk} ) { + $paths{$disk}->{'id'}->{$path_id}->{'prio'}=$value; + } + } + #print Data::Dumper->Dump([\%paths]); +} + + # option -p +# get all path priorities for sddpcm disks +sub get_all_sdd_path_prio { + return if ( ! -x "$pcmpath" ); + my $pcmpath_data = qx { $pcmpath query device}; + my $rc=$? >> 8; + if ( $rc == 0 ) { # only when there is no error from pcmpath! There are rare occasions, where this is not right, + # e.g. after sddpcm upgrade without reboot + + my $line_no = 0; + my $devname=""; my @path_info=(); + foreach my $entry ( split ( /^DEV#:\s+\S+\s+|^Path#.*Errors\s*\n/m, $pcmpath_data ) ) { + next if not $line_no++; # remove first block of junk + # there are 2 types of entries: device name and paths + if ( $entry =~ /DEVICE NAME:\s+(\S+)/ ) { + $devname = $1; + #print "dev=$devname\n"; + } else { + @path_info=split( /\n/, $entry ); + foreach my $path_element ( @path_info ) { + # fscsi0/path4 -> 4 corresponds to lspath path_id and can start with any number + # so p_path is not path_id!!! + # 0* fscsi0/path0 OPEN NORMAL 21341235 0 + # or + # 0 fscsi2/path1 OPEN NORMAL 21341235 0 + if ( $path_element=~ m/\s+(\S+)\s+(\S+)\/path(\d+)\s+(\S+)\s+(\S+)\s+(\d+)\s+(\d+)/ ) { + # $p_path=$3; $p_prio=$1; $p_adapter=$2; $p_state=$4; $p_mode=$5; $p_select=$6; $p_errors=$7; + $paths{$devname}{'id'}{$3}{'prio'}=$1; + $paths{$devname}{'id'}{$3}{'select'}=$6; + $paths{$devname}{'id'}{$3}{'errors'}=$7; + } + } + } + } + } + #print Data::Dumper->Dump([\%paths]); +} + +# list content of cfg file -> data center locations, storage and managed system serials, racks and comments +# option -L +if ( $options{'list'} ) { + foreach my $location ( sort keys %{$cfg->{'location'}} ) { + printf("DC %s comment %s\n", + $location, + defined $cfg->{'location'}->{$location}->{'comment'} && length($cfg->{'location'}->{$location}->{'comment'}) >0 ? $cfg->{'location'}->{$location}->{'comment'}:"-" + ); + foreach my $element ( reverse sort keys %{$cfg->{'location'}->{$location}} ) { + for ( $element) { + if ( /storage/ ) { + foreach my $box ( sort keys %{$cfg->{'location'}->{$location}->{$element}} ) { + printf(" storage box %s comment %s\n", + $box, + defined $cfg->{'location'}->{$location}->{$element}->{$box}->{'comment'} && length($cfg->{'location'}->{$location}->{$element}->{$box}->{'comment'}) >0 ? $cfg->{'location'}->{$location}->{$element}->{$box}->{'comment'}:"-" + ); + } + + } elsif ( /ms/ ) { + foreach my $ms ( sort keys %{$cfg->{'location'}->{$location}->{$element}} ) { + printf(" managed system %s serial %s rack %s comment %s\n", + defined $cfg->{'location'}->{$location}->{$element}->{$ms}->{'ms-name'}?$cfg->{'location'}->{$location}->{$element}->{$ms}->{'ms-name'}:"UNKNOWN", + $ms, + defined $cfg->{'location'}->{$location}->{$element}->{$ms}->{'rack'}?$cfg->{'location'}->{$location}->{$element}->{$ms}->{'rack'}:"-", + defined $cfg->{'location'}->{$location}->{$element}->{$ms}->{'comment'} && length($cfg->{'location'}->{$location}->{$element}->{$ms}->{'comment'}) >0 ? $cfg->{'location'}->{$location}->{$element}->{$ms}->{'comment'}:"-" + ); + } + } + } + } + print"\n"; + } +} +# option -i +elsif ( $options{'info'} ) { + get_all_disk_devs; + get_all_pv_vg_sizes; + get_unique_ids; + get_all_lun_params; + get_all_path_info; + + # use dynamic widths for some columns + # calculate various max lengths + # always apply minimum sizes just in case there are no elements so the header can be displayed correctly + #my $max_disk_len=(sort { $b <=> $a } map { length($_) } map( $disks{$_}->{'name'}, keys %disks))[0]; + my $max_disk_len=max(map length($_->{'name'}||0), values %disks); + $max_disk_len++; # add one more space for visibility + if ( $max_disk_len<5 ) { $max_disk_len=5; } # min size + my $max_size_len=max(map length($_->{'size'}), values %disks); + if ( $max_size_len<4 ) { $max_size_len=4; } # min size + #my $max_loc_len=max(map { length($_||0) } map( $locations{$disks{$_}->{'box'}}, keys %disks)); + my $max_loc_len=max(map { length($locations{$_->{'box'}}||0)} values %disks); + if ( $max_loc_len<3 ) { $max_loc_len=3; } # min size + my $max_box_len=max(map length($_->{'box'}||0), values %disks); + if ( $max_box_len<3 ) { $max_box_len=3; } # min size + my $max_vg_len=max(map length($_->{'vg_name'}||0), values %disks); + if ( $max_vg_len<3 ) { $max_vg_len=3; } # min size + my $max_vg_state_len=max(map length($_->{'vg_state'}||0), values %disks); + if ( $max_vg_state_len<7 ) { $max_vg_state_len=7; } # min size, VGSTATE header is 7 chars long + + # print the disk summary + if ( $options{'long'} ) { + # option -i -l + # get mirror pool names. This takes very long on 6.1 /dev/null|") or die "Error getting mirror pool names using lspv -L -P !\n"; + while() { + chomp; + next if (/^Physical/); + my ($pv, $vg, $mirror_pool)=split; + if ( $mirror_pool eq "" ) { $mirror_pool="None"; } + # set only if the disk is defined (we threw out some disk types earlier!) + if ( defined $disks{$pv} ) { $disks{$pv}->{'mirror_pool'}=$mirror_pool; } + } + close(CMD); + + #print Data::Dumper->Dump([\%disks]); + + if ( ! $options{'noheader'} ) { # option -H + printf("%-*s %*s %-*s %*s %34s %5s %4s %5s %10s %-16s %-*s %*s %6s %-8s\n", + $max_disk_len, "#DISK", + $max_size_len, "SIZE", + $max_loc_len, "LOC", + $max_box_len, "BOX", + "UUID", + "UID", + "SCSI", + "PATHS", + "STATE", + "PVID", + $max_vg_len, "VG", + $max_vg_state_len, "VGSTATE", + "TYPE", + "MPOOL" + ); + } + foreach my $disk ( sort device_numerically keys %disks) { + printf("%-*s %*s %-*s %*s %34s %5s %4s %2s/%-2s %10s %-16s %-*s %*s %6s %-8s\n", + $max_disk_len, $disks{$disk}->{'name'}, + $max_size_len, defined $disks{$disk}->{'size'} ? $disks{$disk}->{'size'} : "none", + $max_loc_len, defined $locations{$disks{$disk}->{'box'}} ? $locations{$disks{$disk}->{'box'}} : "-", + $max_box_len, $disks{$disk}->{'box'}, + $disks{$disk}->{'uuid'}, + $disks{$disk}->{'suid'}, + defined $disks{$disk}->{'scsi_id'} ? $disks{$disk}->{'scsi_id'} : "-", + $paths{$disk}->{'enabled'}, + $paths{$disk}->{'count'}, + $disks{$disk}->{'status'}, + defined $disks{$disk}->{'pvid'} ? $disks{$disk}->{'pvid'} : "none", + $max_vg_len, defined $disks{$disk}->{'vg_name'} ? $disks{$disk}->{'vg_name'} : "-", + $max_vg_state_len, defined $disks{$disk}->{'vg_state'} ? substr($disks{$disk}->{'vg_state'},0,6) : "-", # compatibility with old luninfo + $disks{$disk}->{'box_type'}, + defined $disks{$disk}->{'mirror_pool'}?$disks{$disk}->{'mirror_pool'}:"none" + ); + } + } else { # plain option -i + if ( ! $options{'noheader'} ) { # option -H + printf("%-*s %*s %-*s %*s %5s %4s %5s %10s %-16s %-*s %*s %6s\n", + $max_disk_len, "#DISK", + $max_size_len, "SIZE", + $max_loc_len, "LOC", + $max_box_len, "BOX", + "UID", + "SCSI", + "PATHS", + "STATE", + "PVID", + $max_vg_len, "VG", + $max_vg_state_len, "VGSTATE", + "TYPE" + ); + } + foreach my $disk ( sort device_numerically keys %disks) { + printf("%-*s %*s %-*s %*s %5s %4s %2s/%-2s %10s %-16s %-*s %*s %6s\n", + $max_disk_len, $disks{$disk}->{'name'}, + $max_size_len, defined $disks{$disk}->{'size'} ? $disks{$disk}->{'size'} : "none", + $max_loc_len, defined $locations{$disks{$disk}->{'box'}} ? $locations{$disks{$disk}->{'box'}} : "-", + $max_box_len, $disks{$disk}->{'box'}, + $disks{$disk}->{'suid'}, + defined $disks{$disk}->{'scsi_id'} ? $disks{$disk}->{'scsi_id'} : "-", + $paths{$disk}->{'enabled'}, + $paths{$disk}->{'count'}, + $disks{$disk}->{'status'}, + defined $disks{$disk}->{'pvid'} ? $disks{$disk}->{'pvid'} : "none", + $max_vg_len, defined $disks{$disk}->{'vg_name'} ? $disks{$disk}->{'vg_name'} : "-", + $max_vg_state_len, defined $disks{$disk}->{'vg_state'} ? substr($disks{$disk}->{'vg_state'},0,6) : "-", # compatibility with old luninfo + $disks{$disk}->{'box_type'} + ); + + } + } + +} elsif ( $options{'disks'} ) { + # option -d + + get_all_disk_devs; + get_all_lun_params; + get_all_path_info; + + if ( $options{'long'} ) { + get_queuedepth_from_kernel; + if ( ! $options{'noheader'} ) { # option -H + printf("%-9s %3s %3s %3s %5s %-6s %-17s %-12s %-13s %-9s %-9s %-8s\n", + "#DISK","QD","QDK","WR","PATHS","PSTATE","ALGORITHM","RESERVE","HCHECK_C","HCHECK_I","HCHECK_M","PR_KEY_V"); + } + foreach my $disk ( sort device_numerically keys %disks) { + printf("%-9s %3s %3s %3s %2s/%-2s %-6s %-17s %-12s %-13s %-9s %-9s %-8s\n", + $disks{$disk}->{'name'}, + $disks{$disk}->{'queue_depth'}, + defined $disks{$disk}->{'queue_depth_kernel'} ? $disks{$disk}->{'queue_depth_kernel'} : "-", + defined $disks{$disk}->{'rw_timeout'} ? $disks{$disk}->{'rw_timeout'} : "-", + $paths{$disk}->{'enabled'}, + $paths{$disk}->{'count'}, + ($paths{$disk}->{'enabled'} == $paths{$disk}->{'count'}) ? "OK" : "NOT_OK", + defined $disks{$disk}->{'algorithm'} ? $disks{$disk}->{'algorithm'} : "-", + defined $disks{$disk}->{'reserve_policy'} ? $disks{$disk}->{'reserve_policy'} : "-", + defined $disks{$disk}->{'hcheck_cmd'} ? $disks{$disk}->{'hcheck_cmd'} : "-", + defined $disks{$disk}->{'hcheck_interval'} ? $disks{$disk}->{'hcheck_interval'} : "-", + defined $disks{$disk}->{'hcheck_mode'} ? $disks{$disk}->{'hcheck_mode'} : "-", + defined $disks{$disk}->{'PR_key_value'} ? $disks{$disk}->{'PR_key_value'} : "-" + ); + } + } else { + if ( ! $options{'noheader'} ) { # option -H + printf("%-9s %3s %3s %5s %-6s %-17s %-12s %-13s %-9s %-9s %-8s\n", + "#DISK","QD","WR","PATHS","PSTATE","ALGORITHM","RESERVE","HCHECK_C","HCHECK_I","HCHECK_M","PR_KEY_V"); + } + foreach my $disk ( sort device_numerically keys %disks) { + printf("%-9s %3s %3s %2s/%-2s %-6s %-17s %-12s %-13s %-9s %-9s %-8s\n", + $disks{$disk}->{'name'}, + $disks{$disk}->{'queue_depth'}, + defined $disks{$disk}->{'rw_timeout'} ? $disks{$disk}->{'rw_timeout'} : "-", + $paths{$disk}->{'enabled'}, + $paths{$disk}->{'count'}, + ($paths{$disk}->{'enabled'} == $paths{$disk}->{'count'}) ? "OK" : "NOT_OK", + defined $disks{$disk}->{'algorithm'} ? $disks{$disk}->{'algorithm'} : "-", + defined $disks{$disk}->{'reserve_policy'} ? $disks{$disk}->{'reserve_policy'} : "-", + defined $disks{$disk}->{'hcheck_cmd'} ? $disks{$disk}->{'hcheck_cmd'} : "-", + defined $disks{$disk}->{'hcheck_interval'} ? $disks{$disk}->{'hcheck_interval'} : "-", + defined $disks{$disk}->{'hcheck_mode'} ? $disks{$disk}->{'hcheck_mode'} : "-", + defined $disks{$disk}->{'PR_key_value'} ? $disks{$disk}->{'PR_key_value'} : "-" + ); + } + } + +} elsif ( $options{'ms'} ) { + # option -m + # serial, machine type, firmware etc... + my %system; + open(CMD,'/usr/sbin/lsattr -El sys0|') or die "Error running lsattr -El sys0 !"; + while() { + # systemid IBM,0306C090G Hardware system identifier False + # system serial is 06C090G! + if ( /^fwversion\s+IBM,(\S+)/) { $system{'fwversion'}=$1; } + # modelname IBM,9117-MMA Machine name False + if ( /^modelname\s+IBM,(\S+)/) { $system{'modelname'}=$1; } + # fwversion IBM,EM350_107 Firmware version and revision levels False + if ( /^systemid\s+IBM,..(\S+)/) { $system{'systemid'}=$1; } + } + close(CMD); + open(CMD,'/usr/sbin/lsmcode -r -t system|') or die "Error running lsmcode -r -t system !"; + while() { + # system:EM350_159 (t) EM350_149 (p) EM350_159 (t) + if ( /^system:(\S+)\s+\(t\)\s+(\S+)\s+\(p\)\s+(\S+)\s+\(.\)/ ) { + $system{'active_firmware'}=$3; + $system{'permanent_firmware'}=$2; + $system{'temporary_firmware'}=$1; + } + } + close(CMD); + + if ( $options{'long'} ) { # option -l + if ( ! $options{'noheader'} ) { # option -H + printf("%-15s %-10s %-11s %-11s %-11s %-10s %-8s %-8s\n","#MODELNAME","SYSTEMID","FIRMWARE","FIRMWARE(t)","FIRMWARE(p)","MSYSNAME","LOCATION","RACK"); + } + printf("%-15s %-10s %-11s %-11s %-11s %-10s %-8s %-8s\n", + $system{'modelname'}, + $system{'systemid'}, + # just in case we are on a machine with "weird" lsmcode output -> print sys0 data + defined $system{'active_firmware'} ? $system{'active_firmware'} : $system{'fwversion'}, + defined $system{'temporary_firmware'} ? $system{'temporary_firmware'} : "-", + defined $system{'permanent_firmware'} ? $system{'permanent_firmware'} : "-", + ( defined $serials{$system{'systemid'}}->{"name"} and length $serials{$system{'systemid'}}->{"name"} ) ? $serials{$system{'systemid'}}->{"name"} : "UNKNOWN", + ( defined $serials{$system{'systemid'}}->{"location"} and length $serials{$system{'systemid'}}->{"location"} ) ? $serials{$system{'systemid'}}->{"location"} : "UNKNOWN", + ( defined $serials{$system{'systemid'}}->{"rack"} and length($serials{$system{'systemid'}}->{"rack"}) ) ? $serials{$system{'systemid'}}->{"rack"} : "-" + ); + } else { + if ( ! $options{'noheader'} ) { # option -H + printf("%-15s %-10s %-10s %-10s\n","#MODELNAME","SYSTEMID","FIRMWARE","MSYSNAME"); + } + printf("%-15s %-10s %-10s %-10s\n", + $system{'modelname'}, + $system{'systemid'}, + # just in case we are on a machine with "weird" lsmcode output -> print sys0 data + defined $system{'active_firmware'} ? $system{'active_firmware'} : $system{'fwversion'}, + defined $serials{$system{'systemid'}} ? $serials{$system{'systemid'}}->{"name"} : "UNKNOWN" + ); + + } +} + +elsif ( $options{'fscsi'} ) { + # option -f + # get FibreChannel hba data + my %adapters; + open(CMD,'/usr/sbin/lsdev -Cc adapter -F name:status:type|') or die "Error running /usr/sbin/lsdev -Cc adapter -F name:status:type !\n"; + # -S Available -> would list only available adapters + while () { + chomp; + next if ! /^fcs|IBM,vfc-client$/; # only physical and virtual fcs adapters, more to include here? + my ($adapter,$status,$type)=split(/:/); + $adapters{$adapter}->{'status'}=$status; + $adapters{$adapter}->{'name'}=$adapter; + $adapters{$adapter}->{'type'}=$type; + + if ( $adapter =~ /^fcs/ ) { + # fcs0 U78AB.001.WZSGDZK-P1-C4-T1 4Gb FC PCI Express Adapter (df1000fe) + # + # Part Number.................10N7255 + # Serial Number...............1A109002A3 + # Manufacturer................001A + # EC Level....................D77040 + # Customer Card ID Number.....5774 + # FRU Number..................10N7255 # sometimes there are spaces before the value! + # Device Specific.(ZM)........3 + # Network Address.............10000000C9B775FA + # ROS Level and ID............02E8277F + # Device Specific.(Z0)........2057706D + # Device Specific.(Z1)........00000000 + # Device Specific.(Z2)........00000000 + # Device Specific.(Z3)........03000909 + # Device Specific.(Z4)........FFE01212 + # Device Specific.(Z5)........02E8277F + # Device Specific.(Z6)........06E12715 + # Device Specific.(Z7)........07E1277F + # Device Specific.(Z8)........20000000C9B775FA + # Device Specific.(Z9)........ZS2.71X15 + # Device Specific.(ZA)........Z1F2.70A5 + # Device Specific.(ZB)........Z2F2.71X15 + # Device Specific.(ZC)........00000000 + # Hardware Location Code......U78AB.001.WZSGDZK-P1-C4-T1 + open(CMD1,"/usr/sbin/lscfg -vl $adapter|") or die "Error running /usr/sbin/lscfg -vl $adapter !\n"; + while() { + if ( /\s+$adapter\s+(\S+)/ ) { $adapters{$adapter}->{'hw_path'}=$1; } + if ( /\s+FRU Number\.+\s*(\S+)/ ) { $adapters{$adapter}->{'fru_number'}=$1; } + if ( /\s+Network Address\.+\s*(\S+)/ ) { $adapters{$adapter}->{'wwpn'}=$1; } + if ( /\s+Part Number\.+\s*(\S+)/ ) { $adapters{$adapter}->{'part_number'}=$1; } + if ( /\s+Customer Card ID Number\.+\s*(\S+)/ ) { $adapters{$adapter}->{'card_id'}=$1; } + if ( /\s+Device Specific\.\(CC\)\.+\s*(\S+)/ ) { $adapters{$adapter}->{'card_id'}=$1; } # historic + if ( /\s+Feature Code\/Marketing ID\.+\s*(\S+)/ ) { $adapters{$adapter}->{'card_id'}=$1; } # historic + if ( /\s+Device Specific\.\(Z9\)\.+\s*..(\S+)/ ) { $adapters{$adapter}->{'firmware'}=$1; } + + } + close(CMD1); + # get tuning params from the adapter + open(CMD1,"/usr/sbin/lsattr -El $adapter|") or die "Error running /usr/sbin/lsattr -El $adapter !\n"; + while() { + if ( /^max_xfer_size\s+(\S+)/ ) { $adapters{$adapter}->{'max_xfer_size'}=$1; } + if ( /^num_cmd_elems\s+(\S+)/ ) { $adapters{$adapter}->{'num_cmd_elems'}=$1; } + } + close(CMD1); + # This will hopefully work on all FC HBAs!? + # get the fscsi protocol device from the HBA device parent, there is only one!(?) + my $device=qx{ /usr/sbin/lsdev -p $adapter -F name -t efscsi }; # this will also find "Defined" adapters! + chomp($device); + if ( $device eq "" ) { + $device=qx{ /usr/sbin/lsdev -p $adapter -F name -t emfscsi }; # newer models have emfscsi as type, and -t can only be used once with lsdev + chomp($device); + } + $adapters{$adapter}{'device'}=$device; + if ( $device ne "" ) { # device is empty when fcsX is Defined + open(CMD1,"/usr/sbin/lsattr -El $device|"); + while() { + if ( /^attach\s+(\S+)/ ) { $adapters{$adapter}->{'attach'}=$1; } # needed when no devscan is available + if ( /^dyntrk\s+(\S+)/ ) { $adapters{$adapter}->{'dyntrk'}=$1; } + if ( /^fc_err_recov\s+(\S+)/ ) { $adapters{$adapter}->{'fc_err_recov'}=$1; } + } + close(CMD1); + } + # override rubbish found in lscfg output for virtual fc adapters + if ( $adapters{$adapter}->{'type'} eq "IBM,vfc-client" ) { + $adapters{$adapter}->{'fru_number'}="VIRT"; + $adapters{$adapter}->{'part_number'}="VIRT"; + $adapters{$adapter}->{'card_id'}="VIRT"; + $adapters{$adapter}->{'firmware'}="VIRT"; + + } + } + } + close(CMD); + + # get wwpn, port speed and link state (attention type) via fcstat + foreach my $adapter ( keys %adapters ) { + # run fcstat only on adapters which are "switch" or "loop" attached. "none" will hang 30s! + # we work around this with -d, see below + # fcstat -D is undocumented but should run on all AIX versions including 5.3 + # Attention Type is available in normal fcstat (also with -d) output in 6.1 TL9 and 7.1 TL3 + # + # so first try to detect port speed and attention type in diag mode (-d) and + # only when this returns an error -> use -D + # it works because fcstat -d returns error 0d immediately when the adapter is in use + # this way hangs are reduced when an adapter has no link and is in "switch" mode (happens quite often) + open(CMD,"/usr/bin/fcstat -d $adapter 2>/dev/null|") or die "Error running /usr/bin/fcstat -d $adapter!\n"; + while() { + chomp; + if ( /Port Speed \(running\):\s+(\d+)\s+GBIT/ ) { $adapters{$adapter}->{'linkspeed'}= $1; } + #if ( /Port Type:\s+(\S+)/ ) { $adapters{$adapter}->{'attach'}= lc($1); } # "Fabric" or "Private Loop" + if ( /Attention\s+Type:\s+Link\s+(\S+)/ ) { $adapters{$adapter}->{'linkstate'}= lc($1); } + if ( /^World Wide Port Name:\s0x(\S+)/ ) { $adapters{$adapter}->{'wwpn'}=$1; } + } + close(CMD); # only close will set $? ! :-) + my $rc=$? >> 8; + if ( $rc != 0 ) { # run fcstat -D only when fcstat -d did not give any results + open(CMD,"/usr/bin/fcstat -D $adapter 2>/dev/null|") or die "Error running /usr/bin/fcstat -D $adapter!\n"; + while() { + chomp; + if ( /Port Speed \(running\):\s+(\d+)\s+GBIT/ ) { $adapters{$adapter}->{'linkspeed'}= $1; } + #if ( /Port Type:\s+(\S+)/ ) { $adapters{$adapter}->{'attach'}= lc($1); } # "Fabric" or "Private Loop" + if ( /Attention\s+Type:\s+Link\s+(\S+)/ ) { $adapters{$adapter}->{'linkstate'}= lc($1); } + if ( /^World Wide Port Name:\s0x(\S+)/ ) { $adapters{$adapter}->{'wwpn'}=$1; } + } + close(CMD); + } + } + + # get SDDPCM adapter state. Important on NPIV installations since AIX adapter state (speed, link state) does not reflect reality + if ( -x "$pcmpath" ) { + my $start_marker_not_found=1; + open(CMD,"$pcmpath query adapter|") or die "Error running $pcmpath query adapter!\n"; + # 0 fscsi0 NORMAL ACTIVE 68 0 2 2 + # IMPORTANT: The adapter name is fscsi, NOT fcs!! + while() { + chomp; + if ( /^Adpt/ ) { $start_marker_not_found=0; } + next if ( $start_marker_not_found ); + if ( /\s+\d+\s+(\S+)\s+(\S+)\s+(\S+)\s+/ ) { + my $pdev=$1; + my $sdd_state=$2; # FAILED, DEGRAD, NORMAL + # now find associated fcs device + foreach my $adapter ( keys %adapters ) { + if ( defined $adapters{$adapter}->{'device'} && $adapters{$adapter}->{'device'} eq $pdev ) { + $adapters{$adapter}->{'sdd_state'}=$sdd_state; + } + } + } + } + close(CMD); + } + #} + #print Data::Dumper->Dump([\%adapters]); + + # use dynamic widths for some columns + # calculate various max lengths + # always apply minimum sizes just in case there are no elements so the header can be displayed correctly + my $max_loc_len=max(map length($_->{'hw_path'}||0), values %adapters); + if ( $max_loc_len<8 ) { $max_loc_len=8; } # min size + my $max_err_recv_len=max(map length($_->{'fc_err_recov'}||0), values %adapters); + if ( $max_err_recv_len<10 ) { $max_err_recv_len=10; } # min size + my $max_fw_len=max(map length($_->{'firmware'}||0), values %adapters); + if ( $max_fw_len<4 ) { $max_fw_len=4; } # min size + my $max_fru_len=max(map length($_->{'fru_number'}||0), values %adapters); + if ( $max_fru_len<4 ) { $max_fru_len=4; } # min size + my $max_pn_len=max(map length($_->{'part_number'}||0), values %adapters); + if ( $max_pn_len<4 ) { $max_pn_len=4; } # min size + my $max_fc_len=max(map length($_->{'card_id'}||0), values %adapters); + if ( $max_fc_len<4 ) { $max_fc_len=4; } # min size + + # print FC hba status + if ( $options{'long'} ) { # option -l + if ( ! $options{'noheader'} ) { # option -H + printf("%-5s %-9s %-6s %-6s %4s %5s %-*s %-*s %-*s %-*s %-*s %-16s %-7s %-6s %-*s %9s %-6s\n", + "#HBA", + "STATE", + "SDD", + "ATTACH", + "LINK", + "SPEED", + $max_loc_len,"HW_PATH", + $max_fw_len,"FW", + $max_fru_len,"FRU", + $max_pn_len,"PN", + $max_fc_len,"FC", + "WWPN", + "FSCSI", + "DYNTRK", + $max_err_recv_len,"FC_ERR_REC", + "MAX_XFER", + "NUMCMD"); + } + foreach my $adapter ( sort device_numerically keys %adapters ) { + #next if ( $adapter !~/^fcs/ ); # adapters are filtered out earlier + printf("%-5s %-9s %-6s %-6s %4s %5s %-*s %-*s %-*s %-*s %-*s %-16s %-7s %-6s %-*s %9s %-6s\n", + $adapters{$adapter}->{'name'}, + $adapters{$adapter}->{'status'}, + defined $adapters{$adapter}->{'sdd_state'} ? $adapters{$adapter}->{'sdd_state'} : "-", + defined $adapters{$adapter}->{'attach'} ? $adapters{$adapter}->{'attach'} : "UNKN", + defined $adapters{$adapter}->{'linkstate'} ? $adapters{$adapter}->{'linkstate'} : "UNKN", + defined $adapters{$adapter}->{'linkspeed'} ? $adapters{$adapter}->{'linkspeed'} : "-", + $max_loc_len, $adapters{$adapter}->{'hw_path'}, + $max_fw_len, defined $adapters{$adapter}->{'firmware'} ? $adapters{$adapter}->{'firmware'} : "UNKN", + $max_fru_len, defined $adapters{$adapter}->{'fru_number'} ? $adapters{$adapter}->{'fru_number'} : "UNKN", + $max_pn_len, defined $adapters{$adapter}->{'part_number'} ? $adapters{$adapter}->{'part_number'} : "UNKN", + $max_fc_len, defined $adapters{$adapter}->{'card_id'} ? $adapters{$adapter}->{'card_id'} : "-", + defined $adapters{$adapter}->{'wwpn'} ? $adapters{$adapter}->{'wwpn'} : "-", + ( defined $adapters{$adapter}->{'device'} and length $adapters{$adapter}->{'device'} ) ? $adapters{$adapter}->{'device'} : "UNKN", + defined $adapters{$adapter}->{'dyntrk'} ? $adapters{$adapter}->{'dyntrk'} : "-", + $max_err_recv_len, defined $adapters{$adapter}->{'fc_err_recov'} ? $adapters{$adapter}->{'fc_err_recov'} : "-", + $adapters{$adapter}->{'max_xfer_size'}, + $adapters{$adapter}->{'num_cmd_elems'} + ); + } + } else { # plain option -f + if ( ! $options{'noheader'} ) { # option -H + printf("%-5s %-9s %-6s %-6s %4s %5s %-*s %-16s %-7s %-6s %-*s %9s %-6s\n", + "#HBA", + "STATE", + "SDD", + "ATTACH", + "LINK", + "SPEED", + $max_loc_len,"HW_PATH", + "WWPN", + "FSCSI", + "DYNTRK", + $max_err_recv_len,"FC_ERR_REC", + "MAX_XFER", + "NUMCMD"); + } + foreach my $adapter ( sort device_numerically keys %adapters ) { + #next if ( $adapter !~/^fcs/ ); # adapters are filtered out earlier + printf("%-5s %-9s %-6s %-6s %4s %5s %-*s %-16s %-7s %-6s %-*s %9s %-6s\n", + $adapters{$adapter}->{'name'}, + $adapters{$adapter}->{'status'}, + defined $adapters{$adapter}->{'sdd_state'} ? $adapters{$adapter}->{'sdd_state'} : "-", + defined $adapters{$adapter}->{'attach'} ? $adapters{$adapter}->{'attach'} : "UNKN", + defined $adapters{$adapter}->{'linkstate'} ? $adapters{$adapter}->{'linkstate'} : "UNKN", + defined $adapters{$adapter}->{'linkspeed'} ? $adapters{$adapter}->{'linkspeed'} : "-", + $max_loc_len, $adapters{$adapter}->{'hw_path'}, + defined $adapters{$adapter}->{'wwpn'} ? $adapters{$adapter}->{'wwpn'} : "-", + ( defined $adapters{$adapter}->{'device'} and length $adapters{$adapter}->{'device'} ) ? $adapters{$adapter}->{'device'} : "UNKN", + defined $adapters{$adapter}->{'dyntrk'} ? $adapters{$adapter}->{'dyntrk'} : "-", + $max_err_recv_len, defined $adapters{$adapter}->{'fc_err_recov'} ? $adapters{$adapter}->{'fc_err_recov'} : "-", + $adapters{$adapter}->{'max_xfer_size'}, + $adapters{$adapter}->{'num_cmd_elems'} + ); + } + } +} + +elsif ( $options{'virtual'} ) { + # option -v + # get vSCSI hba data + my %adapters; + open(CMD,'/usr/sbin/lsdev -Cc adapter -t IBM,v-scsi -F name,status,physloc|') or die "Error running /usr/sbin/lsdev -Cc adapter -t IBM,v-scsi -F name,status,physloc !\n"; + while () { + chomp; + #next if ! /^vscsi/; # any more patterns to match/exclude here? + my ($adapter,$status,$hwpath) = split(/,/); + $adapters{$adapter}->{'status'} = $status; + $adapters{$adapter}->{'name'} = $adapter; + $adapters{$adapter}->{'physloc'} = $hwpath; + if ( $hwpath =~ /[^-]+\-V[0-9]+\-(C[0-9]+)/ ) { + $adapters{$adapter}->{'controller'} = $1; + } + if ( $adapter =~ /^vscsi/ ) { # probably redundant + open(CMD1,"/usr/sbin/lsattr -El $adapter|"); + while() { + if ( /^vscsi_err_recov\s+(\S+)/ ) { $adapters{$adapter}->{'vscsi_err_recov'}=$1; } + if ( /^vscsi_path_to\s+(\S+)/ ) { $adapters{$adapter}->{'vscsi_path_to'}=$1; } + } + close(CMD1); + + # get parent vhost and vio server later + # set proper defaults (e.g. for 5.3) for vhost and vio server + $adapters{$adapter}->{'vhost'}="-"; + $adapters{$adapter}->{'vios'}="-"; + } + } + close(CMD); + + open(CMD,'/usr/sbin/lsdev -Cc adapter -t IBM,vfc-client -F name,status,physloc|') or die "Error running /usr/sbin/lsdev -Cc adapter -t IBM,vfc-client -F name,status,physloc !\n"; + while () { + chomp; + #next if ! /^fcs/; # any more patterns to match/exclude here? + my ($adapter,$status,$hwpath) = split(/,/); + $adapters{$adapter}->{'status'} = $status; + $adapters{$adapter}->{'name'} = $adapter; + $adapters{$adapter}->{'physloc'} = $hwpath; + if ( $hwpath =~ /[^-]+\-V[0-9]+\-(C[0-9]+)/ ) { + $adapters{$adapter}->{'controller'} = $1; + } + + # just get fc_err_recov here, for more info use -f ! + + # get the fscsi protocol device from the HBA device parent, there is only one!(?) + my $device=qx{ /usr/sbin/lsdev -p $adapter -F name -t efscsi }; # this will also find "Defined" adapters! + chomp($device); + if ( $device eq "" ) { + $device=qx{ /usr/sbin/lsdev -p $adapter -F name -t emfscsi }; # newer models have emfscsi as type, and -t can only be used once with lsdev + chomp($device); + } + $adapters{$adapter}{'device'}=$device; + if ( $device ne "" ) { # device is empty when fcsX is Defined + open(CMD1,"/usr/sbin/lsattr -El $device|"); + while() { + if ( /^fc_err_recov\s+(\S+)/ ) { $adapters{$adapter}->{'vscsi_err_recov'}=$1; } # vscsi is used for the sake of simplicity only! :-) + } + close(CMD1); + } + # get parent vhost and vio server later + # set proper defaults (e.g. for 5.3) for vhost and vio server + $adapters{$adapter}->{'vhost'}="-"; + $adapters{$adapter}->{'vios'}="-"; + } + close(CMD); + #print Data::Dumper->Dump([\%adapters]); + + # get vhost,vfchost and vio servers from kdb... only if vscsi/vfc adapters are present + # will not return anything for defined adapters! + + # fcs adapters temporarily removed from grep expression because it sometimes doesn't work (see below) + # this will reduce unnecessary delays + #if ( grep (/^vscsi|^fcs/, keys %adapters) ) { # don't run kdb when there are no vscsi or fcs adapters (real fc adapters don't show up) + if ( grep (/^vscsi/, keys %adapters) ) { # don't run kdb when there are no vscsi or fcs adapters (real fc adapters don't show up) + my $pid=open2(\*CMDIN, \*CMDOUT, "/usr/sbin/kdb -script"); # this will only work as root, errors (perm denied etc for non-root) are ignored + print CMDOUT "cvai;vfcs\n"; # "cvai" for vscsi devices, "vfcs" for vfscsi devices + close CMDOUT; + + while() { + # NAME STATE CMDS_ACTIVE ACTIVE_QUEUE HOST + # vscsi0 0x000007 0x0000000000 0x0 axvio62t->vhost34 + # normal operation is state = 0x000007, change if other state doesn't give valid data + if ( /^(vscsi\d+)\s+\S+\s+\S+\s+\S+\s+(\S+)->(\S+)/) { + $adapters{$1}->{'vhost'} = $3; + $adapters{$1}->{'vios'} = $2; + # NAME ADDRESS STATE HOST HOST_ADAP OPENED NUM_ACTIVE + # fcs0 0xF1000100267D4000 0x0008 axvio66t vfchost0 0x01 0x0000 + # normal operation is state = 0x0008 + # state = 0x0001: Mapping is missing on VIOS, HOST and HOST_ADAP are not valid + } elsif ( /^(fcs\d+)\s+\S+\s+(\S+)\s+(\S+)\s+(\S+)/ ) { + if ( $2 eq "0x0008" ) { + $adapters{$1}->{'vhost'} = $4; + $adapters{$1}->{'vios'} = $3; + } else { + $adapters{$1}->{'vhost'} = "ERROR"; + $adapters{$1}->{'vios'} = "ERROR"; + } + } + } + close(CMDIN); + } + + # vfc adapters need special handling because sometimes the kdb vfcs command stops working because some internal kernel + # data was paged out (APAR IV79359) + # the next code block is a slow (+1..2s) workaround, the vfcs code above is still present, so the workaround can be removed easily later + if ( grep (/^fcs/, keys %adapters) ) { # don't run kdb when there are no fcs adapters (real fc adapters don't show up) + my $lke=""; + my $le_data=""; + my $vfcs_adap_info=""; + my $pid=open2(\*CMDIN, \*CMDOUT, "/usr/sbin/kdb -script"); + print CMDOUT "lke\n"; + close(CMDOUT); + + # get vfcdd driver number from list of kernel extension + while() { + chomp; + if ( /^\s*(\d+)\s+.*vfcdd$/ ) { + $lke=$1; + last; + } + } + close(CMDIN); + + if ( $lke ne "" ) { + my $pid=open2(\*CMDIN, \*CMDOUT, "/usr/sbin/kdb -script"); + print CMDOUT "lke -s $lke\n"; + close(CMDOUT); + + # get le_data address + while() { + chomp; + if ( /^\s*le_data\.+\s(\S+)\s+.*$/ ) { + $le_data=$1; + last; + } + } + close(CMDIN); + } + + if ( $lke ne "" && $le_data ne "" ) { + my $pid=open2(\*CMDIN, \*CMDOUT, "/usr/sbin/kdb -script"); + print CMDOUT "vfcsglob\n$le_data\n"; + close(CMDOUT); + + # get adapter info from vfcsglob data structure + while() { + chomp; + if ( /^\s*struct\s+vfc_adap_info\s+\S+\s+=\s+([0-9A-Fx]+);/ ) { + $vfcs_adap_info=$1; + + my $pid2=open2(\*KDBIN, \*KDBOUT, "/usr/sbin/kdb -script"); + print KDBOUT "vfcs $vfcs_adap_info\n"; + close(KDBOUT); + + local $/ = undef; # slurp all lines into variable + my $vfcs_struct=; + + #print "########\n$vfcs_struct\n########\n; + if ( $vfcs_struct =~ /.*adapter:\s+(\S+)\s+.*state:\s(\S+)\s+.*host_name:\s+(\S+)\s+host_device:\s(\S+)/s ) { + #print "adapter:state:host_name:host_device: $1 $2 $3 $4\n"; + if ( $2 eq "0x8" ) { + $adapters{$1}->{'vhost'} = $4; + $adapters{$1}->{'vios'} = $3; + } else { + $adapters{$1}->{'vhost'} = "ERROR"; + $adapters{$1}->{'vios'} = "ERROR"; + } + } + close(KDBIN); + } + } + close(CMDIN); + } + } + + #print Data::Dumper->Dump([\%adapters]); + + # print vscsi/vfc hba status + if ( ! $options{'noheader'} ) { # option -H + printf("%-7s %-9s %-30s %-4s %-10s %-10s %-12s %-7s\n", + "#HBA","STATE","HW_PATH","SLOT","VIOS","VHOST","ERR_RECOV","PATH_TO"); + } + foreach my $adapter ( sort keys %adapters ) { + next if ( $adapter !~/^vscsi|^fcs/ ); + printf("%-7s %-9s %-30s %-4s %-10s %-10s %-12s %-7s\n", + $adapters{$adapter}->{'name'}, + $adapters{$adapter}->{'status'}, + $adapters{$adapter}->{'physloc'}, + $adapters{$adapter}->{'controller'}, + $adapters{$adapter}->{'vios'}, + $adapters{$adapter}->{'vhost'}, + defined $adapters{$adapter}->{'vscsi_err_recov'} ? $adapters{$adapter}->{'vscsi_err_recov'} : "-", # not present in early 5.3 + defined $adapters{$adapter}->{'vscsi_path_to'} ? $adapters{$adapter}->{'vscsi_path_to'} : "-" + ); + } +} elsif ( $options{'paths'} ) { + + # option -p + get_all_disk_devs; + get_all_lun_params; # because of PCM option to find default prio + get_default_path_prio; + get_all_path_info; + get_all_path_prio; + get_all_sdd_path_prio; + + #print Data::Dumper->Dump([\%disks]); + #print Data::Dumper->Dump([\%path_defaults]); + #print Data::Dumper->Dump([\%paths]); + + # view: + # DISK DEVICE STATE PRIO SELECT ERRORS + # hdisk vscsi Enabled vscsi-prio - - from vscsi values + # hdisk fscsi Enabled pcmpath-path pcmpath-select -errors from pcmpath query device + if ( ! $options{'noheader'} ) { # option -H + printf("%-9s %-8s %-8s %-10s %-5s %-10s %-10s\n", + "#DISK","ADAPTER","STATE","MODE","PRIO","SELECT","ERRORS"); + } + foreach my $disk ( sort device_numerically keys %paths ) { + foreach my $path_id ( sort keys %{$paths{$disk}{'id'}} ) { + if ( defined $disks{$disk} ) { # print only paths for devices we want (e.g. no scsi/sas disks) + next if ( $disks{$disk}->{'type'} =~ /scsd/ ); # skip scsi/sas disks + printf("%-9s %-8s %-8s %-10s %-5s %-10s %-10s\n", + $disk, + $paths{$disk}{'id'}->{$path_id}->{'adapter'}, + $paths{$disk}{'id'}->{$path_id}->{'state'}, + $paths{$disk}{'id'}->{$path_id}->{'path_status'}, + $paths{$disk}{'id'}->{$path_id}->{'prio'}, + $paths{$disk}{'id'}->{$path_id}->{'select'}, + $paths{$disk}{'id'}->{$path_id}->{'errors'} + ); + } + } + } +} +# +# +# The end. diff --git a/luninfo.cfg b/luninfo.cfg new file mode 100644 index 0000000..917d795 --- /dev/null +++ b/luninfo.cfg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/show_vio_disk_mappings b/show_vio_disk_mappings new file mode 100755 index 0000000..7aad5ec --- /dev/null +++ b/show_vio_disk_mappings @@ -0,0 +1,365 @@ +#!/usr/bin/perl +# +# Shows LUN mappings of a VIOS environment faster than lsmap +# +# inspired by FUM :-) +# +# Armin Kunaschik +# Version 1.0 +# Version 1.1 2011-11-29 added SCSI id's for 2145 +# Version 1.2 2012-03-12 added Nuernberg box serials +# Version 1.3 2012-06-13 added short UUID (SUID), fixed column width +# Version 1.4 2013-01-16 added 5th character to identify SVC serials +# Version 1.5 2013-01-22 added new box serials, changes location naming conventions +# Version 2.0 2014-03-21 code rewrite, changed 2145 scsi id detection, added lpar names to vhost mappings from kernel (kdb) +# Version 2.1 2014-08-29 support for displaying disks sizes as large as 10TB +# Version 2.2 2015-02-24 LPARID added, "support" for empty/defined vhosts or stopped LPARs +# Version 2.3 2015-04-30 cosmetics: slot numbers left justified +# Version 2.4 2020-11-07 moved serials and locations into luninfo.cfg +# +# Bugs: Script might display garbage for LPAR and/or LPARID when executed while a lpar is booting +# there is no documentation on all possible kdb output :-( +# +# Workaround: Don't execute while lpars are booting :-) + + +use strict; +#use Data::Dumper; +use XML::Simple; # for config file + +my %disks; +my %slots; +my %locations; + +my $cfgfile="/etc/luninfo.cfg"; +my $cfg; #configuration hash + +# read locations from config file +if ( -e $cfgfile ) { + $cfg=XMLin($cfgfile, ForceArray => 1); +} + +foreach my $location ( keys %{$cfg->{'location'}} ) { + foreach my $element ( keys %{$cfg->{'location'}->{$location}} ) { + for ( $element) { + if ( /storage/ ) { + foreach my $box ( keys %{$cfg->{'location'}->{$location}->{$element}} ) { + if ( defined $locations{$box} ) { + print STDERR "ERROR in $cfgfile: Duplicate use of storage serial $box! Using first definition.\n"; + next; + } + $locations{$box}=$location; + } + } + } + } +} + +#print Data::Dumper->Dump([\%locations]); + +# read the disk types and availability +open(CMD,"/usr/sbin/lsdev -c disk -F'name subclass type status'|") or die "Error running lsdev -c disk -F'name subclass type'!"; +while() { + chomp; + if ( m/(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/ ) { + $disks{$1}->{'name'}=$1; + $disks{$1}->{'subclass'}=$2; + $disks{$1}->{'type'}=$3; + $disks{$1}->{'status'}=$4; + } +} +close(CMD); + +# read all PVs and volume group names +open(CMD,"/usr/sbin/lspv|") or die "Error running /usr/sbin/lspv!"; +while() { + chomp; + if ( m/(\S+)\s+(\S+)\s+(\S+)/ ) { + #$disks{$1}->{'name'}=$1; + $disks{$1}->{'pvid'}=$2; + $disks{$1}->{'vg_name'}=$3; + my $disk=$1; + # get size of each available disk... this is the most time consuming part + if ( $disks{$disk}->{'status'} eq "Available" ) { + open(CMD1,"/usr/bin/getconf DISK_SIZE /dev/$disk 2>/dev/null|") or die "Error getting disk size for disk $disk\n"; + while() { + chomp; + $disks{$disk}->{'size'}=$_; + } + close(CMD1); + # check error of finished process (shift right by 8) + my $rc=$? >> 8; + if ( $rc != 0 ) { + # disksize -> none if getconf gives error + $disks{$disk}->{'size'}="none"; + } + } else { + # disksize -> none for defined disks + $disks{$disk}->{'size'}="none"; + } + } +} +close(CMD); + +# get VTD to disk name mapping from ODM +my @vtd_list; +{ + local $/="\n\n"; # record delimiter is 2 new lines = paragraph + @vtd_list=qx { /usr/bin/odmget -q "attribute=aix_tdev" CuAt }; +} +#CuAt: +# name = "server1_dc1_lun1" +# attribute = "aix_tdev" +# value = "hdisk459" +# type = "R" +# generic = "D" +# rep = "s" +# nls_index = 5 + +foreach my $vtd ( @vtd_list ) { + my $vtd_name=""; + my $vtd_disk=""; + if ( $vtd =~ m/\s+name\s+=\s+"(\S+)"/m ) { $vtd_name = $1; } + if ( $vtd =~ m/\s+value\s+=\s+"(\S+)"/m ) { $vtd_disk = $1; } + if ( $vtd_disk ne "" && $vtd_name ne "" ) { + $disks{$vtd_disk}->{'vtd'}=$vtd_name; + } +} +undef @vtd_list; +#print Data::Dumper->Dump([\%disks]); + +# get controller id's from slot list; will also show defined devices, although defined devices are currently not used +open(CMD,'/usr/sbin/lsdev -Cc adapter -t IBM,v-scsi-host -F name,status,physloc|') or die "Error running /usr/sbin/lsdev -Cc adapter -t IBM,v-scsi-host -F name,status,physloc !\n"; +while() { + chomp; + my ($device,$status,$hwpath) = split(/,/); + if ( $hwpath =~ /[^-]+\-V[0-9]+\-(C[0-9]+)/ ) { + $slots{$device}=$1; + } +} + +# get VTD to vhost mapping from ODM +my %vhost2lpar; +my @vtd_vhost_list; +{ + local $/="\n\n"; + @vtd_vhost_list=qx { /usr/bin/odmget -q PdDvLn="virtual_target/vtdev/scdisk" CuDv }; +} +#CuDv: +# name = "server1_dc1_lun1" +# status = 1 +# chgstatus = 0 +# ddins = "" +# location = "" +# parent = "vhost50" +# connwhere = "5" +# PdDvLn = "virtual_target/vtdev/scdisk" +foreach my $vtd ( @vtd_vhost_list ) { + my $vtd_name=""; + my $vtd_parent=""; + if ( $vtd =~ m/\s+name\s+=\s+"(\S+)"/m ) { $vtd_name = $1; } + if ( $vtd =~ m/\s+parent\s+=\s+"(\S+)"/m ) { $vtd_parent = $1; } + if ( $vtd_parent ne "" && $vtd_name ne "" ) { + $vhost2lpar{$vtd_parent}->{'vhost'}=$vtd_parent; # will be filled later, only available/configured vhosts will be put here + foreach my $disk ( keys %disks ) { + if ( defined $disks{$disk}->{'vtd'} && $disks{$disk}->{'vtd'} eq $vtd_name ) { + $disks{$disk}->{'vhost'}=$vtd_parent; + last; + } + } + } +} +undef @vtd_vhost_list; + +#print Data::Dumper->Dump([\%disks]); +#print Data::Dumper->Dump([\%vhost2lpar]); + +# get all unique_id from ODM if there is a matching disk +my @uuid_list; +{ + local $/="\n\n"; + @uuid_list=qx { /usr/bin/odmget -q "attribute=unique_id" CuAt }; +} +foreach my $uuid ( @uuid_list ) { + my $unique_id=""; + my $name=""; + if ( $uuid =~ m/\s+name\s+=\s+"(\S+)"/m ) { $name=$1; } + if ( $uuid =~ m/\s+value\s+=\s+"(\S+)"/m ) { $unique_id=$1; } + if ( $name ne "" && $unique_id ne "" && defined $disks{$name} ) { + $disks{$name}->{'unique_id'}=$unique_id; + } +} +undef @uuid_list; + +# get all lun_id's from ODM +my @lunid_list; +{ + local $/="\n\n"; + @lunid_list=qx { /usr/bin/odmget -q "attribute=lun_id" CuAt }; +} +foreach my $luid ( @lunid_list ) { + my $lun_id=""; + my $name=""; + if ( $luid =~ m/\s+name\s+=\s+"(\S+)"/m ) { $name=$1; } + if ( $luid =~ m/\s+value\s+=\s+"(\S+)"/m ) { $lun_id=$1; } + if ( $name ne "" && $lun_id ne "" && defined $disks{$name} ) { + $disks{$name}->{'lun_id'}=$lun_id; + } +} +undef @uuid_list; +#print Data::Dumper->Dump([\%disks]); + +foreach my $disk ( keys %disks ) { + #open(CMD,"/usr/sbin/lsattr -El $disk|") or die "Error running /usr/sbin/lsattr -El $disk !\n"; + #while() { + # if ( /^lun_id\s+(\S+)/ ) { $disks{$disk}->{'lun_id'}=$1; } # only for some physical disks (e.g. 2145, 1818..) + #} + #close(CMD); + # + # use type and subclass to separate disks + for ($disks{$disk}{'type'}) { + #2105 - 2105 models (ESS) + #2145 - 2145 models (SVC) + #2147 - 2147 models (SVC) + #1750 - 1750 devices (DS 6000) + #2107 - 2107 devices (DS 8000) + #1724 - 1724 devices (DS 4100) + #1814 - 1814 devices (DS 4200 and DS 4700) + #1722 - 1722 devices (DS 4300) + #1742 - 1742 devices (DS 4400 and DS 4500) + #1815 - 1815 devices (DS 4800) + #1818 - 1818 devices (DS 5000) + + if (/2145/) { + # 3321360050768019081CFD800000000000A8404214503IBMfcp + # IBM storage WWN always start with 6005076 + if ( $disks{$disk}->{'unique_id'} =~ m/^\S+(6005076......(.....)..........(..)(..))..2145..IBMfcp/ ) { + $disks{$disk}->{'guid'}=$1; + $disks{$disk}->{'box'}=$2; + $disks{$disk}->{'suid'}="$3:$4"; + $disks{$disk}->{'box_type'}="2145"; + } + # This should work from AIX 5.3 upwards for 2145 + # This might work for DS5000 (type 1818) too, although I don't have a machine to test with + if ( defined $disks{$disk}->{'lun_id'} ) { + if ( $disks{$disk}->{'lun_id'} =~ m/0x(?:([\da-f]+)0{12}|0)\b/ ) { # either 0 or value without 12 zeros + $disks{$disk}->{'scsi_id'}= defined $1 ? hex($1):0; + # m/0x([\da-f]+)\b/; scsi_id = hex ($1) >> 48 works only with 64 bit perl! sigh + } + } + } + elsif (/2107/) { + # 200B75G6831015607210790003IBMfcp + if ( $disks{$disk}->{'unique_id'} =~ m/^....(.......)((..)(..))..2107.....IBMfcp/ ) { + $disks{$disk}{'box'}=$1; + $disks{$disk}{'guid'}="$1_$2"; # this is normally $2 ONLY, but we don't print the box + #undef $disks{$name}; # we don't care about 2107 disks + $disks{$disk}{'suid'}="$3:$4"; + $disks{$disk}->{'box_type'}="2107"; + # There is a lun_id for DS8000 too, but this contains the same info as $3$4 (0x40XX40XX00000000) + $disks{$disk}->{'scsi_id'}="-" + } + } + elsif (/vdisk/) { + + if ( $disks{$disk}->{'unique_id'} =~ m/^\S+(6005076......(.....)..........(..)(..))..2145..IBMfcp..VDASD..AIXvscsi/ ) { + $disks{$disk}{'guid'}=$1; + $disks{$disk}{'box'}=$2; + $disks{$disk}{'suid'}="$3:$4"; + $disks{$disk}->{'box_type'}="2145"; + } + # 3520200B75CF371014A07210790003IBMfcp05VDASD03AIXvscsi" + elsif ( $disks{$disk}->{'unique_id'} =~ m/........(.......)((..)(..))..2107.....IBMfcp..VDASD..AIXvscsi/ ) { + $disks{$disk}{'box'}=$1; + $disks{$disk}{'guid'}="$1_$2"; + $disks{$disk}{'suid'}="$3:$4"; + $disks{$disk}->{'scsi_id'}="-"; + #undef $disks{$disk}; # we don't care about 2107 disks + } + + } + elsif (/scsd/) { # parallel or serial SCSI disk (subclass would be scsi and sas) + delete $disks{$disk}; # we are not interested in those -> remove from hash + } else { + print STDERR "Unknown disk type $disks{$disk}{'type'} for disk $disk found!\n"; + undef $disks{$disk}; + } + } +} + +# get vhost to lpar mapping from kernel kdb... this takes some time! :-) +# kdb queries are slow. let's query some info with the same kdb call (in "parallel"), 15 seems OK, 20 is NOT +# looking for 2 lines: +# Target vSCSI Adapter Structure vhost0 +# client_data.srp_version: 16.aviosserver1 client_data.partition_name: server1 +my $parallel_kdb_queries=15; # 15 queries in 1 kdb call. Reduce if you see errors or missin lpar names +my $kdb_count=0; +my $number_of_vhosts= scalar(keys %vhost2lpar); +my $initial_kdb_command= "svCmdIni; svPrQs; "; +my $kdb_command=$initial_kdb_command; +foreach my $vhost ( keys %vhost2lpar ) { + $kdb_count++; $number_of_vhosts--; + $kdb_command .= "svva $vhost; "; + if ( $kdb_count == $parallel_kdb_queries || $number_of_vhosts == 0 ) { + my $vhost_name=""; my $lpar_name=""; my $lpar_number=""; + open(CMD,"echo \"$kdb_command\" \|/usr/sbin/kdb -script|" ) or die "Error running /usr/sbin/kdb -script !\n"; + while() { + if ( m/\s+Target vSCSI Adapter Structure\s+(\S+)\b/ ) { + $vhost_name=$1; + } + if ( m/client_data.srp_version:\s+\S+\s+client_data.partition_name:\s+(\S+)\b/ ) { + $lpar_name=$1; + } + if ( m/client_data.srp_version:\s+client_data.partition_name:\s+$/ ) { + $lpar_name='none'; # special case of empty vhost + } + if ( m/client_data.partition_number:\s+([\da-fA-F]+)\b/ ) { + $lpar_number=$1; # 0 means "any partition" -> vhost is empty + # this reflects HMC profile values (partition id). + } + if ( $lpar_name ne "" && $vhost_name ne "" && $lpar_number ne "" ) { + $vhost2lpar{$vhost_name}->{'lpar_name'}=$lpar_name; + $vhost2lpar{$vhost_name}->{'lpar_number'}=hex($lpar_number); + $vhost_name=""; $lpar_name=""; $lpar_number=""; + } + } + close(CMD); + $kdb_count=0; $kdb_command=$initial_kdb_command; + } +} + +#print Data::Dumper->Dump([\%vhost2lpar]); + +# subroutine to sort the disks numerically rather than alphabetically +sub disks_numerically { + $a=~ /\D+(\d+)/; + my $aa=$1; + $b=~ /\D+(\d+)/; + my $bb=$1; + $aa <=> $bb; +} +# print the whole thing +printf("%-9s %32s %5s %8s %-8s %7s %4s %18s %-4s %8s %16s %12s %6s\n", "#DISK","UUID","SUID","SIZE","BOX","LOC","SCSI","PVID","SLOT","VHOST","VTD","LPAR","LPARID"); +foreach my $disk ( sort disks_numerically keys %disks) { + # for now just look at available disk... + if ( defined $disks{$disk}->{'status'} && $disks{$disk}->{'status'} eq "Available" ) { + printf("%-9s %32s %5s %8s %-8s %7s %4s %18s %-4s %8s %16s %12s %6s\n", + $disks{$disk}->{'name'}, + $disks{$disk}->{'guid'}, + $disks{$disk}->{'suid'}, + $disks{$disk}->{'size'}, + $disks{$disk}->{'box'}, + defined $locations{$disks{$disk}->{'box'}} ? $locations{$disks{$disk}->{'box'}} : "UNKN", + $disks{$disk}->{'scsi_id'}, + $disks{$disk}->{'pvid'}, + defined $slots{$disks{$disk}->{'vhost'}} ? $slots{$disks{$disk}->{'vhost'}} : "-", + defined $disks{$disk}->{'vhost'} ? $disks{$disk}->{'vhost'} : "-", + defined $disks{$disk}->{'vtd'} ? $disks{$disk}->{'vtd'} : "-", + defined $vhost2lpar{$disks{$disk}->{'vhost'}}->{'lpar_name'} ? $vhost2lpar{$disks{$disk}->{'vhost'}}->{'lpar_name'} : "-", + defined $vhost2lpar{$disks{$disk}->{'vhost'}}->{'lpar_number'} ? $vhost2lpar{$disks{$disk}->{'vhost'}}->{'lpar_number'} : "-" + ); + } +} +#print Data::Dumper->Dump([\%disks]); + +# The end. diff --git a/show_vio_host_mappings b/show_vio_host_mappings new file mode 100755 index 0000000..5badef3 --- /dev/null +++ b/show_vio_host_mappings @@ -0,0 +1,455 @@ +#!/usr/bin/perl +# +# Shows virtual controller (vhost and vfchost) mappings in a VIOS environment +# +# Armin Kunaschik +# Version 1.0 initial release +# Version 1.1 added WWPN of VIO HBA (because SAN guys requested it) +# Version 1.2 removed unnecessary loop that caused bad performance +# Version 1.3 2015-04-30 cosmetic changes, slot numbers are left justified now, ioscli APAR mentioned +# Version 1.4 2020-11-07 added code to read luninfo.cfg, currently not used +# +# Bugs: Script might display garbage for LPAR and/or LPARID when executed while a lpar is booting +# there is no documentation on all possible kdb output :-( +# +# Workaround: Don't execute while lpars are booting :-) +# + +use strict; +#use warnings; +use Getopt::Long; +use XML::Simple; # for config file +#use Data::Dumper; + +my $cfgfile="/etc/luninfo.cfg"; +my $cfg; #configuration hash + +my %options = ( 'disks' => 0, + 'noheader' => 0, + 'help' => 0 +); + +sub usage { + print STDERR "Usage: $0 [-h] | [-H ] | [ -d ]\n"; + print STDERR " display vhost to LPAR mapping\n"; + print STDERR " -h display this help\n"; + print STDERR " -d display vhost to LPAR mapping with mapped disks and vtds (vscsi only)\n"; + print STDERR " -H don't display header line\n"; + exit 1; + +} + +Getopt::Long::Configure ("bundling"); # enable bundling of options like -lp same as -l -p +GetOptions( "d" => \$options{'disks'}, + "H" => \$options{'noheader'}, + "h" => \$options{'help'}, + ) or usage; + +# any option list containing -h will display usage +if ( $options{'help'} ) { usage; }; + +my %locations; + +if ( -e $cfgfile ) { + $cfg=XMLin($cfgfile, ForceArray => 1); +} + +foreach my $location ( keys %{$cfg->{'location'}} ) { + foreach my $element ( keys %{$cfg->{'location'}->{$location}} ) { + for ( $element) { + if ( /storage/ ) { + foreach my $box ( keys %{$cfg->{'location'}->{$location}->{$element}} ) { + if ( defined $locations{$box} ) { + print STDERR "ERROR in $cfgfile: Duplicate use of storage serial $box! Using first definition.\n"; + next; + } + $locations{$box}=$location; + } + } + } + } +} + +#print Data::Dumper->Dump([\%locations]); + +# hashes for vscsi, vfc and physical FC adapters +my %vscsi; +my %vfc; +my %adapters; +# disks hash holds hdisk-vtd mappings and other storage info (location, box, uuid etc) +my %disks; + +# get list of virtual adapters, status and physical locations +open(CMD,"/usr/sbin/lsdev -c adapter -t IBM,v-scsi-host -F'name status physloc'|") or die "Error running lsdev -c adapter -t IBM,v-scsi-host -F'name status physloc'!"; +while() { + chomp; + my ($name, $status, $physloc)=split; + $vscsi{$name}->{"name"}=$name; + $vscsi{$name}->{"status"}=$status; + $vscsi{$name}->{"type"}='v-scsi-host'; + $vscsi{$name}->{"physloc"}=$physloc; + if ( $physloc =~ /[^-]+\-V[0-9]+\-(C[0-9]+)/ ) { + $vscsi{$name}->{"slot"}=$1; + } + +} + +open(CMD,"/usr/sbin/lsdev -c adapter -t IBM,vfc-server -F'name status physloc'|") or die "Error running lsdev -c adapter -t IBM,vfc-server -F'name status physloc'!"; +while() { + chomp; + my ($name, $status, $physloc)=split; + $vfc{$name}->{"name"}=$name; + $vfc{$name}->{"status"}=$status; + $vfc{$name}->{"type"}='vfc-server'; + $vfc{$name}->{"physloc"}=$physloc; + if ( $physloc =~ /[^-]+\-V[0-9]+\-(C[0-9]+)/ ) { + $vfc{$name}->{"slot"}=$1; + } + +} + +open(CMD,'/usr/sbin/lsdev -Cc adapter -F name:status:type|') or die "Error running /usr/sbin/lsdev -Cc adapter -F name:status:type !\n"; +while () { + chomp; + next if ! /^fcs/; # only physical fcs adapters, more to include here? + my ($adapter,$status,$type)=split(/:/); + $adapters{$adapter}->{'status'}=$status; + $adapters{$adapter}->{'name'}=$adapter; + $adapters{$adapter}->{'type'}=$type; + if ( $adapter =~ /^fcs/ ) { + open(CMD1,"/usr/sbin/lscfg -vl $adapter|") or die "Error running /usr/sbin/lscfg -vl $adapter !\n"; + while() { + # get WWPN of physical adapter, easy way, lscfg works only for physical adapters + if ( /\s+Network Address\.+\s*(\S+)/ ) { $adapters{$adapter}->{'wwpn'}=$1; } + } + close(CMD1); + } +} +close(CMD); + +# get mapped adapter name like this: +# lsattr -El vfchost0 -a map_port +# map_port fcs1 Physical FC Port False + +# BEWARE: the man page states, that the field order is not always followed in the output! +# ioscli lsmap -all -npiv -fmt , -field name clntname status fc clntid physloc vfcclient vfcclientdrc +# vfchost0,server1,52,U9117.MMC.0601234-V6-C11,LOGGED_IN,fcs1,fcs3,U9117.MMC.0601234-V52-C17 +# vfchost1,server1,52,U9117.MMC.0601234-V6-C10,LOGGED_IN,fcs2,fcs2,U9117.MMC.0601234-V52-C16 +# vfchost0, ,27,U9117.MMC.0601234-V3-C10,NOT_LOGGED_IN,fcs0, , +# in the future, IV72745 might fix some of this... no, it didn't. +# Wow! IV86769 did not fix lsmap -fmt but added a new option -fmt2... just for me(!), and it took only a few years +# TODO: Change code to use -fmt2 when there is no VIOS 2 anymore + +open(CMD,"cd /tmp; /usr/ios/cli/ioscli lsmap -all -npiv -fmt , -field name clntname status fc clntid physloc vfcclient vfcclientdrc|") or die "Error running ioscli command\n"; +# cd /tmp because ioscli generates ioscli.log in current work directory +while() { + chomp; + my ( $vhost, $lpar_name, $lpar_number, $vio_hw_path, $san_status, $vio_adapter, $lpar_adapter, $lpar_hw_path)=split(/,/); + $lpar_name =~ s/^\s+|\s+$//g; # trim spaces; f*ck IBM (empty entries are no empty strings but " " grrrr), IV86769 adds -fmt2, no trimming anymore + $lpar_adapter =~ s/^\s+|\s+$//g; # trim more spaces; same reason + $lpar_hw_path =~ s/^\s+|\s+$//g; # still triming + $vfc{$vhost}->{'lpar_name'}=$lpar_name; + $vfc{$vhost}->{'lpar_number'}=$lpar_number; + $vfc{$vhost}->{'vio_adapter'}=$vio_adapter; + $vfc{$vhost}->{'lpar_adapter'}=$lpar_adapter; + $vfc{$vhost}->{'san_status'}=$san_status; + $vfc{$vhost}->{'lpar_hw_path'}=$lpar_hw_path; + if ( $lpar_hw_path =~ /[^-]+\-V[0-9]+\-(C[0-9]+)/ ) { + $vfc{$vhost}->{"lpar_slot"}=$1; + } +} + +# get vhost to lpar mapping from kernel kdb... this takes some time! :-) +# kdb queries are slow. let's query some info with the same kdb call (in "parallel"), 15 seems OK, 20 is NOT +# looking for 2 lines: +# Target vSCSI Adapter Structure vhost0 +# client_data.srp_version: 16.aviosserver1 client_data.partition_name: server1 +# client_data.partition_number: A +my $parallel_kdb_queries=15; # 15 queries in 1 kdb call. Reduce if you see errors or missing lpar names +my $kdb_count=0; +my $number_of_vhosts= scalar(keys %vscsi); +my $initial_kdb_command= "svCmdIni; svPrQs; "; +my $kdb_command=$initial_kdb_command; + +foreach my $vhost ( keys %vscsi ) { + $kdb_count++; $number_of_vhosts--; + $kdb_command .= "svva $vhost; "; + if ( $kdb_count == $parallel_kdb_queries || $number_of_vhosts == 0 ) { + my $vhost_name=""; my $lpar_name=""; my $lpar_number=""; + open(CMD,"echo \"$kdb_command\" \|/usr/sbin/kdb -script|" ) or die "Error running /usr/sbin/kdb -script !\n"; + while() { + if ( m/\s+Target vSCSI Adapter Structure\s+(\S+)\b/ ) { + $vhost_name=$1; + } + if ( m/client_data.srp_version:\s+\S+\s+client_data.partition_name:\s+(\S+)\b/ ) { + $lpar_name=$1; # lpar_name is only set correctly when LPAR is running + # when lpar is down, the old name before shutdown is used + # when lpar was never active, name is empty. Same applies to defined adapters that made available + } + if ( m/client_data.srp_version:\s+client_data.partition_name:\s+$/ ) { + $lpar_name='none'; # special case of empty vhost + } + if ( m/client_data.partition_number:\s+([\da-fA-F]+)\b/ ) { + $lpar_number=$1; # 0 means "any partition" -> vhost is empty + # this reflects HMC profile values (partition id). + } + if ( $lpar_name ne "" && $vhost_name ne "" && $lpar_number ne "" ) { + $vscsi{$vhost_name}->{"lpar_name"}=$lpar_name; + $vscsi{$vhost_name}->{"lpar_number"}=hex($lpar_number); + $vhost_name=""; $lpar_name=""; $lpar_number=""; #prevent leftover from earlier values + } + } + close(CMD); + $kdb_count=0; $kdb_command=$initial_kdb_command; + } +} + +# subroutine to sort the disks numerically rather than alphabetically +sub device_numerically { + $a=~ /\D+(\d+)/; + my $aa=$1; + $b=~ /\D+(\d+)/; + my $bb=$1; + $aa <=> $bb; +} + +#print Data::Dumper->Dump([\%vscsi]); +#print Data::Dumper->Dump([\%vfc]); + +# read the disk types and availability +open(CMD,"/usr/sbin/lsdev -c disk -F'name subclass type status'|") or die "Error running lsdev -c disk -F'name subclass type'!"; +while() { + chomp; + if ( m/(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/ ) { + $disks{$1}->{'name'}=$1; + $disks{$1}->{'subclass'}=$2; + $disks{$1}->{'type'}=$3; + $disks{$1}->{'status'}=$4; + } +} +close(CMD); + +# get VTD to disk name mapping from ODM +my @vtd_list; +{ + local $/="\n\n"; # record delimiter is 2 new lines = paragraph + @vtd_list=qx { /usr/bin/odmget -q "attribute=aix_tdev" CuAt }; +} +#CuAt: +# name = "server1_dc1_lun1" +# attribute = "aix_tdev" +# value = "hdisk459" +# type = "R" +# generic = "D" +# rep = "s" +# nls_index = 5 + +foreach my $vtd ( @vtd_list ) { + my $vtd_name=""; + my $vtd_disk=""; + if ( $vtd =~ m/\s+name\s+=\s+"(\S+)"/m ) { $vtd_name = $1; } + if ( $vtd =~ m/\s+value\s+=\s+"(\S+)"/m ) { $vtd_disk = $1; } + if ( $vtd_disk ne "" && $vtd_name ne "" ) { + $disks{$vtd_disk}->{'vtd'}=$vtd_name; + } +} +undef @vtd_list; +#print Data::Dumper->Dump([\%disks]); + +# get VTD to vhost mapping from ODM +my @vtd_vhost_list; +{ + local $/="\n\n"; + @vtd_vhost_list=qx { /usr/bin/odmget -q PdDvLn="virtual_target/vtdev/scdisk" CuDv }; +} +#CuDv: +# name = "server1_dc1_lun1" +# status = 1 +# chgstatus = 0 +# ddins = "" +# location = "" +# parent = "vhost50" +# connwhere = "5" +# PdDvLn = "virtual_target/vtdev/scdisk" +foreach my $vtd ( @vtd_vhost_list ) { + my $vtd_name=""; + my $vtd_parent=""; + if ( $vtd =~ m/\s+name\s+=\s+"(\S+)"/m ) { $vtd_name = $1; } + if ( $vtd =~ m/\s+parent\s+=\s+"(\S+)"/m ) { $vtd_parent = $1; } + if ( $vtd_parent ne "" && $vtd_name ne "" ) { + foreach my $disk ( keys %disks ) { + if ( defined $disks{$disk}->{'vtd'} && $disks{$disk}->{'vtd'} eq $vtd_name ) { + $disks{$disk}->{'vhost'}=$vtd_parent; + $vscsi{$vtd_parent}->{'disks'}->{$disk}=$vtd_name; + last; + } + } + } +} +undef @vtd_vhost_list; + +# get all unique_id from ODM if there is a matching disk +my @uuid_list; +{ + local $/="\n\n"; + @uuid_list=qx { /usr/bin/odmget -q "attribute=unique_id" CuAt }; +} +foreach my $uuid ( @uuid_list ) { + my $unique_id=""; + my $name=""; + if ( $uuid =~ m/\s+name\s+=\s+"(\S+)"/m ) { $name=$1; } + if ( $uuid =~ m/\s+value\s+=\s+"(\S+)"/m ) { $unique_id=$1; } + if ( $name ne "" && $unique_id ne "" && defined $disks{$name} ) { + $disks{$name}->{'unique_id'}=$unique_id; + } +} +undef @uuid_list; + +#print Data::Dumper->Dump([\%disks]); +#print Data::Dumper->Dump([\%vscsi]); + +# copy & paste from luninfo, few lines removed (like lun_id and scsi_id stuff) +foreach my $disk ( keys %disks ) { + # use type and subclass to separate disks + for ($disks{$disk}{'type'}) { + #2105 - 2105 models (ESS) + #2145 - 2145 models (SVC) + #2147 - 2147 models (SVC) + #1750 - 1750 devices (DS 6000) + #2107 - 2107 devices (DS 8000) + #1724 - 1724 devices (DS 4100) + #1814 - 1814 devices (DS 4200 and DS 4700) + #1722 - 1722 devices (DS 4300) + #1742 - 1742 devices (DS 4400 and DS 4500) + #1815 - 1815 devices (DS 4800) + #1818 - 1818 devices (DS 5000) + + if (/2145/) { + # 3321360050768019081CFD800000000000A8404214503IBMfcp + # IBM storage WWN always start with 6005076 + if ( $disks{$disk}->{'unique_id'} =~ m/^\S+(6005076......(.....)..........(..)(..))..2145..IBMfcp/ ) { + $disks{$disk}->{'guid'}=$1; + $disks{$disk}->{'box'}=$2; + $disks{$disk}->{'suid'}="$3:$4"; + $disks{$disk}->{'box_type'}="2145"; + } + } + elsif (/2107/) { + # 200B75G6831015607210790003IBMfcp + if ( $disks{$disk}->{'unique_id'} =~ m/^....(.......)((..)(..))..2107.....IBMfcp/ ) { + $disks{$disk}{'box'}=$1; + $disks{$disk}{'guid'}="$1_$2"; # this is normally $2 ONLY, but we don't print the box + #undef $disks{$name}; # we don't care about 2107 disks + $disks{$disk}{'suid'}="$3:$4"; + $disks{$disk}->{'box_type'}="2107"; + } + } + elsif (/vdisk/) { + + if ( $disks{$disk}->{'unique_id'} =~ m/^\S+(6005076......(.....)..........(..)(..))..2145..IBMfcp..VDASD..AIXvscsi/ ) { + $disks{$disk}{'guid'}=$1; + $disks{$disk}{'box'}=$2; + $disks{$disk}{'suid'}="$3:$4"; + $disks{$disk}->{'box_type'}="2145"; + } + # 3520200B75CF371014A07210790003IBMfcp05VDASD03AIXvscsi" + elsif ( $disks{$disk}->{'unique_id'} =~ m/........(.......)((..)(..))..2107.....IBMfcp..VDASD..AIXvscsi/ ) { + $disks{$disk}{'box'}=$1; + $disks{$disk}{'guid'}="$1_$2"; + $disks{$disk}{'suid'}="$3:$4"; + #undef $disks{$disk}; # we don't care about 2107 disks + } + + } + elsif (/scsd/) { # parallel or serial SCSI disk (subclass would be scsi and sas) + delete $disks{$disk}; # we are not interested in those -> remove from hash + } else { + print STDERR "Unknown disk type $disks{$disk}{'type'} for disk $disk found!\n"; + undef $disks{$disk}; + } + } +} + +#print Data::Dumper->Dump([\%disks]); + +# Output +# -d +if ( $options{'disks'} ) { + + if ( ! $options{'noheader'} ) { # option -H + printf("%-9s %-9s %-4s %6s %-10s %-8s %-16s %-5s %-7s %-5s\n", + "#ADAPTER","STATE","SLOT","LPARID","LPAR","HDISK","VTD","LOC","BOX","UID"); + } + # only vscsi vhosts have luns associated + foreach my $vhost (sort device_numerically keys %vscsi) { + # do we have disks connected to this vhost? + if ( scalar ( keys %{$vscsi{$vhost}->{'disks'}}) != 0 ) { + foreach my $disk ( keys %{$vscsi{$vhost}->{'disks'}} ) { + printf("%-9s %-9s %-4s %6s %-10s %-8s %-16s %-5s %-7s %-5s\n", + $vscsi{$vhost}->{'name'}, + $vscsi{$vhost}->{'status'}, + $vscsi{$vhost}->{'slot'}, + defined $vscsi{$vhost}->{'lpar_number'} ? $vscsi{$vhost}->{'lpar_number'} : "-", + defined $vscsi{$vhost}->{'lpar_name'} ? $vscsi{$vhost}->{'lpar_name'} : "-", + $disk, + $vscsi{$vhost}->{'disks'}->{$disk}, + defined $locations{$disks{$disk}->{'box'}} ? $locations{$disks{$disk}->{'box'}} : "UNKN", + defined $disks{$disk}->{'box'} ? $disks{$disk}->{'box'} : "UNKN", + defined $disks{$disk}->{'suid'} ? $disks{$disk}->{'suid'} : "-" + ); + } + } else { # empty vhost + printf("%-9s %-9s %-4s %6s %-10s %-8s %-16s %-5s %-7s %-5s\n", + $vscsi{$vhost}->{'name'}, + $vscsi{$vhost}->{'status'}, + $vscsi{$vhost}->{'slot'}, + defined $vscsi{$vhost}->{'lpar_number'} ? $vscsi{$vhost}->{'lpar_number'} : "-", + defined $vscsi{$vhost}->{'lpar_name'} ? $vscsi{$vhost}->{'lpar_name'} : "-", + 'none', + 'none', + '-', + '-', + '-' + ); + } + } +} else { +# no option + + if ( ! $options{'noheader'} ) { # option -H + printf("%-9s %-9s %-4s %-5s %-16s %6s %-10s %-5s %-5s %14s\n", + "#ADAPTER","STATE","SLOT","HBA","WWPN","LPARID","LPAR","LHBA","VSLOT","SAN"); + } + # vscsi goes first, some fields are not available -> "-" + foreach my $vhost (sort device_numerically keys %vscsi) { + printf("%-9s %-9s %-4s %-5s %-16s %6s %-10s %-5s %-5s %14s\n", + $vscsi{$vhost}->{'name'}, + $vscsi{$vhost}->{'status'}, + $vscsi{$vhost}->{'slot'}, + "-", + "-", + $vscsi{$vhost}->{'lpar_number'}, + $vscsi{$vhost}->{'lpar_name'}, + "-", + "-", + "-" + ); + } + foreach my $vhost (sort device_numerically keys %vfc) { + printf("%-9s %-9s %-4s %-5s %-16s %6s %-10s %-5s %-5s %14s\n", + $vfc{$vhost}->{'name'}, + $vfc{$vhost}->{'status'}, + $vfc{$vhost}->{'slot'}, + $vfc{$vhost}->{'vio_adapter'}, + defined $adapters{$vfc{$vhost}->{'vio_adapter'}}->{'wwpn'} ? $adapters{$vfc{$vhost}->{'vio_adapter'}}->{'wwpn'} : "-", + $vfc{$vhost}->{'lpar_number'}, + ( defined $vfc{$vhost}->{'lpar_name'} && $vfc{$vhost}->{'lpar_name'} ne "" ) ? $vfc{$vhost}->{'lpar_name'} : "-", + ( defined $vfc{$vhost}->{'lpar_adapter'} && $vfc{$vhost}->{'lpar_adapter'} ne "" ) ? $vfc{$vhost}->{'lpar_adapter'} : "-", + ( defined $vfc{$vhost}->{'lpar_slot'} && $vfc{$vhost}->{'lpar_slot'} ne "" ) ? $vfc{$vhost}->{'lpar_slot'} : "-", + $vfc{$vhost}->{'san_status'} + ); + + } +} + +# The end.