commit
						29b110f5e2
					
				|  | @ -128,10 +128,13 @@ sub run_lun_command { | ||||||
| 
 | 
 | ||||||
|     syslog("info",(caller(0))[3] . " : $method(@params)"); |     syslog("info",(caller(0))[3] . " : $method(@params)"); | ||||||
| 
 | 
 | ||||||
|     if(!defined($scfg->{'freenas_user'}) || !defined($scfg->{'freenas_password'})) { |     if (defined($scfg->{'truenas_token_auth'}) && $scfg->{'truenas_token_auth'}) { | ||||||
|         die "Undefined freenas_user and/or freenas_password."; |         if (!defined($scfg->{'truenas_secret'})) { | ||||||
|  |             die "Undefined `truenas_secret` variable."; | ||||||
|  |         } | ||||||
|  |     } elsif (!defined($scfg->{'freenas_user'}) || !defined($scfg->{'freenas_password'})) { | ||||||
|  |         die "Undefined `freenas_user` and/or `freenas_password` variables."; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     if (!defined $freenas_server_list->{defined($scfg->{freenas_apiv4_host}) ? $scfg->{freenas_apiv4_host} : $scfg->{portal}}) { |     if (!defined $freenas_server_list->{defined($scfg->{freenas_apiv4_host}) ? $scfg->{freenas_apiv4_host} : $scfg->{portal}}) { | ||||||
|         freenas_api_check($scfg); |         freenas_api_check($scfg); | ||||||
|     } |     } | ||||||
|  | @ -341,7 +344,13 @@ sub freenas_api_connect { | ||||||
|     } |     } | ||||||
|     $freenas_server_list->{$apihost}->setHost($scheme . '://' . $apihost); |     $freenas_server_list->{$apihost}->setHost($scheme . '://' . $apihost); | ||||||
|     $freenas_server_list->{$apihost}->addHeader('Content-Type', 'application/json'); |     $freenas_server_list->{$apihost}->addHeader('Content-Type', 'application/json'); | ||||||
|     $freenas_server_list->{$apihost}->addHeader('Authorization', 'Basic ' . encode_base64($scfg->{freenas_user} . ':' . $scfg->{freenas_password})); |     if (defined($scfg->{'truenas_token_auth'})) { | ||||||
|  |         syslog("info", (caller(0))[3] . " : Authentication using Bearer Token Auth"); | ||||||
|  |         $freenas_server_list->{$apihost}->addHeader('Authorization', 'Bearer ' . $scfg->{truenas_secret}); | ||||||
|  |     } else { | ||||||
|  |         syslog("info", (caller(0))[3] . " : Authentication using Basic Auth"); | ||||||
|  |         $freenas_server_list->{$apihost}->addHeader('Authorization', 'Basic ' . encode_base64($scfg->{freenas_user} . ':' . $scfg->{freenas_password})); | ||||||
|  |     } | ||||||
|     # If using SSL, don't verify SSL certs |     # If using SSL, don't verify SSL certs | ||||||
|     if ($scfg->{freenas_use_ssl}) { |     if ($scfg->{freenas_use_ssl}) { | ||||||
|         $freenas_server_list->{$apihost}->getUseragent()->ssl_opts(verify_hostname => 0); |         $freenas_server_list->{$apihost}->getUseragent()->ssl_opts(verify_hostname => 0); | ||||||
|  |  | ||||||
|  | @ -0,0 +1,157 @@ | ||||||
|  | --- ZFSPlugin.pm.orig	2023-12-31 09:56:18.895228853 -0500
 | ||||||
|  | +++ ZFSPlugin.pm	2023-12-31 09:57:08.830488875 -0500
 | ||||||
|  | @@ -10,6 +10,7 @@
 | ||||||
|  |   | ||||||
|  |  use base qw(PVE::Storage::ZFSPoolPlugin); | ||||||
|  |  use PVE::Storage::LunCmd::Comstar; | ||||||
|  | +use PVE::Storage::LunCmd::FreeNAS;
 | ||||||
|  |  use PVE::Storage::LunCmd::Istgt; | ||||||
|  |  use PVE::Storage::LunCmd::Iet; | ||||||
|  |  use PVE::Storage::LunCmd::LIO; | ||||||
|  | @@ -26,13 +27,14 @@
 | ||||||
|  |      modify_lu   => 1, | ||||||
|  |      add_view    => 1, | ||||||
|  |      list_view   => 1, | ||||||
|  | +    list_extent => 1,
 | ||||||
|  |      list_lu     => 1, | ||||||
|  |  }; | ||||||
|  |   | ||||||
|  |  my $zfs_unknown_scsi_provider = sub { | ||||||
|  |      my ($provider) = @_; | ||||||
|  |   | ||||||
|  | -    die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
 | ||||||
|  | +    die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
 | ||||||
|  |  }; | ||||||
|  |   | ||||||
|  |  my $zfs_get_base = sub { | ||||||
|  | @@ -40,6 +42,8 @@
 | ||||||
|  |   | ||||||
|  |      if ($scfg->{iscsiprovider} eq 'comstar') { | ||||||
|  |          return PVE::Storage::LunCmd::Comstar::get_base; | ||||||
|  | +    } elsif ($scfg->{iscsiprovider} eq 'freenas') {
 | ||||||
|  | +        return PVE::Storage::LunCmd::FreeNAS::get_base;
 | ||||||
|  |      } elsif ($scfg->{iscsiprovider} eq 'istgt') { | ||||||
|  |          return PVE::Storage::LunCmd::Istgt::get_base; | ||||||
|  |      } elsif ($scfg->{iscsiprovider} eq 'iet') { | ||||||
|  | @@ -62,6 +66,8 @@
 | ||||||
|  |      if ($lun_cmds->{$method}) { | ||||||
|  |          if ($scfg->{iscsiprovider} eq 'comstar') { | ||||||
|  |              $msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params); | ||||||
|  | +        } elsif ($scfg->{iscsiprovider} eq 'freenas') {
 | ||||||
|  | +            $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
 | ||||||
|  |          } elsif ($scfg->{iscsiprovider} eq 'istgt') { | ||||||
|  |              $msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params); | ||||||
|  |          } elsif ($scfg->{iscsiprovider} eq 'iet') { | ||||||
|  | @@ -166,6 +172,15 @@
 | ||||||
|  |      die "lun_number for guid $guid is not a number"; | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | +# Part of the multipath enhancement
 | ||||||
|  | +sub zfs_get_wwid_number {
 | ||||||
|  | +    my ($class, $scfg, $guid) = @_;
 | ||||||
|  | +
 | ||||||
|  | +    die "could not find lun_number for guid $guid" if !$guid;
 | ||||||
|  | +
 | ||||||
|  | +    return $class->zfs_request($scfg, undef, 'list_extent', $guid);
 | ||||||
|  | +}
 | ||||||
|  | +
 | ||||||
|  |  # Configuration | ||||||
|  |   | ||||||
|  |  sub type { | ||||||
|  | @@ -184,6 +199,32 @@
 | ||||||
|  |  	    description => "iscsi provider", | ||||||
|  |  	    type => 'string', | ||||||
|  |  	}, | ||||||
|  | +	# This is for FreeNAS iscsi and API intergration
 | ||||||
|  | +	# And some enhancements asked by the community
 | ||||||
|  | +	freenas_user => {
 | ||||||
|  | +	    description => "FreeNAS API Username",
 | ||||||
|  | +	    type => 'string',
 | ||||||
|  | +	},
 | ||||||
|  | +	freenas_password => {
 | ||||||
|  | +	    description => "FreeNAS API Password (Deprecated)",
 | ||||||
|  | +	    type => 'string',
 | ||||||
|  | +	},
 | ||||||
|  | +	truenas_secret => {
 | ||||||
|  | +	    description => "TrueNAS API Secret",
 | ||||||
|  | +	    type => 'string',
 | ||||||
|  | +	},
 | ||||||
|  | +	truenas_token_auth => {
 | ||||||
|  | +	    description => "TrueNAS API Authentication with Token",
 | ||||||
|  | +	    type => 'boolean',
 | ||||||
|  | +	},
 | ||||||
|  | +	freenas_use_ssl => {
 | ||||||
|  | +	    description => "FreeNAS API access via SSL",
 | ||||||
|  | +	    type => 'boolean',
 | ||||||
|  | +	},
 | ||||||
|  | +	freenas_apiv4_host => {
 | ||||||
|  | +	    description => "FreeNAS API Host",
 | ||||||
|  | +	    type => 'string',
 | ||||||
|  | +	},
 | ||||||
|  |  	# this will disable write caching on comstar and istgt. | ||||||
|  |  	# it is not implemented for iet. iet blockio always operates with | ||||||
|  |  	# writethrough caching when not in readonly mode | ||||||
|  | @@ -211,14 +252,20 @@
 | ||||||
|  |  	nodes => { optional => 1 }, | ||||||
|  |  	disable => { optional => 1 }, | ||||||
|  |  	portal => { fixed => 1 }, | ||||||
|  | -	target => { fixed => 1 },
 | ||||||
|  | -	pool => { fixed => 1 },
 | ||||||
|  | +	target => { fixed => 0 },
 | ||||||
|  | +	pool => { fixed => 0 },
 | ||||||
|  |  	blocksize => { fixed => 1 }, | ||||||
|  |  	iscsiprovider => { fixed => 1 }, | ||||||
|  |  	nowritecache => { optional => 1 }, | ||||||
|  |  	sparse => { optional => 1 }, | ||||||
|  |  	comstar_hg => { optional => 1 }, | ||||||
|  |  	comstar_tg => { optional => 1 }, | ||||||
|  | +	freenas_user => { optional => 1 },
 | ||||||
|  | +	freenas_password => { optional => 1 },
 | ||||||
|  | +	truenas_secret => { optional => 1 },
 | ||||||
|  | +	truenas_token_auth => { optional => 1 },
 | ||||||
|  | +	freenas_use_ssl => { optional => 1 },
 | ||||||
|  | +	freenas_apiv4_host => { optional => 1 },
 | ||||||
|  |  	lio_tpg => { optional => 1 }, | ||||||
|  |  	content => { optional => 1 }, | ||||||
|  |  	bwlimit => { optional => 1 }, | ||||||
|  | @@ -243,6 +290,40 @@
 | ||||||
|  |   | ||||||
|  |      my $path = "iscsi://$portal/$target/$lun"; | ||||||
|  |   | ||||||
|  | +    # Multipath enhancement
 | ||||||
|  | +    eval {
 | ||||||
|  | +	my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
 | ||||||
|  | +#	syslog(info,"JD: path get_lun_number guid $guid");
 | ||||||
|  | +
 | ||||||
|  | +	if ($wwid =~ /^([-\@\w.]+)$/) {
 | ||||||
|  | +	    $wwid = $1;                     # $data now untainted
 | ||||||
|  | +	} else {
 | ||||||
|  | +	    die "Bad data in '$wwid'";      # log this somewhere
 | ||||||
|  | +	}
 | ||||||
|  | +	my $wwid_end = substr $wwid, 16;
 | ||||||
|  | +
 | ||||||
|  | +	my $mapper = '';
 | ||||||
|  | +	sleep 3;
 | ||||||
|  | +	run_command("iscsiadm -m session --rescan");
 | ||||||
|  | +	sleep 3;
 | ||||||
|  | +	my $line = `/usr/sbin/multipath -ll | grep \"$wwid_end\"`;
 | ||||||
|  | +	my ($mapper_device) = split(' ', $line);
 | ||||||
|  | +	$mapper_device = "" unless $mapper_device;
 | ||||||
|  | +	$mapper .= $mapper_device;
 | ||||||
|  | +
 | ||||||
|  | +	if ($mapper =~ /^([-\@\w.]+)$/) {
 | ||||||
|  | +	    $mapper = $1;                   # $data now untainted
 | ||||||
|  | +	} else {
 | ||||||
|  | +	    $mapper = '';
 | ||||||
|  | +	}
 | ||||||
|  | +
 | ||||||
|  | +#	syslog(info,"Multipath mapper found: $mapper\n");
 | ||||||
|  | +	if ($mapper ne "") {
 | ||||||
|  | +	    $path = "/dev/mapper/$mapper";
 | ||||||
|  | +	    sleep 5;
 | ||||||
|  | +	}
 | ||||||
|  | +    };
 | ||||||
|  | +
 | ||||||
|  |      return ($path, $vmid, $vtype); | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| --- ZFSPlugin.pm.orig	2022-02-04 12:08:01.000000000 -0500
 | --- ZFSPlugin.pm.orig	2023-12-31 09:56:18.895228853 -0500
 | ||||||
| +++ ZFSPlugin.pm	2022-03-26 13:51:40.660068908 -0400
 | +++ ZFSPlugin.pm	2023-12-31 09:57:08.830488875 -0500
 | ||||||
| @@ -10,6 +10,7 @@
 | @@ -10,6 +10,7 @@
 | ||||||
|   |   | ||||||
|  use base qw(PVE::Storage::ZFSPoolPlugin); |  use base qw(PVE::Storage::ZFSPoolPlugin); | ||||||
|  | @ -58,7 +58,7 @@ | ||||||
|  # Configuration |  # Configuration | ||||||
|   |   | ||||||
|  sub type { |  sub type { | ||||||
| @@ -184,6 +199,24 @@
 | @@ -184,6 +199,32 @@
 | ||||||
|  	    description => "iscsi provider", |  	    description => "iscsi provider", | ||||||
|  	    type => 'string', |  	    type => 'string', | ||||||
|  	}, |  	}, | ||||||
|  | @ -69,9 +69,17 @@ | ||||||
| +	    type => 'string',
 | +	    type => 'string',
 | ||||||
| +	},
 | +	},
 | ||||||
| +	freenas_password => {
 | +	freenas_password => {
 | ||||||
| +	    description => "FreeNAS API Password",
 | +	    description => "FreeNAS API Password (Deprecated)",
 | ||||||
| +	    type => 'string',
 | +	    type => 'string',
 | ||||||
| +	},
 | +	},
 | ||||||
|  | +	truenas_secret => {
 | ||||||
|  | +	    description => "TrueNAS API Secret",
 | ||||||
|  | +	    type => 'string',
 | ||||||
|  | +	},
 | ||||||
|  | +	truenas_token_auth => {
 | ||||||
|  | +	    description => "TrueNAS API Authentication with Token",
 | ||||||
|  | +	    type => 'boolean',
 | ||||||
|  | +	},
 | ||||||
| +	freenas_use_ssl => {
 | +	freenas_use_ssl => {
 | ||||||
| +	    description => "FreeNAS API access via SSL",
 | +	    description => "FreeNAS API access via SSL",
 | ||||||
| +	    type => 'boolean',
 | +	    type => 'boolean',
 | ||||||
|  | @ -83,7 +91,7 @@ | ||||||
|  	# this will disable write caching on comstar and istgt. |  	# this will disable write caching on comstar and istgt. | ||||||
|  	# it is not implemented for iet. iet blockio always operates with |  	# it is not implemented for iet. iet blockio always operates with | ||||||
|  	# writethrough caching when not in readonly mode |  	# writethrough caching when not in readonly mode | ||||||
| @@ -211,14 +244,18 @@
 | @@ -211,14 +252,20 @@
 | ||||||
|  	nodes => { optional => 1 }, |  	nodes => { optional => 1 }, | ||||||
|  	disable => { optional => 1 }, |  	disable => { optional => 1 }, | ||||||
|  	portal => { fixed => 1 }, |  	portal => { fixed => 1 }, | ||||||
|  | @ -99,12 +107,14 @@ | ||||||
|  	comstar_tg => { optional => 1 }, |  	comstar_tg => { optional => 1 }, | ||||||
| +	freenas_user => { optional => 1 },
 | +	freenas_user => { optional => 1 },
 | ||||||
| +	freenas_password => { optional => 1 },
 | +	freenas_password => { optional => 1 },
 | ||||||
|  | +	truenas_secret => { optional => 1 },
 | ||||||
|  | +	truenas_token_auth => { optional => 1 },
 | ||||||
| +	freenas_use_ssl => { optional => 1 },
 | +	freenas_use_ssl => { optional => 1 },
 | ||||||
| +	freenas_apiv4_host => { optional => 1 },
 | +	freenas_apiv4_host => { optional => 1 },
 | ||||||
|  	lio_tpg => { optional => 1 }, |  	lio_tpg => { optional => 1 }, | ||||||
|  	content => { optional => 1 }, |  	content => { optional => 1 }, | ||||||
|  	bwlimit => { optional => 1 }, |  	bwlimit => { optional => 1 }, | ||||||
| @@ -243,6 +280,40 @@
 | @@ -243,6 +290,40 @@
 | ||||||
|   |   | ||||||
|      my $path = "iscsi://$portal/$target/$lun"; |      my $path = "iscsi://$portal/$target/$lun"; | ||||||
|   |   | ||||||
|  |  | ||||||
|  | @ -0,0 +1,91 @@ | ||||||
|  | --- apidoc.js.orig	2024-01-06 13:02:06.730512378 -0500
 | ||||||
|  | +++ apidoc.js	2024-01-06 13:02:55.349787105 -0500
 | ||||||
|  | @@ -50336,6 +50336,37 @@
 | ||||||
|  |                             "type" : "string", | ||||||
|  |                             "typetext" : "<string>" | ||||||
|  |                          }, | ||||||
|  | +                        "freenas_user" : {
 | ||||||
|  | +                           "description" : "FreeNAS user for API access",
 | ||||||
|  | +                           "optional" : 1,
 | ||||||
|  | +                           "type" : "string",
 | ||||||
|  | +                           "typetext" : "<string>"
 | ||||||
|  | +                        },
 | ||||||
|  | +                        "freenas_password" : {
 | ||||||
|  | +                           "description" : "FreeNAS password for API access (Deprecated)",
 | ||||||
|  | +                           "optional" : 1,
 | ||||||
|  | +                           "type" : "string",
 | ||||||
|  | +                           "typetext" : "<string>"
 | ||||||
|  | +                        },
 | ||||||
|  | +                        "truenas_secret" : {
 | ||||||
|  | +                           "description" : "TrueNAS Secret for API access",
 | ||||||
|  | +                           "optional" : 1,
 | ||||||
|  | +                           "type" : "string",
 | ||||||
|  | +                           "typetext" : "<string>"
 | ||||||
|  | +                        },
 | ||||||
|  | +                        "freenas_use_ssl" : {
 | ||||||
|  | +                           "description" : "FreeNAS API access via SSL",
 | ||||||
|  | +                           "optional" : 1,
 | ||||||
|  | +                           "type" : "boolean",
 | ||||||
|  | +                           "typetext" : "<boolean>"
 | ||||||
|  | +                        },
 | ||||||
|  | +                        "freenas_apiv4_host" : {
 | ||||||
|  | +                           "description" : "FreeNAS API Host via IPv4",
 | ||||||
|  | +                           "format" : "address",
 | ||||||
|  | +                           "optional" : 1,
 | ||||||
|  | +                           "type" : "string",
 | ||||||
|  | +                           "typetext" : "<string>"
 | ||||||
|  | +                        },
 | ||||||
|  |                          "fuse" : { | ||||||
|  |                             "description" : "Mount CephFS through FUSE.", | ||||||
|  |                             "optional" : 1, | ||||||
|  | @@ -50555,6 +50586,12 @@
 | ||||||
|  |                             "type" : "boolean", | ||||||
|  |                             "typetext" : "<boolean>" | ||||||
|  |                          }, | ||||||
|  | +                        "target" : {
 | ||||||
|  | +                           "description" : "iSCSI target.",
 | ||||||
|  | +                           "optional" : 1,
 | ||||||
|  | +                           "type" : "string",
 | ||||||
|  | +                           "typetext" : "<string>"
 | ||||||
|  | +                        },
 | ||||||
|  |                          "transport" : { | ||||||
|  |                             "description" : "Gluster transport: tcp or rdma", | ||||||
|  |                             "enum" : [ | ||||||
|  | @@ -50854,6 +50891,37 @@
 | ||||||
|  |                       "optional" : 1, | ||||||
|  |                       "type" : "string", | ||||||
|  |                       "typetext" : "<string>" | ||||||
|  | +                  },
 | ||||||
|  | +                  "freenas_user" : {
 | ||||||
|  | +                     "description" : "FreeNAS user for API access",
 | ||||||
|  | +                     "optional" : 1,
 | ||||||
|  | +                     "type" : "string",
 | ||||||
|  | +                     "typetext" : "<string>"
 | ||||||
|  | +                  },
 | ||||||
|  | +                  "freenas_password" : {
 | ||||||
|  | +                     "description" : "FreeNAS password for API access (Deprecated)",
 | ||||||
|  | +                     "optional" : 1,
 | ||||||
|  | +                     "type" : "string",
 | ||||||
|  | +                     "typetext" : "<string>"
 | ||||||
|  | +                  },
 | ||||||
|  | +                  "truenas_secret" : {
 | ||||||
|  | +                     "description" : "TrueNAS secret for API access",
 | ||||||
|  | +                     "optional" : 1,
 | ||||||
|  | +                     "type" : "string",
 | ||||||
|  | +                     "typetext" : "<string>"
 | ||||||
|  | +                  },
 | ||||||
|  | +                  "freenas_use_ssl" : {
 | ||||||
|  | +                     "description" : "FreeNAS API access via SSL",
 | ||||||
|  | +                     "optional" : 1,
 | ||||||
|  | +                     "type" : "boolean",
 | ||||||
|  | +                     "typetext" : "<boolean>"
 | ||||||
|  | +                  },
 | ||||||
|  | +                  "freenas_apiv4_host" : {
 | ||||||
|  | +                     "description" : "FreeNAS API Host via IPv4",
 | ||||||
|  | +                     "format" : "address",
 | ||||||
|  | +                     "optional" : 1,
 | ||||||
|  | +                     "type" : "string",
 | ||||||
|  | +                     "typetext" : "<string>"
 | ||||||
|  |                    }, | ||||||
|  |                    "fuse" : { | ||||||
|  |                       "description" : "Mount CephFS through FUSE.", | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| --- apidoc.js.orig	2021-11-15 10:07:34.000000000 -0500
 | --- apidoc.js.orig      2023-08-16 06:03:55.000000000 -0400
 | ||||||
| +++ apidoc.js	2021-12-06 08:04:01.648822707 -0500
 | +++ apidoc.js   2023-12-26 14:45:47.202566775 -0500
 | ||||||
| @@ -44064,6 +44064,31 @@
 | @@ -47579,6 +47579,37 @@
 | ||||||
|                             "type" : "string", |                             "type" : "string", | ||||||
|                             "typetext" : "<string>" |                             "typetext" : "<string>" | ||||||
|                          }, |                          }, | ||||||
|  | @ -11,7 +11,13 @@ | ||||||
| +                           "typetext" : "<string>"
 | +                           "typetext" : "<string>"
 | ||||||
| +                        },
 | +                        },
 | ||||||
| +                        "freenas_password" : {
 | +                        "freenas_password" : {
 | ||||||
| +                           "description" : "FreeNAS password for API access",
 | +                           "description" : "FreeNAS password for API access (Deprecated)",
 | ||||||
|  | +                           "optional" : 1,
 | ||||||
|  | +                           "type" : "string",
 | ||||||
|  | +                           "typetext" : "<string>"
 | ||||||
|  | +                        },
 | ||||||
|  | +                        "truenas_secret" : {
 | ||||||
|  | +                           "description" : "TrueNAS Secret for API access",
 | ||||||
| +                           "optional" : 1,
 | +                           "optional" : 1,
 | ||||||
| +                           "type" : "string",
 | +                           "type" : "string",
 | ||||||
| +                           "typetext" : "<string>"
 | +                           "typetext" : "<string>"
 | ||||||
|  | @ -32,7 +38,7 @@ | ||||||
|                          "fuse" : { |                          "fuse" : { | ||||||
|                             "description" : "Mount CephFS through FUSE.", |                             "description" : "Mount CephFS through FUSE.", | ||||||
|                             "optional" : 1, |                             "optional" : 1, | ||||||
| @@ -44275,6 +44300,12 @@
 | @@ -47798,6 +47829,12 @@
 | ||||||
|                             "type" : "boolean", |                             "type" : "boolean", | ||||||
|                             "typetext" : "<boolean>" |                             "typetext" : "<boolean>" | ||||||
|                          }, |                          }, | ||||||
|  | @ -45,7 +51,7 @@ | ||||||
|                          "transport" : { |                          "transport" : { | ||||||
|                             "description" : "Gluster transport: tcp or rdma", |                             "description" : "Gluster transport: tcp or rdma", | ||||||
|                             "enum" : [ |                             "enum" : [ | ||||||
| @@ -44547,6 +44578,31 @@
 | @@ -48097,6 +48134,37 @@
 | ||||||
|                       "optional" : 1, |                       "optional" : 1, | ||||||
|                       "type" : "string", |                       "type" : "string", | ||||||
|                       "typetext" : "<string>" |                       "typetext" : "<string>" | ||||||
|  | @ -57,7 +63,13 @@ | ||||||
| +                     "typetext" : "<string>"
 | +                     "typetext" : "<string>"
 | ||||||
| +                  },
 | +                  },
 | ||||||
| +                  "freenas_password" : {
 | +                  "freenas_password" : {
 | ||||||
| +                     "description" : "FreeNAS password for API access",
 | +                     "description" : "FreeNAS password for API access (Deprecated)",
 | ||||||
|  | +                     "optional" : 1,
 | ||||||
|  | +                     "type" : "string",
 | ||||||
|  | +                     "typetext" : "<string>"
 | ||||||
|  | +                  },
 | ||||||
|  | +                  "truenas_secret" : {
 | ||||||
|  | +                     "description" : "TrueNAS secret for API access",
 | ||||||
| +                     "optional" : 1,
 | +                     "optional" : 1,
 | ||||||
| +                     "type" : "string",
 | +                     "type" : "string",
 | ||||||
| +                     "typetext" : "<string>"
 | +                     "typetext" : "<string>"
 | ||||||
|  |  | ||||||
|  | @ -1,58 +1,162 @@ | ||||||
| --- pvemanagerlib.js.orig	2022-03-17 09:08:40.000000000 -0400
 | --- pvemanagerlib.js.orig	2023-12-30 15:36:27.913505863 -0500
 | ||||||
| +++ pvemanagerlib.js	2022-04-03 08:54:10.229689187 -0400
 | +++ pvemanagerlib.js	2024-01-02 09:30:56.000000000 -0500
 | ||||||
| @@ -8068,6 +8068,7 @@
 | @@ -9228,6 +9228,7 @@
 | ||||||
|      alias: ['widget.pveiScsiProviderSelector'], |      alias: ['widget.pveiScsiProviderSelector'], | ||||||
|      comboItems: [ |      comboItems: [ | ||||||
|  	['comstar', 'Comstar'], |  	['comstar', 'Comstar'], | ||||||
| +	['freenas', 'FreeNAS-API'],
 | +    ['freenas', 'FreeNAS/TrueNAS API'],
 | ||||||
|  	['istgt', 'istgt'], |  	['istgt', 'istgt'], | ||||||
|  	['iet', 'IET'], |  	['iet', 'IET'], | ||||||
|  	['LIO', 'LIO'], |  	['LIO', 'LIO'], | ||||||
| @@ -49636,6 +49637,7 @@
 | @@ -58017,16 +58018,24 @@
 | ||||||
|  |  	me.callParent(); | ||||||
|  |      }, | ||||||
|  |  }); | ||||||
|  | +
 | ||||||
|  |  Ext.define('PVE.storage.ZFSInputPanel', { | ||||||
|  |      extend: 'PVE.panel.StorageBase', | ||||||
|  |   | ||||||
|  |      viewModel: { | ||||||
|  |  	parent: null, | ||||||
|  	data: { |  	data: { | ||||||
|  | +isComstar: true,
 | ||||||
|  | +	    isFreeNAS: false,
 | ||||||
|  	    isLIO: false, |  	    isLIO: false, | ||||||
|  	    isComstar: true, | -	    isComstar: true,
 | ||||||
| +        isFreeNAS: false,
 | +	    isToken: false,
 | ||||||
|  	    hasWriteCacheOption: true, |  	    hasWriteCacheOption: true, | ||||||
|  	}, |  	}, | ||||||
|  | +formulas: {
 | ||||||
|  | +            hideUsername: function(get) {
 | ||||||
|  | +                return (!get('isFreeNAS') || !(get('isFreeNAS') && !get('isToken')));
 | ||||||
|  | +            },
 | ||||||
|  | +	},
 | ||||||
|      }, |      }, | ||||||
| @@ -49648,10 +49650,26 @@
 |   | ||||||
|  |      controller: { | ||||||
|  | @@ -58034,13 +58043,42 @@
 | ||||||
|  |  	control: { | ||||||
|  |  	    'field[name=iscsiprovider]': { | ||||||
|  |  		change: 'changeISCSIProvider', | ||||||
|  | +},
 | ||||||
|  | +	    'field[name=truenas_token_auth]': {
 | ||||||
|  | +		change: 'changeUsername',
 | ||||||
|  	    }, |  	    }, | ||||||
|  	}, |  	}, | ||||||
|  	changeISCSIProvider: function(f, newVal, oldVal) { |  	changeISCSIProvider: function(f, newVal, oldVal) { | ||||||
| +	    var me = this;
 | +var me = this;
 | ||||||
|  	    var vm = this.getViewModel(); |  	    var vm = this.getViewModel(); | ||||||
|  	    vm.set('isLIO', newVal === 'LIO'); |  	    vm.set('isLIO', newVal === 'LIO'); | ||||||
|  	    vm.set('isComstar', newVal === 'comstar'); |  	    vm.set('isComstar', newVal === 'comstar'); | ||||||
| -	    vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
 | -	    vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
 | ||||||
| +	    vm.set('isFreeNAS', newVal === 'freenas');
 | +	    vm.set('isFreeNAS', newVal === 'freenas');
 | ||||||
| +	    vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
 | +        vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
 | ||||||
| +	    if (newVal !== 'freenas') {
 | +        if (newVal !== 'freenas') {
 | ||||||
| +		me.lookupReference('freenas_use_ssl_field').setValue(false);
 | +            me.lookupReference('freenas_use_ssl_field').setValue(false);
 | ||||||
| +		me.lookupReference('freenas_apiv4_host_field').setValue('');
 | +            me.lookupReference('truenas_token_auth_field').setValue(false);
 | ||||||
| +		me.lookupReference('freenas_user_field').setValue('');
 | +            me.lookupReference('freenas_apiv4_host_field').setValue('');
 | ||||||
| +		me.lookupReference('freenas_user_field').allowBlank = true;
 | +            me.lookupReference('freenas_user_field').setValue('');
 | ||||||
| +		me.lookupReference('freenas_password_field').setValue('');
 | +            me.lookupReference('freenas_user_field').allowBlank = true;
 | ||||||
| +		me.lookupReference('freenas_password_field').allowBlank = true;
 | +            me.lookupReference('truenas_secret_field').setValue('');
 | ||||||
| +		me.lookupReference('freenas_confirmpw_field').setValue('');
 | +            me.lookupReference('truenas_secret_field').allowBlank = true;
 | ||||||
| +		me.lookupReference('freenas_confirmpw_field').allowBlank = true;
 | +            me.lookupReference('truenas_confirm_secret_field').setValue('');
 | ||||||
| +	    } else {
 | +            me.lookupReference('truenas_confirm_secret_field').allowBlank = true;
 | ||||||
| +		me.lookupReference('freenas_user_field').allowBlank = false;
 | +        } else {
 | ||||||
| +		me.lookupReference('freenas_password_field').allowBlank = false;
 | +            me.lookupReference('freenas_user_field').allowBlank = false;
 | ||||||
| +		me.lookupReference('freenas_confirmpw_field').allowBlank = false;
 | +            me.lookupReference('truenas_secret_field').allowBlank = false;
 | ||||||
| +	    }
 | +            me.lookupReference('truenas_confirm_secret_field').allowBlank = false;
 | ||||||
|  | +        }
 | ||||||
|  | +    },
 | ||||||
|  | +    changeUsername: function(f, newVal, oldVal) {
 | ||||||
|  | +        var me = this;
 | ||||||
|  | +        var vm = me.getViewModel();
 | ||||||
|  | +        vm.set('isToken', newVal);
 | ||||||
|  | +        me.lookupReference('freenas_user_field').allowBlank = newVal;
 | ||||||
|  | +        if (newVal) {
 | ||||||
|  | +            me.lookupReference('freenas_user_field').setValue('');
 | ||||||
|  | +        }
 | ||||||
|  	}, |  	}, | ||||||
|      }, |      }, | ||||||
|   |   | ||||||
| @@ -49669,6 +49687,7 @@
 | @@ -58053,28 +58091,78 @@
 | ||||||
|  |   | ||||||
|  |  	values.nowritecache = values.writecache ? 0 : 1; | ||||||
|  |  	delete values.writecache; | ||||||
|  | +    console.warn(values.freenas_password);
 | ||||||
|  | +	if (values.freenas_password) {
 | ||||||
|  | +	    values.truenas_secret = values.freenas_password;
 | ||||||
|  | +	}
 | ||||||
|  | +	console.warn(values.truenas_secret);
 | ||||||
|  |   | ||||||
|  |  	return me.callParent([values]); | ||||||
|      }, |      }, | ||||||
|   |   | ||||||
|      setValues: function(values) { |      setValues: function(values) { | ||||||
| +        values.freenas_confirmpw = values.freenas_password;
 | -	values.writecache = values.nowritecache ? 0 : 1;
 | ||||||
|  	values.writecache = values.nowritecache ? 0 : 1; | -	this.callParent([values]);
 | ||||||
|  	this.callParent([values]); | +        if (values.freenas_password) {
 | ||||||
|  | +            values.truenas_secret = values.freenas_password;
 | ||||||
|  | +        }
 | ||||||
|  | +        values.truenas_confirm_secret = values.truenas_secret;
 | ||||||
|  | +        values.writecache = values.nowritecache ? 0 : 1;
 | ||||||
|  | +        this.callParent([values]);
 | ||||||
|      }, |      }, | ||||||
| @@ -49685,7 +49704,7 @@
 |   | ||||||
|  |      initComponent: function() { | ||||||
|  | -	var me = this;
 | ||||||
|  | +    var me = this;
 | ||||||
|  | +
 | ||||||
|  | +    var tnsecret = Ext.create('Ext.form.TextField', { 
 | ||||||
|  | +        xtype: 'proxmoxtextfield',
 | ||||||
|  | +        name: 'truenas_secret',
 | ||||||
|  | +       reference: 'truenas_secret_field',
 | ||||||
|  | +        inputType: me.isCreate ? '' : 'password',
 | ||||||
|  | +        value: '',
 | ||||||
|  | +        editable: true,
 | ||||||
|  | +        emptyText: Proxmox.Utils.noneText,
 | ||||||
|  | +        bind: {
 | ||||||
|  | +            hidden: '{!isFreeNAS}'
 | ||||||
|  | +        },
 | ||||||
|  | +        fieldLabel: gettext('API Password'),
 | ||||||
|  | +        change: function(f, value) {
 | ||||||
|  | +            if (f.rendered) {
 | ||||||
|  | +                f.up().down('field[name=truenas_confirm_secret]').validate();
 | ||||||
|  | +            }
 | ||||||
|  | +        },
 | ||||||
|  | +    });
 | ||||||
|  |   | ||||||
|  | -	me.column1 = [
 | ||||||
|  | -	    {
 | ||||||
|  | -		xtype: me.isCreate ? 'textfield' : 'displayfield',
 | ||||||
|  | -		name: 'portal',
 | ||||||
|  | +    var tnconfirmsecret = Ext.create('Ext.form.TextField', {
 | ||||||
|  | +        xtype: 'proxmoxtextfield',
 | ||||||
|  | +        name: 'truenas_confirm_secret',
 | ||||||
|  | +        reference: 'truenas_confirm_secret_field',
 | ||||||
|  | +        inputType: me.isCreate ? '' : 'password',
 | ||||||
|  | +        value: '',
 | ||||||
|  | +        editable: true,
 | ||||||
|  | +        submitValue: false,
 | ||||||
|  | +        emptyText: Proxmox.Utils.noneText,
 | ||||||
|  | +        bind: {
 | ||||||
|  | +            hidden: '{!isFreeNAS}'
 | ||||||
|  | +        },
 | ||||||
|  | +        fieldLabel: gettext('Confirm API Password'),
 | ||||||
|  | +        validator: function(value) {
 | ||||||
|  | +            var pw = me.up().down('field[name=truenas_secret]').getValue();
 | ||||||
|  | +            if (pw !== value) {
 | ||||||
|  | +                return "Secrets do not match!";
 | ||||||
|  | +            }
 | ||||||
|  | +            return true;
 | ||||||
|  | +        },
 | ||||||
|  | +    });
 | ||||||
|  | +
 | ||||||
|  | +    me.column1 = [
 | ||||||
|  | +        {
 | ||||||
|  | +        xtype: me.isCreate ? 'textfield' : 'displayfield',
 | ||||||
|  | +        name: 'portal',
 | ||||||
|  |  		value: '', | ||||||
|  |  		fieldLabel: gettext('Portal'), | ||||||
|  		allowBlank: false, |  		allowBlank: false, | ||||||
|  	    }, |  	    }, | ||||||
|  	    { |  	    { | ||||||
|  | @ -61,7 +165,7 @@ | ||||||
|  		name: 'pool', |  		name: 'pool', | ||||||
|  		value: '', |  		value: '', | ||||||
|  		fieldLabel: gettext('Pool'), |  		fieldLabel: gettext('Pool'), | ||||||
| @@ -49695,11 +49714,11 @@
 | @@ -58084,11 +58172,11 @@
 | ||||||
|  		xtype: me.isCreate ? 'textfield' : 'displayfield', |  		xtype: me.isCreate ? 'textfield' : 'displayfield', | ||||||
|  		name: 'blocksize', |  		name: 'blocksize', | ||||||
|  		value: '4k', |  		value: '4k', | ||||||
|  | @ -75,7 +179,7 @@ | ||||||
|  		name: 'target', |  		name: 'target', | ||||||
|  		value: '', |  		value: '', | ||||||
|  		fieldLabel: gettext('Target'), |  		fieldLabel: gettext('Target'), | ||||||
| @@ -49710,9 +49729,34 @@
 | @@ -58099,8 +58187,59 @@
 | ||||||
|  		name: 'comstar_tg', |  		name: 'comstar_tg', | ||||||
|  		value: '', |  		value: '', | ||||||
|  		fieldLabel: gettext('Target group'), |  		fieldLabel: gettext('Target group'), | ||||||
|  | @ -84,7 +188,7 @@ | ||||||
| +		    hidden: '{!isComstar}'
 | +		    hidden: '{!isComstar}'
 | ||||||
| +		},
 | +		},
 | ||||||
|  		allowBlank: true, |  		allowBlank: true, | ||||||
|  	    }, | +},
 | ||||||
| +	    {
 | +	    {
 | ||||||
| +		xtype: 'proxmoxcheckbox',
 | +		xtype: 'proxmoxcheckbox',
 | ||||||
| +		name: 'freenas_use_ssl',
 | +		name: 'freenas_use_ssl',
 | ||||||
|  | @ -98,6 +202,32 @@ | ||||||
| +		fieldLabel: gettext('API use SSL'),
 | +		fieldLabel: gettext('API use SSL'),
 | ||||||
| +	    },
 | +	    },
 | ||||||
| +	    {
 | +	    {
 | ||||||
|  | +		xtype: 'proxmoxcheckbox',
 | ||||||
|  | +		name: 'truenas_token_auth',
 | ||||||
|  | +		reference: 'truenas_token_auth_field',
 | ||||||
|  | +		inputId: 'truenas_use_token_auth_field',
 | ||||||
|  | +		checked: false,
 | ||||||
|  | +		listeners: {
 | ||||||
|  | +		    change: function(field, newValue) {
 | ||||||
|  | +			if (newValue === true) {
 | ||||||
|  | +			    tnsecret.labelEl.update('API Token');
 | ||||||
|  | +			    tnconfirmsecret.labelEl.update('Confirm API Token');
 | ||||||
|  | +			    me.lookupReference('freenas_user_field').setValue('');
 | ||||||
|  | +			    me.lookupReference('freenas_user_field').allowBlank = true;
 | ||||||
|  | +			} else {
 | ||||||
|  | +			    tnsecret.labelEl.update('API Password');
 | ||||||
|  | +			    tnconfirmsecret.labelEl.update('Confirm API Password');
 | ||||||
|  | +			    me.lookupReference('freenas_user_field').allowBlank = false;
 | ||||||
|  | +			}
 | ||||||
|  | +		    },
 | ||||||
|  | +		},
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{!isFreeNAS}'
 | ||||||
|  | +		},
 | ||||||
|  | +		uncheckedValue: 0,
 | ||||||
|  | +		fieldLabel: gettext('API Token Auth'),
 | ||||||
|  | +	    },
 | ||||||
|  | +	    {
 | ||||||
| +		xtype: 'textfield',
 | +		xtype: 'textfield',
 | ||||||
| +		name: 'freenas_user',
 | +		name: 'freenas_user',
 | ||||||
| +		reference: 'freenas_user_field',
 | +		reference: 'freenas_user_field',
 | ||||||
|  | @ -105,13 +235,12 @@ | ||||||
| +		value: '',
 | +		value: '',
 | ||||||
| +		fieldLabel: gettext('API Username'),
 | +		fieldLabel: gettext('API Username'),
 | ||||||
| +		bind: {
 | +		bind: {
 | ||||||
| +		    hidden: '{!isFreeNAS}'
 | +		    hidden: '{hideUsername}'
 | ||||||
| +		},
 | +		},
 | ||||||
| +	    },
 |  	    }, | ||||||
|  	]; |  	]; | ||||||
|   |   | ||||||
|  	me.column2 = [ | @@ -58131,7 +58270,9 @@
 | ||||||
| @@ -49742,7 +49786,9 @@
 |  | ||||||
|  		xtype: me.isCreate ? 'textfield' : 'displayfield', |  		xtype: me.isCreate ? 'textfield' : 'displayfield', | ||||||
|  		name: 'comstar_hg', |  		name: 'comstar_hg', | ||||||
|  		value: '', |  		value: '', | ||||||
|  | @ -122,18 +251,19 @@ | ||||||
|  		fieldLabel: gettext('Host group'), |  		fieldLabel: gettext('Host group'), | ||||||
|  		allowBlank: true, |  		allowBlank: true, | ||||||
|  	    }, |  	    }, | ||||||
| @@ -49750,9 +49796,62 @@
 | @@ -58139,15 +58280,32 @@
 | ||||||
