-
Notifications
You must be signed in to change notification settings - Fork 0
/
show_vio_disk_mappings
executable file
·517 lines (471 loc) · 17.1 KB
/
show_vio_disk_mappings
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
#!/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
# Version 2.5 2021-03-17 storage subs, added purestorage, codereview
# Version 2.6 2021-04-12 added Nimble storage
#
# 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 5.010; # because if //
#use Data::Dumper;
use List::Util qw( max );
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(<CMD>) {
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(<CMD>) {
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(<CMD1>) {
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 "" and $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(<CMD>) {
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 "" and $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'} and $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 "" and $unique_id ne "" and 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 "" and $lun_id ne "" and defined $disks{$name} ) {
$disks{$name}->{'lun_id'}=$lun_id;
}
}
undef @uuid_list;
#print Data::Dumper->Dump([\%disks]);
# identify various disks from unique_id
sub identify_2145 {
my $disksref = shift;
my $d = shift;
# SVC disks:
# 3321360050768019081CFD800000000000A8404214503IBMfcp
# SVC LUNs visible directly via FC or NPIV but without SDDPCM:
# 33213600507680180867968000000000006E804214503IBMfcp
# SVC LUNs visible through VIO:
# 48333321360050768019081CFD8000000000009D804214503IBMfcp05VDASD03AIXvscsi
# IBM storage WWN always start with 6005076
if ( defined $disksref->{$d}->{'unique_id'} and $disksref->{$d}->{'unique_id'} =~ m/^\S+(6005076......(.....)..........(..)(..))..2145..IBMfcp/ ) {
$disksref->{$d}->{'uuid'}=$1;
$disksref->{$d}->{'box'}=$2;
$disksref->{$d}->{'suid'}="$3:$4";
$disksref->{$d}->{'box_type'}="2145";
if ( defined $disksref->{$d}->{'lun_id'} ) {
if ( $disksref->{$d}->{'lun_id'} =~ m/0x(?:([\da-f]+)0{12}|0)\b/ ) { # either 0 or value without 12 zeros
$disksref->{$d}->{'scsi_id'}= defined $1 ? hex($1):0;
}
}
# 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
return 1; # true
}
return 0; # false
}
sub identify_2107 {
my $disksref = shift;
my $d = shift;
# DS8000 family LUN
# 200B75G6831015607210790003IBMfcp
if ( defined $disksref->{$d}->{'unique_id'} and $disksref->{$d}->{'unique_id'}=~m/^....(.......)((..)(..))..2107900..IBMfcp/ ) {
$disksref->{$d}->{'box'}=$1;
$disksref->{$d}->{'uuid'}="$1_$2"; # this is normally $2 ONLY, but we don't print the box
$disksref->{$d}->{'suid'}="$3:$4";
$disksref->{$d}->{'box_type'}="2107";
# There is a lun_id for DS8000 too, but this contains the same info as $3$4 (0x40XX40XX00000000)
return 1; # true
}
return 0; # false
}
# identify Hitachi VSP GX00 disks
sub identify_htc_vsp_gx00 {
my $disksref = shift;
my $d = shift;
# 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 $disksref->{$d}->{'unique_id'} and $disksref->{$d}->{'unique_id'}=~ m/........(....)((..)(..))..OPEN-...HITACHIfcp/ ) {
$disksref->{$d}->{'box'}=$1;
$disksref->{$d}->{'uuid'}="$1_$2";
$disksref->{$d}->{'suid'}="$3:$4";
$disksref->{$d}->{'box_type'}="HTC";
if ( defined $disksref->{$d}->{'lun_id'} ) {
if ( $disksref->{$d}->{'lun_id'} =~ m/0x(?:([\da-f]+)0{12}|0)\b/ ) { # either 0 or value without 12 zeros
$disksref->{$d}->{'scsi_id'}= defined $1 ? hex($1):0;
}
}
return 1; # true
}
return 0; # false
}
# identify Purestorage disks
sub identify_purestorage {
my $disksref = shift;
my $d = shift;
# puredisk
# tested with Purestorage, box type unknown
# 3A213624A93708D4089CD6DB5405A000113E70AFlashArray04PUREfcp
# Important: The box or array id does not change when a lun is moved later on to another box! Take care!
# see https://www.codyhosterman.com/2016/01/vmfs-snapshots-and-the-flasharray-part-iv-how-to-correlate-a-vmfs-to-a-flasharray-volume/
# and https://www.codyhosterman.com/2018/02/volume-matching-via-the-api-in-purity-5-0/
if ( defined $disksref->{$d}->{'unique_id'} and $disksref->{$d}->{'unique_id'}=~ m/.....624A9370((................)(........))..FlashArray04PUREfcp/ ) {
$disksref->{$d}->{'box'}=$2; # ????
$disksref->{$d}->{'uuid'}="$1";
my $tmpsuid=$3;
$tmpsuid=~ s/^0+//g; # remove leading zeros
$disksref->{$d}->{'suid'}="$tmpsuid";
$disksref->{$d}->{'box_type'}="PURE";
if ( defined $disksref->{$d}->{'lun_id'} ) {
if ( $disksref->{$d}->{'lun_id'} =~ m/0x(?:([\da-f]+)0{12}|0)\b/ ) { # either 0 or value without 12 zeros
$disksref->{$d}->{'scsi_id'}= defined $1 ? hex($1):0;
}
}
return 1; # true
}
return 0; # false
}
# identify Nimble volumes
sub identify_nimblevolume {
my $disksref = shift;
my $d = shift;
# nimblevolume
# tested with Nimble box (unknown version). ODM package necessary!
# 372024b0c462a17e9aa76c9ce9004c7b937406Server06Nimblefcp
# the numbers in front of Server and Nimble seem to be the length of the following string, let's not depend on that 8-)
if ( defined $disksref->{$d}->{'unique_id'} and $disksref->{$d}->{'unique_id'}=~ m/....((................)6c9ce(...........))..Server..Nimblefcp/ ) {
$disksref->{$d}->{'box'}=$3;
$disksref->{$d}->{'uuid'}="$1";
$disksref->{$d}->{'suid'}="$2";
$disksref->{$d}->{'box_type'}="NIMB";
if ( defined $disksref->{$d}->{'lun_id'} ) {
if ( $disksref->{$d}->{'lun_id'} =~ m/0x(?:([\da-f]+)0{12}|0)\b/ ) { # either 0 or value without 12 zeros
$disksref->{$d}->{'scsi_id'}= defined $1 ? hex($1):0;
}
}
return 1; # true
}
return 0; # false
}
# copy & paste from luninfo, few lines removed (like lun_id and scsi_id stuff)
foreach my $disk ( keys %disks ) {
#open(CMD,"/usr/sbin/lsattr -El $disk|") or die "Error running /usr/sbin/lsattr -El $disk !\n";
#while(<CMD>) {
# 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/) {
identify_2145(\%disks, $disk);
}
elsif (/2107/) {
identify_2107(\%disks, $disk);
}
elsif (/puredisk/) {
identify_purestorage(\%disks, $disk);
}
elsif (/nimblevolume/) {
identify_nimblevolume(\%disks, $disk);
}
elsif (/vdisk/) {
identify_2145(\%disks, $disk) or
identify_purestorage(\%disks, $disk) or
identify_nimblevolume(\%disks, $disk) or
identify_2107(\%disks, $disk);
}
elsif (/mpioosdisk/) { # generic AIX MPIO
identify_2145(\%disks, $disk) or
identify_purestorage(\%disks, $disk) or
identify_nimblevolume(\%disks, $disk) or
identify_2107(\%disks, $disk);
}
elsif (/htcvspgx00mpio/) { # Hitachi VSP GX00 MPIO
identify_htc_vsp_gx00(\%disks, $disk);
}
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 (/osdisk/) { # generic disk without ODM package or driver
print STDERR "Generic $disks{$disk}->{'type'} for disk $disk is not supported! Please install MPIO ODM or driver!\n";
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 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 or $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(<CMD>) {
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 "" and $vhost_name ne "" and $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;
}
# set dynamic width for some columns
my $max_box_len=max(3, map length($_->{'box'}//""), values %disks);
my $max_uuid_len=max(5, map length($_->{'uuid'}//""), values %disks);
my $max_uid_len=max(5, map length($_->{'suid'}//""), values %disks);
# print the whole thing
printf("%-9s %*s %*s %8s %-*s %7s %4s %18s %-4s %8s %16s %12s %6s\n",
"#DISK",
$max_uuid_len,"UUID",
$max_uid_len,"SUID",
"SIZE",
$max_box_len,"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'} and $disks{$disk}->{'status'} eq "Available" ) {
printf("%-9s %*s %*s %8s %-*s %7s %4s %18s %-4s %8s %16s %12s %6s\n",
$disks{$disk}->{'name'},
$max_uuid_len, $disks{$disk}->{'uuid'} // "-",
$max_uid_len, $disks{$disk}->{'suid'} // "-",
$disks{$disk}->{'size'} // "-",
$max_box_len, $disks{$disk}->{'box'} // "-",
$locations{$disks{$disk}->{'box'}} // "UNKN",
$disks{$disk}->{'scsi_id'} // "-",
$disks{$disk}->{'pvid'} // "-",
(defined $disks{$disk}->{'vhost'} and $slots{$disks{$disk}->{'vhost'}} ) ? $slots{$disks{$disk}->{'vhost'}} : "-",
$disks{$disk}->{'vhost'} // "-",
$disks{$disk}->{'vtd'} // "-",
(defined $disks{$disk}->{'vhost'} and $vhost2lpar{$disks{$disk}->{'vhost'}}->{'lpar_name'} ) ? $vhost2lpar{$disks{$disk}->{'vhost'}}->{'lpar_name'} : "-",
(defined $disks{$disk}->{'vhost'} and $vhost2lpar{$disks{$disk}->{'vhost'}}->{'lpar_number'} ) ? $vhost2lpar{$disks{$disk}->{'vhost'}}->{'lpar_number'} : "-"
);
}
}
#print Data::Dumper->Dump([\%disks]);
# The end.