mirror of
https://github.com/kolesa-team/pve-purestorage-plugin.git
synced 2026-08-12 21:53:06 -06:00
Initial ActiveCluster support (#42)
* ActiveCluster is enabled by providing comma-separated cluster arrays parameters in "address" and "token" * retry non-API errors on a second array * connect/disconnect volumes to host on both arrays * related code refactoring and improvements
This commit is contained in:
+117
-76
@@ -226,103 +226,144 @@ sub purestorage_name {
|
||||
}
|
||||
|
||||
### BLOCK: Local multipath => PVE::Storage::Custom::PureStoragePlugin::sub::s
|
||||
|
||||
my $psfa_api = "2.26";
|
||||
my $PSFA_API = "2.26";
|
||||
|
||||
sub purestorage_api_request {
|
||||
my ( $scfg, $action ) = @_;
|
||||
my ( $scfg, $action, $all ) = @_;
|
||||
print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::purestorage_api_request\n" if $DEBUG;
|
||||
|
||||
my $url = $scfg->{ address } or die "Error :: Pure Storage host address is not defined.\n";
|
||||
$all //= 0;
|
||||
|
||||
my $type = $action->{ type };
|
||||
$url .= '/api/' . $psfa_api . '/' . $type;
|
||||
|
||||
my $params = prepare_api_params( $action->{ params } );
|
||||
$url .= "?$params" if length( $params );
|
||||
|
||||
my $ua = LWP::UserAgent->new;
|
||||
my $ua = LWP::UserAgent->new( timeout => 15 );
|
||||
$ua->ssl_opts(
|
||||
verify_hostname => 0,
|
||||
SSL_verify_mode => 0x00
|
||||
) unless $scfg->{ check_ssl };
|
||||
|
||||
my $body = $action->{ body } ? encode_json( $action->{ body } ) : undef;
|
||||
my $headers = HTTP::Headers->new( 'Content-Type' => 'application/json' );
|
||||
my $type = $action->{ type };
|
||||
my $login = $type eq 'login' ? 1 : 0;
|
||||
|
||||
my $token_status;
|
||||
if ( $type eq 'login' ) {
|
||||
$token_status = 0; # login request
|
||||
$headers->header( 'api-token' => $scfg->{ token } );
|
||||
} elsif ( $scfg->{ x_auth_token } ) {
|
||||
$token_status = 1; # have cached token
|
||||
} else {
|
||||
$token_status = 2; # need token
|
||||
}
|
||||
my $params = prepare_api_params( $action->{ params } );
|
||||
my $path = $type;
|
||||
$path .= '?' . $params if length( $params );
|
||||
|
||||
my $success;
|
||||
my $response;
|
||||
while ( 1 ) {
|
||||
if ( $token_status > 0 ) {
|
||||
if ( $token_status == 1 ) {
|
||||
print "Debug :: Using existing session token\n" if $DEBUG;
|
||||
} else {
|
||||
print "Debug :: Requesting new session token\n" if $DEBUG;
|
||||
purestorage_api_request( $scfg, { name => 'Authentication', type => 'login', method => 'POST' } );
|
||||
my $method = $action->{ method };
|
||||
|
||||
my $body = $action->{ body };
|
||||
|
||||
my $error;
|
||||
my $content;
|
||||
my $url;
|
||||
|
||||
my @urls = split( ',', $scfg->{ address } // '' );
|
||||
my @tokens = split( ',', $scfg->{ token } // '' );
|
||||
|
||||
foreach my $i ( 0, 1 ) {
|
||||
$url = $urls[$i] // '';
|
||||
my $token = $tokens[$i] // '';
|
||||
next if $i && $url eq '' && $token eq '';
|
||||
|
||||
my $cf = $url eq '' ? 'address' : $token eq '' ? 'token' : '';
|
||||
die "Error :: Pure Storage \"$cf\" parameter" . ( $i == 0 ? '' : ' for second array' ) . " is not defined.\n" unless $cf eq '';
|
||||
|
||||
my $config = {
|
||||
ua => $ua,
|
||||
url => $url,
|
||||
token => $token,
|
||||
auth_token => $scfg->{ '_auth_token' . $i },
|
||||
request_id => $scfg->{ '_request_id' . $i }
|
||||
};
|
||||
|
||||
( $error, $content ) = purestorage_api_request1( $config, $path, $method, $login, $body );
|
||||
if ( $error == -1 ) {
|
||||
$scfg->{ '_auth_token' . $i } = $config->{ auth_token };
|
||||
$scfg->{ '_request_id' . $i } = $config->{ request_id };
|
||||
} elsif ( $error == 1 ) {
|
||||
my $ignore = $action->{ ignore };
|
||||
if ( defined( $ignore ) ) {
|
||||
$ignore = [$ignore] if ref( $ignore ) eq '';
|
||||
my $first = $content->{ errors }->[0]->{ message };
|
||||
$error = 0 if grep { $_ eq $first } @$ignore;
|
||||
}
|
||||
$headers->header( 'x-auth-token' => $scfg->{ x_auth_token } );
|
||||
}
|
||||
$headers->header( 'X-Request-ID' => $scfg->{ x_request_id } ) if $scfg->{ x_request_id };
|
||||
|
||||
my $request = HTTP::Request->new( $action->{ method }, $url, $headers, $body );
|
||||
$response = $ua->request( $request );
|
||||
|
||||
$success = $response->is_success;
|
||||
if ( !$success && $token_status == 1 && $response->code == 401 ) {
|
||||
print "Debug :: Session token expired\n";
|
||||
$token_status = 2;
|
||||
next;
|
||||
}
|
||||
|
||||
last;
|
||||
last if $error == 1 || $error <= 0 && !$all;
|
||||
}
|
||||
|
||||
my $content_type = $response->header( "Content-Type" );
|
||||
my $content =
|
||||
defined $content_type && $content_type =~ /application\/json/ && $response->content ne ''
|
||||
? decode_json( $response->content )
|
||||
: $response->decoded_content;
|
||||
|
||||
$content = {} if $content eq '';
|
||||
|
||||
if ( $success ) {
|
||||
if ( $token_status == 0 ) {
|
||||
$headers = $response->headers;
|
||||
$scfg->{ x_auth_token } = $headers->header( 'x-auth-token' ) or die "Error :: Header 'x-auth-token' is missing.\n";
|
||||
$scfg->{ x_request_id } = $headers->header( 'x-request-id' );
|
||||
}
|
||||
} else {
|
||||
my $ignore_errors = $action->{ ignore };
|
||||
if ( defined( $ignore_errors ) ) {
|
||||
$ignore_errors = [$ignore_errors] if ref( $ignore_errors ) eq '';
|
||||
my $first = $content->{ errors }->[0]->{ message };
|
||||
$success = 1 if grep { $_ eq $first } @$ignore_errors;
|
||||
}
|
||||
|
||||
if ( !$success ) {
|
||||
my $message = $action->{ name } || "Action '$type' (method '" . $action->{ method } . "')";
|
||||
$message = substr( $message, 0, 1 ) eq uc( substr( $message, 0, 1 ) ) ? $message . ' failed' : 'Failed to ' . $message;
|
||||
die "Error :: PureStorage API :: $message.\n"
|
||||
. "=> Trace:\n"
|
||||
. "==> Code: "
|
||||
. $response->code . "\n"
|
||||
. ( $content ? "==> Message: " . Dumper( $content ) : '' );
|
||||
}
|
||||
if ( $error > 0 ) {
|
||||
my $message = $error == 3 ? 'Authentication' : $action->{ name } || "Action '$type' (method '$method')";
|
||||
$message = substr( $message, 0, 1 ) eq uc( substr( $message, 0, 1 ) ) ? $message . ' failed' : 'Failed to ' . $message;
|
||||
$message = 'PureStorage API :: ' . $message if $error == 1;
|
||||
die "Error :: $message.\n" . "=> Trace:\n" . "==> address: " . $url . "\n" . ( $content ? "==> Message: " . Dumper( $content ) : '' );
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
sub purestorage_api_request1 {
|
||||
my ( $config, $path, $method, $login, $body ) = @_;
|
||||
|
||||
my $headers = HTTP::Headers->new( 'Content-Type' => 'application/json' );
|
||||
|
||||
my $token_state;
|
||||
if ( $login ) {
|
||||
$token_state = 0; # login request
|
||||
$headers->header( 'api-token' => $config->{ token } );
|
||||
} elsif ( $config->{ auth_token } ) {
|
||||
$token_state = 2; # have cached token
|
||||
} else {
|
||||
$token_state = 1; # need token
|
||||
}
|
||||
|
||||
my $error;
|
||||
my $response;
|
||||
my $content;
|
||||
while ( 1 ) {
|
||||
if ( $token_state > 0 ) {
|
||||
if ( $token_state == 1 ) {
|
||||
print "Debug :: Requesting new session token\n" if $DEBUG;
|
||||
( $error, $content ) = purestorage_api_request1( $config, 'login', 'POST', 1 );
|
||||
return ( $error, $content ) if $error > 0;
|
||||
} else {
|
||||
print "Debug :: Using existing session token\n" if $DEBUG;
|
||||
}
|
||||
$headers->header( 'x-auth-token' => $config->{ auth_token } );
|
||||
}
|
||||
$headers->header( 'X-Request-ID' => $config->{ request_id } ) if $config->{ request_id };
|
||||
|
||||
my $request = HTTP::Request->new( $method, $config->{ url } . '/api/' . $PSFA_API . '/' . $path, $headers, length( $body ) ? encode_json( $body ) : undef );
|
||||
$response = $config->{ ua }->request( $request );
|
||||
|
||||
$error = $response->is_success ? 0 : 1;
|
||||
if ( $error && $token_state == 2 && $response->code == 401 ) {
|
||||
print "Debug :: Session token expired\n";
|
||||
$token_state = 1;
|
||||
next;
|
||||
}
|
||||
last;
|
||||
}
|
||||
|
||||
$headers = $response->headers;
|
||||
if ( $error == 0 ) {
|
||||
if ( $token_state == 0 ) {
|
||||
$config->{ auth_token } = $headers->header( 'x-auth-token' ) or die "Error :: PureStorage API :: Header 'x-auth-token' is missing.\n";
|
||||
$config->{ request_id } = $headers->header( 'x-request-id' );
|
||||
}
|
||||
$error = -1 if $token_state < 2; # auth_token was updated
|
||||
}
|
||||
|
||||
$content = $response->decoded_content;
|
||||
my $content_type = $headers->header( 'Content-Type' ) // '';
|
||||
if ( $content_type =~ /application\/json/ ) {
|
||||
$content = decode_json( $content );
|
||||
} else {
|
||||
$error = $login ? 3 : 2 if $error == 1; # non-API error (connectivity, etc.)
|
||||
$content = { response => $content };
|
||||
}
|
||||
|
||||
return ( $error, $content );
|
||||
}
|
||||
|
||||
sub purestorage_list_volumes {
|
||||
my ( $class, $scfg, $vmid, $storeid, $destroyed ) = @_;
|
||||
print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::purestorage_list_volumes\n" if $DEBUG;
|
||||
@@ -476,7 +517,7 @@ sub purestorage_volume_connection {
|
||||
}
|
||||
};
|
||||
|
||||
my $response = purestorage_api_request( $scfg, $action );
|
||||
my $response = purestorage_api_request( $scfg, $action, 1 );
|
||||
|
||||
my $message = ( $response->{ errors } ? 'already ' : '' ) . ( $mode ? 'connected to' : 'disconnected from' );
|
||||
print "Info :: Volume \"$volname\" is $message host \"$hname\".\n";
|
||||
|
||||
Reference in New Issue
Block a user