freenas-proxmox/perl5/PVE/Storage/Custom/TrueNAS.pm

256 lines
8.7 KiB
Perl

package PVE::Storage::Custom::TrueNASPlugin;
use strict;
use warnings;
use Data::Dumper;
use IO::File;
use PVE::Tools qw(run_command trim file_read_firstline dir_glob_foreach);
use PVE::Storage::Plugin;
use PVE::JSONSchema qw(get_standard_option);
use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
use LWP::UserAgent;
use HTTP::Request;
use XML::Simple;
use base qw(PVE::Storage::Plugin);
# Configuration
# API version
sub api {
return 1;
}
sub type {
return 'truenas';
}
sub plugindata {
return {
content => [ {images => 1}, { images => 1 }],
};
}
sub properties {
return {
freenas_user => {
description => "FreeNAS API Username",
type => 'string',
},
freenas_password => {
description => "FreeNAS API Password",
type => 'string',
},
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
nowritecache => {
description => "disable write caching on the target",
type => 'boolean',
},
};
}
sub options {
return {
nodes => { optional => 1 },
disable => { optional => 1 },
portal => { fixed => 1 },
target => { fixed => 0 },
pool => { fixed => 0 },
blocksize => { fixed => 1 },
iscsiprovider => { fixed => 1 },
nowritecache => { optional => 1 },
sparse => { optional => 1 },
freenas_user => { optional => 1 },
freenas_password => { optional => 1 },
freenas_use_ssl => { optional => 1 },
freenas_apiv4_host => { optional => 1 },
content => { optional => 1 },
bwlimit => { optional => 1 },
};
}
sub properties {
return {
adminserver => {
description => "Management IP or DNS name of storage.",
type => 'string', format => 'pve-storage-server',
},
login => {
description => "login",
type => 'string',
},
password => {
description => "password",
type => 'string',
},
igroup => {
description => "Initiator group name",
type => 'string',
},
api => {
description => "API version (7 or 8)",
type => 'string',
},
media => {
description => "iscsi/multipath",
type => 'string',
default => 'multipath',
enum => ['iscsi', 'multipath'],
},
efficiency => {
description => "Enable Storage Efficiency",
type => 'boolean',
},
};
}
sub options {
return {
adminserver => { fixed => 1 },
login => { fixed => 1 },
password => { optional => 1 },
vserver => { optional => 1 },
aggregate => { fixed => 1 },
nodes => { optional => 1 },
disable => { optional => 1 },
content => { optional => 1 },
igroup => { optional => 1 },
api => { optional => 1 },
media => { optional => 1 },
target => { optional => 1 },
shared => { optional => 1 },
efficiency => { optional => 1 },
}
}
sub truenas_connect {
my ($scfg) = @_;
syslog("info", (caller(0))[3] . " : called");
my $scheme = $scfg->{freenas_use_ssl} ? "https" : "http";
my $apihost = defined($scfg->{freenas_apiv4_host}) ? $scfg->{freenas_apiv4_host} : $scfg->{portal};
if (! defined $freenas_server_list->{$apihost}) {
$freenas_server_list->{$apihost} = REST::Client->new();
}
$freenas_server_list->{$apihost}->setHost($scheme . '://' . $apihost);
$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 using SSL, don't verify SSL certs
if ($scfg->{freenas_use_ssl}) {
$freenas_server_list->{$apihost}->getUseragent()->ssl_opts(verify_hostname => 0);
$freenas_server_list->{$apihost}->getUseragent()->ssl_opts(SSL_verify_mode => SSL_VERIFY_NONE);
}
# Check if the APIs are accessable via the selected host and scheme
my $code = $freenas_server_list->{$apihost}->request('GET', $apiping)->responseCode();
if ($code == 200) { # Successful connection
syslog("info", (caller(0))[3] . " : REST connection successful to '" . $apihost . "' using the '" . $scheme . "' protocol");
$runawayprevent = 0;
} elsif ($runawayprevent > 1) { # Make sure we are not recursion calling.
truenas_log_error($freenas_server_list->{$apihost}, "truenas_connect");
die "Loop recursion prevention";
} elsif ($code == 302) { # A 302 from FreeNAS means it doesn't like v1.0 APIs.
syslog("info", (caller(0))[3] . " : Changing to v2.0 API's");
$runawayprevent++;
$apiping =~ s/v1\.0/v2\.0/;
truenas_connect($scfg);
} elsif ($code == 307) { # A 307 from FreeNAS means rediect http to https.
syslog("info", (caller(0))[3] . " : Redirecting to HTTPS protocol");
$runawayprevent++;
$scfg->{freenas_use_ssl} = 1;
truenas_connect($scfg);
} else { # For now, any other code we fail.
truenas_log_error($freenas_server_list->{$apihost}, "truenas_connect");
die "Unable to connect to the FreeNAS API service at '" . $apihost . "' using the '" . $scheme . "' protocol";
}
$freenas_rest_connection = $freenas_server_list->{$apihost};
return;
}
#
# Check to see what FreeNAS version we are running and set
# the FreeNAS.pm to use the correct API version of FreeNAS
#
sub truenas_check {
my ($scfg, $timeout) = @_;
my $result = {};
my $apihost = defined($scfg->{freenas_apiv4_host}) ? $scfg->{freenas_apiv4_host} : $scfg->{portal};
syslog("info", (caller(0))[3] . " : called");
if (! defined $freenas_rest_connection->{$apihost}) {
truenas_connect($scfg);
eval {
$result = decode_json($freenas_rest_connection->responseContent());
};
if ($@) {
$result->{'fullversion'} = $freenas_rest_connection->responseContent();
$result->{'fullversion'} =~ s/^"//g;
}
syslog("info", (caller(0))[3] . " : successful : Server version: " . $result->{'fullversion'});
$result->{'fullversion'} =~ s/^(\w+)\-(\d+)\.(\d+)\-(?:U|BETA)(\d?)\.?(\d?)//;
my $freenas_version = sprintf("%02d%02d%02d%02d", $2, $3 || 0, $4 || 0, $5 || 0);
$product_name = $1;
syslog("info", (caller(0))[3] . " : ". $product_name . " Unformatted Version: " . $freenas_version);
if ($freenas_version >= 11030100) {
$freenas_api_version = "v2.0";
$dev_prefix = "/dev/";
}
} else {
syslog("info", (caller(0))[3] . " : REST Client already initialized");
}
syslog("info", (caller(0))[3] . " : Using " . $product_name ." API version " . $freenas_api_version);
$freenas_api_methods = $freenas_api_version_matrix->{$freenas_api_version}->{'methods'};
$freenas_api_variables = $freenas_api_version_matrix->{$freenas_api_version}->{'variables'};
$freenas_global_config = $freenas_global_config_list->{$apihost} = (!defined($freenas_global_config_list->{$apihost})) ? freenas_iscsi_get_globalconfiguration($scfg) : $freenas_global_config_list->{$apihost};
return;
}
#
### FREENAS API CALLING ROUTINE ###
#
sub truenas_request {
my ($scfg, $method, $path, $data) = @_;
my $apihost = defined($scfg->{freenas_apiv4_host}) ? $scfg->{freenas_apiv4_host} : $scfg->{portal};
syslog("info", (caller(0))[3] . " : called for host '" . $apihost . "'");
$method = uc($method);
if (! $method =~ /^(?>GET|DELETE|POST)$/) {
syslog("info", (caller(0))[3] . " : Invalid HTTP RESTful service method '$method'");
die "Invalid HTTP RESTful service method '$method' used.";
}
if (! defined $freenas_server_list->{$apihost}) {
freenas_api_check($scfg);
}
$freenas_rest_connection = $freenas_server_list->{$apihost};
$freenas_global_config = $freenas_global_config_list->{$apihost};
my $json_data = (defined $data) ? encode_json($data) : undef;
$freenas_rest_connection->$method($path, $json_data);
syslog("info", (caller(0))[3] . " : successful");
return;
}
#
# Writes the Response and Content to SysLog
#
sub freenas_api_log_error {
my ($method) = @_;
syslog("info","[ERROR]FreeNAS::API::" . $method . " : Response code: " . $freenas_rest_connection->responseCode());
syslog("info","[ERROR]FreeNAS::API::" . $method . " : Response content: " . $freenas_rest_connection->responseContent());
return 1;
}