Skip to content

Instantly share code, notes, and snippets.

@rbocchinfuso
Created March 5, 2018 19:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbocchinfuso/57d4a4dc2eb4b823d0f00c4fce188427 to your computer and use it in GitHub Desktop.
Save rbocchinfuso/57d4a4dc2eb4b823d0f00c4fce188427 to your computer and use it in GitHub Desktop.
sbd-xsnap
#!/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