diff --git a/PureStoragePlugin.pm b/PureStoragePlugin.pm index fddc6ce..e78e8b6 100644 --- a/PureStoragePlugin.pm +++ b/PureStoragePlugin.pm @@ -8,6 +8,7 @@ use Data::Dumper qw( Dumper ); # DEBUG use IO::File (); use Net::IP (); use File::Path (); +use File::Spec (); use PVE::JSONSchema (); use PVE::Network (); @@ -31,8 +32,9 @@ $Data::Dumper::Terse = 1; # Removes `$VAR1 =` in output $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 = ""; +my $purestorage_wwn_prefix = '624a9370'; +my $default_hgsuffix = ''; +my $default_protocol = 'iscsi'; my $DEBUG = 0; @@ -76,29 +78,34 @@ sub properties { return { hgsuffix => { description => "Host group suffx.", - type => "string", + type => 'string', default => $default_hgsuffix }, address => { description => "PureStorage Management IP address or DNS name.", - type => "string" + type => 'string' }, token => { description => "Storage API token.", - type => "string" + type => 'string' }, podname => { - description => 'PureStorage pod name', + description => "PureStorage pod name", type => 'string' }, vnprefix => { - description => 'Prefix to add to volume name before sending it to PureStorage array', + description => "Prefix to add to volume name before sending it to PureStorage array", type => 'string' }, check_ssl => { description => "Verify the server's TLS certificate", - type => "boolean", - default => "no" + type => 'boolean', + default => 'no' + }, + protocol => { + description => "Set storage protocol ( iscsi | fc | nvme )", + type => 'string', + default => $default_protocol }, }; } @@ -113,6 +120,7 @@ sub options { podname => { optional => 1 }, vnprefix => { optional => 1 }, check_ssl => { optional => 1 }, + protocol => { optional => 1 }, nodes => { optional => 1 }, disable => { optional => 1 }, content => { optional => 1 }, @@ -139,6 +147,57 @@ sub exec_command { } } +sub scsi_scan_new { + print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::scsi_scan_new\n" if $DEBUG; + my $fc_base = '/sys/class/fc_host'; + my @fc_hosts = glob( "$fc_base/*" ); + + die "Error :: sub::scsi_scan_new did not find fibre channel hosts.\n" unless @fc_hosts; + + foreach my $fc_host ( @fc_hosts ) { + next unless ( $fc_host =~ m/^(\/sys\/class\/fc_host\/\w+)$/ ); + my $adapter = basename( $1 ); + my $scsi_host = File::Spec->catfile( "/sys/class/scsi_host/", $adapter ); + + if ( -d $scsi_host ) { + open my $fh, '>', File::Spec->catfile( $scsi_host, "scan" ) or die "Error :: Cannot open file: $!"; + print $fh "- - -\n"; + close $fh; + } else { + warn "Warning :: SCSI host path $scsi_host does not exist.\n"; + } + } +} + +sub scsi_rescan_device { + my ( $wwid ) = @_; + print "Debug :: PVE::Storage::Custom::PureStoragePlugin::sub::scsi_rescan_device\n" if $DEBUG; + die "Error :: sub::scsi_rescan_device did not recive a wwid.\n" unless $wwid; + + foreach my $device ( glob( '/sys/class/scsi_device/*' ) ) { + next unless ( $device =~ m/^(\/sys\/class\/scsi_device\/[\d\:\\]+)$/ ); + my $tmppath = $1; + + my $wwid_file = File::Spec->catfile( $tmppath, "device/wwid" ); + next unless -f $wwid_file; + + open( my $wwid_fh, '<', $wwid_file ) or die "Error :: Cannot open file: $!"; + my $tmpwwid = <$wwid_fh>; + close( $wwid_fh ); + + $tmpwwid =~ s/^naa\.//; + $tmpwwid = "3" . lc( $tmpwwid ); + chomp( $tmpwwid ); + + if ( $tmpwwid eq $wwid ) { + open my $rescan, '>', File::Spec->catfile( $tmppath, "device/rescan" ) or die "Error :: Cannot open file: $!"; + print $rescan "1\n"; + close $rescan; + } + } + exec_command( [ $cmd->{ multipath }, '-r', $wwid ], 1 ); +} + sub multipath_check { my ( $wwid ) = @_; my $output = `$cmd->{ "multipath" } -l $wwid`; @@ -651,7 +710,16 @@ sub purestorage_resize_volume { my ( $path, $wwid ) = $class->purestorage_get_wwn( $scfg, $volname ); - exec_command( [ $cmd->{ iscsiadm }, '--mode', 'node', '--rescan' ], 1 ); + my $protocol = $scfg->{ protocol } // $default_protocol; + if ( $protocol eq 'iscsi' ) { + exec_command( [ $cmd->{ iscsiadm }, '--mode', 'node', '--rescan' ], 1 ); + } elsif ( $protocol eq 'fc' ) { + scsi_rescan_device( $wwid ); + } elsif ( $protocol eq 'nvme' ) { + die qq{Error :: Protocol: "$protocol" isn't implemented yet.\n}; + } else { + die qq{Error :: Protocol: "$protocol" isn't a valid protocol.\n}; + } exec_command( [ $cmd->{ multipathd }, 'resize', 'map', $wwid ], 1 ); @@ -963,7 +1031,16 @@ sub map_volume { print "Debug :: Mapping volume \"$volname\" with WWN: " . uc( $wwid ) . ".\n" if $DEBUG; - exec_command( [ $cmd->{ iscsiadm }, '--mode', 'session', '--rescan' ], 1 ); + my $protocol = $scfg->{ protocol } // $default_protocol; + if ( $protocol eq 'iscsi' ) { + exec_command( [ $cmd->{ iscsiadm }, '--mode', 'session', '--rescan' ], 1 ); + } elsif ( $protocol eq 'fc' ) { + scsi_scan_new(); + } elsif ( $protocol eq 'nvme' ) { + die qq{Error :: Protocol: "$protocol" isn't implemented yet.\n}; + } else { + die qq{Error :: Protocol: "$protocol" isn't a valid protocol.\n}; + } my $path_exists = sub { return -e $path; diff --git a/README.md b/README.md index 9c3489e..d599045 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,7 @@ 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) | > **_NOTE:_** Ensure that the token and other sensitive information are kept secure and not exposed publicly.