Files
pve-purestorage-plugin/tests/token_cache_test.pl
T
Timur Kumakbayev f9f8eed94b Active Cluster Support, Pod Quota Fixes,Token Cache and CI Improvements (#81)
feat: add Active Cluster support and improve plugin stability

## Major Features

### Active Cluster Support (Experimental) (#42)
- Add support for multiple PureStorage arrays in Active Cluster configuration
- Automatic volume connection on all arrays for high availability
- Configure via comma-separated addresses and tokens

### Pod Quota Limit Support (#69)
- Add proper handling of pod quota_limit parameter
- Use quota_limit when set, fall back to array capacity when unlimited
- Fix capacity reporting for pods with quotas

### Session Token Caching
- Implement session token caching in /etc/pve/priv/purestorage/
- Automatic token refresh at 80% of TTL to prevent expiration
- In-memory and file-based caching with jitter to prevent thundering herd

### Debug Logging
- Add configurable debug logging with 4 levels (0-3)
- Support both config parameter and PURESTORAGE_DEBUG environment variable

## Bug Fixes
- Fix volume deletion when connected to multiple hosts
- Query all connections before destroy
- Disconnect from all hosts on all arrays
- Prevents "Cannot destroy volume because it is currently connected" error

## CI/CD Improvements
- Add workflow_dispatch trigger to checks workflow
- Add option to check all files or only changed files
- Add comprehensive markdown linting with markdownlint-cli2

## Testing
- Add token caching tests (tests/token_cache_test.pl)
- Test token validation, expiration, and race conditions
- Test cleanup of expired cache files

## Refactoring (#72)
- Refactor volume removal logic to handle multiple host connections
- Improve device cleanup sequence (LVM, partitions, multipath)
- Extract connection querying logic before volume destruction
- Refactor CI/CD workflows for better maintainability and flexibility
- Improve error handling and logging throughout the plugin
- Enhance Active Cluster support with proper multi-array operations

## Code Formatting
- Format all Perl files with perltidy using .perltidyrc configuration
- Ensure consistent code style across the codebase
- Fix formatting issues in PureStoragePlugin.pm and test files

## Documentation
- Update README with new functionality
2026-01-17 17:20:28 +05:00

206 lines
5.5 KiB
Perl
Executable File

#!/usr/bin/env perl
use strict;
use warnings;
use Test::More tests => 15;
use File::Temp qw( tempdir );
use JSON::XS qw( encode_json decode_json );
# Mock PVE::Tools for testing
BEGIN {
package PVE::Tools;
use Exporter 'import';
our @EXPORT_OK = qw( file_get_contents );
sub file_get_contents {
my ( $path ) = @_;
open my $fh, '<', $path or die "Cannot read $path: $!";
local $/;
my $content = <$fh>;
close $fh;
return $content;
}
}
# Test token cache implementation
package main;
my $test_dir = tempdir( CLEANUP => 1 );
my $cache_path = "$test_dir/test_cache.json";
# Helper function to create mock token data
sub create_token_data {
my ( $age ) = @_;
my $now = time();
return {
auth_token => 'test-token-' . int( rand( 1000 ) ),
request_id => 'test-request-id',
created_at => $now - $age,
ttl => 3600,
expires_at => $now - $age + 3600
};
}
# Helper function to write token cache
sub write_test_cache {
my ( $token_data ) = @_;
my $json = encode_json( $token_data );
open my $fh, '>', $cache_path or die "Cannot write cache: $!";
print $fh $json;
close $fh;
}
# Test 1: Token validation - fresh token
{
my $token_data = create_token_data( 100 ); # 100s old
my $ttl = 3600;
my $threshold = $ttl * 0.8; # 2880s
ok( 100 < $threshold, 'Fresh token is valid (age < 80% TTL)' );
}
# Test 2: Token validation - expired token
{
my $token_data = create_token_data( 3000 ); # 3000s old
my $ttl = 3600;
my $threshold = $ttl * 0.8; # 2880s
ok( 3000 >= $threshold, 'Expired token needs refresh (age >= 80% TTL)' );
}
# Test 3: Cache file write and read
{
my $token_data = create_token_data( 50 );
write_test_cache( $token_data );
ok( -f $cache_path, 'Cache file created' );
my $json_text = PVE::Tools::file_get_contents( $cache_path );
my $read_data = decode_json( $json_text );
is( $read_data->{ auth_token }, $token_data->{ auth_token }, 'Token data matches after read' );
}
# Test 4: Cache file validation - valid token
{
my $token_data = create_token_data( 100 );
write_test_cache( $token_data );
my $json_text = PVE::Tools::file_get_contents( $cache_path );
my $cached = decode_json( $json_text );
my $age = time() - $cached->{ created_at };
my $threshold = 3600 * 0.8;
ok( $age < $threshold, 'Cached token is still valid' );
}
# Test 5: Cache file validation - expired token
{
my $token_data = create_token_data( 3000 );
write_test_cache( $token_data );
my $json_text = PVE::Tools::file_get_contents( $cache_path );
my $cached = decode_json( $json_text );
my $age = time() - $cached->{ created_at };
my $threshold = 3600 * 0.8;
ok( $age >= $threshold, 'Cached token is expired and should be refreshed' );
}
# Test 6: Race condition mitigation - newer token exists
{
my $old_token = create_token_data( 200 );
my $new_token = create_token_data( 50 );
ok( $new_token->{ created_at } > $old_token->{ created_at }, 'Newer token has later created_at timestamp' );
}
# Test 7: TTL validation
{
my $ttl = 3600;
my $refresh_threshold = $ttl * 0.8;
is( $refresh_threshold, 2880, 'Refresh threshold is 80% of TTL' );
# Test jitter range (±2.5%)
my $jitter_min = $ttl * ( 0.8 - 0.025 );
my $jitter_max = $ttl * ( 0.8 + 0.025 );
ok( $jitter_min < $refresh_threshold && $refresh_threshold < $jitter_max, 'Jitter keeps threshold within ±2.5% of 80% TTL' );
}
# Test 8: Multiple token files
{
my $cache1 = "$test_dir/storage1_array0.json";
my $cache2 = "$test_dir/storage2_array0.json";
my $token1 = create_token_data( 100 );
my $token2 = create_token_data( 200 );
open my $fh1, '>', $cache1 or die $!;
print $fh1 encode_json( $token1 );
close $fh1;
open my $fh2, '>', $cache2 or die $!;
print $fh2 encode_json( $token2 );
close $fh2;
ok( -f $cache1 && -f $cache2, 'Multiple cache files can coexist' );
}
# Test 9: Token cache path generation
{
my $storeid = 'pure-n1';
my $array_index = 0;
my $expected_path = "/etc/pve/priv/purestorage/${storeid}_array${array_index}.json";
like( $expected_path, qr/\/etc\/pve\/priv\/purestorage\/pure-n1_array0\.json$/, 'Cache path follows expected format' );
}
# Test 10: Atomic write simulation
{
my $temp_path = "$cache_path.tmp.$$";
my $token_data = create_token_data( 75 );
# Write to temp file
open my $fh, '>', $temp_path or die $!;
print $fh encode_json( $token_data );
close $fh;
ok( -f $temp_path, 'Temp file created' );
# Atomic rename
rename( $temp_path, $cache_path ) or die "Cannot rename: $!";
ok( -f $cache_path && !-f $temp_path, 'Atomic rename completed' );
}
# Test 11: Concurrent token creation scenario
{
my $node_a_token = create_token_data( 0 ); # Fresh token
my $node_b_token = create_token_data( 0 ); # Another fresh token
# Both tokens created ~same time
my $time_diff = abs( $node_a_token->{ created_at } - $node_b_token->{ created_at } );
ok( $time_diff < 2, 'Concurrent tokens created within 2 seconds' );
# Race condition check: should skip write if another token exists within 5s
ok( $time_diff < 5, 'Falls within race condition mitigation window (5s)' );
}
done_testing();
print "\nToken Cache Tests Summary:\n";
print "=" x 50 . "\n";
print "All tests validate the token caching mechanism:\n";
print "- Token TTL validation (80% refresh threshold)\n";
print "- Cache file operations (read/write)\n";
print "- Race condition mitigation\n";
print "- Concurrent token handling\n";
print "- Atomic write operations\n";
print "=" x 50 . "\n";