|  		xtype: me.isCreate ? 'textfield' : 'displayfield', |  		xtype: me.isCreate ? 'textfield' : 'displayfield', | ||||||
|  		name: 'lio_tpg', |  		name: 'lio_tpg', | ||||||
|  		value: '', |  		value: '', | ||||||
| -		bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
 | -		bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
 | ||||||
| -		allowBlank: false,
 | -		allowBlank: false,
 | ||||||
|  | -		fieldLabel: gettext('Target portal group'),
 | ||||||
| +		bind: {
 | +		bind: {
 | ||||||
| +		    hidden: '{!isLIO}'
 | +		    hidden: '{!isLIO}'
 | ||||||
| +		},
 | +		},
 | ||||||
|  		fieldLabel: gettext('Target portal group'), | +				fieldLabel: gettext('Target portal group'),
 | ||||||
| +		allowBlank: true
 | +	    allowBlank: true
 | ||||||
| +	    },
 |  	    }, | ||||||
| +	    {
 | +	    {
 | ||||||
| +		xtype: 'proxmoxtextfield',
 | +		xtype: 'proxmoxtextfield',
 | ||||||
| +		name: 'freenas_apiv4_host',
 | +		name: 'freenas_apiv4_host',
 | ||||||
|  | @ -146,44 +276,14 @@ | ||||||
| +		},
 | +		},
 | ||||||
