diff --git a/perl5/PVE/Storage/LunCmd/FreeNAS.pm b/perl5/PVE/Storage/LunCmd/FreeNAS.pm index 86d34a0..6ec21d7 100644 --- a/perl5/PVE/Storage/LunCmd/FreeNAS.pm +++ b/perl5/PVE/Storage/LunCmd/FreeNAS.pm @@ -9,69 +9,108 @@ use REST::Client; use MIME::Base64; use JSON; +# Max LUNS per target on the iSCSI server my $MAX_LUNS = 255; -sub get_base { return '/dev/zvol'; } +# +# +# +sub get_base { + return '/dev/zvol'; +} +# +# +# sub run_lun_command { my ($scfg, $timeout, $method, @params) = @_; # TODO : Move configuration of the storage - if( ! defined( $scfg->{'freenas_user'} ) ) { - $scfg->{'freenas_user'} = 'root'; - $scfg->{'freenas_password'} = '*** password ***'; + if(!defined($scfg->{'freenas_user'})) { + $scfg->{'freenas_user'} = 'root'; + $scfg->{'freenas_password'} = '*** password ***'; } syslog("info","FreeNAS::lun_command : $method(@params)"); - if( $method eq "create_lu" ) { return run_create_lu($scfg,$timeout,$method,@params); } - if( $method eq "delete_lu" ) { return run_delete_lu($scfg,$timeout,$method,@params); } - if( $method eq "import_lu" ) { return run_create_lu($scfg,$timeout,$method,@params); } - if( $method eq "modify_lu" ) { return run_modify_lu($scfg,$timeout,$method,@params); } - if( $method eq "add_view" ) { return run_add_view($scfg,$timeout,$method,@params); } - if( $method eq "list_view" ) { return run_list_view($scfg,$timeout,$method, @params); } - if( $method eq "list_lu" ) { return run_list_lu($scfg,$timeout,$method,"name", @params); } + if($method eq "create_lu") { + return run_create_lu($scfg, $timeout, $method, @params); + } + if($method eq "delete_lu") { + return run_delete_lu($scfg, $timeout, $method, @params); + } + if($method eq "import_lu") { + return run_create_lu($scfg, $timeout, $method, @params); + } + if($method eq "modify_lu") { + return run_modify_lu($scfg, $timeout, $method, @params); + } + if($method eq "add_view") { + return run_add_view($scfg, $timeout, $method, @params); + } + if($method eq "list_view") { + return run_list_view($scfg, $timeout, $method, @params); + } + if($method eq "list_lu") { + return run_list_lu($scfg, $timeout, $method, "name", @params); + } syslog("error","FreeNAS::lun_command : unknown method $method"); return undef; } -sub run_add_view { return ''; } +# +# +# +sub run_add_view { + return ''; +} +# # a modify_lu occur by example on a zvol resize. we just need to destroy and recreate the lun with the same zvol. # Be careful, the first param is the new size of the zvol, we must shift params +# sub run_modify_lu { my ($scfg, $timeout, $method, @params) = @_; shift(@params); - run_delete_lu($scfg,$timeout,$method,@params); - return run_create_lu($scfg,$timeout,$method,@params); + run_delete_lu($scfg, $timeout, $method, @params); + return run_create_lu($scfg, $timeout, $method, @params); } +# +# +# sub run_list_view { my ($scfg, $timeout, $method, @params) = @_; - return run_list_lu($scfg,$timeout,$method,"lun-id", @params); + return run_list_lu($scfg, $timeout, $method, "lun-id", @params); } +# +# +# sub run_list_lu { - my ($scfg, $timeout, $method, $result_value_type ,@params) = @_; + my ($scfg, $timeout, $method, $result_value_type, @params) = @_; my $object = $params[0]; my $result = undef; my $luns = freenas_list_lu($scfg); - foreach my $lun ( @$luns ) { - if ($lun->{'iscsi_target_extent_path'} =~ /^$object$/) { - $result = $result_value_type eq "lun-id" ? $lun->{'iscsi_lunid'} : $lun->{'iscsi_target_extent_path'}; - syslog("info","FreeNAS::list_lu($object):$result_value_type : lun found $result"); - last; - } + foreach my $lun (@$luns) { + if ($lun->{'iscsi_target_extent_path'} =~ /^$object$/) { + $result = $result_value_type eq "lun-id" ? $lun->{'iscsi_lunid'} : $lun->{'iscsi_target_extent_path'}; + syslog("info","FreeNAS::list_lu($object):$result_value_type : lun found $result"); + last; + } } - if( !defined($result) ) { + if(!defined($result)) { syslog("info","FreeNAS::list_lu($object):$result_value_type : lun not found"); } return $result; } +# +# +# sub run_create_lu { my ($scfg, $timeout, $method, @params) = @_; @@ -79,16 +118,16 @@ sub run_create_lu { my $lun_id = freenas_get_first_available_lunid($scfg); die "Maximum number of LUNs per target is $MAX_LUNS" if scalar $lun_id >= $MAX_LUNS; - die "$params[0]: LUN $lun_path exists" if defined(run_list_lu($scfg,$timeout,$method,"name", @params)); + die "$params[0]: LUN $lun_path exists" if defined(run_list_lu($scfg, $timeout, $method, "name", @params)); my $target_id = freenas_get_targetid($scfg); die "Unable to find the target id for $scfg->{target}" if !defined($target_id); my $bs=$scfg->{blocksize}; - if (index($bs, "k") >= 0 ) { + if (index($bs, "k") >= 0) { chop($bs); $bs = $bs * 1024; syslog("info","FreeNAS::create_lu(lun_path=$lun_path, lun_id=$lun_id) : blocksize convert $scfg->{blocksize} = $bs"); - } else { + } else { syslog("info","FreeNAS::create_lu(lun_path=$lun_path, lun_id=$lun_id) : blocksize $bs"); } @@ -96,17 +135,20 @@ sub run_create_lu { my $extent = freenas_iscsi_create_extent($scfg, $lun_path, $bs); # Associate the new extent to the target - my $link = freenas_iscsi_create_target_to_extent($scfg , $target_id , $extent->{'id'} , $lun_id ); + my $link = freenas_iscsi_create_target_to_extent($scfg, $target_id, $extent->{'id'}, $lun_id); - if (defined($link) ) { + if (defined($link)) { syslog("info","FreeNAS::create_lu(lun_path=$lun_path, lun_id=$lun_id) : sucessfull"); - } else { + } else { die "Unable to create lun $lun_path"; } return ""; } +# +# +# sub run_delete_lu { my ($scfg, $timeout, $method, @params) = @_; @@ -115,8 +157,8 @@ sub run_delete_lu { my $lun = undef; my $link = undef; - foreach my $item ( @$luns ) { - if( $item->{'iscsi_target_extent_path'} =~ /^$lun_path$/ ) { + foreach my $item (@$luns) { + if($item->{'iscsi_target_extent_path'} =~ /^$lun_path$/) { $lun = $item; last; } @@ -130,79 +172,92 @@ sub run_delete_lu { # find the target to extent my $target2extents = freenas_iscsi_get_target_to_extent($scfg); - foreach my $item ( @$target2extents ) { - if( $item->{'iscsi_target'} == $target_id && + foreach my $item (@$target2extents) { + if($item->{'iscsi_target'} == $target_id && $item->{'iscsi_lunid'} == $lun->{'iscsi_lunid'} && - $item->{'iscsi_extent'} == $lun->{'id'} ) { + $item->{'iscsi_extent'} == $lun->{'id'}) { - $link = $item; - last; - } + $link = $item; + last; + } } die "Unable to find the link for the lun $lun_path for $scfg->{target}" if !defined($link); # Remove the link - my $remove_link = freenas_iscsi_remove_target_to_extent($scfg,$link->{'id'}); + my $remove_link = freenas_iscsi_remove_target_to_extent($scfg, $link->{'id'}); # Remove the extent - my $remove_extent = freenas_iscsi_remove_extent($scfg,$lun->{'id'}); + my $remove_extent = freenas_iscsi_remove_extent($scfg, $lun->{'id'}); - if( $remove_link == 1 && $remove_extent == 1 ) { - syslog("info","FreeNAS::delete_lu(lun_path=$lun_path) : sucessfull"); - } else { - die "Unable to delete lun $lun_path"; + if($remove_link == 1 && $remove_extent == 1) { + syslog("info","FreeNAS::delete_lu(lun_path=$lun_path) : sucessfull"); + } else { + die "Unable to delete lun $lun_path"; } return ""; } - +# ### FREENAS API CALLING ### - +# sub freenas_api_call { my ($scfg, $method, $path, $data) = @_; my $client = undef; $client = REST::Client->new(); - $client->setHost('http://'. $scfg->{portal} ); - $client->addHeader('Content-Type' , 'application/json' ); - $client->addHeader('Authorization' , 'Basic ' . encode_base64( $scfg->{freenas_user} . ':' . $scfg->{freenas_password} ) ); + $client->setHost('http://'. $scfg->{portal}); + $client->addHeader('Content-Type' , 'application/json'); + $client->addHeader('Authorization' , 'Basic ' . encode_base64( $scfg->{freenas_user} . ':' . $scfg->{freenas_password})); - if ($method eq 'GET') { $client->GET($path); } - if ($method eq 'DELETE') { $client->DELETE($path); } - if ($method eq 'POST') { $client->POST($path, encode_json($data) ); } + if ($method eq 'GET') { + $client->GET($path); + } + if ($method eq 'DELETE') { + $client->DELETE($path); + } + if ($method eq 'POST') { + $client->POST($path, encode_json($data)); + } return $client } +# +# Writes the Response and Content to SysLog +# sub freenas_api_log_error { my ($client, $method) = @_; - syslog("info","[ERROR]FreeNAS::API::" . $method . " : Response code: ".$client->responseCode()); - syslog("info","[ERROR]FreeNAS::API::" . $method . " : Response content: ".$client->responseContent()); + syslog("info","[ERROR]FreeNAS::API::" . $method . " : Response code: " . $client->responseCode()); + syslog("info","[ERROR]FreeNAS::API::" . $method . " : Response content: " . $client->responseContent()); return 1; } +# +# +# sub freenas_iscsi_get_globalconfiguration { my ($scfg) = @_; - my $client = freenas_api_call($scfg,'GET',"/api/v1.0/services/iscsi/globalconfiguration/",undef); + my $client = freenas_api_call($scfg, 'GET', "/api/v1.0/services/iscsi/globalconfiguration/", undef); my $code = $client->responseCode(); if ($code == 200) { - my $result = decode_json($client->responseContent()); - syslog("info","FreeNAS::API::get_globalconfig : target_basename=". $result->{'iscsi_basename'}); - return $result; + my $result = decode_json($client->responseContent()); + syslog("info","FreeNAS::API::get_globalconfig : target_basename=" . $result->{'iscsi_basename'}); + return $result; } else { - freenas_api_log_error($client, "get_globalconfig"); - return undef; + freenas_api_log_error($client, "get_globalconfig"); + return undef; } } +# # Returns a list of all extents. # http://api.freenas.org/resources/iscsi/index.html#get--api-v1.0-services-iscsi-extent- - +# sub freenas_iscsi_get_extent { my ($scfg) = @_; - my $client = freenas_api_call($scfg,'GET',"/api/v1.0/services/iscsi/extent/?limit=0",undef); + my $client = freenas_api_call($scfg, 'GET', "/api/v1.0/services/iscsi/extent/?limit=0", undef); my $code = $client->responseCode(); if ($code == 200) { @@ -215,13 +270,14 @@ sub freenas_iscsi_get_extent { } } +# # Create an extent on FreeNas # http://api.freenas.org/resources/iscsi/index.html#create-resource # Parameters: # - target config (scfg) # - lun_path # - lun_bs - +# sub freenas_iscsi_create_extent { my ($scfg, $lun_path, $lun_bs) = @_; @@ -242,71 +298,75 @@ sub freenas_iscsi_create_extent { my $client = freenas_api_call($scfg, 'POST', "/api/v1.0/services/iscsi/extent/", $request); my $code = $client->responseCode(); if ($code == 201) { - my $result = decode_json($client->responseContent()); - syslog("info","FreeNAS::API::create_extent(lun_path=". $result->{'iscsi_target_extent_path'} . ", lun_bs=$lun_bs) : sucessfull"); - return $result; + my $result = decode_json($client->responseContent()); + syslog("info", "FreeNAS::API::create_extent(lun_path=" . $result->{'iscsi_target_extent_path'} . ", lun_bs=$lun_bs) : sucessfull"); + return $result; } else { - freenas_api_log_error($client, "create_extent"); - return undef; + freenas_api_log_error($client, "create_extent"); + return undef; } } +# # Remove an extent by it's id # http://api.freenas.org/resources/iscsi/index.html#delete-resource # Parameters: # - scfg # - extent_id - +# sub freenas_iscsi_remove_extent { - my ($scfg,$extent_id) = @_; + my ($scfg, $extent_id) = @_; my $client = freenas_api_call($scfg, 'DELETE', "/api/v1.0/services/iscsi/extent/$extent_id/", undef); my $code = $client->responseCode(); if ($code == 204) { - syslog("info","FreeNAS::API::remove_extent(extent_id=$extent_id) : sucessfull"); - return 1; + syslog("info","FreeNAS::API::remove_extent(extent_id=$extent_id) : sucessfull"); + return 1; } else { - freenas_api_log_error($client, "remove_extent"); - return 0; + freenas_api_log_error($client, "remove_extent"); + return 0; } } +# # Returns a list of all targets # http://api.freenas.org/resources/iscsi/index.html#get--api-v1.0-services-iscsi-target- - +# sub freenas_iscsi_get_target { my ($scfg) = @_; - my $client = freenas_api_call($scfg,'GET',"/api/v1.0/services/iscsi/target/?limit=0",undef); + my $client = freenas_api_call($scfg, 'GET', "/api/v1.0/services/iscsi/target/?limit=0", undef); my $code = $client->responseCode(); if ($code == 200) { - my $result = decode_json($client->responseContent()); - syslog("info","FreeNAS::API::get_target() : sucessfull"); - return $result; + my $result = decode_json($client->responseContent()); + syslog("info","FreeNAS::API::get_target() : sucessfull"); + return $result; } else { - freenas_api_log_error($client, "get_target"); - return undef; + freenas_api_log_error($client, "get_target"); + return undef; } } +# # Returns a list of associated extents to targets # http://api.freenas.org/resources/iscsi/index.html#get--api-v1.0-services-iscsi-targettoextent- - +# sub freenas_iscsi_get_target_to_extent { my ($scfg) = @_; - my $client = freenas_api_call($scfg,'GET',"/api/v1.0/services/iscsi/targettoextent/?limit=0",undef); + my $client = freenas_api_call($scfg, 'GET', "/api/v1.0/services/iscsi/targettoextent/?limit=0", undef); my $code = $client->responseCode(); if ($code == 200) { - my $result = decode_json($client->responseContent()); - syslog("info","FreeNAS::API::get_target_to_extent() : sucessfull"); - return $result; + my $result = decode_json($client->responseContent()); + syslog("info","FreeNAS::API::get_target_to_extent() : sucessfull"); + return $result; } else { - freenas_api_log_error($client, "get_target_to_extent"); - return undef; + freenas_api_log_error($client, "get_target_to_extent"); + return undef; } } +# # Associate a FreeNas extent to a FreeNas Target # http://api.freenas.org/resources/iscsi/index.html#post--api-v1.0-services-iscsi-targettoextent- # Parameters: @@ -314,121 +374,127 @@ sub freenas_iscsi_get_target_to_extent { # - FreeNas Target ID # - FreeNas Extent ID # - Lun ID - +# sub freenas_iscsi_create_target_to_extent { - my ($scfg,$target_id,$extent_id,$lun_id) = @_; + my ($scfg, $target_id, $extent_id, $lun_id) = @_; my $request = { - "iscsi_target" => $target_id, - "iscsi_extent" => $extent_id, - "iscsi_lunid" => $lun_id + "iscsi_target" => $target_id, + "iscsi_extent" => $extent_id, + "iscsi_lunid" => $lun_id }; my $client = freenas_api_call($scfg, 'POST', "/api/v1.0/services/iscsi/targettoextent/", $request); my $code = $client->responseCode(); if ($code == 201) { - my $result = decode_json($client->responseContent()); - syslog("info","FreeNAS::API::create_target_to_extent(target_id=$target_id, extent_id=$extent_id, lun_id=$lun_id) : sucessfull"); - return $result; + my $result = decode_json($client->responseContent()); + syslog("info","FreeNAS::API::create_target_to_extent(target_id=$target_id, extent_id=$extent_id, lun_id=$lun_id) : sucessfull"); + return $result; } else { - freenas_api_log_error($client, "create_target_to_extent"); - return undef; + freenas_api_log_error($client, "create_target_to_extent"); + return undef; } } +# # Remove a Target to extent by it's id # http://api.freenas.org/resources/iscsi/index.html#delete--api-v1.0-services-iscsi-targettoextent-(int-id)- # Parameters: # - scfg # - link_id - +# sub freenas_iscsi_remove_target_to_extent { - my ($scfg,$link_id) = @_; + my ($scfg, $link_id) = @_; my $client = freenas_api_call($scfg, 'DELETE', "/api/v1.0/services/iscsi/targettoextent/$link_id/", undef); my $code = $client->responseCode(); if ($code == 204) { - syslog("info","FreeNAS::API::remove_target_to_extent(link_id=$link_id) : sucessfull"); - return 1; + syslog("info","FreeNAS::API::remove_target_to_extent(link_id=$link_id) : sucessfull"); + return 1; } else { - freenas_api_log_error($client, "remove_target_to_extent"); - return 0; + freenas_api_log_error($client, "remove_target_to_extent"); + return 0; } } +# # Returns all luns associated to the current target defined by $scfg->{target} # This method returns an array reference like "freenas_iscsi_get_extent" do # but with an additionnal hash entry "iscsi_lunid" retrieved from "freenas_iscsi_get_target_to_extent" # sub freenas_list_lu { - my ($scfg) = @_; + my ($scfg) = @_; - my $targets = freenas_iscsi_get_target($scfg); - my $target_id = freenas_get_targetid($scfg); + my $targets = freenas_iscsi_get_target($scfg); + my $target_id = freenas_get_targetid($scfg); - my @luns = (); + my @luns = (); - if( defined($target_id) ) { - my $target2extents = freenas_iscsi_get_target_to_extent($scfg); - my $extents = freenas_iscsi_get_extent($scfg); + if(defined($target_id)) { + my $target2extents = freenas_iscsi_get_target_to_extent($scfg); + my $extents = freenas_iscsi_get_extent($scfg); - foreach my $item ( @$target2extents ) { - if( $item->{'iscsi_target'} == $target_id ) { - foreach my $node ( @$extents ) { - if( $node->{'id'} == $item->{'iscsi_extent'} ) { - $node->{'iscsi_lunid'} .= $item->{'iscsi_lunid'}; - push( @luns , $node ); - } + foreach my $item (@$target2extents) { + if($item->{'iscsi_target'} == $target_id) { + foreach my $node (@$extents) { + if($node->{'id'} == $item->{'iscsi_extent'}) { + $node->{'iscsi_lunid'} .= $item->{'iscsi_lunid'}; + push( @luns , $node); + } + } + } } - } } - } - syslog("info","FreeNAS::API::freenas_list_lu : sucessfull"); - return \@luns; + syslog("info", "FreeNAS::API::freenas_list_lu : sucessfull"); + return \@luns; } +# # Returns the first available "lunid" (in all targets namespaces) # sub freenas_get_first_available_lunid { - my ($scfg) = @_; + my ($scfg) = @_; - my $target_id = freenas_get_targetid($scfg); - my $target2extents = freenas_iscsi_get_target_to_extent($scfg); - my @luns = (); + my $target_id = freenas_get_targetid($scfg); + my $target2extents = freenas_iscsi_get_target_to_extent($scfg); + my @luns = (); - foreach my $item ( @$target2extents ) { - push(@luns, $item->{'iscsi_lunid'}) if ($item->{'iscsi_target'} == $target_id); - } + foreach my $item (@$target2extents) { + push(@luns, $item->{'iscsi_lunid'}) if ($item->{'iscsi_target'} == $target_id); + } - my @sorted_luns = sort { $a <=> $b } @luns; - my $lun_id = 0; + my @sorted_luns = sort {$a <=> $b} @luns; + my $lun_id = 0; - # find the first hole, if not, give the +1 of the last lun - foreach my $lun ( @sorted_luns ) { - last if $lun != $lun_id; - $lun_id = $lun_id + 1; - } + # find the first hole, if not, give the +1 of the last lun + foreach my $lun (@sorted_luns) { + last if $lun != $lun_id; + $lun_id = $lun_id + 1; + } - syslog("info","FreeNAS::API::freenas_get_first_available_lunid : return $lun_id"); - return $lun_id; + syslog("info", "FreeNAS::API::freenas_get_first_available_lunid : return $lun_id"); + return $lun_id; } # # Returns the target id on FreeNas of the currently configured target of this PVE storage # sub freenas_get_targetid { - my ($scfg) = @_; + my ($scfg) = @_; - my $global = freenas_iscsi_get_globalconfiguration($scfg); - my $targets = freenas_iscsi_get_target($scfg); - my $target_id = undef; + my $global = freenas_iscsi_get_globalconfiguration($scfg); + my $targets = freenas_iscsi_get_target($scfg); + my $target_id = undef; - foreach my $target ( @$targets ) { - my $iqn = $global->{'iscsi_basename'} . ':' . $target->{'iscsi_target_name'}; - if( $iqn eq $scfg->{target} ) { $target_id = $target->{'id'}; last } - } + foreach my $target (@$targets) { + my $iqn = $global->{'iscsi_basename'} . ':' . $target->{'iscsi_target_name'}; + if($iqn eq $scfg->{target}) { + $target_id = $target->{'id'}; + last; + } + } - return $target_id; + return $target_id; }