#!/usr/bin/perl use strict; use Getopt::Long; my %vmhba; my $port; my $spa; my $spb; # my $spa = "cx1a"; ### testing # my $spb = "cx1b"; ### testing my $sp_uid; my %lun_id2uid; my %lun_uid2id; my %lun2canonical; my $lun_id; my $lun_uid; my $policy; my %lun_hba; my %lun_port; my %lun_path; my $path; my $active_path; my $preferred_path; my $luns_balanced; my @paths; my %seen; my $lun_number; my $lun_count; my $path_count; my @active; my @sort_active; my $hba; my $path_state; my $navi_user; my $navi_password; my $navi_scope; my $list_type; my @spa_luns; my @spb_luns; my $num_spa_luns; my $num_spb_luns; my $luns2trespass; my $dest_sp; my @spa_ports; my @spb_ports; my $num_spa_ports; my $num_spb_ports; #my $num_sp_port3; ### future - handle all 4 SP ports #my $num_sp_port4; my @port0_luns; my @port1_luns; my $luns2failover; my $source_port; my $destination_port; my @luns2move; my $default_owner; my $verbose = 0; GetOptions("un=s" => \$navi_user, "pw=s" => \$navi_password, "scope=i" => \$navi_scope, "list=s" => \$list_type, "verbose!" => \$verbose, "spa=s" => \$spa, "spb=s" => \$spb, ); system("clear"); print "\n"; sub numerically { $a <=> $b; } sub rescan # rescan specified HBAs { warn "HBA rescan not performed - no HBA(s) specified!\n" if (not @_); # no arguments passed to &rescan my @hba2scan; for (@_) # step through arguments passed to &rescan { if (/all/i) # add all HBAs to the list { @hba2scan = sort keys %vmhba; last; } else # add specified HBAs to the list { @hba2scan = sort @_; } } for (@hba2scan) # rescan the HBAs. Don't allow duplicates { my %hba_done; system("esxcfg-rescan $_ > /dev/null") if not $hba_done{$_}; print "Scanning HBA $_ ...\n" if not $hba_done{$_}; $hba_done{$_}++; } } sub clear_info { @active = (); @sort_active = (); %lun_hba = (); %lun_port = (); @paths = (); @spa_luns = (); @spb_luns = (); @port0_luns = (); @port1_luns = (); } sub get_esx_info # get ESX Server LUN information { my @mpath_verbose = `esxcfg-mpath -v -l`; for (@mpath_verbose) { my $string; if (/^Disk\s+(vmhba\d+:\d+:\d+)\s+vml\.[\da-f]{10}(6006016[\da-f]{25})[\da-f]{12}\s+(\/dev\/sd\w+)\s+.+\d+\w+.+(\d+)\s+paths.+policy of\s+(.+)/i) { if (exists $lun_uid2id{uc $2}) { $lun_number = $lun_uid2id{uc $2}; if ($5 eq "Most Recently Used") {$policy = "MRU"} $lun2canonical{$lun_number} = $1; print "\n$1 [LUN $lun_number]: $4 paths policy $policy\n" if $verbose; } } if (/FC.+[\da-f]{16}:[\da-f]{16}<->5006016([\da-f])[\da-f]$sp_uid:[\da-f]{16}\s+(vmhba\d+):\d+:\d+\s+(.+)/i) { if ($1 le "7") { $port = "SPA$1" } elsif (uc $1 ge "A") { $port = "SPB" . chr((ord(uc($1)) - 15)) } elsif ($1 le "9") {$port = "SPB" . ($1 - "8")} $hba = $2; $path_state = $3; $path = "$hba -> $port"; push @paths, $path if not $seen{$path}; $seen{$path}++; # print "$hba -> $port : $path_state\n"; print "$path : $path_state\n" if $verbose; if ($path_state =~ /Active/i) { $lun_path{$path}++; # increment the active counter for this path $active_path = $path; $string = "LUN $lun_number UID $lun_id2uid{$lun_number} $hba $port $active_path"; # print "$string\n"; $lun_hba{$hba}++; $lun_port{$port}++; $port =~ /SPA/ ? push @spa_luns, $lun_number : push @spb_luns, $lun_number; } if ($path_state =~ /Preferred/i) { $preferred_path = $path; } } $string .= " $preferred_path"; push @active, $string; } @sort_active = sort @active; } my @hba_info = `esxcfg-mpath -a`; # command to get HBA information for (@hba_info) { if (/^(vmhba\d+)\s+(\w{16})\s+.+/) { $vmhba{$1} = $2; } } # start by trespassing LUNs to their rightful owner print "Trespassing LUNs to their default owners ...\n"; system("naviseccli -h $spa trespass mine > /dev/null"); system("naviseccli -h $spb trespass mine > /dev/null"); system("naviseccli -h $spa getlun -uid > /dev/null 2>&1"); # force a poll system("naviseccli -h $spb getlun -uid > /dev/null 2>&1"); print "Waiting 60 seconds for ESX Server to fail over ...\n"; for my $i (1 .. 60) { # system("clear"); print "."; sleep 1; # print "\n"; } print "\n"; # make sure ESX Server gets the picture rescan("all"); sleep 10; my @getlun_output = `naviseccli -h $spa getlun -uid`; for (@getlun_output) { # chomp; if (/^LOGICAL UNIT NUMBER\s+(\d+)/) { $lun_id = $1; } if (/^UID:\s+((?:[\da-f]{2}:){15}[\da-f]{2})/i) { ($lun_uid = $1) =~ s/://g; $lun_id2uid{$lun_id} = $lun_uid; } } %lun_uid2id = reverse %lun_id2uid; # get this CLARiiON's WWN my @output = `naviseccli -h $spa getarrayuid`; for (@output) { if (/.+\s+(?:[\da-f]{2}:){4}[\da-f](.+)/i) { ($sp_uid = $1) =~ s/://g; } } get_esx_info; for my $hba (sort keys %lun_hba) { print "\nHBA $hba has $lun_hba{$hba} active LUN"; print "s" if ($lun_hba{$hba} > 1); print ":\n"; print " ID\t\tPort\t Active\t Preferred\n"; for my $string (@sort_active) { # print "$string\n"; my @words = split / /, $string; if ($words[4] eq $hba) { printf "%4d\t\t%4s\t%6s %2s %4s\t%6s %2s %4s\n",@words[1, 5, 6, 7, 8, 9, 10, 11]; } } } for (sort keys %lun_port) { print "\nSP port $_ has $lun_port{$_} active LUN"; print "s" if ($lun_port{$_} > 1); print ":\n"; # print " ID\t\tUID\t\t\tHBA\n"; print " ID\t\t HBA\t Active\t Preferred\n"; for my $string (@sort_active) { # print "$string\n"; my @words = split / /, $string; if ($words[5] eq $_) { # print "@words[1, 3, 4]\n"; # print "@words[1, 4]\n"; printf "%4d\t\t%6s\t%6s %2s %4s\t%6s %2s %4s\n",@words[1, 4, 6, 7, 8, 9, 10, 11]; } } } print "\nPath information:\n"; $lun_count = 0; for my $path (@paths) { $lun_path{$path} = 0 if not defined $lun_path{$path}; # clean this!! print "Path $path has $lun_path{$path} active LUNs\n"; $lun_count += $lun_path{$path}; } # trespass LUNs and change default owners $num_spa_luns = @spa_luns; $num_spb_luns = @spb_luns; print "\nSPA owns " . $num_spa_luns . " LUNs\n"; print "SPB owns " . $num_spb_luns . " LUNs\n\n"; if ($num_spa_luns > $num_spb_luns) { $luns2trespass = int(($num_spa_luns - $num_spb_luns) / 2); $dest_sp = $spb; @luns2move = @spa_luns; # print "LUNs available for trespass: @luns2move\n"; $default_owner = 1; # SPB } else { $luns2trespass = int(($num_spb_luns - $num_spa_luns) / 2); $dest_sp = $spa; @luns2move = @spb_luns; # print "LUNs available for trespass: @luns2move\n"; $default_owner = 0; # SPA } if ($luns2trespass) { print "Trespassing $luns2trespass LUNs to $dest_sp ...\n"; $luns_balanced = 0; for (1 .. $luns2trespass) { my $owning_sp; my $working_lun = $luns2move[$_ * 2 + 1]; print "\tTrespassing LUN $working_lun ...\n"; my @current_owner = `naviseccli -h $dest_sp getlun $working_lun -owner`; for (@current_owner) { if (/Current owner:\s+SP\s+(\w)/) { $owning_sp = $1; } } if ($default_owner == (ord($owning_sp) - 65)) { print "LUN $working_lun already owned by correct SP!\n" } else { system("naviseccli -h $dest_sp trespass lun $working_lun"); } print "\tChanging default owner for LUN $working_lun ...\n\n"; system ("naviseccli -h $dest_sp chglun -l $working_lun -d $default_owner"); } } else { $luns_balanced = 1; print "LUNs are balanced acrosss SPs - no trespass will be performed ...\n"; } if ($luns_balanced == 0) { print "Rescanning HBAs ...\n"; rescan("all"); print "Waiting for 60 seconds for ESX Server to finish rescanning ...\n"; for (1 .. 60) { print "."; sleep 1; } print "\n"; clear_info; get_esx_info; } # assign preferred paths print "Assigning preferred paths ...\n"; for $port (sort keys %lun_port) { print "\nSP port $port has $lun_port{$port} active LUN"; print "s" if ($lun_port{$port} > 1); print ":\n"; print " ID\t\t HBA\t Active\t Preferred\n"; for my $string (@sort_active) { # print "$string\n"; my @words = split / /, $string; if ($words[5] eq $port) { # print "@words[1, 3, 4]\n"; # print "@words[1, 4]\n"; printf "%4d\t\t%6s\t%6s %2s %4s\t%6s %2s %4s\n",@words[1, 4, 6, 7, 8, 9, 10, 11]; } } if ($port =~ /SPA/) { push @spa_ports, $port; } else { push @spb_ports, $port; } } # print "SPA ports: @spa_ports\tSPB ports: @spb_ports\n"; $num_spa_ports = @spa_ports; $num_spb_ports = @spb_ports; if ($num_spa_ports > 2 or $num_spb_ports > 2) { die "Fatal error - script currently handles connections to only 2 ports per SP!\n"; } else { my $difference = int(($lun_port{$spa_ports[0]} - $lun_port{$spa_ports[1]}) / 2); if ($difference < 0) { $source_port = $spa_ports[1]; $destination_port = $spa_ports[0]; $difference = abs($difference); for my $string (@sort_active) { my @words = split / /, $string; if ($words[5] eq $spa_ports[1]) { push @port1_luns, $words[1]; # print "@words[1, 4]\n"; # printf "%4d\t\t%6s\t%6s %2s %4s\t%6s %2s %4s\n",@words[1, 4, 6, 7, 8, 9, 10, 11]; } } print "@port1_luns\n"; @luns2move = @port1_luns[(0 .. $difference - 1)]; } elsif ($difference > 0) { $source_port = $spa_ports[0]; $destination_port = $spa_ports[1]; for my $string (@sort_active) { my @words = split / /, $string; if ($words[5] eq $spa_ports[0]) { push @port0_luns, $words[1]; } } print "port0_luns\n"; @luns2move = @port0_luns[(0 .. $difference - 1)]; } else { print "LUNs are balanced across ports $spa_ports[0] and $spa_ports[1]\n"; } print "Moving $difference LUNs from $source_port to $destination_port ...\n" if ($difference > 0); print "LUNs being moved:\n"; for (@luns2move) { print "LUN $_:\t$lun2canonical{$_}\n"; } # $luns2failover = $lun_port{$spa_ports[0]} - $lun_port{$spa_ports[1]}; # fail to preferred path print "\tFailing LUNs to alternate path ...\n"; for my $lun (@luns2move) { system("esxcfg-mpath -q --lun=$lun2canonical{$lun}"); my @paths = ` esxcfg-mpath -q --lun=$lun2canonical{$lun}`; for (@paths) { if (/FC\s+.+[\da-f]{16}<->[\da-f]{16}\s+(vmhba\d+:\d+:\d+)\s+.+active.+/) { system("esxcfg-mpath --path=$1 --state=off --lun=$lun2canonical{$lun}"); } } system("esxcfg-mpath -q --lun=$lun2canonical{$lun}"); } # To set preferred path for disk vmhba0:0:1 # esxcfg-mpath --preferred --path=vmhba1:0:1 --lun=vmhba0:0:1 # # To enable a path for disk vmhba0:0:1 # esxcfg-mpath --path=vmhba1:0:1 --lun=vmhba0:0:1 --state=on # # To disable a path for disk vmhba0:0:1 # esxcfg-mpath --path=vmhba0:1:1 --state=off --lun=vmhba0:0:1 # print new path information print "\nNew SPA path assignments:\n"; } @port0_luns = (); @port1_luns = (); { my $difference = int(($lun_port{$spb_ports[0]} - $lun_port{$spb_ports[1]}) / 2); if ($difference < 0) { $source_port = $spb_ports[1]; $destination_port = $spb_ports[0]; $difference = abs($difference); for my $string (@sort_active) { my @words = split / /, $string; if ($words[5] eq $spb_ports[1]) { push @port1_luns, $words[1]; # print "@words[1, 4]\n"; # printf "%4d\t\t%6s\t%6s %2s %4s\t%6s %2s %4s\n",@words[1, 4, 6, 7, 8, 9, 10, 11]; } } print "@port1_luns\n"; @luns2move = @port1_luns[(0 .. $difference - 1)]; } elsif ($difference > 0) { $source_port = $spb_ports[0]; $destination_port = $spb_ports[1]; for my $string (@sort_active) { my @words = split / /, $string; if ($words[5] eq $spb_ports[0]) { push @port0_luns, $words[1]; } } print "port0_luns\n"; @luns2move = @port0_luns[(0 .. $difference - 1)]; } else { print "LUNs are balanced across ports $spb_ports[0] and $spb_ports[1]\n"; } print "Moving $difference LUNs from $source_port to $destination_port ...\n" if ($difference > 0); print "LUNs being moved:\n"; for (@luns2move) { print "LUN $_:\t$lun2canonical{$_}\n"; } # $luns2failover = $lun_port{$spa_ports[0]} - $lun_port{$spa_ports[1]}; # fail to preferred path print "\tFailing LUNs to alternate path ...\n"; for my $lun (@luns2move) { system("esxcfg-mpath -q --lun=$lun2canonical{$lun}"); my @paths = ` esxcfg-mpath -q --lun=$lun2canonical{$lun}`; for (@paths) { if (/FC\s+.+[\da-f]{16}<->[\da-f]{16}\s+(vmhba\d+:\d+:\d+)\s+.+active.+/) { system("esxcfg-mpath --path=$1 --state=off --lun=$lun2canonical{$lun}"); } } system("esxcfg-mpath -q --lun=$lun2canonical{$lun}"); } # To set preferred path for disk vmhba0:0:1 # esxcfg-mpath --preferred --path=vmhba1:0:1 --lun=vmhba0:0:1 # # To enable a path for disk vmhba0:0:1 # esxcfg-mpath --path=vmhba1:0:1 --lun=vmhba0:0:1 --state=on # # To disable a path for disk vmhba0:0:1 # esxcfg-mpath --path=vmhba0:1:1 --state=off --lun=vmhba0:0:1 # print new path information print "\nNew SPB path assignments:\n"; } #for (sort keys %lun_id2uid) #{ # print "$_ -> $lun_id2uid{$_}\n"; #} # #for (sort keys %lun_uid2id) #{ # print "$_ -> $lun_uid2id{$_}\n"; #} # print "ESX LUN\t\tHBA\t\tSP Port\t\tActive\t\tPreferred\t\tOn/Standby"; # rescan("all");