#!/usr/bin/env perl # Script: HP-UX-11iv3-list-disks.pl # Last Update: 17 June 2011 # Designed by: Dusan U. Baljevic (dusan.baljevic@ieee.org) # Coded by: Dusan U. Baljevic (dusan.baljevic@ieee.org) # # Copyright 2011-2015 Dusan Baljevic # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # It audits the PV/LUNs on HP-UX 11.31 servers and # also checks if they are on local or remote storage # (based on their Serial Numbers that are listed # in conffile). # # If you obtain this script via Web, convert it to Unix format: # dos2ux HP-UX-11iv3-list-disks.pl.txt > HP-UX-11iv3-list-disks.pl # # Usage: # HP-UX-11iv3-list-disks.pl [-h] [-l "location"] -f conffile [> myserv.csv] # # -h Print this help message # -f conffile Lookup disk parameters from a config file # -l "location" Site location of the local server # # Here is a typical scenario how the conffile should look like: # (Serial Numbers or the beginning of them), obtained via # "scsimgr get_info -D disk". # # ZU3VLF29SZM5C8 = "DS Site1" # 7VB36XM = "DS Site2" # RKMU4A29SZM4T0AFV77648CZ = "SWC Site2" # 9AN52C1 = "XP Site3" # pp796a29Szm4t0afvkb72dan0021zk = "EVA Site4" # 46F73DS = "XP Disaster Recovery Site5" # # ... where DS, SWC, XP, EVA... define the storage type, # and the rest of the string on the right-hand side # define the PV's physical site location (for example, # "Site2", "Disaster Recovery Site5", and so on). # In this specific case, "DS" storage type contains LUN numbers # in the last four characters of the PV's serial number and # all other storage types have LUNs defined as last four # characters of their WWNs. Change it if the rule in your # environment is different. # # The names of storage types (DS, XP, EVA,...) are entirely personal # choice and each customer can choose their own ones! # # The script has been developed over several hectic days, so errors # (although not planned) might exist. Please use with care. # # Like all scripts and programs, this one will continue to # change as needs change. # # I must admit the documentation of the code needs to improve! # Define important environment variables # $ENV{'PATH'} = "/bin:/usr/sbin:/sbin:/usr/bin:/opt/ignite/bin:/opt/ignite/lbin"; $ENV{'PATH'} = "$ENV{PATH}:/usr/sam/lbin:/usr/lbin:/usr/local/sbin"; $ENV{'PATH'} = "$ENV{PATH}:/usr/contrib/bin"; # Occasionally commands hang, so we need to manage # how long they run (900 seconds in this case). # my $TIMEOUT = 900; # Initialized some variables. # my @PVALLARR = (); my @PVARR = (); my @PVS = (); my $IOSCAN_FLAG = 0; # Enforce strictness # if ( eval "require strict" ) { import strict; use strict; no strict 'refs'; } # Check Perl code # # use diagnostics; # use warnings; # Display usage if "-h" option is used. # sub Prusage { print < myserv.csv] -h Print this help message -f conffile Lookup disk parameters from a config file -l "location" Site location of the local server (use double quotes if location name contains whitespaces) MYMSG ; exit(0); } # If option "-h" is used, show script usage options and exit. # sub Usage { if ( eval "require File::Basename" ) { import File::Basename; $CMD = basename( "$0", ".pl" ); Prusage(); } else { $CMD = `basename $0`; chomp($CMD); Prusage(); } } # Subroutine to run ioscan and audit PVs. # sub IOSCAN_NO_HW { if ( $IOSCAN_FLAG > 0 ) { print "Hardware ioscan already initiated by some other process\n"; die "Multiple ioscans typically relate to faulty devices\n"; } eval { # Occasionally (for example, when PVs are in NO_HW state) ioscan hangs, # so we need to manage how long it runs. # local $SIG{ALRM} = sub {die "\n$WARNSTR Alarm - command interrupted\n"}; alarm $TIMEOUT; @PVALLARR = `ioscan -funNFC disk | awk '!/:/ {print \$1}' | sed -e 's/_p1/_p2/g' -e 's/_p3/_p2/g'| sort |uniq`; alarm 0; }; if ( $@ ) { die "Command \"ioscan\" timed out after $TIMEOUT seconds\n"; } if ( @PVALLARR ) { chomp(@PVALLARR); } my @swaps = `swapinfo -ad | awk '/^dev/ {print \$NF}'`; my @vginfo = `vginfo -v 2>/dev/null`; # my @ioscanF = `ioscan -F -m dsf | awk '!/_p1|_p3/ {print}'`; # Process all LVs. # if ( open( LVOL, "lvlist -p 2>/dev/null |" ) ) { while () { @lvolarr = split( /\|/, $_ ); if ( open( NN, "lvdisplay -v $lvolarr[0] 2>/dev/null |" ) ) { while () { next if ( grep( /^$/, $_ ) ); $_ =~ s/^\s+//g; chomp; @LVTOT = split( /\//, $lvolarr[0] ); $lvreal = $LVTOT[-1]; if ( grep( /^Mirror copies/, $_ ) ) { ( undef, undef, $mirrno ) = split( /\s+/, $_ ); $LVMIRR{$lvolarr[0]} = $mirrno; } my @swpgr = split( /\s+/, $_ ); if ( grep( /^\/dev\/dsk|^\/dev\/disk/, $swpgr[0] ) ) { my $vals = $swpgr[0]; push( @{ $disklist{$vals} }, "$lvolarr[0];$LVMIRR{$lvolarr[0]}" ); } } close(NN); } } close(LVOL); } # Print header of the final report. # push(@FINAL, "DISK;VG;USAGE;SIZE(KB);S/N;WWN;STORAGE;PV_LOCATION;LUN;VGSTATUS;DISKOWNER;STALE_EXTENTS;PV_REMOTE_STATUS;LV;LV_MIRRORS\n"); # Firstly, process PVs that are defined in VGs (LVM initialized). # if ( @vginfo ) { foreach my $ssr (@vginfo) { chomp($ssr); my @lssfarr = split( /:/, $ssr ); my $lssfdev = $lssfarr[ $#lssfarr - 2 ]; my @pvl = split( /@/, $lssfdev ); my $pvs1 = $pvl[0]; my $pvs2 = $pvs1; $pvs2 =~ s/_p2//g; push(@PVS, $pvs1); if ( "$pvs1" ne "$pvs2" ) { if ( ! grep(/$pvs1/, @PVS) ) { push(@PVS, $pvs2); } } $vgname = $lssfarr[0]; $vgstatus = $lssfarr[1]; $vgstatus =~ s/\@$//g; CHECKDISK($pvs1); } } # Secondly, process PVs that are listed in ioscan but are not # initialized in LVM. We calculate the difference between the two # arrays (@PVS and @PVALLARR). # my @union = my @intersection = my @difference = (); my %count = (); foreach my $element (sort @PVS, sort @PVALLARR) { chomp($element); $count{$element}++; } foreach $element (keys %count) { push @union, $element; push @{ $count{$element} > 1 ? \@intersection : \@difference }, $element; } if ( @difference ) { foreach my $diffdk (@difference) { $vgname = q{}; $vgstatus = q{}; if ( ! grep(/${diffdk}_p2/, @difference) ) { if ( ! grep(/\Q$diffdk\E/, @PVS) ) { CHECKDISK($diffdk); } } } } } # Who "owns" the PV (as reported by command diskowner(1M)). # sub CKDISKOWN { my ($whoowns, $pvs) = @_; chomp($whoowns); if ( "$whoowns" eq "lvm." ) { $DISKOWN{$pvs} = "LVM"; } elsif ( "$whoowns" eq "lvm.vxfs." ) { $DISKOWN{$pvs} = "VxVM"; } elsif ( "$whoowns" eq "partition." ) { $DISKOWN{$pvs} = "Virtual Machine"; } else { $DISKOWN{$pvs} = "Not owned by any HP-UX subsystem"; } } # Print PV details in the final report. # sub MYDISK { my ($SPV1, $PVS1) = @_; if ( ! "$PVDANGER{$PVS1}" ) { $PVDANGER{$PVS1} = "BLUE: Unknown status"; } $RESULT = sprintf("%-10s;%-10s;%-12s;%-15d;%-25s;%-35s;%-10s;%-10s;%-4s;%-55s;%-10s;%-5s;%-35s", $SPV1, $VG{$PVS1}, $USAGE{$PVS1}, $CAP{$PVS1}, $SN{$PVS1}, $WWN{$PVS1}, $STORAGE{$PVS1}, $LOCATION{$PVS1}, uc($LUN{$PVS1}), $VGSTAT{$PVS1}, $DISKOWN{$PVS1}, $STALE{$PVS1}, $PVDANGER{$PVS1}, ); if ( ! (grep/$RESULT/, @FINAL) ) { push(@FINAL, $RESULT); } $counter = 1; # Process all LVs that belong to a PV. # foreach $servdsk ( sort keys %disklist ) { if ( ("$servdsk" eq "/dev/disk/$spv") || ("$servdsk" eq "/dev/dsk/$spv") ) { foreach $lv ( @{$disklist{$servdsk}} ) { if ( $counter > 1) { $R2 = sprintf("%-10s;%-10s;%-12s;%-15s;%-25s;%-35s;%-10s;%-10s;%-15s;%-55s;%-10s;%-5s;%-35s;%s\n", " ", " "," "," "," "," "," ", " "," "," "," "," "," ",$lv ); } else { $R2 = ";$lv\n", } $counter++; if ( ! (grep/\Q$R2\E/, @FINAL) ) { push(@FINAL, $R2); } } } } push(@FINAL, "\n"); } # Check each PV. # sub CHECKDISK { my $pvs = shift; chomp($pvs); push(@PVARR, $pvs); $USAGE{$pvs} = "Data"; my $rawa = $pvs; $rawa =~ s/\/dsk\//\/rdsk\//g; $rawa =~ s/\/disk\//\/rdisk\//g; if ( open( SSRR, "scsimgr get_info -D $rawa 2>/dev/null |" ) ) { while () { chomp($_); if ( grep(/^Serial number/, $_) ) { (undef, $SNNO) = split(/=/, $_ ); $SNNO =~ s/^\s+//g ; $SNNO =~ s/\s+$//g ; $SNNO =~ s/"//g ; } if ( grep(/^Capacity in number of blocks/, $_) ) { (undef, $CAP) = split(/=/, $_ ); $CAP =~ s/^\s+//g ; $CAP =~ s/\s+$//g ; $CAPFIN = int($CAP / 2); } if ( grep(/^World Wide Identifier/, $_) ) { (undef, $WWN) = split(/=/, $_ ); $WWN =~ s/^\s+//g ; $WWN =~ s/\s+$//g ; } } close(SSRR); } # Run pvdisplay(1M) on each PV and check stale extents. # if ( open( VTB, "pvdisplay $pvs 2>/dev/null |" ) ) { while () { next if ( grep( /^$/, $_ ) ); $_ =~ s/^\s+//g; if ( grep( /^Stale PE/, $_ ) ) { $_ =~ s/^\s+//g; ( undef, undef, $STALEPE ) = split( /\s+/, $_ ); chomp($STALEPE); if ( "$STALEPE" > 0 ) { $STALE{$pvs} = "Yes"; } else { $STALE{$pvs} = "No"; } } } } close(VTB); if ( grep(/_p2|-s2/, $pvs) ) { $USAGE{$pvs} = "System Disk"; } $WWN{$pvs} = $WWN; $SN{$pvs} = $SNNO; $SN{$pvs} =~ s/^\s+//g ; $SN{$pvs} =~ s/\s+$//g ; $CAP{$pvs} = $CAPFIN; $VG{$pvs} = $vgname; $VGSTAT{$pvs} = $vgstatus; $diskowner = `diskowner $pvs 2>&1 | grep -i owned`; if ( ! grep(/_p2|s2/, $pvs) ) { $diskowner2 = `diskowner ${pvs}_p2 2>&1 | grep -i owned`; @disko2 = split( /\s+/, $diskowner2 ); } @disko = split( /\s+/, $diskowner ); if ( "@disko2" ) { CKDISKOWN("$disko2[-1]", "$pvs"); } else { CKDISKOWN("$disko[-1]", "$pvs"); } @shortpv = split( /\//, $pvs ); $spv = $shortpv[3]; if ( grep(/shared/, $VGSTAT{$pvs}) ) { $DISKOWN{$pvs} = "SLVM"; } # Is the S/N for PV listed in the configuration file? # If so, process it. # if ( ! grep(/\Q$SN{$pvs}\E/i, @CONFARR) ) { $LUN{$pvs} = q{}; $STORAGE{$pvs} = q{}; $LOCATION{$pvs} = q{}; $DS{$pvs} = q{}; if ( ! ( "$SN{$pvs}" && "$WWN{$pvs}") ) { $SN{$pvs} = "N/A"; $STORAGE{$pvs} = "CD/DVD DRIVE"; } &MYDISK("$spv", "$pvs"); } # If the S/N for PV is NOT listed in the configuration file, # we process it without checking LUN details. # else { foreach $Config_key (keys %MYCONF) { $LUN{$pvs} = q{}; $STORAGE{$pvs} = q{}; $LOCATION{$pvs} = q{}; $DS{$pvs} = q{}; $MYCONF{$Config_key} =~ s/"//g; $PVDANGER{$pvs} = q{}; if ( "uc($Config_key)" eq "uc($SN{$pvs})" ) { ($STORAGE{$pvs}, $LOCATION{$pvs}) = split( /\s+/, $MYCONF{$Config_key} ); $LUN{$pvs} = substr($WWN{$pvs}, -4); # "DS" storage types that contain LUN numbers in the last four # characters of the serial numbers (all other storage types have # LUNs defined as last four characters of their WWNs). # if ( "$STORAGE{$pvs}" eq "DS" ) { $DS{$pvs} = "$Config_key"; if ( "$SN{$pvs}" =~ m/^$DS{$pvs}.*/ ) { $LUN{$pvs} = substr($SN{$pvs}, -4); } } # Check strings for server location and the PV/LUN location. # The string comparison is NOT case-sensitive! # if ( "$MYLOCATION" ) { if ( lc("$MYLOCATION") ne lc("$LOCATION{$pvs}") ) { $PVDANGER{$pvs} = "RED: Local site $MYLOCATION has PV on remote storage $LOCATION{$pvs}"; } elsif ( lc("$MYLOCATION") eq lc("$LOCATION{$pvs}") ) { $PVDANGER{$pvs} = "GREEN: Local site $MYLOCATION has PV on local storage"; } else { $PVDANGER{$pvs} = "BLUE: Unknown status"; } } if ( ! ( "$SN{$pvs}" && "$WWN{$pvs}") ) { $SN{$pvs} = "N/A"; $STORAGE{$pvs} = "CD/DVD DRIVE"; } &MYDISK("$spv", "$pvs"); } } # push(@FINAL, "\n"); } } # Verify if another ioscan is already running. # Old or long-running ioscan are signs of hung # or faulty devices. # sub rawpscheck { # Under XPG4 (Unix95), "-H" flag option gives pstree-line results # if ( "$ENV{'UNIX95'}" == 1 ) { $pstreeflag = "H"; } if ( open( KM, "ps -ef${pstreeflag} |" ) ) { while () { if ( grep( /ioscan/, $_ ) ) { $IOSCAN_FLAG++; } } } close(KM); } # Parse conffile to find location of # PVs based on their Serial Numbers. # sub parse_config_file { local ($config_line, $Name, $Value, $MYCONF); ($File, $MYCONF) = @_; if (!open (CONFIG, "$File")) { print "ERROR: Config file not found : $File"; exit(1); } while () { $config_line=$_; chomp($config_line); $config_line =~ s/^\s+//; $config_line =~ s/\s+$//; if ( ($config_line !~ /^#/) && ($config_line ne "") ){ ($Name, $Value) = split (/=/, $config_line); $Name =~ s/^\s+//; $Name =~ s/\s+$//; $Value =~ s/^\s+//; $Value =~ s/\s+$//; $MYCONF{$Name} = $Value; push(@CONFARR, $Name); } } close(CONFIG); } # Find the intersection between two arrays. # sub intersection { my ($first, $second) = @_; } # Do not allow to run as unprivileged user. # if ( $> != 0 ) { print "This script should be run with root privileges\n"; exit(1); } # Ensure that modules are loaded. # if ( eval "require Getopt::Std" ) { import Getopt::Std; $opts{f} = q{}; getopts( 'f:l:h', \%opts ); if ( $opts{h} ) { &Usage; } $CONFFILE = $opts{f} || q{}; $MYLOCATION = $opts{l} || q{}; # Read in conffile with variables. # if ( -s "$CONFFILE" ) { &parse_config_file ($CONFFILE, \%MYCONF); } else { die "Disk assessment configuration file cannot be processed\n"; } } else { die "Perl module Getopt::Std not found\n"; } rawpscheck(); IOSCAN_NO_HW(); if ( "@FINAL" ) { print @FINAL; } exit(0);