| +		fieldLabel: gettext('API IPv4 Host'),
 | +		fieldLabel: gettext('API IPv4 Host'),
 | ||||||
| +	    },
 | +	    },
 | ||||||
| +	    {
 | +	    tnsecret,
 | ||||||
| +		xtype: 'proxmoxtextfield',
 | +	    tnconfirmsecret,
 | ||||||
| +		name: 'freenas_password',
 |  | ||||||
| +		reference: 'freenas_password_field',
 |  | ||||||
| +		inputType: me.isCreate ? '' : 'password',
 |  | ||||||
| +		value: '',
 |  | ||||||
| +		editable: true,
 |  | ||||||
| +		emptyText: Proxmox.Utils.noneText,
 |  | ||||||
| +		bind: {
 |  | ||||||
| +		    hidden: '{!isFreeNAS}'
 |  | ||||||
| +		},
 |  | ||||||
| +		fieldLabel: gettext('API Password'),
 |  | ||||||
| +		change: function(f, value) {
 |  | ||||||
| +		    if (f.rendered) {
 |  | ||||||
| +			f.up().down('field[name=freenas_confirmpw]').validate();
 |  | ||||||
| +		    }
 |  | ||||||
| +		},
 |  | ||||||
| +	    },
 |  | ||||||
| +	    {
 |  | ||||||
| +		xtype: 'proxmoxtextfield',
 |  | ||||||
| +		name: 'freenas_confirmpw',
 |  | ||||||
| +		reference: 'freenas_confirmpw_field',
 |  | ||||||
| +		inputType: me.isCreate ? '' : 'password',
 |  | ||||||
| +		value: '',
 |  | ||||||
| +		editable: true,
 |  | ||||||
| +		submitValue: false,
 |  | ||||||
| +		emptyText: Proxmox.Utils.noneText,
 |  | ||||||
| +		bind: {
 |  | ||||||
| +		    hidden: '{!isFreeNAS}'
 |  | ||||||
| +		},
 |  | ||||||
| +		fieldLabel: gettext('Confirm Password'),
 |  | ||||||
| +		validator: function(value) {
 |  | ||||||
| +		    var pw = this.up().down('field[name=freenas_password]').getValue();
 |  | ||||||
| +		    if (pw !== value) {
 |  | ||||||
| +			return "Passwords do not match!";
 |  | ||||||
| +		    }
 |  | ||||||
| +		    return true;
 |  | ||||||
| +		},
 |  | ||||||
|  	    }, |  | ||||||
|  	]; |  	]; | ||||||
|   |   | ||||||
|  |  	me.callParent(); | ||||||
|  |      }, | ||||||
|  |  }); | ||||||
|  | +
 | ||||||
|  |  Ext.define('PVE.storage.ZFSPoolSelector', { | ||||||
|  |      extend: 'PVE.form.ComboBoxSetStoreNode', | ||||||
|  |      alias: 'widget.pveZFSPoolSelector', | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -1,189 +0,0 @@ | ||||||
| --- pvemanagerlib.js.orig	2022-03-17 09:08:40.000000000 -0400
 |  | ||||||
| +++ pvemanagerlib.js	2022-04-03 08:54:10.229689187 -0400
 |  | ||||||
| @@ -8068,6 +8068,7 @@
 |  | ||||||
|      alias: ['widget.pveiScsiProviderSelector'], |  | ||||||
|      comboItems: [ |  | ||||||
|  	['comstar', 'Comstar'], |  | ||||||
| +	['freenas', 'FreeNAS-API'],
 |  | ||||||
|  	['istgt', 'istgt'], |  | ||||||
|  	['iet', 'IET'], |  | ||||||
|  	['LIO', 'LIO'], |  | ||||||
| @@ -49636,6 +49637,7 @@
 |  | ||||||
|  	data: { |  | ||||||
|  	    isLIO: false, |  | ||||||
|  	    isComstar: true, |  | ||||||
| +        isFreeNAS: false,
 |  | ||||||
|  	    hasWriteCacheOption: true, |  | ||||||
|  	}, |  | ||||||
|      }, |  | ||||||
| @@ -49648,10 +49650,26 @@
 |  | ||||||
|  	    }, |  | ||||||
|  	}, |  | ||||||
|  	changeISCSIProvider: function(f, newVal, oldVal) { |  | ||||||
| +	    var me = this;
 |  | ||||||
|  	    var vm = this.getViewModel(); |  | ||||||
|  	    vm.set('isLIO', newVal === 'LIO'); |  | ||||||
|  	    vm.set('isComstar', newVal === 'comstar'); |  | ||||||
| -	    vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
 |  | ||||||
| +	    vm.set('isFreeNAS', newVal === 'freenas');
 |  | ||||||
| +	    vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
 |  | ||||||
| +	    if (newVal !== 'freenas') {
 |  | ||||||
| +		me.lookupReference('freenas_use_ssl_field').setValue(false);
 |  | ||||||
| +		me.lookupReference('freenas_apiv4_host_field').setValue('');
 |  | ||||||
| +		me.lookupReference('freenas_user_field').setValue('');
 |  | ||||||
| +		me.lookupReference('freenas_user_field').allowBlank = true;
 |  | ||||||
| +		me.lookupReference('freenas_password_field').setValue('');
 |  | ||||||
| +		me.lookupReference('freenas_password_field').allowBlank = true;
 |  | ||||||
| +		me.lookupReference('freenas_confirmpw_field').setValue('');
 |  | ||||||
| +		me.lookupReference('freenas_confirmpw_field').allowBlank = true;
 |  | ||||||
| +	    } else {
 |  | ||||||
| +		me.lookupReference('freenas_user_field').allowBlank = false;
 |  | ||||||
| +		me.lookupReference('freenas_password_field').allowBlank = false;
 |  | ||||||
| +		me.lookupReference('freenas_confirmpw_field').allowBlank = false;
 |  | ||||||
| +	    }
 |  | ||||||
|  	}, |  | ||||||
|      }, |  | ||||||
|   |  | ||||||
| @@ -49669,6 +49687,7 @@
 |  | ||||||
