12 Commits

Author SHA1 Message Date
timansky 4214cf9ff5 perl lint 2025-01-24 04:25:39 +05:00
timansky 918baa8a14 Added retry attempts if x-auth-token invalid (#24) 2025-01-24 04:18:16 +05:00
Timur Kumakbayev 78a46db7c4 Merge pull request #17 from kolesa-team/development
Do not convert to prerelease
2025-01-20 03:03:24 +05:00
timansky 3a76010d96 Do not convert to prerelease 2025-01-20 03:02:51 +05:00
Timur Kumakbayev fc989677bb Merge pull request #16 from kolesa-team/temp
More fixes
2025-01-20 02:33:23 +05:00
Timur Kumakbayev d9018eec0f Merge pull request #14 from PwrBank/main
Troubleshooting commands
2025-01-19 20:56:17 +05:00
PwrBank c6770fc4cd Update README.md
Updated troubleshooting commands, as suggested by @timansky
2025-01-17 06:49:47 -06:00
amulet1 6474161796 Unmap volume before renaming it
PVE storage subsystem does not unmap disk before moving it to another guest.

Also added check to make sure the target volume (if specified) does not exist.
2025-01-16 15:41:31 -05:00
amulet1 829cf63e1f Added purestorage_get_volume_info() and purestorage_get_existing_volume_info()
The functions return first matching volume object.

Use purestorage_get_existing_volume_info() in purestorage_get_wwn()
2025-01-16 15:41:07 -05:00
amulet1 660d9bff76 Make hgsuffix truly optional
Replaced the default value "pve" with ""

Resolves #5
2025-01-16 15:40:30 -05:00
PwrBank e9342bee43 Merge branch 'kolesa-team:main' into main 2025-01-16 11:32:40 -06:00
PwrBank d97f93c340 Update README.md
Added some troubleshooting commands
2025-01-16 11:32:29 -06:00
3 changed files with 93 additions and 34 deletions
+1 -1
View File
@@ -50,7 +50,7 @@ jobs:
tag_name: ${{ github.ref_name }}
generate_release_notes: true
make_latest: true
prerelease: true
prerelease: false
files: |
*.deb
sha256sums
+86 -33
View File
@@ -32,7 +32,7 @@ $Data::Dumper::Indent = 1; # Outputs everything in one line
$Data::Dumper::Useqq = 1; # Uses quotes for strings
my $purestorage_wwn_prefix = "624a9370";
my $default_hgsuffix = "pve";
my $default_hgsuffix = "";
my $DEBUG = 0;
@@ -127,12 +127,15 @@ sub exec_command {
### BLOCK: Local multipath => PVE::Storage::Custom::PureStoragePlugin::sub::s
sub purestorage_request {
my ( $class, $scfg, $type, $method, $params, $body ) = @_;
my ( $class, $scfg, $type, $method, $params, $body, $attempt ) = @_;
print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::purestorage_request\n" if $DEBUG;
my $api = "2.26";
my $url = $scfg->{ address };
my $check_ssl = $scfg->{ check_ssl } ? 1 : 0;
my $api = "2.26";
my $url = $scfg->{ address };
my $check_ssl = $scfg->{ check_ssl } ? 1 : 0;
my $max_attempts = 5;
my $interval = 1;
$attempt //= 1; # Initialize the attempt counter to 1 if not provided
$url .= "/api/$api/$type";
$url .= "?$params" if $params;
@@ -141,6 +144,7 @@ sub purestorage_request {
$type eq "login"
? $scfg->{ token }
: $class->purestorage_get_auth_token( $scfg );
my $headers = HTTP::Headers->new(
( $type eq "login" ? "api-token" : "x-auth-token" ) => $token,
"Content-Type" => "application/json"
@@ -155,14 +159,31 @@ sub purestorage_request {
verify_hostname => 0,
SSL_verify_mode => 0x00
) if !$check_ssl;
my $request = HTTP::Request->new( $method, $url, $headers, $body ? encode_json( $body ) : undef );
my $response = $ua->request( $request );
my $request = HTTP::Request->new( $method, $url, $headers, $body ? encode_json( $body ) : undef );
my $response = $ua->request( $request );
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;
if ( !$response->is_success ) {
if ( $response->code == 401 && $attempt < $max_attempts ) {
$attempt++;
print "Error :: Invalid session. Retrying... Attempt: " . ( $attempt ) . "\n";
# Reset the token cache
$scfg->{ x_auth_token } = 0;
sleep $interval;
# Recursively call the function with an incremented attempt counter
return $class->purestorage_request( $scfg, $type, $method, $params, $body, $attempt );
}
}
return {
content => $content,
headers => $response->headers,
@@ -251,19 +272,19 @@ sub purestorage_volume_info {
sub purestorage_list_volumes {
my ( $class, $scfg, $vmid, $storeid, $destroyed ) = @_;
print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::purestorage_list_volumes\n" if $DEBUG;
my $names = defined ($vmid) ? "vm-$vmid-disk-*,vm-$vmid-cloudinit,vm-$vmid-state-*" : "*";
my $names = defined( $vmid ) ? "vm-$vmid-disk-*,vm-$vmid-cloudinit,vm-$vmid-state-*" : "*";
return $class->purestorage_list_volumes2( $scfg, $names, $storeid, $destroyed );
return $class->purestorage_get_volumes( $scfg, $names, $storeid, $destroyed );
}
sub purestorage_list_volumes2 {
sub purestorage_get_volumes {
my ( $class, $scfg, $names, $storeid, $destroyed ) = @_;
print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::purestorage_list_volumes\n" if $DEBUG;
my $vgname = $scfg->{ vgname } || die "Error :: Volume group name is not defined.\n";
my @names_list = map { "name='$vgname/$_'" } split( ',', $names );
my $filter = join( ' or ', @names_list );
if ( defined( $destroyed ) ) {
@@ -302,13 +323,31 @@ sub purestorage_list_volumes2 {
return \@volumes;
}
sub purestorage_get_volume_info {
my ( $class, $scfg, $volname, $storeid, $destroyed ) = @_;
print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::purestorage_get_volume_info\n" if $DEBUG;
my $volumes = $class->purestorage_get_volumes( $scfg, $volname, $storeid, $destroyed );
foreach my $volume ( @$volumes ) {
return $volume;
}
return undef;
}
sub purestorage_get_existing_volume_info {
my ( $class, $scfg, $volname, $storeid ) = @_;
return $class->purestorage_get_volume_info( $scfg, $volname, $storeid, 0 );
}
sub purestorage_get_wwn {
my ( $class, $scfg, $volname ) = @_;
print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::purestorage_get_wwn\n" if $DEBUG;
my $volumes = $class->purestorage_list_volumes2( $scfg, $volname, undef, 0 );
my $volume = $class->purestorage_get_existing_volume_info( $scfg, $volname );
if ( $volume ) {
foreach my $volume ( @$volumes ) {
# Construct the WWN path
my $path = lc( "/dev/disk/by-id/wwn-0x" . $purestorage_wwn_prefix . $volume->{ serial } );
my $wwn = lc( "3" . $purestorage_wwn_prefix . $volume->{ serial } );
@@ -370,7 +409,7 @@ sub purestorage_volume_connection {
my $vgname = $scfg->{ vgname } || die "Error :: Volume group name is not defined.\n";
my $url = $scfg->{ address } || die "Error :: Pure Storage host is not defined.\n";
my $hname = PVE::INotify::nodename();
my $hname = PVE::INotify::nodename();
my $hgsuffix = $scfg->{ hgsuffix } // $default_hgsuffix;
$hname .= "-" . $hgsuffix if $hgsuffix ne "";
@@ -382,7 +421,7 @@ sub purestorage_volume_connection {
$message = $response->{ content }->{ errors }->[0]->{ message } || '*';
if ( $message eq "Connection already exists." ) {
$message = '' if $action eq 'POST';
} elsif ( $message eq "Volume has been destroyed." || $message eq "Connection does not exist.") {
} elsif ( $message eq "Volume has been destroyed." || $message eq "Connection does not exist." ) {
$message = '' if $action eq 'DELETE';
}
if ( $message ne '' ) {
@@ -398,7 +437,7 @@ sub purestorage_volume_connection {
$message = 'is';
}
$message .= ' ' . ($action eq 'DELETE' ? 'removed from' : 'added to');
$message .= ' ' . ( $action eq 'DELETE' ? 'removed from' : 'added to' );
print "Info :: Volume \"$vgname/$volname\" $message host \"$hname\".\n";
return 1;
}
@@ -442,7 +481,7 @@ sub purestorage_remove_volume {
my $url = $scfg->{ address } || die "Error :: Pure Storage host is not defined.\n";
my $params = "names=$vgname/$volname";
my $body = { destroyed => \1 };
my $body = { destroyed => \1 };
my $response = $class->purestorage_request( $scfg, "volumes", "PATCH", $params, $body );
if ( $response->{ error } ) {
@@ -486,7 +525,9 @@ sub purestorage_get_device_size {
print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::purestorage_get_device_size\n" if $DEBUG;
my $size = 0;
exec_command( [ $cmd->{ blockdev }, '--getsize64', $path ], 1,
exec_command(
[ $cmd->{ blockdev }, '--getsize64', $path ],
1,
outfunc => sub {
$size = $_[0];
chomp $size;
@@ -524,7 +565,7 @@ sub purestorage_resize_volume {
my ( $path, undef, undef, $wwid ) = $class->filesystem_path( $scfg, $volname );
exec_command( [ $cmd->{ iscsiadm }, '--mode', 'node', '--rescan' ], 1);
exec_command( [ $cmd->{ iscsiadm }, '--mode', 'node', '--rescan' ], 1 );
# FIXME: wwid is probably ignored
exec_command( [ $cmd->{ multipath }, '-r', $wwid ], 1 );
@@ -545,7 +586,7 @@ sub purestorage_resize_volume {
print "Info :: New size detected for volume \"$vgname/$volname\": $new_size bytes.\n";
return $new_size;
}
sleep $interval;
++$iteration;
}
@@ -698,7 +739,7 @@ sub parse_volname {
# ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format)
return ( $vtype, $name, $vmid, undef, undef, undef, 'raw' );
}
die "Error :: Invalid volume name ($volname).\n";
return 0;
}
@@ -735,8 +776,8 @@ sub find_free_diskname {
my ( $class, $storeid, $scfg, $vmid, $fmt, $add_fmt_suffix ) = @_;
print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::find_free_diskname\n" if $DEBUG;
my $volumes = $class->purestorage_list_volumes( $scfg, $vmid, $storeid );
my @disk_list = map { $_->{ name } } @$volumes;
my $volumes = $class->purestorage_list_volumes( $scfg, $vmid, $storeid );
my @disk_list = map { $_->{ name } } @$volumes;
return PVE::Storage::Plugin::get_next_vm_diskname( \@disk_list, $storeid, $vmid, undef, $scfg );
}
@@ -756,11 +797,11 @@ sub alloc_image {
$name = $class->find_free_diskname( $storeid, $scfg, $vmid ) if !$name;
# Check size (must be between 1MB and 4PB)
if ($size < 1024) {
if ( $size < 1024 ) {
print "Info :: Size is too small ($size kb), adjusting to 1024 kb\n";
$size = 1024;
}
# Convert size from KB to bytes
my $sizeB = $size * 1024; # KB => B
@@ -805,14 +846,15 @@ sub status {
my $current_time = gettimeofday();
if ( $current_time - $cache->{ last_update } >= 60 ) {
print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::status\n" if $DEBUG;
my $response = $class->purestorage_request( $scfg, "arrays/space", "GET" );
# Get storage capacity and used space from the response
$cache->{ total } = $response->{ content }->{ items }->[0]->{ capacity };
$cache->{ used } = $response->{ content }->{ items }->[0]->{ space }->{ total_physical };
# $cache->{ used } = $response->{ content }->{ items }->[0]->{ space }->{ total_used }; # Do not know what is correct
$cache->{ last_update } = $current_time;
} else {
print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::status::cached\n" if $DEBUG;
@@ -863,7 +905,7 @@ sub map_volume {
exec_command( [ $cmd->{ multipath }, '-a', $wwid ], 1 );
exec_command( [ $cmd->{ iscsiadm }, '--mode', 'session', '--rescan' ], 1 );
exec_command( [ $cmd->{ iscsiadm }, '--mode', 'session', '--rescan' ], 1 );
# Wait for the device to apear
my $iteration = 0;
@@ -900,7 +942,7 @@ sub unmap_volume {
exec_command( [ $cmd->{ blockdev }, '--flushbufs', $path ] );
my $device_name = basename( $device_path );
my $slaves_path = "/sys/block/$device_name/slaves";
my $slaves_path = "/sys/block/$device_name/slaves";
my @slaves = ();
if ( -d $slaves_path ) {
@@ -938,7 +980,7 @@ sub unmap_volume {
print "Info :: Device \"$device_name\" removed from system.\n";
return 1;
}
return 0;
}
@@ -959,7 +1001,7 @@ sub deactivate_volume {
my $vgname = $scfg->{ vgname } || die "Error :: Volume group name is not defined.\n";
$class->unmap_volume( $storeid, $scfg, $volname, $snapname );
$class->purestorage_volume_connection( $scfg, $volname, 'DELETE' );
print "Info :: Volume \"$vgname/$volname\" deactivated.\n";
@@ -982,9 +1024,20 @@ sub volume_resize {
sub rename_volume {
my ( $class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname ) = @_;
print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::rename_volume\n" if $DEBUG;
die "Error :: not implemented in storage plugin \"$class\".\n" if $class->can( 'api' ) && $class->api() < 10;
$target_volname = $class->find_free_diskname( $storeid, $scfg, $target_vmid ) if !$target_volname;
if ( $target_volname ) {
# See RBDPlugin.pm (note, currently PVE does not supply $target_volname parameter)
my $volume = $class->purestorage_get_volume_info( $scfg, $target_volname, $storeid );
die "target volume '$target_volname' already exists\n" if $volume;
} else {
$target_volname = $class->find_free_diskname( $storeid, $scfg, $target_vmid );
}
# we need to unmap source volume (see RBDPlugin.pm)
$class->unmap_volume( $storeid, $scfg, $source_volname );
$class->purestorage_rename_volume( $scfg, $source_volname, $target_volname );
+6
View File
@@ -141,6 +141,12 @@ sudo systemctl restart pve-cluster.service pvedaemon.service pvestatd.service pv
- Verify Network Connectivity: Ensure that the Proxmox VE nodes can reach the Pure Storage array over the network. Check for firewall rules or network issues that might be blocking communication.
- Review Logs: Check the Proxmox VE logs for any error messages related to storage or the plugin. Logs are typically found in /var/log/pve.
These commands are helpful for troubleshooting:
```bash
multipath -ll -v3 #diagnose issues with the multipath service
iscsiadm -m node #list what iscsi nodes are mounted
ls -l /dev/mapper/3624a9370* #list wwids of Pure mapped devices on the system
```
- Multipath Configuration: Verify that your multipath.conf is correctly configured and that multipath devices are recognized. Use multipath -ll to list the current multipath devices.
- API Token Permissions: Ensure that the API token used has the necessary permissions to create and manage volumes on the Pure Storage array.
- Plugin Updates: Ensure you are using the latest version of the plugin. Check the GitHub repository for updates.