#!/usr/bin/perl -w # See comments at the tail of this script for collected diagrams of # the data structures. use strict; use Data::Dumper; use Getopt::Long; $ENV{PATH} .= ":/usr/storapi/bin"; my $debug = 0; my $help = 0; sub usage { print "Usage:\n $0 \n\n", " --debug, -d Increment degree of debug output\n", " --help, -h Display this message\n\n"; exit 0; } GetOptions('debug+' => \$debug, 'help' => \$help); usage() if ($help); my $sym_id; if (defined($ARGV[0]) && $ARGV[0] =~ m/[0-9A-Fa-f]*/) { $sym_id = $ARGV[0]; } else { print "You must provide a Symmetric ID to scan.\n"; usage(); } my @lines = qx{symdev -sid $sym_id list}; exit $? if $? > 0; sub humanize { my $item = shift(@_); if ($item =~ m/^[0-9]+$/) { my @powers = qw(KB MB GB TB PB); my $power = 0; while ($item > 1024) { $item /= 1024; $power++; } return sprintf "%.2f %s", $item, $powers[$power]; } else { return $item; } } my @devs; foreach my $line (@lines) { my @parts = split(/\s+/, $line); push @devs, $parts[0] if defined($parts[0]) && $parts[0] =~ m/^[0-9A-E]{4}$/; # {4} IS safe for all cases, based on a symdev error message: # "The sym device name contains non-hex characters or is longer # than 4 digits". # It'd be nice to populate %dev_info here, but, for one thing, some # fields (phys dev name & dev attributes) have internal spaces # and, for a second, we're going to have to run a state machine # across each devs symdev show output anyway, so we'll just do it # there. } #@devs = qw(00BC); #@devs = qw(0040 08C7); #@devs = qw(0040 004C 00BC 00BF 08C7 08CB); #@devs = qw(0040 004C 00BC 00BF 08C7 08CB 0328 0329 032A 032B 032C 032D 032E 032F 0330 0331 0332 0333 0334 0335 0336 0337 0498 0499 049A 049B 049C 049D 049E 049F 04A0 04A1 04A2 04A3 04A4 04A5 04A6 04A7 07F3 07F4 07F5 07F6 07F7 07F8 07F9 07FA 07FB 07FC 07FD 07FE 07FF 0800 0801 0802 0803 0804 0805 0806 0807 0808 0809 080A 080B 080C 080D 080E 080F 0810 0811 0812); #@devs = qw(0336 00E3); my (%dev_info, %map, %mask); # %dev_info is a hash of objects with text tags (type, etc) # # %maps (hash of arrays) # +-------------------------+ Notes: # | +-----+ +----------+ | - director:port pairing comes from # | | dev |--->| dir:port | | symdev show and represents mapping # | +-----+ +----------+ | (not masking) # | | dev |-+ | ... | | # | +-----+ | +----------+ | # | | ... | | | # | +-----+ | +----------+ | # | +->| dir:port | | # | +----------+ | # | | ... | | # | +----------+ | # +-------------------------+ # # %mask (2D hash of arrays) # +------------------------------------------+ # | +-----+ +----------+ +-----+-----+ | Notes: # | | dev |--->| dir:port |--->| wwn | ... | | - dir:port pairing # | +-----+ +----------+ +-----+-----+ | represents masking # | | dev |-+ | dir:port |-+ | (not mapping) # | +-----+ | +----------+ | +-----+-----+ | # | | ... | | | ... | +->| wwn | ... | | # | +-----+ | +----------+ +-----+-----+ | # | | | # | | +----------+ +-----+-----+ | # | +->| dir:port |--->| wwn | ... | | # | +----------+ +-----+-----+ | # | | ... | | # | +----------+ | # +------------------------------------------+ print STDERR "Gathering info about metas, mapping, and masking for devs:\n"; DEV: foreach my $dev (sort @devs) { my $meta_head_flag = 0; my $meta_member_flag = 0; my $meta_block_flag = 0; my $dir_path_flag = 0; my $bcv_block_flag = 0; # I know. Shut up. It's a damn state machine, cry me a river: I'm # not writing a language parser for this just to get the reuse of a # {}-enclosed block. my @dir_paths; print STDERR "dev: $dev\n" if $debug; print STDERR "$dev " unless $debug; # Parse through a single dev's symdev show output to push info # about that dev into the meta and map hashes. my @lines = qx{symdev -sid $sym_id show $dev}; SYMDEV_LINE: for my $line (@lines) { my @parts = split(/\s+/, $line); next SYMDEV_LINE if $line =~ m/^ *$/; if ($line =~ m/Device Symmetrix Name.*(VCM)/) { $dev_info{$dev}->{'type'} = 'VCM'; next SYMDEV_LINE; } if ($line =~ m/Device Physical Name.*(GateKeeper)/) { $dev_info{$dev}->{'type'} = 'GK'; next SYMDEV_LINE; } if ($line =~ m/^ *512-byte Blocks *: *[0-9]+ *$/) { $dev_info{$dev}->{'size'} = $parts[4] / 2; # use block count, store in KBs next SYMDEV_LINE; } if ($line =~ m/Device Configuration *:/) { # Can't use @parts in tests in this block because the dev # config types may or may not contain internal spaces. $dev_info{$dev}->{'type'} = $parts[4] unless defined($dev_info{$dev}->{'type'}); $meta_head_flag = 1 if $line =~ m/Meta Head, *$/; $meta_member_flag = 1 if $line =~ m/Meta Member, *$/; # )) -- return sanity to :se sm # # ($meta_member_flag is used to okay avoid complaining that # meta members don't have masking) next SYMDEV_LINE; } # Doubly flagging because I'm not certain we won't want to do # something else special for meta heads... if ($meta_head_flag) { if ($meta_block_flag == 1) { if ($line =~ m/^ *[0-9A-E]{4}/) { # members; $dev_info{$parts[1]}->{'meta head'} = $dev } elsif ($line =~ m/^ *-->/ && $parts[2] eq $dev) { $dev_info{$dev}->{'meta head'} = $dev } # maintain :se sm sanity { $meta_block_flag = 0 if $parts[1] eq "}"; next SYMDEV_LINE; } elsif ($line =~ m/^ *Meta Device Members/) { $meta_block_flag = 1; next SYMDEV_LINE; } } $bcv_block_flag = 1 if ($line =~ m/BCV Pair Information *$/); if ($bcv_block_flag) { # maintain :se sm sanity { $bcv_block_flag = 0 if $parts[1] eq "}"; next SYMDEV_LINE unless $line =~ m/:/; if ($dev_info{$dev}->{'type'} =~ m/BCV/) { $dev_info{$dev}->{'bcv pair'} = $parts[7] if ($line =~ m/Standard \(STD\) Device Symmetrix Name/); $dev_info{$dev}->{'dev group'} = $parts[7] if ($line =~ m/BCV Device Associated Group Name/); } else { $dev_info{$dev}->{'bcv pair'} = $parts[6] if ($line =~ m/BCV Device Symmetrix Name/); $dev_info{$dev}->{'dev group'} = $parts[7] if ($line =~ m/Standard \(STD\) Device Group Name/); } next SYMDEV_LINE; } # Build %map (dev to directors) if we're in the "Front Director # Paths" block if ($dir_path_flag) { next SYMDEV_LINE if $line =~ m/^ *{$/ || $line =~ m/^ *-+/ || $line =~ m/^ *POWERPATH/ || $line =~ m/^ *PdevName/; if ($line =~ m/^ *}$/) { $dir_path_flag = 0; # symdev output includes a leading 0, symmask[db] doesn't, go fig map($_ =~ s/0//, @dir_paths); $map{$dev} = [@dir_paths]; next SYMDEV_LINE; } if ($parts[1] =~ m/^\//) { # /dev/rdsk/c35t1d7 PARENT FA 09A:1 ... push @dir_paths, $parts[3] . "-" . $parts[4]; } elsif ($parts[1] =~ m/^Not$/) { # Not Visible N/A FA 08A:0 ... push @dir_paths, $parts[4] . "-" . $parts[5]; } else { print STDERR "HELP! - dir path\n" if $debug; print STDERR $line if $debug; } next SYMDEV_LINE; } $dir_path_flag = 1 if $line =~ m/Front Director Paths/; } # If we never hit a front director path block, the dev isn't # mapped to any directors (which may be okay if it's a BCV, R1, # BRBCV, or so forth... in fact, being mapped in that context # would be bad). unless (defined($map{$dev})) { my @arr = qw(NOMAP); $map{$dev} = [@arr]; } # Parse through a single dev's symmaskdb list assignments output # to push info about that dev into the mask hash. @lines = qx{symmaskdb -sid $sym_id list assignments -dev $dev -v 2>/dev/null}; if ($#lines < 0) { # symmaskdb produced nothing on STDOUT, so there's no masking # for this dev. Modulo a couple of cases, that means it's a waste # of space. # unless ($dev_info{$dev}->{'type'} eq 'BCV'; # XXX for my $dir_port (@{$map{$dev}}) { push @{$mask{$dev}{$dir_port}}, 'NOMASK'; } } else { my ($wwn, @ports); SYMMASKDB_LINE: foreach my $line (@lines) { if ($line =~ m/^ *WWN *:/) { $wwn = (split(/\s+/, $line))[3]; } elsif ($line =~ m/^ *Directors *:/) { @ports = split(/,/, (split(/\s+/, $line))[3]); for my $port (@ports) { push @{$mask{$dev}{$port}}, $wwn; } } } } } print STDERR "\n"; # Final pre-work: generate a 2D hash for present-on-fabric and logged in # state for HBAs across ports and a regular hash for WWNs to node/port # (nick)names based on the output of symmask list logins. print STDERR "Building hashes about fabric logins for Symm ID ${sym_id}...\n"; my ($dir_port, %login_status, %wwn_nicknames); # %wwn_nickname is just a simple hash, how 'bout that. # # %login_status (2D hash) # +---------------------------------------+ # | +----------+ +-----+ +--------+ | Notes: # | | dir:port |--->| wwn |--->| status | | - ALL fields come from symmask # | +----------+ +-----+ +--------+ | list logins (including # | | dir:port |-+ | wwn |-+ | dir:port) and represent what # | +----------+ | +-----+ | +--------+ | WWNs directors see, not whether # | | ... | | | ... | +->| status | | those WWNs have devs masked to # | +----------+ | +-----+ +--------+ | them on the director. # | | | - Values for status are: # | | +-----+ +--------+ | 0 = neither # | +->| wwn |--->| status | | 1 = on fabric # | +-----+ +--------+ | 2 = logged in (imp. state) # | | ... | ... | 3 = both # | +-----+ | # +---------------------------------------+ @lines = qx{symmask -sid $sym_id list logins}; SYMMASK_LINE: for my $line (@lines) { next SYMMASK_LINE if $line =~ m/^ *$/ || $line =~ m/^Symmetrix/ || $line =~ m/^ *User-generated/ || $line =~ m/^Identifier/ || $line =~ m/^-+/; my @parts = split(/\s+/, $line); if ($parts[1] eq "Identification") { # Starting on a new director/port block, reset our reference var. # Director Identification : FA-8A $dir_port = $parts[3]; } elsif ($parts[1] eq "Port") { # Director Port : 0 $dir_port .= ":" . $parts[3]; } else { # Something like: # User-generated Logged On # Identifier Type Node Name Port Name FCID In Fabric # ---------------- ----- -------------------- ------ ------ ------ # 10000000c930229a Fibre OH01PVMW03 HBA0 630713 No No # 10000000c9485e3b Fibre OH01PVMW05 HBA0 654213 Yes Yes my $status = 0; $status += 1 if ($parts[6] eq "Yes"); # "on fabric" $status += 2 if ($parts[5] eq "Yes"); # "logged in" my $wwn = $parts[0]; $login_status{$dir_port}{$wwn} = $status; $wwn_nicknames{$wwn} = $parts[2] . " " . $parts[3] unless $wwn eq $parts[2] && $wwn eq $parts[3]; # XXX } } print "\nmaps:\n", Dumper(%map) if $debug > 1; print "\nmasks:\n", Dumper(%mask) if $debug > 1; print "\ndev_info:\n", Dumper(%dev_info) if $debug > 1; print "\nWWN nicknames:\n", Dumper(%wwn_nicknames) if $debug > 2; print "\nLogin status:\n", Dumper(%login_status) if $debug > 2; printf "%4s %-6s %-12s %4s %10s %8s %-16s %3s %3s\n", "dev", "meta", "type", "pair", "size", "dir:port", "nickname / wwn", "fab", "log"; # XXX devgroup? for my $dev (sort @devs) { for my $map_path (@{$map{$dev}}) { my $masked_dev = $dev; my $meta_status = ''; if (defined($dev_info{$dev}->{'meta head'})) { $masked_dev = $dev_info{$dev}->{'meta head'}; if ($dev eq $dev_info{$dev}->{'meta head'}) { $meta_status = "head"; } else { $meta_status = "m-$dev_info{$dev}->{'meta head'}"; } } for my $mask_wwn (@{$mask{$masked_dev}{$map_path}}) { my $on_fab = 'no'; my $login = 'no'; if (defined($login_status{$map_path}{$mask_wwn})) { $on_fab = 'yes' if $login_status{$map_path}{$mask_wwn} >= 1; $login = 'yes' if $login_status{$map_path}{$mask_wwn} >= 2; } printf "%4s %-6s %-12s %4s %10s %8s %-16s %-3s %-3s\n", $dev, $meta_status, $dev_info{$dev}->{'type'}, defined($dev_info{$dev}->{'bcv pair'}) ? $dev_info{$dev}->{'bcv pair'} : "", humanize($dev_info{$dev}->{'size'}), $map_path, (defined($wwn_nicknames{$mask_wwn})) ? $wwn_nicknames{$mask_wwn} : $mask_wwn, $on_fab, $login; # status val reminder: # # 0 = neither # 1 = on fabric # 2 = logged in (impossible state, but the math is easier this way) # 3 = both } } } # Data structure diagrams collected: # # %maps (hash of arrays) # +-------------------------+ Notes: # | +-----+ +----------+ | - director:port pairing comes from # | | dev |--->| dir:port | | symdev show and represents mapping # | +-----+ +----------+ | (not masking) # | | dev |-+ | ... | | # | +-----+ | +----------+ | # | | ... | | | # | +-----+ | +----------+ | # | +->| dir:port | | # | +----------+ | # | | ... | | # | +----------+ | # +-------------------------+ # # %mask (2D hash of arrays) # +------------------------------------------+ # | +-----+ +----------+ +-----+-----+ | Notes: # | | dev |--->| dir:port |--->| wwn | ... | | - dir:port pairing # | +-----+ +----------+ +-----+-----+ | represents masking # | | dev |-+ | dir:port |-+ | (not mapping) # | +-----+ | +----------+ | +-----+-----+ | # | | ... | | | ... | +->| wwn | ... | | # | +-----+ | +----------+ +-----+-----+ | # | | | # | | +----------+ +-----+-----+ | # | +->| dir:port |--->| wwn | ... | | # | +----------+ +-----+-----+ | # | | ... | | # | +----------+ | # +------------------------------------------+ # # %wwn_nickname is just a simple hash, how 'bout that. # # %login_status (2D hash) # +---------------------------------------+ # | +----------+ +-----+ +--------+ | Notes: # | | dir:port |--->| wwn |--->| status | | - ALL fields come from symmask # | +----------+ +-----+ +--------+ | list logins (including # | | dir:port |-+ | wwn |-+ | dir:port) and represent what # | +----------+ | +-----+ | +--------+ | WWNs directors see, not whether # | | ... | | | ... | +->| status | | those WWNs have devs masked to # | +----------+ | +-----+ +--------+ | them on the director. # | | | - Values for status are: # | | +-----+ +--------+ | 0 = neither # | +->| wwn |--->| status | | 1 = on fabric # | +-----+ +--------+ | 2 = logged in (imp. state) # | | ... | ... | 3 = both # | +-----+ | # +---------------------------------------+