|      }, |  | ||||||
|   |  | ||||||
|      setValues: function(values) { |  | ||||||
| +        values.freenas_confirmpw = values.freenas_password;
 |  | ||||||
|  	values.writecache = values.nowritecache ? 0 : 1; |  | ||||||
|  	this.callParent([values]); |  | ||||||
|      }, |  | ||||||
| @@ -49685,7 +49704,7 @@
 |  | ||||||
|  		allowBlank: false, |  | ||||||
|  	    }, |  | ||||||
|  	    { |  | ||||||
| -		xtype: me.isCreate ? 'textfield' : 'displayfield',
 |  | ||||||
| +		xtype: 'textfield',
 |  | ||||||
|  		name: 'pool', |  | ||||||
|  		value: '', |  | ||||||
|  		fieldLabel: gettext('Pool'), |  | ||||||
| @@ -49695,11 +49714,11 @@
 |  | ||||||
|  		xtype: me.isCreate ? 'textfield' : 'displayfield', |  | ||||||
|  		name: 'blocksize', |  | ||||||
|  		value: '4k', |  | ||||||
| -		fieldLabel: gettext('Block Size'),
 |  | ||||||
| +		fieldLabel: gettext('ZFS Block Size'),
 |  | ||||||
|  		allowBlank: false, |  | ||||||
|  	    }, |  | ||||||
|  	    { |  | ||||||
| -		xtype: me.isCreate ? 'textfield' : 'displayfield',
 |  | ||||||
| +		xtype: 'textfield',
 |  | ||||||
|  		name: 'target', |  | ||||||
|  		value: '', |  | ||||||
|  		fieldLabel: gettext('Target'), |  | ||||||
| @@ -49710,9 +49729,34 @@
 |  | ||||||
|  		name: 'comstar_tg', |  | ||||||
|  		value: '', |  | ||||||
|  		fieldLabel: gettext('Target group'), |  | ||||||
| -		bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
 |  | ||||||
| +		bind: {
 |  | ||||||
| +		    hidden: '{!isComstar}'
 |  | ||||||
| +		},
 |  | ||||||
|  		allowBlank: true, |  | ||||||
|  	    }, |  | ||||||
| +	    {
 |  | ||||||
| +		xtype: 'proxmoxcheckbox',
 |  | ||||||
| +		name: 'freenas_use_ssl',
 |  | ||||||
| +		reference: 'freenas_use_ssl_field',
 |  | ||||||
| +		inputId: 'freenas_use_ssl_field',
 |  | ||||||
| +		checked: false,
 |  | ||||||
| +		bind: {
 |  | ||||||
| +		    hidden: '{!isFreeNAS}'
 |  | ||||||
| +		},
 |  | ||||||
| +		uncheckedValue: 0,
 |  | ||||||
| +		fieldLabel: gettext('API use SSL'),
 |  | ||||||
| +	    },
 |  | ||||||
| +	    {
 |  | ||||||
| +		xtype: 'textfield',
 |  | ||||||
| +		name: 'freenas_user',
 |  | ||||||
| +		reference: 'freenas_user_field',
 |  | ||||||
| +		inputId: 'freenas_user_field',
 |  | ||||||
| +		value: '',
 |  | ||||||
| +		fieldLabel: gettext('API Username'),
 |  | ||||||
| +		bind: {
 |  | ||||||
| +		    hidden: '{!isFreeNAS}'
 |  | ||||||
| +		},
 |  | ||||||
| +	    },
 |  | ||||||
|  	]; |  | ||||||
|   |  | ||||||
|  	me.column2 = [ |  | ||||||
| @@ -49742,7 +49786,9 @@
 |  | ||||||
|  		xtype: me.isCreate ? 'textfield' : 'displayfield', |  | ||||||
|  		name: 'comstar_hg', |  | ||||||
|  		value: '', |  | ||||||
| -		bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
 |  | ||||||
| +		bind: {
 |  | ||||||
| +		    hidden: '{!isComstar}'
 |  | ||||||
| +		},
 |  | ||||||
|  		fieldLabel: gettext('Host group'), |  | ||||||
|  		allowBlank: true, |  | ||||||
|  	    }, |  | ||||||
| @@ -49750,9 +49796,62 @@
 |  | ||||||
|  		xtype: me.isCreate ? 'textfield' : 'displayfield', |  | ||||||
|  		name: 'lio_tpg', |  | ||||||
|  		value: '', |  | ||||||
| -		bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
 |  | ||||||
| -		allowBlank: false,
 |  | ||||||
| +		bind: {
 |  | ||||||
| +		    hidden: '{!isLIO}'
 |  | ||||||
| +		},
 |  | ||||||
|  		fieldLabel: gettext('Target portal group'), |  | ||||||
| +		allowBlank: true
 |  | ||||||
| +	    },
 |  | ||||||
| +	    {
 |  | ||||||
| +		xtype: 'proxmoxtextfield',
 |  | ||||||
| +		name: 'freenas_apiv4_host',
 |  | ||||||
| +		reference: 'freenas_apiv4_host_field',
 |  | ||||||
| +		value: '',
 |  | ||||||
| +		editable: true,
 |  | ||||||
| +		emptyText: Proxmox.Utils.noneText,
 |  | ||||||
| +		bind: {
 |  | ||||||
| +		    hidden: '{!isFreeNAS}'
 |  | ||||||
| +		},
 |  | ||||||
| +		fieldLabel: gettext('API IPv4 Host'),
 |  | ||||||
| +	    },
 |  | ||||||
| +	    {
 |  | ||||||
| +		xtype: 'proxmoxtextfield',
 |  | ||||||
| +		name: 'freenas_password',
 |  | ||||||
| +		reference: 'freenas_password_field',
 |  | ||||||
| +		inputType: me.isCreate ? '' : 'password',
 |  | ||||||
| +		value: '',
 |  | ||||||
| +		editable: true,
 |  | ||||||
| +		emptyText: Proxmox.Utils.noneText,
 |  | ||||||
| +		bind: {
 |  | ||||||
| +		    hidden: '{!isFreeNAS}'
 |  | ||||||
| +		},
 |  | ||||||
| +		fieldLabel: gettext('API Password'),
 |  | ||||||
| +		change: function(f, value) {
 |  | ||||||
| +		    if (f.rendered) {
 |  | ||||||
| +			f.up().down('field[name=freenas_confirmpw]').validate();
 |  | ||||||
| +		    }
 |  | ||||||
| +		},
 |  | ||||||
| +	    },
 |  | ||||||
| +	    {
 |  | ||||||
| +		xtype: 'proxmoxtextfield',
 |  | ||||||
| +		name: 'freenas_confirmpw',
 |  | ||||||
| +		reference: 'freenas_confirmpw_field',
 |  | ||||||
| +		inputType: me.isCreate ? '' : 'password',
 |  | ||||||
| +		value: '',
 |  | ||||||
| +		editable: true,
 |  | ||||||
| +		submitValue: false,
 |  | ||||||
| +		emptyText: Proxmox.Utils.noneText,
 |  | ||||||
| +		bind: {
 |  | ||||||
| +		    hidden: '{!isFreeNAS}'
 |  | ||||||
| +		},
 |  | ||||||
| +		fieldLabel: gettext('Confirm Password'),
 |  | ||||||
| +		validator: function(value) {
 |  | ||||||
| +		    var pw = this.up().down('field[name=freenas_password]').getValue();
 |  | ||||||
| +		    if (pw !== value) {
 |  | ||||||
| +			return "Passwords do not match!";
 |  | ||||||
| +		    }
 |  | ||||||
| +		    return true;
 |  | ||||||
| +		},
 |  | ||||||
|  	    }, |  | ||||||
|  	]; |  | ||||||
|   |  | ||||||
|  | @ -0,0 +1,157 @@ | ||||||
|  | --- ZFSPlugin.pm.orig	2023-12-31 09:56:18.895228853 -0500
 | ||||||
|  | +++ ZFSPlugin.pm	2023-12-31 09:57:08.830488875 -0500
 | ||||||
|  | @@ -10,6 +10,7 @@
 | ||||||
|  |   | ||||||
|  |  use base qw(PVE::Storage::ZFSPoolPlugin); | ||||||
|  |  use PVE::Storage::LunCmd::Comstar; | ||||||
|  | +use PVE::Storage::LunCmd::FreeNAS;
 | ||||||
|  |  use PVE::Storage::LunCmd::Istgt; | ||||||
|  |  use PVE::Storage::LunCmd::Iet; | ||||||
|  |  use PVE::Storage::LunCmd::LIO; | ||||||
|  | @@ -26,13 +27,14 @@
 | ||||||
|  |      modify_lu   => 1, | ||||||
|  |      add_view    => 1, | ||||||
|  |      list_view   => 1, | ||||||
|  | +    list_extent => 1,
 | ||||||
