sbd-xsnap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
# ----------------------------------------------------- # | |
# SUMMARY: | |
# This script provides REST integration with EMC arrays. | |
# | |
# EXIT CODES: | |
# 0 --> Completed successfully | |
# 1 --> Invalid usage, server parameter required | |
# 2 --> Configuration files missing | |
# 3 --> VPLEX REST call failed | |
# 4 --> XtremIO REST call failed | |
# ----------------------------------------------------- # | |
use strict; | |
use LWP; | |
use JSON; | |
use Getopt::Long; | |
# ----------------------------------------------------- # | |
# CREDENTIALS/CONSTANTS: | |
# ----------------------------------------------------- # | |
my $vplex_url = ''; | |
my $vplex_username = ''; | |
my $vplex_password = ''; | |
my $xtremio_url = ''; | |
my $xtremio_username = ''; | |
my $xtremio_password = ''; | |
# ----------------------------------------------------- # | |
# GLOBALS: | |
# ----------------------------------------------------- # | |
my $browser = LWP::UserAgent->new( | |
ssl_opts => { verify_hostname => 0 }, | |
); | |
my %opts; | |
my %systems; | |
my %map; | |
my $basedir = "/storage/scripts/snap"; | |
my $controldir = "/storage/data/snap"; | |
my $configdir = "/storage/scripts/config"; | |
my $logdir = "/storage/snaplogs"; | |
# ----------------------------------------------------- # | |
# EXECUTION: | |
# ----------------------------------------------------- # | |
main(); | |
# ----------------------------------------------------- # | |
# SUBROUTINES:GENERAL: | |
# ----------------------------------------------------- # | |
sub main { | |
GetOptions( 'help' => \$opts{'help'}, | |
'vplex=s' => \$opts{'vplex'}, | |
'cluster:s' => \$opts{'cluster'}, | |
'consistency_group:s' => \$opts{'consistency_group'}, | |
'get_addresses' => \$opts{'get_addresses'}, | |
'get_consistency_groups' => \$opts{'get_consistency_groups'}, | |
'invalidate_cache' => \$opts{'invalidate_cache'}, | |
'resume_cg' => \$opts{'resume_cg'}, | |
'resume_all' => \$opts{'resume_all'}, | |
'set_detach_cg' => \$opts{'set_detach_cg'}, | |
'set_detach_all' => \$opts{'set_detach_all'}, | |
'xtremio=s' => \$opts{'xtremio'}, | |
'server:s' => \$opts{'server'}, | |
'get_ssets' => \$opts{'get_ssets'}, | |
'refresh_sset' => \$opts{'refresh_sset'}, | |
); | |
foreach my $key (sort keys %opts) { | |
logging("$key -> $opts{$key}"); | |
} | |
if ($opts{'help'}) { | |
usage(); | |
exit 1; | |
} | |
read_systems(); | |
if ($opts{'vplex'} ne '') { | |
if (defined($systems{'vplex'}{$opts{'vplex'}}{'url'})) { | |
$vplex_url = $systems{'vplex'}{$opts{'vplex'}}{'url'}; | |
$vplex_username = $systems{'vplex'}{$opts{'vplex'}}{'username'}; | |
$vplex_password = $systems{'vplex'}{$opts{'vplex'}}{'password'}; | |
} else { | |
logging("invalid vplex system id"); | |
exit 1; | |
} | |
# operations specific to cluster | |
if ($opts{'cluster'} ne '' && $opts{'get_consistency_groups'}) { | |
get_consistency_group_summary($opts{'cluster'}); | |
} elsif ($opts{'cluster'} ne '' && $opts{'consistency_group'} ne '' && $opts{'resume_cg'} ) { | |
resume_at_loser($opts{'cluster'}, $opts{'consistency_group'}); | |
} elsif ($opts{'cluster'} ne '' && $opts{'resume_all'}) { | |
resume_all($opts{'cluster'}); | |
} elsif ($opts{'cluster'} ne '' && $opts{'consistency_group'} ne '' && $opts{'set_detach_cg'} ) { | |
set_detach($opts{'cluster'}, $opts{'consistency_group'}); | |
} elsif ($opts{'cluster'} ne '' && $opts{'set_detach_all'}) { | |
set_detach_all($opts{'cluster'}); | |
} | |
# operations specific to servers | |
if ($opts{'server'} ne '' && $opts{'invalidate_cache'}) { | |
invalidate_cache($opts{'server'}); | |
} | |
# general operations | |
if ($opts{'get_addresses'}) { | |
get_director_addresses(); | |
} | |
} | |
if ($opts{'xtremio'} ne '') { | |
if (defined($systems{'xtremio'}{$opts{'xtremio'}}{'url'})) { | |
$xtremio_url = $systems{'xtremio'}{$opts{'xtremio'}}{'url'}; | |
$xtremio_username = $systems{'xtremio'}{$opts{'xtremio'}}{'username'}; | |
$xtremio_password = $systems{'xtremio'}{$opts{'xtremio'}}{'password'}; | |
} else { | |
logging("invalid xtremio system id"); | |
exit 1; | |
} | |
if ($opts{'get_ssets'}) { | |
get_snapshot_sets(); | |
} elsif ($opts{'server'} ne '' && $opts{'refresh_sset'}) { | |
refresh_snapshot_set($opts{'server'}); | |
} | |
} | |
} | |
sub usage { | |
logging("Usage:"); | |
logging("rest.pl -vplex <id> -cluster <cluster> -get_consistency_groups"); | |
logging("rest.pl -vplex <id> -cluster <cluster> -consistency_group <cg> -resume_cg"); | |
logging("rest.pl -vplex <id> -cluster <cluster> -resume_all"); | |
logging("rest.pl -vplex <id> -cluster <cluster> -consistency_group <cg> -set_detach_cg"); | |
logging("rest.pl -vplex <id> -cluster <cluster> -set_detach_all"); | |
logging("rest.pl -vplex <id> -server <server> -invalidate_cache"); | |
logging("rest.pl -vplex <id> -get_addresses"); | |
logging("rest.pl -xtremio <id> -get_ssets"); | |
logging("rest.pl -xtremio <id> -server <server> -refresh_sset"); | |
exit 1; | |
} | |
sub logging { | |
my $message = $_[0]; | |
my @caller = caller(1); | |
my $timestamp = timestamp_log(); | |
open(LOG, ">>$logdir/log_$timestamp.txt"); | |
print LOG timestamp() . "$caller[3](): $message\n"; | |
print timestamp() . "$caller[3](): $message\n"; | |
close(LOG); | |
} | |
sub timestamp { | |
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); | |
my $nice_timestamp = sprintf("\[%04d/%02d/%02d %02d:%02d:%02d\] ", $year+1900,$mon+1,$mday,$hour,$min,$sec); | |
return $nice_timestamp; | |
} | |
sub timestamp_log { | |
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); | |
my $nice_timestamp = sprintf("%04d%02d%02d", $year+1900,$mon+1,$mday,$hour,$min,$sec); | |
return $nice_timestamp; | |
} | |
sub read_systems { | |
if (-e "$configdir/systems.config") { | |
open(FILE, "<$configdir/systems.config"); | |
while(<FILE>) { | |
chomp; | |
next if (/^#/); | |
my ($type,$id,$url,$username,$password) = split(',', $_); | |
$systems{$type}{$id}{'url'} = $url; | |
$systems{$type}{$id}{'username'} = $username; | |
$systems{$type}{$id}{'password'} = $password; | |
} | |
close(FILE); | |
foreach my $type (sort keys %systems) { | |
foreach my $id (sort keys %{$systems{$type}}) { | |
logging("$id -> $systems{$type}{$id}{'url'} : $systems{$type}{$id}{'username'}, ########"); | |
} | |
} | |
} else { | |
logging("$configdir/systems.config file missing"); | |
exit 2; | |
} | |
} | |
sub read_vvol { | |
my $server = $_[0]; | |
my @vvols; | |
if (-e "$controldir/$server.vvol") { | |
open(FILE, "<$controldir/$server.vvol"); | |
while (<FILE>) { | |
chomp; | |
push(@vvols,$_); | |
} | |
close(FILE); | |
} else { | |
logging("$controldir/$server.vvol file missing"); | |
exit 2; | |
} | |
return @vvols; | |
} | |
# ----------------------------------------------------- # | |
# SUBROUTINES:VPLEX: | |
# ----------------------------------------------------- # | |
sub get_rest_vplex { | |
my $url = @_[0]; | |
my $request = HTTP::Request->new(GET => $url); | |
$request->header('Content-Type' => 'application/json'); | |
$request->header('Username' => "$vplex_username"); | |
$request->header('Password' => "$vplex_password"); | |
logging("executing $url"); | |
my $response = $browser->request($request); | |
if ($response->code == 200 || $response->code == 202) { | |
logging("completed successfully with " . $response->code . " code"); | |
} else { | |
logging("failed with " . $response->code . " code"); | |
logging("exception " . decode_json($response->content)->{'response'}->{'exception'}); | |
exit 3; | |
} | |
return decode_json($response->content); | |
} | |
sub post_rest_vplex { | |
my $url = @_[0]; | |
my $json = @_[1]; | |
my $request = HTTP::Request->new(POST => $url); | |
$request->header('Content-Type' => 'application/json'); | |
$request->header('Username' => "$vplex_username"); | |
$request->header('Password' => "$vplex_password"); | |
$request->content($json); | |
logging("executing $url --> $json"); | |
my $response = $browser->request($request); | |
if ($response->code == 200 || $response->code == 202) { | |
logging("completed successfully with " . $response->code . " code"); | |
} else { | |
logging("failed with " . $response->code . " code"); | |
logging("exception " . decode_json($response->content)->{'response'}->{'exception'}); | |
exit 3; | |
} | |
return decode_json($response->content); | |
} | |
sub get_context { | |
my $url = "$vplex_url/vplex"; | |
my $obj = get_rest_vplex($url); | |
logging(encode_json($obj)); | |
} | |
sub get_engines { | |
my @engines; | |
my $url = "$vplex_url/vplex/engines"; | |
my $obj = get_rest_vplex($url); | |
foreach my $tmp (@{$obj->{'response'}->{'context'}->[0]->{'children'}}) { | |
my $engine = $tmp->{'name'}; | |
push(@engines, $engine); | |
logging("$engine"); | |
} | |
return @engines; | |
} | |
sub get_directors { | |
my @engines = @{$_[0]}; | |
my %map; | |
foreach my $engine (@engines) { | |
my $url = "$vplex_url/vplex/engines/$engine/directors"; | |
my $obj = get_rest_vplex($url); | |
foreach my $tmp (@{$obj->{'response'}->{'context'}->[0]->{'children'}}) { | |
my $director = $tmp->{'name'}; | |
$map{$engine}{$director} = undef; | |
logging("$engine -> $director"); | |
} | |
} | |
return %map; | |
} | |
sub get_director_ports { | |
my %map = %{$_[0]}; | |
foreach my $engine (sort keys %map) { | |
foreach my $director (sort keys %{$map{$engine}}) { | |
my $url = "$vplex_url/vplex/engines/$engine/directors/$director/hardware/ports"; | |
my $obj = get_rest_vplex($url); | |
foreach my $tmp (@{$obj->{'response'}->{'context'}->[0]->{'children'}}) { | |
my $port = $tmp->{'name'}; | |
my $type = $tmp->{'type'}; | |
$map{$engine}{$director}{$port}{'type'} = $type; | |
if ($type eq 'ethernet-port') { | |
logging("$engine -> $director -> $port : $type"); | |
} | |
} | |
} | |
} | |
} | |
sub get_director_addresses { | |
my @engines = get_engines(); | |
my %map = get_directors(\@engines); | |
get_director_ports(\%map); | |
open(FILE1, ">$logdir/output_vplex_director_mac_addresses.txt"); | |
open(FILE2, ">$logdir/output_vplex_director_wwns.txt"); | |
foreach my $engine (sort keys %map) { | |
foreach my $director (sort keys %{$map{$engine}}) { | |
foreach my $port (sort keys %{$map{$engine}{$director}}) { | |
my $url = "$vplex_url/vplex/engines/$engine/directors/$director/hardware/ports/$port"; | |
my $obj = get_rest_vplex($url); | |
my $type = $obj->{'response'}->{'context'}->[0]->{'type'}; | |
foreach my $tmp (@{$obj->{'response'}->{'context'}->[0]->{'attributes'}}) { | |
my $attribute = $tmp->{'name'}; | |
my $value = $tmp->{'value'}; | |
if ($type eq 'ethernet-port' && $attribute eq 'mac-address') { | |
$map{$engine}{$director}{$port}{'mac-address'} = $value; | |
logging("$engine -> $director -> $port -> $value"); | |
} elsif ($type eq 'ethernet-port' && $attribute eq 'address') { | |
$map{$engine}{$director}{$port}{'address'} = $value; | |
logging("$engine -> $director -> $port -> $value"); | |
} elsif ($type eq 'fc-port' && $attribute eq 'node-wwn') { | |
$map{$engine}{$director}{$port}{'node-wwn'} = $value; | |
logging("$engine -> $director -> $port -> $value"); | |
} elsif ($type eq 'fc-port' && $attribute eq 'port-wwn') { | |
$map{$engine}{$director}{$port}{'port-wwn'} = $value; | |
logging("$engine -> $director -> $port -> $value"); | |
} | |
} | |
if ($type eq 'ethernet-port') { | |
print FILE1 "$engine,$director,$port,$map{$engine}{$director}{$port}{'mac-address'},$map{$engine}{$director}{$port}{'address'}\n"; | |
} elsif ($type eq 'fc-port') { | |
print FILE2 "$engine,$director,$port,$map{$engine}{$director}{$port}{'node-wwn'},$map{$engine}{$director}{$port}{'port-wwn'}\n"; | |
} | |
} | |
} | |
} | |
close(FILE); | |
} | |
sub get_consistency_groups { | |
my $cluster = $_[0]; | |
my @cgids; | |
my $url = "$vplex_url/vplex/clusters/$cluster/consistency-groups"; | |
my $obj = get_rest_vplex($url); | |
foreach my $tmp (@{$obj->{'response'}->{'context'}->[0]->{'children'}}) { | |
my $cg = $tmp->{'name'}; | |
push(@cgids, $cg); | |
#logging("$cg"); | |
} | |
return @cgids; | |
} | |
sub get_consistency_group_summary { | |
my $cluster = $_[0]; | |
my @cgids = get_consistency_groups($cluster); | |
open(FILE, ">$logdir/output_vplex_consistency_group_summary.txt"); | |
foreach my $cg (@cgids) { | |
my $url = "$vplex_url/vplex/clusters/$cluster/consistency-groups/$cg"; | |
my $obj = get_rest_vplex($url); | |
my $detach = ""; | |
my @status = (); | |
my $status = ""; | |
my $output = ""; | |
foreach my $tmp (@{$obj->{'response'}->{'context'}->[0]->{'attributes'}}) { | |
my $attribute = $tmp->{'name'}; | |
my $value = $tmp->{'value'}; | |
if ($attribute eq 'detach-rule') { | |
$detach = $value; | |
logging("$cg -> $detach"); | |
} elsif ($attribute eq 'operational-status') { | |
@status = @{$value}; | |
foreach my $tmp2 (@status) { | |
logging("$cg -> $tmp2"); | |
$tmp2 =~ s/,/;/g; | |
$status .= "$tmp2"; | |
$status .= ","; | |
} | |
} | |
} | |
print FILE "$cg,$detach,$status\n"; | |
} | |
close(FILE); | |
} | |
sub resume_at_loser { | |
my $cluster = $_[0]; | |
my $group = $_[1]; | |
my $url = "$vplex_url/vplex/consistency-group+resume-at-loser"; | |
my $json = "{\"args\":\"-c $cluster -g $group\"}"; | |
logging("$url -> $json"); | |
#post_rest_vplex($url,$json); | |
} | |
sub resume_all { | |
my $cluster = $_[0]; | |
my @cgids = get_consistency_groups($cluster); | |
foreach my $cg (@cgids) { | |
resume_at_loser($cluster, $cg); | |
} | |
} | |
sub set_detach { | |
my $cluster = $_[0]; | |
my $group = $_[1]; | |
my $url = "$vplex_url/vplex/consistency-group+set-detach-rule+winner"; | |
my $json = "{\"args\":\"-c $cluster -d 5 -g $group -f\"}"; | |
logging("$url -> $json"); | |
post_rest_vplex($url,$json); | |
} | |
sub set_detach_all { | |
my $cluster = $_[0]; | |
my @cgids = get_consistency_groups($cluster); | |
foreach my $cg (@cgids) { | |
set_detach($cluster, $cg); | |
} | |
} | |
sub invalidate_cache { | |
my $server = $_[0]; | |
my @vvols = read_vvol($server); | |
foreach my $vvol (@vvols) { | |
logging("invalidating vplex cache for $vvol"); | |
my $url = "$vplex_url/vplex/virtual-volume+cache-invalidate"; | |
my $json = "{\"args\":\"-v $vvol --force\"}"; | |
post_rest_vplex($url,$json); | |
} | |
} | |
# ----------------------------------------------------- # | |
# SUBROUTINES:XTREMIO: | |
# ----------------------------------------------------- # | |
sub get_rest_xio { | |
my $url = @_[0]; | |
my $request = HTTP::Request->new(GET => "$url"); | |
$request->authorization_basic("$xtremio_username","$xtremio_password"); | |
logging("executing $url"); | |
my $response = $browser->request($request); | |
if ($response->code == 200) { | |
#print $response->content; | |
#print "\n\n"; | |
} else { | |
logging("failed with " . $response->code . " code"); | |
logging("exception " . decode_json($response->content)->{'response'}->{'exception'}); | |
exit 4; | |
} | |
return decode_json($response->content); | |
} | |
sub post_rest_xio { | |
my $url = @_[0]; | |
my $json = @_[1]; | |
my $request = HTTP::Request->new(POST => $url); | |
$request->authorization_basic("$xtremio_username","$xtremio_password"); | |
$request->header('Content-Type' => 'application/json'); | |
$request->content($json); | |
logging("executing $url --> $json"); | |
my $response = $browser->request($request); | |
if (($response->code == 200) || ($response->code == 201)) { | |
#print $response->content; | |
#print "\n\n"; | |
logging("completed successfully with " . $response->code . " code"); | |
} else { | |
logging("failed with " . $response->code . " code"); | |
logging("exception " . decode_json($response->content)->{'response'}->{'exception'}); | |
exit 4; | |
} | |
return decode_json($response->content); | |
} | |
# deprecated, no longer needed | |
sub read_snapid { | |
my $server = $_[0]; | |
my $snapid; | |
if (-e "$controldir/$server.snapid") { | |
open(FILE, "<$controldir/$server.snapid"); | |
$snapid = <FILE>; | |
chomp $snapid; | |
logging("snapid read as $snapid"); | |
close(FILE); | |
} else { | |
logging("$controldir/$server.snapid file missing"); | |
exit 2; | |
} | |
return $snapid; | |
} | |
sub incr_snapid { | |
my $snapid = $_[0]; | |
$snapid++; | |
$snapid = 0 if ($snapid == 10); | |
logging("targeting snapid $snapid for refresh"); | |
return $snapid; | |
} | |
sub read_cgid { | |
my $server = $_[0]; | |
my @cgids; | |
if (-e "$controldir/$server.cgid") { | |
open(FILE, "<$controldir/$server.cgid"); | |
while (<FILE>) { | |
chomp; | |
push(@cgids,$_); | |
} | |
close(FILE); | |
} else { | |
logging("$controldir/$server.cgid file missing"); | |
exit 2; | |
} | |
return @cgids; | |
} | |
sub get_snapshot_sets { | |
logging("retrieving snapshot-set"); | |
my $url = "$xtremio_url/api/json/types/snapshot-sets"; | |
my $output = get_rest_xio($url); | |
foreach my $item (@{$output->{'snapshot-sets'}}) { | |
my $sset = $$item{'name'}; | |
my $temp = get_rest_xio("$url?name=$sset"); | |
my $cgid = $$temp{'content'}{'cg-oid'}[1]; | |
$map{$cgid} = $sset; | |
} | |
foreach my $cgid (sort keys %map) { | |
logging("mapped $cgid --> $map{$cgid}"); | |
} | |
} | |
sub create_snapshot_set { | |
my $cgid = @_[0]; | |
logging("creating snapshot-set $cgid"); | |
my $url = "$xtremio_url/api/json/v2/types/snapshots"; | |
my $json = "{\"consistency-group-id\":\"$cgid\", \"snapshot-set-name\":\"${cgid}_snapshot0\"}"; | |
post_rest_xio($url,$json); | |
} | |
sub refresh_snapshot_set { | |
my $server = $_[0]; | |
my @cgids = read_cgid($server); | |
get_snapshot_sets(); | |
foreach my $cgid (@cgids) { | |
my $current = substr($map{$cgid}, -1); | |
my $updated = incr_snapid($current); | |
logging("refreshing snapshot-set for cg $cgid"); | |
my $url = "$xtremio_url/api/json/v2/types/snapshots"; | |
my $json = "{\"from-consistency-group-id\":\"$cgid\", \"to-snapshot-set-id\":\"${cgid}_snapshot${current}\", \"snapshot-set-name\":\"${cgid}_snapshot${updated}\", \"no-backup\":\"true\"}"; | |
post_rest_xio($url,$json); | |
} | |
} | |
# future, not currently in use | |
# snapshot-set rename feature only available in 4.0.2 code, sbd is currently on 4.0.1 code | |
sub refresh_snapshot_set_402 { | |
my $server = $_[0]; | |
my @cgids = read_cgid($server); | |
foreach my $cgid (@cgids) { | |
logging("refreshing snapshot-set ${cgid}_snapshot"); | |
my $url = "$xtremio_url/api/json/v2/types/snapshots"; | |
my $json = "{\"from-consistency-group-id\":\"$cgid\", \"to-snapshot-set-id\":\"${cgid}_snapshot\", \"snapshot-set-name\":\"${cgid}_snapshot_tmp\", \"no-backup\":\"true\"}"; | |
post_rest_xio($url,$json); | |
logging("resetting snapshot-set name back to ${cgid}_snapshot"); | |
$url = "$xtremio_url/api/json/v2/types/snapshot-sets?name=${cgid}_snapshot_tmp"; | |
$json = "{\"new-set-name\":\"${cgid}_snapshot\"}"; | |
post_rest_xio($url,$json); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment