X-Git-Url: https://git.proxmox.com/?p=pve-storage.git;a=blobdiff_plain;f=PVE%2FStorage%2FLunCmd%2FLIO.pm;h=15ddabf380b4d6de3aceea0547fe88f52ed528fd;hp=b98edc283a0fdf4d431daf043e685f5f5f325331;hb=HEAD;hpb=6726a2e0376f9d1315a62fe387732f1e4e0a33da diff --git a/PVE/Storage/LunCmd/LIO.pm b/PVE/Storage/LunCmd/LIO.pm deleted file mode 100644 index b98edc2..0000000 --- a/PVE/Storage/LunCmd/LIO.pm +++ /dev/null @@ -1,420 +0,0 @@ -package PVE::Storage::LunCmd::LIO; - -# lightly based on code from Iet.pm -# -# additional changes: -# ----------------------------------------------------------------- -# Copyright (c) 2018 BestSolution.at EDV Systemhaus GmbH -# All Rights Reserved. -# -# This software is released under the terms of the -# -# "GNU Affero General Public License" -# -# and may only be distributed and used under the terms of the -# mentioned license. You should have received a copy of the license -# along with this software product, if not you can download it from -# https://www.gnu.org/licenses/agpl-3.0.en.html -# -# Author: udo.rader@bestsolution.at -# ----------------------------------------------------------------- - -use strict; -use warnings; -use PVE::Tools qw(run_command); -use JSON; - -sub get_base; - -# targetcli constants -# config file location differs from distro to distro -my @CONFIG_FILES = ( - '/etc/rtslib-fb-target/saveconfig.json', # Debian 9.x et al - '/etc/target/saveconfig.json' , # ArchLinux, CentOS -); -my $BACKSTORE = '/backstores/block'; - -my $SETTINGS = undef; -my $SETTINGS_TIMESTAMP = 0; -my $SETTINGS_MAXAGE = 15; # in seconds - -my @ssh_opts = ('-o', 'BatchMode=yes'); -my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts); -my $id_rsa_path = '/etc/pve/priv/zfs'; -my $targetcli = '/usr/bin/targetcli'; - -my $execute_remote_command = sub { - my ($scfg, $timeout, $remote_command, @params) = @_; - - my $msg = ''; - my $err = undef; - my $target; - my $cmd; - my $res = (); - - $timeout = 10 if !$timeout; - - my $output = sub { $msg .= "$_[0]\n" }; - my $errfunc = sub { $err .= "$_[0]\n" }; - - $target = 'root@' . $scfg->{portal}; - $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, '--', $remote_command, @params]; - - eval { - run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout); - }; - if ($@) { - $res = { - result => 0, - msg => $err, - } - } else { - $res = { - result => 1, - msg => $msg, - } - } - - return $res; -}; - -# fetch targetcli configuration from the portal -my $read_config = sub { - my ($scfg, $timeout) = @_; - - my $msg = ''; - my $err = undef; - my $luncmd = 'cat'; - my $target; - my $retry = 1; - - $timeout = 10 if !$timeout; - - my $output = sub { $msg .= "$_[0]\n" }; - my $errfunc = sub { $err .= "$_[0]\n" }; - - $target = 'root@' . $scfg->{portal}; - - foreach my $oneFile (@CONFIG_FILES) { - my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $oneFile]; - eval { - run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout); - }; - if ($@) { - die $err if ($err !~ /No such file or directory/); - } - return $msg if $msg ne ''; - } - - die "No configuration found. Install targetcli on $scfg->{portal}\n" if $msg eq ''; - - return $msg; -}; - -my $get_config = sub { - my ($scfg) = @_; - my @conf = undef; - - my $config = $read_config->($scfg, undef); - die "Missing config file" unless $config; - - return $config; -}; - -# Return settings of a specific target -my $get_target_settings = sub { - my ($scfg) = @_; - - my $id = "$scfg->{portal}.$scfg->{target}"; - return undef if !$SETTINGS; - return $SETTINGS->{$id}; -}; - -# fetches and parses targetcli config from the portal -my $parser = sub { - my ($scfg) = @_; - my $tpg = $scfg->{lio_tpg} || die "Target Portal Group not set, aborting!\n"; - my $tpg_tag; - - if ($tpg =~ /^tpg(\d+)$/) { - $tpg_tag = $1; - } else { - die "Target Portal Group has invalid value, must contain string 'tpg' and a suffix number, eg 'tpg17'\n"; - } - - my $config = $get_config->($scfg); - my $jsonconfig = JSON->new->utf8->decode($config); - - my $haveTarget = 0; - foreach my $target (@{$jsonconfig->{targets}}) { - # only interested in iSCSI targets - next if !($target->{fabric} eq 'iscsi' && $target->{wwn} eq $scfg->{target}); - # find correct TPG - foreach my $tpg (@{$target->{tpgs}}) { - if ($tpg->{tag} == $tpg_tag) { - my $res = []; - foreach my $lun (@{$tpg->{luns}}) { - my ($idx, $storage_object); - if ($lun->{index} =~ /^(\d+)$/) { - $idx = $1; - } - if ($lun->{storage_object} =~ m|^($BACKSTORE/.*)$|) { - $storage_object = $1; - } - die "Invalid lun definition in config!\n" - if !(defined($idx) && defined($storage_object)); - push @$res, { index => $idx, storage_object => $storage_object }; - } - - my $id = "$scfg->{portal}.$scfg->{target}"; - $SETTINGS->{$id}->{luns} = $res; - $haveTarget = 1; - last; - } - } - } - - # seriously unhappy if the target server lacks iSCSI target configuration ... - if (!$haveTarget) { - die "target portal group tpg$tpg_tag not found!\n"; - } -}; - -# Get prefix for backstores -my $get_backstore_prefix = sub { - my ($scfg) = @_; - my $pool = $scfg->{pool}; - $pool =~ s/\//-/g; - return $pool . '-'; -}; - -# removes the given lu_name from the local list of luns -my $free_lu_name = sub { - my ($scfg, $lu_name) = @_; - - my $new = []; - my $target = $get_target_settings->($scfg); - foreach my $lun (@{$target->{luns}}) { - if ($lun->{storage_object} ne "$BACKSTORE/$lu_name") { - push @$new, $lun; - } - } - - $target->{luns} = $new; -}; - -# locally registers a new lun -my $register_lun = sub { - my ($scfg, $idx, $volname) = @_; - - my $conf = { - index => $idx, - storage_object => "$BACKSTORE/$volname", - is_new => 1, - }; - my $target = $get_target_settings->($scfg); - push @{$target->{luns}}, $conf; - - return $conf; -}; - -# extracts the ZFS volume name from a device path -my $extract_volname = sub { - my ($scfg, $lunpath) = @_; - my $volname = undef; - - my $base = get_base; - if ($lunpath =~ /^$base\/$scfg->{pool}\/([\w\-]+)$/) { - $volname = $1; - my $prefix = $get_backstore_prefix->($scfg); - my $target = $get_target_settings->($scfg); - foreach my $lun (@{$target->{luns}}) { - # If we have a lun with the pool prefix matching this vol, then return this one - # like pool-pve-vm-100-disk-0 - # Else, just fallback to the old name scheme which is vm-100-disk-0 - if ($lun->{storage_object} =~ /^$BACKSTORE\/($prefix$volname)$/) { - return $1; - } - } - } - - return $volname; -}; - -# retrieves the LUN index for a particular object -my $list_view = sub { - my ($scfg, $timeout, $method, @params) = @_; - my $lun = undef; - - my $object = $params[0]; - my $volname = $extract_volname->($scfg, $object); - my $target = $get_target_settings->($scfg); - - return undef if !defined($volname); # nothing to search for.. - - foreach my $lun (@{$target->{luns}}) { - if ($lun->{storage_object} eq "$BACKSTORE/$volname") { - return $lun->{index}; - } - } - - return $lun; -}; - -# determines, if the given object exists on the portal -my $list_lun = sub { - my ($scfg, $timeout, $method, @params) = @_; - - my $object = $params[0]; - my $volname = $extract_volname->($scfg, $object); - my $target = $get_target_settings->($scfg); - - foreach my $lun (@{$target->{luns}}) { - if ($lun->{storage_object} eq "$BACKSTORE/$volname") { - return $object; - } - } - - return undef; -}; - -# adds a new LUN to the target -my $create_lun = sub { - my ($scfg, $timeout, $method, @params) = @_; - - if ($list_lun->($scfg, $timeout, $method, @params)) { - die "$params[0]: LUN already exists!"; - } - - my $device = $params[0]; - my $volname = $extract_volname->($scfg, $device); - # Here we create a new device, so we didn't get the volname prefixed with the pool name - # as extract_volname couldn't find a matching vol yet - $volname = $get_backstore_prefix->($scfg) . $volname; - my $tpg = $scfg->{lio_tpg} || die "Target Portal Group not set, aborting!\n"; - - # step 1: create backstore for device - my @cliparams = ($BACKSTORE, 'create', "name=$volname", "dev=$device" ); - my $res = $execute_remote_command->($scfg, $timeout, $targetcli, @cliparams); - die $res->{msg} if !$res->{result}; - - # step 2: enable unmap support on the backstore - @cliparams = ($BACKSTORE . '/' . $volname, 'set', 'attribute', 'emulate_tpu=1' ); - $res = $execute_remote_command->($scfg, $timeout, $targetcli, @cliparams); - die $res->{msg} if !$res->{result}; - - # step 3: register lun with target - # targetcli /iscsi/iqn.2018-04.at.bestsolution.somehost:target/tpg1/luns/ create /backstores/block/foobar - @cliparams = ("/iscsi/$scfg->{target}/$tpg/luns/", 'create', "$BACKSTORE/$volname" ); - $res = $execute_remote_command->($scfg, $timeout, $targetcli, @cliparams); - die $res->{msg} if !$res->{result}; - - # targetcli responds with "Created LUN 99" - # not calculating the index ourselves, because the index at the portal might have - # changed without our knowledge, so relying on the number that targetcli returns - my $lun_idx; - if ($res->{msg} =~ /LUN (\d+)/) { - $lun_idx = $1; - } else { - die "unable to determine new LUN index: $res->{msg}"; - } - - $register_lun->($scfg, $lun_idx, $volname); - - # step 3: unfortunately, targetcli doesn't always save changes, no matter - # if auto_save_on_exit is true or not. So saving to be safe ... - $execute_remote_command->($scfg, $timeout, $targetcli, 'saveconfig'); - - return $res->{msg}; -}; - -my $delete_lun = sub { - my ($scfg, $timeout, $method, @params) = @_; - my $res = {msg => undef}; - - my $tpg = $scfg->{lio_tpg} || die "Target Portal Group not set, aborting!\n"; - - my $path = $params[0]; - my $volname = $extract_volname->($scfg, $path); - my $target = $get_target_settings->($scfg); - - foreach my $lun (@{$target->{luns}}) { - next if $lun->{storage_object} ne "$BACKSTORE/$volname"; - - # step 1: delete the lun - my @cliparams = ("/iscsi/$scfg->{target}/$tpg/luns/", 'delete', "lun$lun->{index}" ); - my $res = $execute_remote_command->($scfg, $timeout, $targetcli, @cliparams); - do { - die $res->{msg}; - } unless $res->{result}; - - # step 2: delete the backstore - @cliparams = ($BACKSTORE, 'delete', $volname); - $res = $execute_remote_command->($scfg, $timeout, $targetcli, @cliparams); - do { - die $res->{msg}; - } unless $res->{result}; - - # step 3: save to be safe ... - $execute_remote_command->($scfg, $timeout, $targetcli, 'saveconfig'); - - # update interal cache - $free_lu_name->($scfg, $volname); - - last; - } - - return $res->{msg}; -}; - -my $import_lun = sub { - my ($scfg, $timeout, $method, @params) = @_; - - return $create_lun->($scfg, $timeout, $method, @params); -}; - -# needed for example when the underlying ZFS volume has been resized -my $modify_lun = sub { - my ($scfg, $timeout, $method, @params) = @_; - # Nothing to do on volume modification for LIO - return undef; -}; - -my $add_view = sub { - my ($scfg, $timeout, $method, @params) = @_; - - return ''; -}; - -my %lun_cmd_map = ( - create_lu => $create_lun, - delete_lu => $delete_lun, - import_lu => $import_lun, - modify_lu => $modify_lun, - add_view => $add_view, - list_view => $list_view, - list_lu => $list_lun, -); - -sub run_lun_command { - my ($scfg, $timeout, $method, @params) = @_; - - # fetch configuration from target if we haven't yet or if it is stale - my $timediff = time - $SETTINGS_TIMESTAMP; - my $target = $get_target_settings->($scfg); - if (!$target || $timediff > $SETTINGS_MAXAGE) { - $SETTINGS_TIMESTAMP = time; - $parser->($scfg); - } - - die "unknown command '$method'" unless exists $lun_cmd_map{$method}; - my $msg = $lun_cmd_map{$method}->($scfg, $timeout, $method, @params); - - return $msg; -} - -sub get_base { - return '/dev'; -} - -1;