|  |      list_lu     => 1, | ||||||
|  |  }; | ||||||
|  |   | ||||||
|  |  my $zfs_unknown_scsi_provider = sub { | ||||||
|  |      my ($provider) = @_; | ||||||
|  |   | ||||||
|  | -    die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
 | ||||||
|  | +    die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
 | ||||||
|  |  }; | ||||||
|  |   | ||||||
|  |  my $zfs_get_base = sub { | ||||||
|  | @@ -40,6 +42,8 @@
 | ||||||
|  |   | ||||||
|  |      if ($scfg->{iscsiprovider} eq 'comstar') { | ||||||
|  |          return PVE::Storage::LunCmd::Comstar::get_base; | ||||||
|  | +    } elsif ($scfg->{iscsiprovider} eq 'freenas') {
 | ||||||
|  | +        return PVE::Storage::LunCmd::FreeNAS::get_base;
 | ||||||
|  |      } elsif ($scfg->{iscsiprovider} eq 'istgt') { | ||||||
|  |          return PVE::Storage::LunCmd::Istgt::get_base; | ||||||
|  |      } elsif ($scfg->{iscsiprovider} eq 'iet') { | ||||||
|  | @@ -62,6 +66,8 @@
 | ||||||
|  |      if ($lun_cmds->{$method}) { | ||||||
|  |          if ($scfg->{iscsiprovider} eq 'comstar') { | ||||||
|  |              $msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params); | ||||||
|  | +        } elsif ($scfg->{iscsiprovider} eq 'freenas') {
 | ||||||
|  | +            $msg = PVE::Storage::LunCmd::FreeNAS::run_lun_command($scfg, $timeout, $method, @params);
 | ||||||
|  |          } elsif ($scfg->{iscsiprovider} eq 'istgt') { | ||||||
|  |              $msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params); | ||||||
|  |          } elsif ($scfg->{iscsiprovider} eq 'iet') { | ||||||
|  | @@ -166,6 +172,15 @@
 | ||||||
|  |      die "lun_number for guid $guid is not a number"; | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | +# Part of the multipath enhancement
 | ||||||
|  | +sub zfs_get_wwid_number {
 | ||||||
|  | +    my ($class, $scfg, $guid) = @_;
 | ||||||
|  | +
 | ||||||
|  | +    die "could not find lun_number for guid $guid" if !$guid;
 | ||||||
|  | +
 | ||||||
|  | +    return $class->zfs_request($scfg, undef, 'list_extent', $guid);
 | ||||||
|  | +}
 | ||||||
|  | +
 | ||||||
|  |  # Configuration | ||||||
|  |   | ||||||
|  |  sub type { | ||||||
|  | @@ -184,6 +199,32 @@
 | ||||||
|  |  	    description => "iscsi provider", | ||||||
|  |  	    type => 'string', | ||||||
|  |  	}, | ||||||
|  | +	# This is for FreeNAS iscsi and API intergration
 | ||||||
|  | +	# And some enhancements asked by the community
 | ||||||
|  | +	freenas_user => {
 | ||||||
|  | +	    description => "FreeNAS API Username",
 | ||||||
|  | +	    type => 'string',
 | ||||||
|  | +	},
 | ||||||
|  | +	freenas_password => {
 | ||||||
|  | +	    description => "FreeNAS API Password (Deprecated)",
 | ||||||
|  | +	    type => 'string',
 | ||||||
|  | +	},
 | ||||||
|  | +	truenas_secret => {
 | ||||||
|  | +	    description => "TrueNAS API Secret",
 | ||||||
|  | +	    type => 'string',
 | ||||||
|  | +	},
 | ||||||
|  | +	truenas_token_auth => {
 | ||||||
|  | +	    description => "TrueNAS API Authentication with Token",
 | ||||||
|  | +	    type => 'boolean',
 | ||||||
|  | +	},
 | ||||||
|  | +	freenas_use_ssl => {
 | ||||||
|  | +	    description => "FreeNAS API access via SSL",
 | ||||||
|  | +	    type => 'boolean',
 | ||||||
|  | +	},
 | ||||||
|  | +	freenas_apiv4_host => {
 | ||||||
|  | +	    description => "FreeNAS API Host",
 | ||||||
|  | +	    type => 'string',
 | ||||||
|  | +	},
 | ||||||
|  |  	# this will disable write caching on comstar and istgt. | ||||||
|  |  	# it is not implemented for iet. iet blockio always operates with | ||||||
|  |  	# writethrough caching when not in readonly mode | ||||||
|  | @@ -211,14 +252,20 @@
 | ||||||
|  |  	nodes => { optional => 1 }, | ||||||
|  |  	disable => { optional => 1 }, | ||||||
|  |  	portal => { fixed => 1 }, | ||||||
|  | -	target => { fixed => 1 },
 | ||||||
|  | -	pool => { fixed => 1 },
 | ||||||
|  | +	target => { fixed => 0 },
 | ||||||
|  | +	pool => { fixed => 0 },
 | ||||||
|  |  	blocksize => { fixed => 1 }, | ||||||
|  |  	iscsiprovider => { fixed => 1 }, | ||||||
|  |  	nowritecache => { optional => 1 }, | ||||||
|  |  	sparse => { optional => 1 }, | ||||||
|  |  	comstar_hg => { optional => 1 }, | ||||||
|  |  	comstar_tg => { optional => 1 }, | ||||||
|  | +	freenas_user => { optional => 1 },
 | ||||||
|  | +	freenas_password => { optional => 1 },
 | ||||||
|  | +	truenas_secret => { optional => 1 },
 | ||||||
|  | +	truenas_token_auth => { optional => 1 },
 | ||||||
|  | +	freenas_use_ssl => { optional => 1 },
 | ||||||
|  | +	freenas_apiv4_host => { optional => 1 },
 | ||||||
|  |  	lio_tpg => { optional => 1 }, | ||||||
|  |  	content => { optional => 1 }, | ||||||
|  |  	bwlimit => { optional => 1 }, | ||||||
|  | @@ -243,6 +290,40 @@
 | ||||||
|  |   | ||||||
|  |      my $path = "iscsi://$portal/$target/$lun"; | ||||||
|  |   | ||||||
|  | +    # Multipath enhancement
 | ||||||
|  | +    eval {
 | ||||||
|  | +	my $wwid = $class->zfs_get_wwid_number($scfg, $guid);
 | ||||||
|  | +#	syslog(info,"JD: path get_lun_number guid $guid");
 | ||||||
|  | +
 | ||||||
|  | +	if ($wwid =~ /^([-\@\w.]+)$/) {
 | ||||||
|  | +	    $wwid = $1;                     # $data now untainted
 | ||||||
|  | +	} else {
 | ||||||
|  | +	    die "Bad data in '$wwid'";      # log this somewhere
 | ||||||
|  | +	}
 | ||||||
|  | +	my $wwid_end = substr $wwid, 16;
 | ||||||
|  | +
 | ||||||
|  | +	my $mapper = '';
 | ||||||
|  | +	sleep 3;
 | ||||||
|  | +	run_command("iscsiadm -m session --rescan");
 | ||||||
|  | +	sleep 3;
 | ||||||
|  | +	my $line = `/usr/sbin/multipath -ll | grep \"$wwid_end\"`;
 | ||||||
|  | +	my ($mapper_device) = split(' ', $line);
 | ||||||
|  | +	$mapper_device = "" unless $mapper_device;
 | ||||||
|  | +	$mapper .= $mapper_device;
 | ||||||
|  | +
 | ||||||
|  | +	if ($mapper =~ /^([-\@\w.]+)$/) {
 | ||||||
|  | +	    $mapper = $1;                   # $data now untainted
 | ||||||
|  | +	} else {
 | ||||||
|  | +	    $mapper = '';
 | ||||||
|  | +	}
 | ||||||
|  | +
 | ||||||
|  | +#	syslog(info,"Multipath mapper found: $mapper\n");
 | ||||||
|  | +	if ($mapper ne "") {
 | ||||||
|  | +	    $path = "/dev/mapper/$mapper";
 | ||||||
|  | +	    sleep 5;
 | ||||||
|  | +	}
 | ||||||
|  | +    };
 | ||||||
|  | +
 | ||||||
|  |      return ($path, $vmid, $vtype); | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | @ -0,0 +1,422 @@ | ||||||
|  | package PVE::Storage::ZFSPlugin; | ||||||
|  | 
 | ||||||
|  | use strict; | ||||||
|  | use warnings; | ||||||
|  | use IO::File; | ||||||
|  | use POSIX; | ||||||
|  | use PVE::Tools qw(run_command); | ||||||
|  | use PVE::Storage::ZFSPoolPlugin; | ||||||
|  | use PVE::RPCEnvironment; | ||||||
|  | 
 | ||||||
|  | use base qw(PVE::Storage::ZFSPoolPlugin); | ||||||
|  | use PVE::Storage::LunCmd::Comstar; | ||||||
|  | use PVE::Storage::LunCmd::Istgt; | ||||||
|  | use PVE::Storage::LunCmd::Iet; | ||||||
|  | use PVE::Storage::LunCmd::LIO; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | my @ssh_opts = ('-o', 'BatchMode=yes'); | ||||||
|  | my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts); | ||||||
|  | my $id_rsa_path = '/etc/pve/priv/zfs'; | ||||||
|  | 
 | ||||||
|  | my $lun_cmds = { | ||||||
|  |     create_lu   => 1, | ||||||
|  |     delete_lu   => 1, | ||||||
|  |     import_lu   => 1, | ||||||
|  |     modify_lu   => 1, | ||||||
|  |     add_view    => 1, | ||||||
|  |     list_view   => 1, | ||||||
|  |     list_lu     => 1, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | my $zfs_unknown_scsi_provider = sub { | ||||||
|  |     my ($provider) = @_; | ||||||
|  | 
 | ||||||
|  |     die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]"; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | my $zfs_get_base = sub { | ||||||
|  |     my ($scfg) = @_; | ||||||
|  | 
 | ||||||
|  |     if ($scfg->{iscsiprovider} eq 'comstar') { | ||||||
|  |         return PVE::Storage::LunCmd::Comstar::get_base; | ||||||
|  |     } elsif ($scfg->{iscsiprovider} eq 'istgt') { | ||||||
|  |         return PVE::Storage::LunCmd::Istgt::get_base; | ||||||
|  |     } elsif ($scfg->{iscsiprovider} eq 'iet') { | ||||||
|  |         return PVE::Storage::LunCmd::Iet::get_base; | ||||||
|  |     } elsif ($scfg->{iscsiprovider} eq 'LIO') { | ||||||
|  |         return PVE::Storage::LunCmd::LIO::get_base; | ||||||
|  |     } else { | ||||||
|  |         $zfs_unknown_scsi_provider->($scfg->{iscsiprovider}); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | sub zfs_request { | ||||||
|  |     my ($class, $scfg, $timeout, $method, @params) = @_; | ||||||
|  | 
 | ||||||
|  |     $timeout = PVE::RPCEnvironment->is_worker() ? 60*60 : 10 | ||||||
|  | 	if !$timeout; | ||||||
|  | 
 | ||||||
|  |     my $msg = ''; | ||||||
|  | 
 | ||||||
|  |     if ($lun_cmds->{$method}) { | ||||||
|  |         if ($scfg->{iscsiprovider} eq 'comstar') { | ||||||
|  |             $msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params); | ||||||
|  |         } elsif ($scfg->{iscsiprovider} eq 'istgt') { | ||||||
|  |             $msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params); | ||||||
|  |         } elsif ($scfg->{iscsiprovider} eq 'iet') { | ||||||
|  |             $msg = PVE::Storage::LunCmd::Iet::run_lun_command($scfg, $timeout, $method, @params); | ||||||
|  |         } elsif ($scfg->{iscsiprovider} eq 'LIO') { | ||||||
|  |             $msg = PVE::Storage::LunCmd::LIO::run_lun_command($scfg, $timeout, $method, @params); | ||||||
|  |         } else { | ||||||
|  |             $zfs_unknown_scsi_provider->($scfg->{iscsiprovider}); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  | 
 | ||||||
|  | 	my $target = 'root@' . $scfg->{portal}; | ||||||
|  | 
 | ||||||
|  | 	my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target]; | ||||||
|  | 
 | ||||||
|  |         if ($method eq 'zpool_list') { | ||||||
|  | 	    push @$cmd, 'zpool', 'list'; | ||||||
|  | 	} else { | ||||||
|  | 	    push @$cmd, 'zfs', $method; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 	push @$cmd, @params; | ||||||
|  | 
 | ||||||
|  | 	my $output = sub { | ||||||
|  | 	    my $line = shift; | ||||||
|  | 	    $msg .= "$line\n"; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         run_command($cmd, outfunc => $output, timeout => $timeout); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return $msg; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub zfs_get_lu_name { | ||||||
|  |     my ($class, $scfg, $zvol) = @_; | ||||||
|  | 
 | ||||||
|  |     my $base = $zfs_get_base->($scfg); | ||||||
|  | 
 | ||||||
|  |     $zvol = ($class->parse_volname($zvol))[1]; | ||||||
|  | 
 | ||||||
|  |     my $object = ($zvol =~ /^.+\/.+/) ? "$base/$zvol" : "$base/$scfg->{pool}/$zvol"; | ||||||
|  | 
 | ||||||
|  |     my $lu_name = $class->zfs_request($scfg, undef, 'list_lu', $object); | ||||||
|  | 
 | ||||||
|  |     return $lu_name if $lu_name; | ||||||
|  | 
 | ||||||
|  |     die "Could not find lu_name for zvol $zvol"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub zfs_add_lun_mapping_entry { | ||||||
|  |     my ($class, $scfg, $zvol, $guid) = @_; | ||||||
|  | 
 | ||||||
|  |     if (!defined($guid)) { | ||||||
|  | 	$guid = $class->zfs_get_lu_name($scfg, $zvol); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $class->zfs_request($scfg, undef, 'add_view', $guid); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub zfs_delete_lu { | ||||||
|  |     my ($class, $scfg, $zvol) = @_; | ||||||
|  | 
 | ||||||
|  |     my $guid = $class->zfs_get_lu_name($scfg, $zvol); | ||||||
|  | 
 | ||||||
|  |     $class->zfs_request($scfg, undef, 'delete_lu', $guid); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub zfs_create_lu { | ||||||
|  |     my ($class, $scfg, $zvol) = @_; | ||||||
|  | 
 | ||||||
|  |     my $base = $zfs_get_base->($scfg); | ||||||
|  |     my $guid = $class->zfs_request($scfg, undef, 'create_lu', "$base/$scfg->{pool}/$zvol"); | ||||||
|  | 
 | ||||||
|  |     return $guid; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub zfs_import_lu { | ||||||
|  |     my ($class, $scfg, $zvol) = @_; | ||||||
|  | 
 | ||||||
|  |     my $base = $zfs_get_base->($scfg); | ||||||
|  |     $class->zfs_request($scfg, undef, 'import_lu', "$base/$scfg->{pool}/$zvol"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub zfs_resize_lu { | ||||||
|  |     my ($class, $scfg, $zvol, $size) = @_; | ||||||
|  | 
 | ||||||
|  |     my $guid = $class->zfs_get_lu_name($scfg, $zvol); | ||||||
|  | 
 | ||||||
|  |     $class->zfs_request($scfg, undef, 'modify_lu', "${size}K", $guid); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub zfs_get_lun_number { | ||||||
|  |     my ($class, $scfg, $guid) = @_; | ||||||
|  | 
 | ||||||
|  |     die "could not find lun_number for guid $guid" if !$guid; | ||||||
|  | 
 | ||||||
|  |     if ($class->zfs_request($scfg, undef, 'list_view', $guid) =~ /^(\d+)$/) { | ||||||
|  | 	return $1; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     die "lun_number for guid $guid is not a number"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # Configuration | ||||||
|  | 
 | ||||||
|  | sub type { | ||||||
|  |     return 'zfs'; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub plugindata { | ||||||
|  |     return { | ||||||
|  | 	content => [ {images => 1}, { images => 1 }], | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub properties { | ||||||
|  |     return { | ||||||
|  | 	iscsiprovider => { | ||||||
|  | 	    description => "iscsi provider", | ||||||
|  | 	    type => 'string', | ||||||
|  | 	}, | ||||||
|  | 	# this will disable write caching on comstar and istgt. | ||||||
|  | 	# it is not implemented for iet. iet blockio always operates with | ||||||
|  | 	# writethrough caching when not in readonly mode | ||||||
|  | 	nowritecache => { | ||||||
|  | 	    description => "disable write caching on the target", | ||||||
|  | 	    type => 'boolean', | ||||||
|  | 	}, | ||||||
|  | 	comstar_tg => { | ||||||
|  | 	    description => "target group for comstar views", | ||||||
|  | 	    type => 'string', | ||||||
|  | 	}, | ||||||
|  | 	comstar_hg => { | ||||||
|  | 	    description => "host group for comstar views", | ||||||
|  | 	    type => 'string', | ||||||
|  | 	}, | ||||||
|  | 	lio_tpg => { | ||||||
|  | 	    description => "target portal group for Linux LIO targets", | ||||||
|  | 	    type => 'string', | ||||||
|  | 	}, | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub options { | ||||||
|  |     return { | ||||||
|  | 	nodes => { optional => 1 }, | ||||||
|  | 	disable => { optional => 1 }, | ||||||
|  | 	portal => { fixed => 1 }, | ||||||
|  | 	target => { fixed => 1 }, | ||||||
|  | 	pool => { fixed => 1 }, | ||||||
|  | 	blocksize => { fixed => 1 }, | ||||||
|  | 	iscsiprovider => { fixed => 1 }, | ||||||
|  | 	nowritecache => { optional => 1 }, | ||||||
|  | 	sparse => { optional => 1 }, | ||||||
|  | 	comstar_hg => { optional => 1 }, | ||||||
|  | 	comstar_tg => { optional => 1 }, | ||||||
|  | 	lio_tpg => { optional => 1 }, | ||||||
|  | 	content => { optional => 1 }, | ||||||
|  | 	bwlimit => { optional => 1 }, | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # Storage implementation | ||||||
|  | 
 | ||||||
|  | sub path { | ||||||
|  |     my ($class, $scfg, $volname, $storeid, $snapname) = @_; | ||||||
|  | 
 | ||||||
|  |     die "direct access to snapshots not implemented" | ||||||
|  | 	if defined($snapname); | ||||||
|  | 
 | ||||||
|  |     my ($vtype, $name, $vmid) = $class->parse_volname($volname); | ||||||
|  | 
 | ||||||
|  |     my $target = $scfg->{target}; | ||||||
|  |     my $portal = $scfg->{portal}; | ||||||
|  | 
 | ||||||
|  |     my $guid = $class->zfs_get_lu_name($scfg, $name); | ||||||
|  |     my $lun = $class->zfs_get_lun_number($scfg, $guid); | ||||||
|  | 
 | ||||||
|  |     my $path = "iscsi://$portal/$target/$lun"; | ||||||
|  | 
 | ||||||
|  |     return ($path, $vmid, $vtype); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub create_base { | ||||||
|  |     my ($class, $storeid, $scfg, $volname) = @_; | ||||||
|  | 
 | ||||||
|  |     my $snap = '__base__'; | ||||||
|  | 
 | ||||||
|  |     my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = | ||||||
|  |         $class->parse_volname($volname); | ||||||
|  | 
 | ||||||
|  |     die "create_base not possible with base image\n" if $isBase; | ||||||
|  | 
 | ||||||
|  |     my $newname = $name; | ||||||
|  |     $newname =~ s/^vm-/base-/; | ||||||
|  | 
 | ||||||
|  |     my $newvolname = $basename ? "$basename/$newname" : "$newname"; | ||||||
|  | 
 | ||||||
|  |     $class->zfs_delete_lu($scfg, $name); | ||||||
|  |     $class->zfs_request($scfg, undef, 'rename', "$scfg->{pool}/$name", "$scfg->{pool}/$newname"); | ||||||
|  | 
 | ||||||
|  |     my $guid = $class->zfs_create_lu($scfg, $newname); | ||||||
|  |     $class->zfs_add_lun_mapping_entry($scfg, $newname, $guid); | ||||||
|  | 
 | ||||||
|  |     my $running  = undef; #fixme : is create_base always offline ? | ||||||
|  | 
 | ||||||
|  |     $class->volume_snapshot($scfg, $storeid, $newname, $snap, $running); | ||||||
|  | 
 | ||||||
|  |     return $newvolname; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub clone_image { | ||||||
|  |     my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_; | ||||||
|  | 
 | ||||||
|  |     my $name = $class->SUPER::clone_image($scfg, $storeid, $volname, $vmid, $snap); | ||||||
|  | 
 | ||||||
|  |     # get ZFS dataset name from PVE volname | ||||||
|  |     my (undef, $clonedname) = $class->parse_volname($name); | ||||||
|  | 
 | ||||||
|  |     my $guid = $class->zfs_create_lu($scfg, $clonedname); | ||||||
|  |     $class->zfs_add_lun_mapping_entry($scfg, $clonedname, $guid); | ||||||
|  | 
 | ||||||
|  |     return $name; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub alloc_image { | ||||||
|  |     my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_; | ||||||
|  |      | ||||||
|  |     die "unsupported format '$fmt'" if $fmt ne 'raw'; | ||||||
|  | 
 | ||||||
|  |     die "illegal name '$name' - should be 'vm-$vmid-*'\n" | ||||||
|  |     if $name && $name !~ m/^vm-$vmid-/; | ||||||
|  | 
 | ||||||
|  |     my $volname = $name; | ||||||
|  | 
 | ||||||
|  |     $volname = $class->find_free_diskname($storeid, $scfg, $vmid, $fmt) if !$volname; | ||||||
|  |      | ||||||
|  |     $class->zfs_create_zvol($scfg, $volname, $size); | ||||||
|  |   | ||||||
|  |     my $guid = $class->zfs_create_lu($scfg, $volname); | ||||||
|  |     $class->zfs_add_lun_mapping_entry($scfg, $volname, $guid); | ||||||
|  | 
 | ||||||
|  |     return $volname; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub free_image { | ||||||
|  |     my ($class, $storeid, $scfg, $volname, $isBase) = @_; | ||||||
|  | 
 | ||||||
|  |     my ($vtype, $name, $vmid) = $class->parse_volname($volname); | ||||||
|  | 
 | ||||||
|  |     $class->zfs_delete_lu($scfg, $name); | ||||||
|  | 
 | ||||||
|  |     eval { $class->zfs_delete_zvol($scfg, $name); }; | ||||||
|  |     if (my $err = $@) { | ||||||
|  |         my $guid = $class->zfs_create_lu($scfg, $name); | ||||||
|  |         $class->zfs_add_lun_mapping_entry($scfg, $name, $guid); | ||||||
|  |         die $err; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return undef; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub volume_resize { | ||||||
|  |     my ($class, $scfg, $storeid, $volname, $size, $running) = @_; | ||||||
|  | 
 | ||||||
|  |     $volname = ($class->parse_volname($volname))[1]; | ||||||
|  | 
 | ||||||
|  |     my $new_size = $class->SUPER::volume_resize($scfg, $storeid, $volname, $size, $running); | ||||||
|  | 
 | ||||||
|  |     $class->zfs_resize_lu($scfg, $volname, $new_size); | ||||||
|  | 
 | ||||||
|  |     return $new_size; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub volume_snapshot_delete { | ||||||
|  |     my ($class, $scfg, $storeid, $volname, $snap, $running) = @_; | ||||||
|  | 
 | ||||||
|  |     $volname = ($class->parse_volname($volname))[1]; | ||||||
|  | 
 | ||||||
|  |     $class->zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub volume_snapshot_rollback { | ||||||
|  |     my ($class, $scfg, $storeid, $volname, $snap) = @_; | ||||||
|  | 
 | ||||||
|  |     $volname = ($class->parse_volname($volname))[1]; | ||||||
|  | 
 | ||||||
|  |     $class->zfs_delete_lu($scfg, $volname); | ||||||
|  | 
 | ||||||
|  |     $class->zfs_request($scfg, undef, 'rollback', "$scfg->{pool}/$volname\@$snap"); | ||||||
|  | 
 | ||||||
|  |     $class->zfs_import_lu($scfg, $volname); | ||||||
|  | 
 | ||||||
|  |     $class->zfs_add_lun_mapping_entry($scfg, $volname); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub storage_can_replicate { | ||||||
|  |     my ($class, $scfg, $storeid, $format) = @_; | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub volume_has_feature { | ||||||
|  |     my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_; | ||||||
|  | 
 | ||||||
|  |     my $features = { | ||||||
|  | 	snapshot => { current => 1, snap => 1}, | ||||||
|  | 	clone => { base => 1}, | ||||||
|  | 	template => { current => 1}, | ||||||
|  | 	copy => { base => 1, current => 1}, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = | ||||||
|  | 	$class->parse_volname($volname); | ||||||
|  | 
 | ||||||
|  |     my $key = undef; | ||||||
|  | 
 | ||||||
|  |     if ($snapname) { | ||||||
|  | 	$key = 'snap'; | ||||||
|  |     } else { | ||||||
|  | 	$key = $isBase ? 'base' : 'current'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return 1 if $features->{$feature}->{$key}; | ||||||
|  | 
 | ||||||
|  |     return undef; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub activate_storage { | ||||||
|  |     my ($class, $storeid, $scfg, $cache) = @_; | ||||||
|  | 
 | ||||||
|  |     return 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub deactivate_storage { | ||||||
|  |     my ($class, $storeid, $scfg, $cache) = @_; | ||||||
|  | 
 | ||||||
|  |     return 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub activate_volume { | ||||||
|  |     my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_; | ||||||
|  | 
 | ||||||
|  |     die "unable to activate snapshot from remote zfs storage" if $snapname; | ||||||
|  | 
 | ||||||
|  |     return 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub deactivate_volume { | ||||||
|  |     my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_; | ||||||
|  | 
 | ||||||
|  |     die "unable to deactivate snapshot from remote zfs storage" if $snapname; | ||||||
|  | 
 | ||||||
|  |     return 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 1; | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| --- ZFSPlugin.pm.orig	2022-02-04 12:08:01.000000000 -0500
 | --- ZFSPlugin.pm.orig	2023-12-31 09:56:18.895228853 -0500
 | ||||||
| +++ ZFSPlugin.pm	2022-03-26 13:51:40.660068908 -0400
 | +++ ZFSPlugin.pm	2023-12-31 09:57:08.830488875 -0500
 | ||||||
| @@ -10,6 +10,7 @@
 | @@ -10,6 +10,7 @@
 | ||||||
|   |   | ||||||
|  use base qw(PVE::Storage::ZFSPoolPlugin); |  use base qw(PVE::Storage::ZFSPoolPlugin); | ||||||
|  | @ -58,7 +58,7 @@ | ||||||
|  # Configuration |  # Configuration | ||||||
|   |   | ||||||
|  sub type { |  sub type { | ||||||
| @@ -184,6 +199,24 @@
 | @@ -184,6 +199,32 @@
 | ||||||
|  	    description => "iscsi provider", |  	    description => "iscsi provider", | ||||||
|  	    type => 'string', |  	    type => 'string', | ||||||
|  	}, |  	}, | ||||||
|  | @ -69,9 +69,17 @@ | ||||||
| +	    type => 'string',
 | +	    type => 'string',
 | ||||||
| +	},
 | +	},
 | ||||||
| +	freenas_password => {
 | +	freenas_password => {
 | ||||||
| +	    description => "FreeNAS API Password",
 | +	    description => "FreeNAS API Password (Deprecated)",
 | ||||||
| +	    type => 'string',
 | +	    type => 'string',
 | ||||||
| +	},
 | +	},
 | ||||||
|  | +	truenas_secret => {
 | ||||||
|  | +	    description => "TrueNAS API Secret",
 | ||||||
|  | +	    type => 'string',
 | ||||||
|  | +	},
 | ||||||
|  | +	truenas_token_auth => {
 | ||||||
|  | +	    description => "TrueNAS API Authentication with Token",
 | ||||||
|  | +	    type => 'boolean',
 | ||||||
|  | +	},
 | ||||||
| +	freenas_use_ssl => {
 | +	freenas_use_ssl => {
 | ||||||
| +	    description => "FreeNAS API access via SSL",
 | +	    description => "FreeNAS API access via SSL",
 | ||||||
| +	    type => 'boolean',
 | +	    type => 'boolean',
 | ||||||
|  | @ -83,7 +91,7 @@ | ||||||
|  	# this will disable write caching on comstar and istgt. |  	# this will disable write caching on comstar and istgt. | ||||||
|  	# it is not implemented for iet. iet blockio always operates with |  	# it is not implemented for iet. iet blockio always operates with | ||||||
|  	# writethrough caching when not in readonly mode |  	# writethrough caching when not in readonly mode | ||||||
| @@ -211,14 +244,18 @@
 | @@ -211,14 +252,20 @@
 | ||||||
|  	nodes => { optional => 1 }, |  	nodes => { optional => 1 }, | ||||||
|  	disable => { optional => 1 }, |  	disable => { optional => 1 }, | ||||||
|  	portal => { fixed => 1 }, |  	portal => { fixed => 1 }, | ||||||
|  | @ -99,12 +107,14 @@ | ||||||
|  	comstar_tg => { optional => 1 }, |  	comstar_tg => { optional => 1 }, | ||||||
| +	freenas_user => { optional => 1 },
 | +	freenas_user => { optional => 1 },
 | ||||||
| +	freenas_password => { optional => 1 },
 | +	freenas_password => { optional => 1 },
 | ||||||
|  | +	truenas_secret => { optional => 1 },
 | ||||||
|  | +	truenas_token_auth => { optional => 1 },
 | ||||||
| +	freenas_use_ssl => { optional => 1 },
 | +	freenas_use_ssl => { optional => 1 },
 | ||||||
| +	freenas_apiv4_host => { optional => 1 },
 | +	freenas_apiv4_host => { optional => 1 },
 | ||||||
|  	lio_tpg => { optional => 1 }, |  	lio_tpg => { optional => 1 }, | ||||||
|  	content => { optional => 1 }, |  	content => { optional => 1 }, | ||||||
|  	bwlimit => { optional => 1 }, |  	bwlimit => { optional => 1 }, | ||||||
| @@ -243,6 +280,40 @@
 | @@ -243,6 +290,40 @@
 | ||||||
|   |   | ||||||
|      my $path = "iscsi://$portal/$target/$lun"; |      my $path = "iscsi://$portal/$target/$lun"; | ||||||
|   |   | ||||||
|  | @ -0,0 +1,91 @@ | ||||||
|  | --- apidoc.js.orig	2024-01-06 13:02:06.730512378 -0500
 | ||||||
|  | +++ apidoc.js	2024-01-06 13:02:55.349787105 -0500
 | ||||||
|  | @@ -50336,6 +50336,37 @@
 | ||||||
|  |                             "type" : "string", | ||||||
|  |                             "typetext" : "<string>" | ||||||
|  |                          }, | ||||||
|  | +                        "freenas_user" : {
 | ||||||
|  | +                           "description" : "FreeNAS user for API access",
 | ||||||
|  | +                           "optional" : 1,
 | ||||||
|  | +                           "type" : "string",
 | ||||||
|  | +                           "typetext" : "<string>"
 | ||||||
|  | +                        },
 | ||||||
|  | +                        "freenas_password" : {
 | ||||||
|  | +                           "description" : "FreeNAS password for API access (Deprecated)",
 | ||||||
|  | +                           "optional" : 1,
 | ||||||
|  | +                           "type" : "string",
 | ||||||
|  | +                           "typetext" : "<string>"
 | ||||||
|  | +                        },
 | ||||||
|  | +                        "truenas_secret" : {
 | ||||||
|  | +                           "description" : "TrueNAS Secret for API access",
 | ||||||
|  | +                           "optional" : 1,
 | ||||||
|  | +                           "type" : "string",
 | ||||||
|  | +                           "typetext" : "<string>"
 | ||||||
|  | +                        },
 | ||||||
|  | +                        "freenas_use_ssl" : {
 | ||||||
|  | +                           "description" : "FreeNAS API access via SSL",
 | ||||||
|  | +                           "optional" : 1,
 | ||||||
|  | +                           "type" : "boolean",
 | ||||||
|  | +                           "typetext" : "<boolean>"
 | ||||||
|  | +                        },
 | ||||||
|  | +                        "freenas_apiv4_host" : {
 | ||||||
|  | +                           "description" : "FreeNAS API Host via IPv4",
 | ||||||
|  | +                           "format" : "address",
 | ||||||
|  | +                           "optional" : 1,
 | ||||||
|  | +                           "type" : "string",
 | ||||||
|  | +                           "typetext" : "<string>"
 | ||||||
|  | +                        },
 | ||||||
|  |                          "fuse" : { | ||||||
|  |                             "description" : "Mount CephFS through FUSE.", | ||||||
|  |                             "optional" : 1, | ||||||
|  | @@ -50555,6 +50586,12 @@
 | ||||||
|  |                             "type" : "boolean", | ||||||
|  |                             "typetext" : "<boolean>" | ||||||
|  |                          }, | ||||||
|  | +                        "target" : {
 | ||||||
|  | +                           "description" : "iSCSI target.",
 | ||||||
|  | +                           "optional" : 1,
 | ||||||
|  | +                           "type" : "string",
 | ||||||
|  | +                           "typetext" : "<string>"
 | ||||||
|  | +                        },
 | ||||||
|  |                          "transport" : { | ||||||
|  |                             "description" : "Gluster transport: tcp or rdma", | ||||||
|  |                             "enum" : [ | ||||||
|  | @@ -50854,6 +50891,37 @@
 | ||||||
|  |                       "optional" : 1, | ||||||
|  |                       "type" : "string", | ||||||
|  |                       "typetext" : "<string>" | ||||||
|  | +                  },
 | ||||||
|  | +                  "freenas_user" : {
 | ||||||
|  | +                     "description" : "FreeNAS user for API access",
 | ||||||
|  | +                     "optional" : 1,
 | ||||||
|  | +                     "type" : "string",
 | ||||||
|  | +                     "typetext" : "<string>"
 | ||||||
|  | +                  },
 | ||||||
|  | +                  "freenas_password" : {
 | ||||||
|  | +                     "description" : "FreeNAS password for API access (Deprecated)",
 | ||||||
|  | +                     "optional" : 1,
 | ||||||
|  | +                     "type" : "string",
 | ||||||
|  | +                     "typetext" : "<string>"
 | ||||||
|  | +                  },
 | ||||||
|  | +                  "truenas_secret" : {
 | ||||||
|  | +                     "description" : "TrueNAS secret for API access",
 | ||||||
|  | +                     "optional" : 1,
 | ||||||
|  | +                     "type" : "string",
 | ||||||
|  | +                     "typetext" : "<string>"
 | ||||||
|  | +                  },
 | ||||||
|  | +                  "freenas_use_ssl" : {
 | ||||||
|  | +                     "description" : "FreeNAS API access via SSL",
 | ||||||
|  | +                     "optional" : 1,
 | ||||||
|  | +                     "type" : "boolean",
 | ||||||
|  | +                     "typetext" : "<boolean>"
 | ||||||
|  | +                  },
 | ||||||
|  | +                  "freenas_apiv4_host" : {
 | ||||||
|  | +                     "description" : "FreeNAS API Host via IPv4",
 | ||||||
|  | +                     "format" : "address",
 | ||||||
|  | +                     "optional" : 1,
 | ||||||
|  | +                     "type" : "string",
 | ||||||
|  | +                     "typetext" : "<string>"
 | ||||||
|  |                    }, | ||||||
|  |                    "fuse" : { | ||||||
|  |                       "description" : "Mount CephFS through FUSE.", | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -1,6 +1,6 @@ | ||||||
| --- apidoc.js.orig	2021-11-15 10:07:34.000000000 -0500
 | --- apidoc.js.orig	2024-01-06 13:02:06.730512378 -0500
 | ||||||
| +++ apidoc.js	2021-12-06 08:04:01.648822707 -0500
 | +++ apidoc.js	2024-01-06 13:02:55.349787105 -0500
 | ||||||
| @@ -44064,6 +44064,31 @@
 | @@ -50336,6 +50336,37 @@
 | ||||||
|                             "type" : "string", |                             "type" : "string", | ||||||
|                             "typetext" : "<string>" |                             "typetext" : "<string>" | ||||||
|                          }, |                          }, | ||||||
|  | @ -11,7 +11,13 @@ | ||||||
| +                           "typetext" : "<string>"
 | +                           "typetext" : "<string>"
 | ||||||
| +                        },
 | +                        },
 | ||||||
| +                        "freenas_password" : {
 | +                        "freenas_password" : {
 | ||||||
| +                           "description" : "FreeNAS password for API access",
 | +                           "description" : "FreeNAS password for API access (Deprecated)",
 | ||||||
|  | +                           "optional" : 1,
 | ||||||
|  | +                           "type" : "string",
 | ||||||
|  | +                           "typetext" : "<string>"
 | ||||||
|  | +                        },
 | ||||||
|  | +                        "truenas_secret" : {
 | ||||||
|  | +                           "description" : "TrueNAS Secret for API access",
 | ||||||
| +                           "optional" : 1,
 | +                           "optional" : 1,
 | ||||||
| +                           "type" : "string",
 | +                           "type" : "string",
 | ||||||
| +                           "typetext" : "<string>"
 | +                           "typetext" : "<string>"
 | ||||||
|  | @ -32,7 +38,7 @@ | ||||||
|                          "fuse" : { |                          "fuse" : { | ||||||
|                             "description" : "Mount CephFS through FUSE.", |                             "description" : "Mount CephFS through FUSE.", | ||||||
|                             "optional" : 1, |                             "optional" : 1, | ||||||
| @@ -44275,6 +44300,12 @@
 | @@ -50555,6 +50586,12 @@
 | ||||||
|                             "type" : "boolean", |                             "type" : "boolean", | ||||||
|                             "typetext" : "<boolean>" |                             "typetext" : "<boolean>" | ||||||
|                          }, |                          }, | ||||||
|  | @ -45,7 +51,7 @@ | ||||||
|                          "transport" : { |                          "transport" : { | ||||||
|                             "description" : "Gluster transport: tcp or rdma", |                             "description" : "Gluster transport: tcp or rdma", | ||||||
|                             "enum" : [ |                             "enum" : [ | ||||||
| @@ -44547,6 +44578,31 @@
 | @@ -50854,6 +50891,37 @@
 | ||||||
|                       "optional" : 1, |                       "optional" : 1, | ||||||
|                       "type" : "string", |                       "type" : "string", | ||||||
|                       "typetext" : "<string>" |                       "typetext" : "<string>" | ||||||
|  | @ -57,7 +63,13 @@ | ||||||
| +                     "typetext" : "<string>"
 | +                     "typetext" : "<string>"
 | ||||||
| +                  },
 | +                  },
 | ||||||
| +                  "freenas_password" : {
 | +                  "freenas_password" : {
 | ||||||
| +                     "description" : "FreeNAS password for API access",
 | +                     "description" : "FreeNAS password for API access (Deprecated)",
 | ||||||
|  | +                     "optional" : 1,
 | ||||||
|  | +                     "type" : "string",
 | ||||||
|  | +                     "typetext" : "<string>"
 | ||||||
|  | +                  },
 | ||||||
|  | +                  "truenas_secret" : {
 | ||||||
|  | +                     "description" : "TrueNAS secret for API access",
 | ||||||
| +                     "optional" : 1,
 | +                     "optional" : 1,
 | ||||||
| +                     "type" : "string",
 | +                     "type" : "string",
 | ||||||
| +                     "typetext" : "<string>"
 | +                     "typetext" : "<string>"
 | ||||||
|  | @ -0,0 +1,289 @@ | ||||||
|  | --- pvemanagerlib.js.orig	2023-12-30 15:36:27.913505863 -0500
 | ||||||
|  | +++ pvemanagerlib.js	2024-01-02 09:30:56.000000000 -0500
 | ||||||
|  | @@ -9228,6 +9228,7 @@
 | ||||||
|  |      alias: ['widget.pveiScsiProviderSelector'], | ||||||
|  |      comboItems: [ | ||||||
|  |  	['comstar', 'Comstar'], | ||||||
|  | +    ['freenas', 'FreeNAS/TrueNAS API'],
 | ||||||
|  |  	['istgt', 'istgt'], | ||||||
|  |  	['iet', 'IET'], | ||||||
|  |  	['LIO', 'LIO'], | ||||||
|  | @@ -58017,16 +58018,24 @@
 | ||||||
|  |  	me.callParent(); | ||||||
|  |      }, | ||||||
|  |  }); | ||||||
|  | +
 | ||||||
|  |  Ext.define('PVE.storage.ZFSInputPanel', { | ||||||
|  |      extend: 'PVE.panel.StorageBase', | ||||||
|  |   | ||||||
|  |      viewModel: { | ||||||
|  |  	parent: null, | ||||||
|  |  	data: { | ||||||
|  | +isComstar: true,
 | ||||||
|  | +	    isFreeNAS: false,
 | ||||||
|  |  	    isLIO: false, | ||||||
|  | -	    isComstar: true,
 | ||||||
|  | +	    isToken: false,
 | ||||||
|  |  	    hasWriteCacheOption: true, | ||||||
|  |  	}, | ||||||
|  | +formulas: {
 | ||||||
|  | +            hideUsername: function(get) {
 | ||||||
|  | +                return (!get('isFreeNAS') || !(get('isFreeNAS') && !get('isToken')));
 | ||||||
|  | +            },
 | ||||||
|  | +	},
 | ||||||
|  |      }, | ||||||
|  |   | ||||||
|  |      controller: { | ||||||
|  | @@ -58034,13 +58043,42 @@
 | ||||||
|  |  	control: { | ||||||
|  |  	    'field[name=iscsiprovider]': { | ||||||
|  |  		change: 'changeISCSIProvider', | ||||||
|  | +},
 | ||||||
|  | +	    'field[name=truenas_token_auth]': {
 | ||||||
|  | +		change: 'changeUsername',
 | ||||||
|  |  	    }, | ||||||
|  |  	}, | ||||||
|  |  	changeISCSIProvider: function(f, newVal, oldVal) { | ||||||
|  | +var me = this;
 | ||||||
|  |  	    var vm = this.getViewModel(); | ||||||
|  |  	    vm.set('isLIO', newVal === 'LIO'); | ||||||
|  |  	    vm.set('isComstar', newVal === 'comstar'); | ||||||
|  | -	    vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
 | ||||||
|  | +	    vm.set('isFreeNAS', newVal === 'freenas');
 | ||||||
|  | +        vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
 | ||||||
|  | +        if (newVal !== 'freenas') {
 | ||||||
|  | +            me.lookupReference('freenas_use_ssl_field').setValue(false);
 | ||||||
|  | +            me.lookupReference('truenas_token_auth_field').setValue(false);
 | ||||||
|  | +            me.lookupReference('freenas_apiv4_host_field').setValue('');
 | ||||||
|  | +            me.lookupReference('freenas_user_field').setValue('');
 | ||||||
|  | +            me.lookupReference('freenas_user_field').allowBlank = true;
 | ||||||
|  | +            me.lookupReference('truenas_secret_field').setValue('');
 | ||||||
|  | +            me.lookupReference('truenas_secret_field').allowBlank = true;
 | ||||||
|  | +            me.lookupReference('truenas_confirm_secret_field').setValue('');
 | ||||||
|  | +            me.lookupReference('truenas_confirm_secret_field').allowBlank = true;
 | ||||||
|  | +        } else {
 | ||||||
|  | +            me.lookupReference('freenas_user_field').allowBlank = false;
 | ||||||
|  | +            me.lookupReference('truenas_secret_field').allowBlank = false;
 | ||||||
|  | +            me.lookupReference('truenas_confirm_secret_field').allowBlank = false;
 | ||||||
|  | +        }
 | ||||||
|  | +    },
 | ||||||
|  | +    changeUsername: function(f, newVal, oldVal) {
 | ||||||
|  | +        var me = this;
 | ||||||
|  | +        var vm = me.getViewModel();
 | ||||||
|  | +        vm.set('isToken', newVal);
 | ||||||
|  | +        me.lookupReference('freenas_user_field').allowBlank = newVal;
 | ||||||
|  | +        if (newVal) {
 | ||||||
|  | +            me.lookupReference('freenas_user_field').setValue('');
 | ||||||
|  | +        }
 | ||||||
|  |  	}, | ||||||
|  |      }, | ||||||
|  |   | ||||||
|  | @@ -58053,28 +58091,78 @@
 | ||||||
|  |   | ||||||
|  |  	values.nowritecache = values.writecache ? 0 : 1; | ||||||
|  |  	delete values.writecache; | ||||||
|  | +    console.warn(values.freenas_password);
 | ||||||
|  | +	if (values.freenas_password) {
 | ||||||
|  | +	    values.truenas_secret = values.freenas_password;
 | ||||||
|  | +	}
 | ||||||
|  | +	console.warn(values.truenas_secret);
 | ||||||
|  |   | ||||||
|  |  	return me.callParent([values]); | ||||||
|  |      }, | ||||||
|  |   | ||||||
|  |      setValues: function(values) { | ||||||
|  | -	values.writecache = values.nowritecache ? 0 : 1;
 | ||||||
|  | -	this.callParent([values]);
 | ||||||
|  | +        if (values.freenas_password) {
 | ||||||
|  | +            values.truenas_secret = values.freenas_password;
 | ||||||
|  | +        }
 | ||||||
|  | +        values.truenas_confirm_secret = values.truenas_secret;
 | ||||||
|  | +        values.writecache = values.nowritecache ? 0 : 1;
 | ||||||
|  | +        this.callParent([values]);
 | ||||||
|  |      }, | ||||||
|  |   | ||||||
|  |      initComponent: function() { | ||||||
|  | -	var me = this;
 | ||||||
|  | +    var me = this;
 | ||||||
|  | +
 | ||||||
|  | +    var tnsecret = Ext.create('Ext.form.TextField', { 
 | ||||||
|  | +        xtype: 'proxmoxtextfield',
 | ||||||
|  | +        name: 'truenas_secret',
 | ||||||
|  | +       reference: 'truenas_secret_field',
 | ||||||
|  | +        inputType: me.isCreate ? '' : 'password',
 | ||||||
|  | +        value: '',
 | ||||||
|  | +        editable: true,
 | ||||||
|  | +        emptyText: Proxmox.Utils.noneText,
 | ||||||
|  | +        bind: {
 | ||||||
|  | +            hidden: '{!isFreeNAS}'
 | ||||||
|  | +        },
 | ||||||
|  | +        fieldLabel: gettext('API Password'),
 | ||||||
|  | +        change: function(f, value) {
 | ||||||
|  | +            if (f.rendered) {
 | ||||||
|  | +                f.up().down('field[name=truenas_confirm_secret]').validate();
 | ||||||
|  | +            }
 | ||||||
|  | +        },
 | ||||||
|  | +    });
 | ||||||
|  |   | ||||||
|  | -	me.column1 = [
 | ||||||
|  | -	    {
 | ||||||
|  | -		xtype: me.isCreate ? 'textfield' : 'displayfield',
 | ||||||
|  | -		name: 'portal',
 | ||||||
|  | +    var tnconfirmsecret = Ext.create('Ext.form.TextField', {
 | ||||||
|  | +        xtype: 'proxmoxtextfield',
 | ||||||
|  | +        name: 'truenas_confirm_secret',
 | ||||||
|  | +        reference: 'truenas_confirm_secret_field',
 | ||||||
|  | +        inputType: me.isCreate ? '' : 'password',
 | ||||||
|  | +        value: '',
 | ||||||
|  | +        editable: true,
 | ||||||
|  | +        submitValue: false,
 | ||||||
|  | +        emptyText: Proxmox.Utils.noneText,
 | ||||||
|  | +        bind: {
 | ||||||
|  | +            hidden: '{!isFreeNAS}'
 | ||||||
|  | +        },
 | ||||||
|  | +        fieldLabel: gettext('Confirm API Password'),
 | ||||||
|  | +        validator: function(value) {
 | ||||||
|  | +            var pw = me.up().down('field[name=truenas_secret]').getValue();
 | ||||||
|  | +            if (pw !== value) {
 | ||||||
|  | +                return "Secrets do not match!";
 | ||||||
|  | +            }
 | ||||||
|  | +            return true;
 | ||||||
|  | +        },
 | ||||||
|  | +    });
 | ||||||
|  | +
 | ||||||
|  | +    me.column1 = [
 | ||||||
|  | +        {
 | ||||||
|  | +        xtype: me.isCreate ? 'textfield' : 'displayfield',
 | ||||||
|  | +        name: 'portal',
 | ||||||
|  |  		value: '', | ||||||
|  |  		fieldLabel: gettext('Portal'), | ||||||
|  |  		allowBlank: false, | ||||||
|  |  	    }, | ||||||
|  |  	    { | ||||||
|  | -		xtype: me.isCreate ? 'textfield' : 'displayfield',
 | ||||||
|  | +		xtype: 'textfield',
 | ||||||
|  |  		name: 'pool', | ||||||
|  |  		value: '', | ||||||
|  |  		fieldLabel: gettext('Pool'), | ||||||
|  | @@ -58084,11 +58172,11 @@
 | ||||||
|  |  		xtype: me.isCreate ? 'textfield' : 'displayfield', | ||||||
|  |  		name: 'blocksize', | ||||||
|  |  		value: '4k', | ||||||
|  | -		fieldLabel: gettext('Block Size'),
 | ||||||
|  | +		fieldLabel: gettext('ZFS Block Size'),
 | ||||||
|  |  		allowBlank: false, | ||||||
|  |  	    }, | ||||||
|  |  	    { | ||||||
|  | -		xtype: me.isCreate ? 'textfield' : 'displayfield',
 | ||||||
|  | +		xtype: 'textfield',
 | ||||||
|  |  		name: 'target', | ||||||
|  |  		value: '', | ||||||
|  |  		fieldLabel: gettext('Target'), | ||||||
|  | @@ -58099,8 +58187,59 @@
 | ||||||
|  |  		name: 'comstar_tg', | ||||||
|  |  		value: '', | ||||||
|  |  		fieldLabel: gettext('Target group'), | ||||||
|  | -		bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{!isComstar}'
 | ||||||
|  | +		},
 | ||||||
|  |  		allowBlank: true, | ||||||
|  | +},
 | ||||||
|  | +	    {
 | ||||||
|  | +		xtype: 'proxmoxcheckbox',
 | ||||||
|  | +		name: 'freenas_use_ssl',
 | ||||||
|  | +		reference: 'freenas_use_ssl_field',
 | ||||||
|  | +		inputId: 'freenas_use_ssl_field',
 | ||||||
|  | +		checked: false,
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{!isFreeNAS}'
 | ||||||
|  | +		},
 | ||||||
|  | +		uncheckedValue: 0,
 | ||||||
|  | +		fieldLabel: gettext('API use SSL'),
 | ||||||
|  | +	    },
 | ||||||
|  | +	    {
 | ||||||
|  | +		xtype: 'proxmoxcheckbox',
 | ||||||
|  | +		name: 'truenas_token_auth',
 | ||||||
|  | +		reference: 'truenas_token_auth_field',
 | ||||||
|  | +		inputId: 'truenas_use_token_auth_field',
 | ||||||
|  | +		checked: false,
 | ||||||
|  | +		listeners: {
 | ||||||
|  | +		    change: function(field, newValue) {
 | ||||||
|  | +			if (newValue === true) {
 | ||||||
|  | +			    tnsecret.labelEl.update('API Token');
 | ||||||
|  | +			    tnconfirmsecret.labelEl.update('Confirm API Token');
 | ||||||
|  | +			    me.lookupReference('freenas_user_field').setValue('');
 | ||||||
|  | +			    me.lookupReference('freenas_user_field').allowBlank = true;
 | ||||||
|  | +			} else {
 | ||||||
|  | +			    tnsecret.labelEl.update('API Password');
 | ||||||
|  | +			    tnconfirmsecret.labelEl.update('Confirm API Password');
 | ||||||
|  | +			    me.lookupReference('freenas_user_field').allowBlank = false;
 | ||||||
|  | +			}
 | ||||||
|  | +		    },
 | ||||||
|  | +		},
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{!isFreeNAS}'
 | ||||||
|  | +		},
 | ||||||
|  | +		uncheckedValue: 0,
 | ||||||
|  | +		fieldLabel: gettext('API Token Auth'),
 | ||||||
|  | +	    },
 | ||||||
|  | +	    {
 | ||||||
|  | +		xtype: 'textfield',
 | ||||||
|  | +		name: 'freenas_user',
 | ||||||
|  | +		reference: 'freenas_user_field',
 | ||||||
|  | +		inputId: 'freenas_user_field',
 | ||||||
|  | +		value: '',
 | ||||||
|  | +		fieldLabel: gettext('API Username'),
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{hideUsername}'
 | ||||||
|  | +		},
 | ||||||
|  |  	    }, | ||||||
|  |  	]; | ||||||
|  |   | ||||||
|  | @@ -58131,7 +58270,9 @@
 | ||||||
|  |  		xtype: me.isCreate ? 'textfield' : 'displayfield', | ||||||
|  |  		name: 'comstar_hg', | ||||||
|  |  		value: '', | ||||||
|  | -		bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{!isComstar}'
 | ||||||
|  | +		},
 | ||||||
|  |  		fieldLabel: gettext('Host group'), | ||||||
|  |  		allowBlank: true, | ||||||
|  |  	    }, | ||||||
|  | @@ -58139,15 +58280,32 @@
 | ||||||
|  |  		xtype: me.isCreate ? 'textfield' : 'displayfield', | ||||||
|  |  		name: 'lio_tpg', | ||||||
|  |  		value: '', | ||||||
|  | -		bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
 | ||||||
|  | -		allowBlank: false,
 | ||||||
|  | -		fieldLabel: gettext('Target portal group'),
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{!isLIO}'
 | ||||||
|  | +		},
 | ||||||
|  | +				fieldLabel: gettext('Target portal group'),
 | ||||||
|  | +	    allowBlank: true
 | ||||||
|  |  	    }, | ||||||
|  | +	    {
 | ||||||
|  | +		xtype: 'proxmoxtextfield',
 | ||||||
|  | +		name: 'freenas_apiv4_host',
 | ||||||
|  | +		reference: 'freenas_apiv4_host_field',
 | ||||||
|  | +		value: '',
 | ||||||
|  | +		editable: true,
 | ||||||
|  | +		emptyText: Proxmox.Utils.noneText,
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{!isFreeNAS}'
 | ||||||
|  | +		},
 | ||||||
|  | +		fieldLabel: gettext('API IPv4 Host'),
 | ||||||
|  | +	    },
 | ||||||
|  | +	    tnsecret,
 | ||||||
|  | +	    tnconfirmsecret,
 | ||||||
|  |  	]; | ||||||
|  |   | ||||||
|  |  	me.callParent(); | ||||||
|  |      }, | ||||||
|  |  }); | ||||||
|  | +
 | ||||||
|  |  Ext.define('PVE.storage.ZFSPoolSelector', { | ||||||
|  |      extend: 'PVE.form.ComboBoxSetStoreNode', | ||||||
|  |      alias: 'widget.pveZFSPoolSelector', | ||||||
|  | @ -0,0 +1,289 @@ | ||||||
|  | --- pvemanagerlib.js.orig	2023-12-30 15:36:27.913505863 -0500
 | ||||||
|  | +++ pvemanagerlib.js	2024-01-02 09:30:56.000000000 -0500
 | ||||||
|  | @@ -9228,6 +9228,7 @@
 | ||||||
|  |      alias: ['widget.pveiScsiProviderSelector'], | ||||||
|  |      comboItems: [ | ||||||
|  |  	['comstar', 'Comstar'], | ||||||
|  | +    ['freenas', 'FreeNAS/TrueNAS API'],
 | ||||||
|  |  	['istgt', 'istgt'], | ||||||
|  |  	['iet', 'IET'], | ||||||
|  |  	['LIO', 'LIO'], | ||||||
|  | @@ -58017,16 +58018,24 @@
 | ||||||
|  |  	me.callParent(); | ||||||
|  |      }, | ||||||
|  |  }); | ||||||
|  | +
 | ||||||
|  |  Ext.define('PVE.storage.ZFSInputPanel', { | ||||||
|  |      extend: 'PVE.panel.StorageBase', | ||||||
|  |   | ||||||
|  |      viewModel: { | ||||||
|  |  	parent: null, | ||||||
|  |  	data: { | ||||||
|  | +isComstar: true,
 | ||||||
|  | +	    isFreeNAS: false,
 | ||||||
|  |  	    isLIO: false, | ||||||
|  | -	    isComstar: true,
 | ||||||
|  | +	    isToken: false,
 | ||||||
|  |  	    hasWriteCacheOption: true, | ||||||
|  |  	}, | ||||||
|  | +formulas: {
 | ||||||
|  | +            hideUsername: function(get) {
 | ||||||
|  | +                return (!get('isFreeNAS') || !(get('isFreeNAS') && !get('isToken')));
 | ||||||
|  | +            },
 | ||||||
|  | +	},
 | ||||||
|  |      }, | ||||||
|  |   | ||||||
|  |      controller: { | ||||||
|  | @@ -58034,13 +58043,42 @@
 | ||||||
|  |  	control: { | ||||||
|  |  	    'field[name=iscsiprovider]': { | ||||||
|  |  		change: 'changeISCSIProvider', | ||||||
|  | +},
 | ||||||
|  | +	    'field[name=truenas_token_auth]': {
 | ||||||
|  | +		change: 'changeUsername',
 | ||||||
|  |  	    }, | ||||||
|  |  	}, | ||||||
|  |  	changeISCSIProvider: function(f, newVal, oldVal) { | ||||||
|  | +var me = this;
 | ||||||
|  |  	    var vm = this.getViewModel(); | ||||||
|  |  	    vm.set('isLIO', newVal === 'LIO'); | ||||||
|  |  	    vm.set('isComstar', newVal === 'comstar'); | ||||||
|  | -	    vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'istgt');
 | ||||||
|  | +	    vm.set('isFreeNAS', newVal === 'freenas');
 | ||||||
|  | +        vm.set('hasWriteCacheOption', newVal === 'comstar' || newVal === 'freenas' || newVal === 'istgt');
 | ||||||
|  | +        if (newVal !== 'freenas') {
 | ||||||
|  | +            me.lookupReference('freenas_use_ssl_field').setValue(false);
 | ||||||
|  | +            me.lookupReference('truenas_token_auth_field').setValue(false);
 | ||||||
|  | +            me.lookupReference('freenas_apiv4_host_field').setValue('');
 | ||||||
|  | +            me.lookupReference('freenas_user_field').setValue('');
 | ||||||
|  | +            me.lookupReference('freenas_user_field').allowBlank = true;
 | ||||||
|  | +            me.lookupReference('truenas_secret_field').setValue('');
 | ||||||
|  | +            me.lookupReference('truenas_secret_field').allowBlank = true;
 | ||||||
|  | +            me.lookupReference('truenas_confirm_secret_field').setValue('');
 | ||||||
|  | +            me.lookupReference('truenas_confirm_secret_field').allowBlank = true;
 | ||||||
|  | +        } else {
 | ||||||
|  | +            me.lookupReference('freenas_user_field').allowBlank = false;
 | ||||||
|  | +            me.lookupReference('truenas_secret_field').allowBlank = false;
 | ||||||
|  | +            me.lookupReference('truenas_confirm_secret_field').allowBlank = false;
 | ||||||
|  | +        }
 | ||||||
|  | +    },
 | ||||||
|  | +    changeUsername: function(f, newVal, oldVal) {
 | ||||||
|  | +        var me = this;
 | ||||||
|  | +        var vm = me.getViewModel();
 | ||||||
|  | +        vm.set('isToken', newVal);
 | ||||||
|  | +        me.lookupReference('freenas_user_field').allowBlank = newVal;
 | ||||||
|  | +        if (newVal) {
 | ||||||
|  | +            me.lookupReference('freenas_user_field').setValue('');
 | ||||||
|  | +        }
 | ||||||
|  |  	}, | ||||||
|  |      }, | ||||||
|  |   | ||||||
|  | @@ -58053,28 +58091,78 @@
 | ||||||
|  |   | ||||||
|  |  	values.nowritecache = values.writecache ? 0 : 1; | ||||||
|  |  	delete values.writecache; | ||||||
|  | +    console.warn(values.freenas_password);
 | ||||||
|  | +	if (values.freenas_password) {
 | ||||||
|  | +	    values.truenas_secret = values.freenas_password;
 | ||||||
|  | +	}
 | ||||||
|  | +	console.warn(values.truenas_secret);
 | ||||||
|  |   | ||||||
|  |  	return me.callParent([values]); | ||||||
|  |      }, | ||||||
|  |   | ||||||
|  |      setValues: function(values) { | ||||||
|  | -	values.writecache = values.nowritecache ? 0 : 1;
 | ||||||
|  | -	this.callParent([values]);
 | ||||||
|  | +        if (values.freenas_password) {
 | ||||||
|  | +            values.truenas_secret = values.freenas_password;
 | ||||||
|  | +        }
 | ||||||
|  | +        values.truenas_confirm_secret = values.truenas_secret;
 | ||||||
|  | +        values.writecache = values.nowritecache ? 0 : 1;
 | ||||||
|  | +        this.callParent([values]);
 | ||||||
|  |      }, | ||||||
|  |   | ||||||
|  |      initComponent: function() { | ||||||
|  | -	var me = this;
 | ||||||
|  | +    var me = this;
 | ||||||
|  | +
 | ||||||
|  | +    var tnsecret = Ext.create('Ext.form.TextField', { 
 | ||||||
|  | +        xtype: 'proxmoxtextfield',
 | ||||||
|  | +        name: 'truenas_secret',
 | ||||||
|  | +       reference: 'truenas_secret_field',
 | ||||||
|  | +        inputType: me.isCreate ? '' : 'password',
 | ||||||
|  | +        value: '',
 | ||||||
|  | +        editable: true,
 | ||||||
|  | +        emptyText: Proxmox.Utils.noneText,
 | ||||||
|  | +        bind: {
 | ||||||
|  | +            hidden: '{!isFreeNAS}'
 | ||||||
|  | +        },
 | ||||||
|  | +        fieldLabel: gettext('API Password'),
 | ||||||
|  | +        change: function(f, value) {
 | ||||||
|  | +            if (f.rendered) {
 | ||||||
|  | +                f.up().down('field[name=truenas_confirm_secret]').validate();
 | ||||||
|  | +            }
 | ||||||
|  | +        },
 | ||||||
|  | +    });
 | ||||||
|  |   | ||||||
|  | -	me.column1 = [
 | ||||||
|  | -	    {
 | ||||||
|  | -		xtype: me.isCreate ? 'textfield' : 'displayfield',
 | ||||||
|  | -		name: 'portal',
 | ||||||
|  | +    var tnconfirmsecret = Ext.create('Ext.form.TextField', {
 | ||||||
|  | +        xtype: 'proxmoxtextfield',
 | ||||||
|  | +        name: 'truenas_confirm_secret',
 | ||||||
|  | +        reference: 'truenas_confirm_secret_field',
 | ||||||
|  | +        inputType: me.isCreate ? '' : 'password',
 | ||||||
|  | +        value: '',
 | ||||||
|  | +        editable: true,
 | ||||||
|  | +        submitValue: false,
 | ||||||
|  | +        emptyText: Proxmox.Utils.noneText,
 | ||||||
|  | +        bind: {
 | ||||||
|  | +            hidden: '{!isFreeNAS}'
 | ||||||
|  | +        },
 | ||||||
|  | +        fieldLabel: gettext('Confirm API Password'),
 | ||||||
|  | +        validator: function(value) {
 | ||||||
|  | +            var pw = me.up().down('field[name=truenas_secret]').getValue();
 | ||||||
|  | +            if (pw !== value) {
 | ||||||
|  | +                return "Secrets do not match!";
 | ||||||
|  | +            }
 | ||||||
|  | +            return true;
 | ||||||
|  | +        },
 | ||||||
|  | +    });
 | ||||||
|  | +
 | ||||||
|  | +    me.column1 = [
 | ||||||
|  | +        {
 | ||||||
|  | +        xtype: me.isCreate ? 'textfield' : 'displayfield',
 | ||||||
|  | +        name: 'portal',
 | ||||||
|  |  		value: '', | ||||||
|  |  		fieldLabel: gettext('Portal'), | ||||||
|  |  		allowBlank: false, | ||||||
|  |  	    }, | ||||||
|  |  	    { | ||||||
|  | -		xtype: me.isCreate ? 'textfield' : 'displayfield',
 | ||||||
|  | +		xtype: 'textfield',
 | ||||||
|  |  		name: 'pool', | ||||||
|  |  		value: '', | ||||||
|  |  		fieldLabel: gettext('Pool'), | ||||||
|  | @@ -58084,11 +58172,11 @@
 | ||||||
|  |  		xtype: me.isCreate ? 'textfield' : 'displayfield', | ||||||
|  |  		name: 'blocksize', | ||||||
|  |  		value: '4k', | ||||||
|  | -		fieldLabel: gettext('Block Size'),
 | ||||||
|  | +		fieldLabel: gettext('ZFS Block Size'),
 | ||||||
|  |  		allowBlank: false, | ||||||
|  |  	    }, | ||||||
|  |  	    { | ||||||
|  | -		xtype: me.isCreate ? 'textfield' : 'displayfield',
 | ||||||
|  | +		xtype: 'textfield',
 | ||||||
|  |  		name: 'target', | ||||||
|  |  		value: '', | ||||||
|  |  		fieldLabel: gettext('Target'), | ||||||
|  | @@ -58099,8 +58187,59 @@
 | ||||||
|  |  		name: 'comstar_tg', | ||||||
|  |  		value: '', | ||||||
|  |  		fieldLabel: gettext('Target group'), | ||||||
|  | -		bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{!isComstar}'
 | ||||||
|  | +		},
 | ||||||
|  |  		allowBlank: true, | ||||||
|  | +},
 | ||||||
|  | +	    {
 | ||||||
|  | +		xtype: 'proxmoxcheckbox',
 | ||||||
|  | +		name: 'freenas_use_ssl',
 | ||||||
|  | +		reference: 'freenas_use_ssl_field',
 | ||||||
|  | +		inputId: 'freenas_use_ssl_field',
 | ||||||
|  | +		checked: false,
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{!isFreeNAS}'
 | ||||||
|  | +		},
 | ||||||
|  | +		uncheckedValue: 0,
 | ||||||
|  | +		fieldLabel: gettext('API use SSL'),
 | ||||||
|  | +	    },
 | ||||||
|  | +	    {
 | ||||||
|  | +		xtype: 'proxmoxcheckbox',
 | ||||||
|  | +		name: 'truenas_token_auth',
 | ||||||
|  | +		reference: 'truenas_token_auth_field',
 | ||||||
|  | +		inputId: 'truenas_use_token_auth_field',
 | ||||||
|  | +		checked: false,
 | ||||||
|  | +		listeners: {
 | ||||||
|  | +		    change: function(field, newValue) {
 | ||||||
|  | +			if (newValue === true) {
 | ||||||
|  | +			    tnsecret.labelEl.update('API Token');
 | ||||||
|  | +			    tnconfirmsecret.labelEl.update('Confirm API Token');
 | ||||||
|  | +			    me.lookupReference('freenas_user_field').setValue('');
 | ||||||
|  | +			    me.lookupReference('freenas_user_field').allowBlank = true;
 | ||||||
|  | +			} else {
 | ||||||
|  | +			    tnsecret.labelEl.update('API Password');
 | ||||||
|  | +			    tnconfirmsecret.labelEl.update('Confirm API Password');
 | ||||||
|  | +			    me.lookupReference('freenas_user_field').allowBlank = false;
 | ||||||
|  | +			}
 | ||||||
|  | +		    },
 | ||||||
|  | +		},
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{!isFreeNAS}'
 | ||||||
|  | +		},
 | ||||||
|  | +		uncheckedValue: 0,
 | ||||||
|  | +		fieldLabel: gettext('API Token Auth'),
 | ||||||
|  | +	    },
 | ||||||
|  | +	    {
 | ||||||
|  | +		xtype: 'textfield',
 | ||||||
|  | +		name: 'freenas_user',
 | ||||||
|  | +		reference: 'freenas_user_field',
 | ||||||
|  | +		inputId: 'freenas_user_field',
 | ||||||
|  | +		value: '',
 | ||||||
|  | +		fieldLabel: gettext('API Username'),
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{hideUsername}'
 | ||||||
|  | +		},
 | ||||||
|  |  	    }, | ||||||
|  |  	]; | ||||||
|  |   | ||||||
|  | @@ -58131,7 +58270,9 @@
 | ||||||
|  |  		xtype: me.isCreate ? 'textfield' : 'displayfield', | ||||||
|  |  		name: 'comstar_hg', | ||||||
|  |  		value: '', | ||||||
|  | -		bind: me.isCreate ? { disabled: '{!isComstar}' } : { hidden: '{!isComstar}' },
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{!isComstar}'
 | ||||||
|  | +		},
 | ||||||
|  |  		fieldLabel: gettext('Host group'), | ||||||
|  |  		allowBlank: true, | ||||||
|  |  	    }, | ||||||
|  | @@ -58139,15 +58280,32 @@
 | ||||||
|  |  		xtype: me.isCreate ? 'textfield' : 'displayfield', | ||||||
|  |  		name: 'lio_tpg', | ||||||
|  |  		value: '', | ||||||
|  | -		bind: me.isCreate ? { disabled: '{!isLIO}' } : { hidden: '{!isLIO}' },
 | ||||||
|  | -		allowBlank: false,
 | ||||||
|  | -		fieldLabel: gettext('Target portal group'),
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{!isLIO}'
 | ||||||
|  | +		},
 | ||||||
|  | +				fieldLabel: gettext('Target portal group'),
 | ||||||
|  | +	    allowBlank: true
 | ||||||
|  |  	    }, | ||||||
|  | +	    {
 | ||||||
|  | +		xtype: 'proxmoxtextfield',
 | ||||||
|  | +		name: 'freenas_apiv4_host',
 | ||||||
|  | +		reference: 'freenas_apiv4_host_field',
 | ||||||
|  | +		value: '',
 | ||||||
|  | +		editable: true,
 | ||||||
|  | +		emptyText: Proxmox.Utils.noneText,
 | ||||||
|  | +		bind: {
 | ||||||
|  | +		    hidden: '{!isFreeNAS}'
 | ||||||
|  | +		},
 | ||||||
|  | +		fieldLabel: gettext('API IPv4 Host'),
 | ||||||
|  | +	    },
 | ||||||
|  | +	    tnsecret,
 | ||||||
|  | +	    tnconfirmsecret,
 | ||||||
|  |  	]; | ||||||
|  |   | ||||||
|  |  	me.callParent(); | ||||||
|  |      }, | ||||||
|  |  }); | ||||||
|  | +
 | ||||||
|  |  Ext.define('PVE.storage.ZFSPoolSelector', { | ||||||
|  |      extend: 'PVE.form.ComboBoxSetStoreNode', | ||||||
|  |      alias: 'widget.pveZFSPoolSelector', | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue