diff --git a/PureStoragePlugin.pm b/PureStoragePlugin.pm index 7e9576a..b791094 100644 --- a/PureStoragePlugin.pm +++ b/PureStoragePlugin.pm @@ -96,10 +96,18 @@ sub properties { default => 'no' }, protocol => { - description => "Set storage protocol ( iscsi | fc | nvme )", + description => "Set storage protocol ( iscsi | iscsidirect | fc | nvme )", type => 'string', default => $default_protocol }, + target_addr => { + description => "Set storage iSCSI Target Portal address (only for protocol iscsidirect)", + type => 'string', + }, + target_name => { + description => "Set storage iSCSI Target Name (only for protocol iscsidirect)", + type => 'string', + }, }; } @@ -108,16 +116,18 @@ sub options { address => { fixed => 1 }, token => { fixed => 1 }, - hgsuffix => { optional => 1 }, - vgname => { optional => 1 }, - podname => { optional => 1 }, - vnprefix => { optional => 1 }, - check_ssl => { optional => 1 }, - protocol => { optional => 1 }, - nodes => { optional => 1 }, - disable => { optional => 1 }, - content => { optional => 1 }, - format => { optional => 1 }, + hgsuffix => { optional => 1 }, + vgname => { optional => 1 }, + podname => { optional => 1 }, + vnprefix => { optional => 1 }, + check_ssl => { optional => 1 }, + protocol => { optional => 1 }, + nodes => { optional => 1 }, + disable => { optional => 1 }, + content => { optional => 1 }, + format => { optional => 1 }, + target_addr => { optional => 1 }, + target_name => { optional => 1 }, }; } @@ -619,23 +629,30 @@ sub purestorage_get_wwn { sub purestorage_volume_connection { my ( $class, $scfg, $volname, $mode ) = @_; - my $method = $mode ? 'POST' : 'DELETE'; - print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::purestorage_volume_connection :: $method\n" if $DEBUG; - my $hname = PVE::INotify::nodename(); my $hgsuffix = $scfg->{ hgsuffix } // $default_hgsuffix; $hname .= "-" . $hgsuffix if $hgsuffix ne ""; + my $method; my $name; - my $ignore; - if ( $mode ) { + my $ignore = ''; + if ($mode == 0) { + $name = 'get volume connection'; + $method = 'GET'; + } elsif ($mode == -1) { + $name = 'delete volume connection'; + $ignore = [ 'Volume has been destroyed.', 'Connection does not exist.' ]; + $method = 'DELETE'; + } elsif ($mode == 1) { $name = 'create volume connection'; $ignore = 'Connection already exists.'; + $method = 'POST'; } else { - $name = 'delete volume connection'; - $ignore = [ 'Volume has been destroyed.', 'Connection does not exist.' ]; + die ("Error :: PVE::Storage::Custom::PureStoragePlugin::sub::purestorage_volume_connection :: unknown mode $mode\n"); } + print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::purestorage_volume_connection :: $method\n" if $DEBUG; + my $action = { name => $name, type => 'connections', @@ -649,8 +666,22 @@ sub purestorage_volume_connection { 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"; + if ( $mode == 0 || $mode == 1 ) { + my $lun = 0; + $lun = $response->{ items }->[0]->{ lun } if $response->{ items }->[0] && $response->{ items }->[0]->{ volume }->{ name } eq purestorage_name( $scfg, $volname ); + if ($lun) { + print "Info :: Volume \"$volname\" is " . ( $mode == 1 ? 'now ' : 'currently ' ) . "connected as LUN $lun to Host \"$hname\"\n"; + return $lun; + } elsif ($mode == 0) { + print "Info :: Volume \"$volname\" is currently not connected to Host \"$hname\"\n"; + return 0; + } + die ("Error :: Could not get LUN for Volume \"$volname\" on Host \"$hname\"\n") if ( !$response->{ errors } ); + print "Info :: Volume \"$volname\" is already connected to Host \"$hname\"\n"; + return 0; + } + + print "Info :: Volume \"$volname\" is " . ( $response->{ errors } ? 'already ' : 'now ' ) . "disconnected from Host \"$hname\"\n"; return 1; } @@ -715,6 +746,11 @@ sub purestorage_remove_volume { return 1; } +sub get_protocol() { + my ( $class, $scfg ) = @_; + return $scfg->{ protocol } // $default_protocol; +} + sub purestorage_resize_volume { my ( $class, $scfg, $volname, $size ) = @_; print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::purestorage_resize_volume\n" if $DEBUG; @@ -731,6 +767,13 @@ sub purestorage_resize_volume { my $serial = $response->{ items }->[0]->{ serial } or die "Error :: Failed to retrieve volume serial"; + if ( $class->get_protocol($scfg) eq "iscsidirect" ) { + my $newsize = 0; + $newsize = $response->{ items }->[0]->{ provisioned } if ( $response->{ items }->[0]->{ name } eq purestorage_name( $scfg, $volname ) || $response->{ items }->[0]->{ source }->{ name } eq purestorage_name( $scfg, $volname ) ); + die ("Error :: Could not get new size for Volume \"$volname\"\n") if (!$newsize); + return $newsize; + } + my ( $path, $wwid ) = get_device_path_wwn( $serial ); # return early if the volume is not mapped (normally should not happen) @@ -895,6 +938,21 @@ sub filesystem_path { # do we even need this? my ( $vtype, undef, $vmid ) = $class->parse_volname( $volname ); + if ( $class->get_protocol($scfg) eq "iscsidirect" ) { + # we need to explicitly call GET because otherwise we won't get the LUN id if the volume is already connected. + my $lun = $class->purestorage_volume_connection( $scfg, $volname, 0 ); + if (!$lun) { + $lun = $class->purestorage_volume_connection( $scfg, $volname, 1 ); + } + + my $target = $scfg->{target_name}; + my $portal = $scfg->{target_addr}; + my $path = "iscsi://$portal/$target/$lun"; + + print ("Debug :: path: $path\n") if $DEBUG; + return ($path, $vmid, $vtype); + } + my ( $path, $wwid ) = $class->purestorage_get_wwn( $scfg, $volname ); if ( !defined( $path ) || !defined( $vmid ) || !defined( $vtype ) ) { @@ -1033,11 +1091,14 @@ sub volume_size_info { sub map_volume { my ( $class, $storeid, $scfg, $volname, $snapname ) = @_; print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::map_volume\n" if $DEBUG; + + return 0 if ( $class->get_protocol($scfg) eq "iscsidirect" ); + my ( $path, $wwid ) = $class->purestorage_get_wwn( $scfg, $volname ); print "Debug :: Mapping volume \"$volname\" with WWN: " . uc( $wwid ) . ".\n" if $DEBUG; - my $protocol = $scfg->{ protocol } // $default_protocol; + my $protocol = $class->get_protocol($scfg); if ( $protocol eq 'iscsi' || $protocol eq 'fc' ) { scsi_scan_new( $protocol ); } elsif ( $protocol eq 'nvme' ) { @@ -1064,6 +1125,8 @@ sub unmap_volume { my ( $class, $storeid, $scfg, $volname, $snapname ) = @_; print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::unmap_volume\n" if $DEBUG; + return 0 if ( $class->get_protocol($scfg) eq "iscsidirect" ); + my ( $path, $wwid ) = $class->purestorage_get_wwn( $scfg, $volname ); return 0 unless $path ne '' && -b $path; @@ -1109,7 +1172,7 @@ sub deactivate_volume { $class->unmap_volume( $storeid, $scfg, $volname, $snapname ); - $class->purestorage_volume_connection( $scfg, $volname, 0 ); + $class->purestorage_volume_connection( $scfg, $volname, -1 ); print "Info :: Volume \"$volname\" is deactivated.\n"; diff --git a/README.md b/README.md index d599045..1434a66 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ This plugin enables the integration of Pure Storage arrays with Proxmox Virtual Before installing and using this plugin, ensure that your Proxmox VE environment meets the following prerequisites. -### Multipath Configuration +### Multipath Configuration (not required for protocol iscsidirect) To ensure correct operation with Pure Storage, you need to configure your multipath settings appropriately. Specifically, you need to set find_multipaths to no in your multipath.conf file. This setting disables the automatic detection of multipath devices, which is necessary for Pure Storage devices to be correctly recognized. @@ -131,7 +131,9 @@ purestorage: | vnprefix | (`optional`) The prefix to prepend to name of virtual disks. | | hgsuffix | (`optional`) A suffix that is appended to the hostname when the plugin interacts with the Pure Storage array. This can help differentiate hosts if necessary. | | content | Specifies the types of content that can be stored. For virtual machine disk images, use images. | -| protocol | (`optional`, default is `iscsi`) Specifies the storage protocol (iscsi, fc) | +| protocol | (`optional`, default is `iscsi`) Specifies the storage protocol (iscsi, iscsidirect, fc) | +| target_addr | (`optional`, only for protocol `iscsidirect`) Set storage iSCSI Target Portal address | +| target_name | (`optional`, only for protocol `iscsidirect`) Set storage iSCSI Target Name | > **_NOTE:_** Ensure that the token and other sensitive information are kept secure and not exposed publicly.