#!/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);