| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566 |
- package PVE::Storage::Custom::LunCmd::SCST;
- # iscsi storage running SCST on Linux
- #
- #
- #
- # https://pve.proxmox.com/wiki/Storage:_ZFS_over_iSCSI
- #
- #
- # mkdir /etc/pve/priv/zfs
- # ssh-keygen -f /etc/pve/priv/zfs/<IP>_id_rsa
- #
- # ssh-copy-id -i /etc/pve/priv/zfs/<IP>_id_rsa.pub root@<IP>
- #
- # ssh -i /etc/pve/priv/zfs/<IP>_id_rsa root@<IP>
- #
- # On one of the proxmox nodes:
- # 1) Login as root
- # 2) ssh-copy-id <ip_of_iscsi_storage>
- #
- #
- # On SCST LUN0 is ignored ...
- # make sure it is always configured as a dummy-device
- #
- use strict;
- use warnings;
- use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach);
- use Data::Dumper;
- use PVE::SafeSyslog;
- use POSIX qw(strftime);
- use File::Basename;
- sub get_base;
- # A logical unit can max have 16864 LUNs
- # http://manpages.ubuntu.com/manpages/precise/man5/ietd.conf.5.html
- #my $MAX_LUNS = 16864;
- my $MAX_LUNS = 20; # more handy for development / dumping used luns
- my $SETTINGS = undef;
- my @ssh_opts = ('-o', 'BatchMode=yes');
- my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
- my @scp_cmd = ('/usr/bin/scp', @ssh_opts);
- my $id_rsa_path = '/etc/pve/priv/zfs';
- my $scstadm = '/usr/local/sbin/scstadmin';
- my $debugmsg = sub {
- my ($mtype, $msg, $logfd) = @_;
- chomp $msg;
- return if !$msg;
- # my $pre = $debugstattxt->{$mtype} || $debugstattxt->{'err'};
- my $pre = "SCST-ZFS";
- my $timestr = strftime ("%b %d %H:%M:%S", CORE::localtime);
- syslog ($mtype eq 'info' ? 'info' : 'err', "$pre $msg");
- foreach my $line (split (/\n/, $msg)) {
- print STDERR "$pre $line\n";
- print $logfd "$timestr $pre $line\n" if $logfd;
- }
- };
- my $execute_command = sub {
- my ($scfg, $exec, $timeout, $method, @params) = @_;
- my $msg = '';
- my $err = undef;
- my $target;
- my $cmd;
- my $res = ();
- $timeout = 10 if !$timeout;
- my $output = sub {
- my $line = shift;
- #$debugmsg->('info','#READ '.$line);
- $msg .= "$line\n";
- };
- my $errfunc = sub {
- my $line = shift;
- #$debugmsg->('info','#ERR '.$line);
- $err .= "$line";
- };
- if ($exec eq 'scp') {
- $target = 'root@[' . $scfg->{portal} . ']';
- $cmd = [@scp_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", '--', $method, "$target:$params[0]"];
- } else {
- $target = 'root@' . $scfg->{portal};
- $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, '--', $method, @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;
- };
- my $get_target_path = sub {
- my ($scfg) = @_;
- if(defined $scfg->{ini_group} and length $scfg->{ini_group}) {
- return "/sys/kernel/scst_tgt/targets/iscsi/$scfg->{target}/ini_groups/$scfg->{ini_group}/luns/";
- } else {
- return "/sys/kernel/scst_tgt/targets/iscsi/$scfg->{target}/luns/";
- }
- };
- #
- # Read config via SCST's sysfs, for the target in question
- # no need for a parser with scst as we don't parse an actual config-file
- #
- # $SETTINS should list all LUN's
- # what more ?
- #
- my $read_configured_luns = sub {
- my ($scfg, $timeout) = @_;
- my $msg = '';
- my $err = undef;
- my $target;
- my $targetPath;
- my $output = sub {
- my $line = shift;
- $msg .= "$line\n";
- };
- my $errfunc = sub {
- my $line = shift;
- $err .= "$line";
- };
- $timeout = 10 if !$timeout;
- $target = 'root@' . $scfg->{portal};
- $targetPath = $get_target_path->($scfg);
- # esos find has no posix-extended and can't prinf !
- # my @listLunParams = (
- # $targetPath,
- # ,'-maxdepth','1'
- # ,'-regextype','posix-extended'
- # ,'-regex',"\'^.*/luns/\([0-9]+\)\$\'"
- # ,'-printf',"'%f\n'"
- # );
- my $findRegexp = "\'^.*luns/[0-9][0-9]\?[0-9]\?[0-9]\?\$'";
- if( defined $scfg->{esos} && $scfg->{esos} eq "1") {
- $findRegexp = "\'^.*luns/[0-9][0-9]\\?[0-9]\\?[0-9]\\?\$'";
- }
- my @listLunParams = (
- $targetPath,
- ,'-maxdepth','1'
- ,'-regex', $findRegexp
- ,'-print'
- );
- $debugmsg->('info',"find $targetPath -maxdepth 1 -regex $findRegexp -print");
- my $res = $execute_command->($scfg,'ssh',$timeout,'find',@listLunParams);
- die $res->{msg} unless $res->{result};
- my @lunsList = split "\n", $res->{msg};
- my $currentLun = undef;
- foreach (@lunsList) {
- $currentLun = basename($_); # was genau kommt da ?
- if($currentLun != 0) {
- my $conf = undef;
- $conf->{include} = 1;
- $conf->{lun} = $currentLun;
- my $pathRes = $execute_command->($scfg, 'ssh', undef,
- 'cat', "$targetPath$currentLun/device/filename");
- die $pathRes->{msg} unless $pathRes->{result};
- my @lunPath = split "\n", $pathRes->{msg};
- $conf->{Path} = $lunPath[0];
- push @{$SETTINGS->{luns}}, $conf;
- } else {
- # this is LUN 0 with vdisk_nullio
- my $conf = undef;
- $conf->{include} = 1;
- $conf->{lun} = $currentLun;
- $conf->{Path} = '/dev/null';
- push @{$SETTINGS->{luns}}, $conf;
- }
- $SETTINGS->{used}->{$currentLun} = 1;
- }
- return $res->{msg};
- };
- #
- # read initial config, build settings and read luns
- #
- my $get_config = sub {
- my ($scfg) = @_;
- if(! defined $SETTINGS ) {
- $SETTINGS->{target} = $scfg->{target};
- $read_configured_luns->($scfg, undef);
- }
- };
- my $clear_config = sub {
- my ($scfg) = @_;
- $SETTINGS = undef;
- };
- #
- # Write-out current config of running scst to /etc/scst.conf on Storage
- # Makes use of use scstadmin --write_config
- #
- my $update_config = sub {
- my ($scfg) = @_;
- my $file = "/etc/scst.conf";
- my @params = ('--write_config',$file);
- my $res = $execute_command->($scfg, 'ssh',undef,'scstadmin', @params);
- die $res->{msg} unless $res->{result};
- if( defined $scfg->{esos} && $scfg->{esos} eq "1") {
- my $syncEsosRes = $execute_command->($scfg,'ssh',undef,'/usr/local/sbin/conf_sync.sh');
- die $syncEsosRes->{msg} unless $syncEsosRes->{result};
- }
- };
- my $get_target_tid = sub {
- my ($scfg) = @_;
- return 0;
- # what exactly is output of this ?
- # first * is iscsi
- # find /sys/kernel/scst_tgt/targets/*/iqn*/enabled 2> /dev/null
- # example-result for a enabled target:
- # /sys/kernel/scst_tgt/targets/iscsi/iqn.2016-06-02.modula-shop-systems.de:tgt/enabled
- my $proc = '/sys/kernel/scst_tgt/targets/iscsi/iqn*/enabled 2> /dev/null';
- my $tid = undef;
- my @params = ($proc);
- my $res = $execute_command->($scfg, 'ssh', undef, 'find', @params);
- die $res->{msg} unless $res->{result};
- my @cfg = split "\n", $res->{msg};
- foreach (@cfg) {
- # $debugmsg->('info','target is : '.$scfg->{target}.' $_'. $_);
- # gives:
- # SCST-ZFS target is :
- # iqn.2016-06-02.modula-shop-systems.de:tgt
- # $_/sys/kernel/scst_tgt/targets/iscsi/iqn.2016-06-02.modula-shop-systems.de:tgt/enabled
- if ($_ =~ /^\s*tid:(\d+)\s+name:([\w\-\:\.]+)\s*$/) {
- if ($2 && $2 eq $scfg->{target}) {
- $tid = $1;
- last;
- }
- }
- }
- return $tid;
- };
- #
- # Gets next free LUN-Number to be created
- #
- my $get_free_lun = sub {
- my $usedLuns = ();
- my $i=0;
- # LUN0 on SCST is reserved:
- $usedLuns->{$i} = 1;
- for ($i = 1; $i < $MAX_LUNS; $i++) {
- $usedLuns->{$i} = 0;
- }
- foreach my $lun (@{$SETTINGS->{luns}}) {
- $usedLuns->{$lun->{lun}} = 1;
- }
- $SETTINGS->{used} = $usedLuns;
- for ($i = 0; $i < $MAX_LUNS; $i++) {
- if(!$SETTINGS->{used}->{$i}) {
- #if(!$usedLuns->{$i}) {
- # $SETTINGS->{used}->{$i} = 1;
- # $debugmsg->('info',"get_lu_name result $i");
- return $i;
- }
- }
- };
- #
- # Removes LUN from Settings and free`s the slot / LUN Number
- #
- my $free_lu_name = sub {
- my ($lu_name) = @_;
- my $updated_luns;
- foreach my $lun (@{$SETTINGS->{luns}}) {
- if ($lun->{lun} != $lu_name) {
- push @$updated_luns, $lun;
- }
- }
- $SETTINGS->{luns} = $updated_luns;
- $SETTINGS->{used}->{$lu_name} = undef;
- };
- #
- # Creates a stub for a new LUN
- #
- my $make_lun = sub {
- my ($scfg, $path) = @_;
- die 'Maximum number of LUNs per target is $MAX_LUNS' if scalar @{$SETTINGS->{luns}} >= $MAX_LUNS;
- $get_config->($scfg);
- my $lun = $get_free_lun->();
- my $conf = {
- lun => $lun,
- Path => $path,
- Type => 'blockio',
- include => 1,
- };
- push @{$SETTINGS->{luns}}, $conf;
- return $conf;
- };
- my $list_view = sub {
- my ($scfg, $timeout, $method, @params) = @_;
- my $lun = undef;
- my $object = $params[0];
- foreach my $lun (@{$SETTINGS->{luns}}) {
- next unless $lun->{include} == 1;
- if ($lun->{Path} =~ /^$object$/) {
- return $lun->{lun} if (defined($lun->{lun}));
- die "$lun->{Path}: Missing LUN";
- }
- }
- return $lun;
- };
- #
- # Returns Path of LUN when present, undef when Lun is not found
- # Params: [0] - Path of LUN to search
- #
- my $list_lun = sub {
- my ($scfg, $timeout, $method, @params) = @_;
- my $name = undef;
- my $searchLun = $params[0];
- $clear_config->($scfg);
- $get_config->($scfg);
- foreach my $lun (@{$SETTINGS->{luns}}) {
- next unless $lun->{include} == 1;
- if ($lun->{Path} =~ /^$searchLun$/) {
- return $lun->{Path};
- }
- }
- return $name;
- };
- #
- # Creates a new LUN on scst target
- #
- # First: crate device on scst
- # Second: add the device to target with LUN
- #
- my $create_lun = sub {
- my ($scfg, $timeout, $method, @params) = @_;
- $clear_config->($scfg);
- $get_config->($scfg);
- if ($list_lun->($scfg, $timeout, $method, @params)) {
- die "$params[0]: LUN exists";
- }
- my $lun = $params[0];
- $lun = $make_lun->($scfg, $lun);
- my $tid = $get_target_tid->($scfg);
- my $path = "Path=$lun->{Path},Type=$lun->{Type}";
- # $debugmsg->('info', "CMD: --op new --tid=$tid --lun=$lun->{lun} --params $path");
- # add device in scst
- # echo "add_device disk1 filename=/disk1; blocksize=1" >/sys/kernel/scst_tgt/handlers/vdisk_fileio/mgmt
- #
- # add the device to target at lun:
- # echo "add disk1 0" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/mgmt
- my $scstDiskName = basename($lun->{Path});
- my @paramsAddDevice = ('"','add_device',$scstDiskName,"filename=$lun->{Path}".'"',">/sys/kernel/scst_tgt/handlers/vdisk_blockio/mgmt");
- my $resAddDevice = $execute_command->($scfg, 'ssh', $timeout, 'echo', @paramsAddDevice);
- my $targetPath;
- $targetPath = $get_target_path->($scfg);
- my @paramsAddLun = (
- '"','add ',$scstDiskName,$lun->{lun},'"',
- ">$targetPath/mgmt"
- );
- my $resAddLun = $execute_command->($scfg, 'ssh', $timeout, 'echo', @paramsAddLun);
- do {
- my @paramsRemoveDevice = (
- "\"del_device $scstDiskName\"",
- ">/sys/kernel/scst_tgt/handlers/vdisk_blockio/mgmt"
- );
- my $resRemoveDevice = $execute_command->($scfg, 'ssh', $timeout, 'echo', @paramsRemoveDevice);
- $free_lu_name->($lun->{lun});
- $update_config->($scfg);
- die "Could not create LUN: $lun->{lun} PATH: $lun->{Path} NAME: $scstDiskName: ".$resAddLun->{msg};
- } unless $resAddLun->{result};
- # make config persist here ?
- $update_config->($scfg);
- $clear_config->($scfg);
- return $resAddLun->{msg};
- };
- my $delete_lun = sub {
- my ($scfg, $timeout, $method, @params) = @_;
- my $res = {msg => undef};
- $get_config->($scfg);
- my $path = $params[0];
- my $targetPath;
- $targetPath = $get_target_path->($scfg);
- # my $tid = $get_target_tid->($scfg);
- # $debugmsg->('info',"Delete LUN $path");
- foreach my $lun (@{$SETTINGS->{luns}}) {
- if ($lun->{Path} eq $path) {
- # $debugmsg->('info',"DO DELETE: $lun->{Path} $lun->{lun}");
- # Delete LUN
- @params = (
- "\"del $lun->{lun}\"",
- ">$targetPath/mgmt"
- );
- $res = $execute_command->($scfg, 'ssh', $timeout, 'echo', @params);
- if ($res->{result}) {
- $debugmsg->('info',"[OK - delete LUN]: $lun->{Path} : $res->{msg} -> call: free_lu_name: $lun->{lun}");
- # Delete Device
- my $scstDiskName = basename($lun->{Path});
- @params = (
- "\"del_device $scstDiskName\"",
- ">/sys/kernel/scst_tgt/handlers/vdisk_blockio/mgmt"
- );
- $res = $execute_command->($scfg, 'ssh', $timeout, 'echo', @params);
- if ($res->{result}) {
- $free_lu_name->($lun->{lun});
- $update_config->($scfg);
- $clear_config->($scfg);
- $get_config->($scfg);
- last;
- } else {
- die $res->{msg};
- }
- } else {
- die $res->{msg};
- }
- }
- }
- return $res->{msg};
- };
- my $import_lun = sub {
- my ($scfg, $timeout, $method, @params) = @_;
- return $create_lun->($scfg, $timeout, $method, @params);
- };
- my $modify_lun = sub {
- my ($scfg, $timeout, $method, @params) = @_;
- my $res;
- my $scstDevice = basename($params[1]);
- @params = ('echo 1',">/sys/kernel/scst_tgt/devices/$scstDevice/resync_size");
- $res = $execute_command->($scfg, 'ssh', $timeout, @params);
- die $res->{msg} unless $res->{result};
- return $res->{msg};
- };
- my $add_view = sub {
- my ($scfg, $timeout, $method, @params) = @_;
- return '';
- };
- my $get_lun_cmd_map = sub {
- my ($method) = @_;
- my $cmdmap = {
- create_lu => { cmd => $create_lun },
- delete_lu => { cmd => $delete_lun },
- import_lu => { cmd => $import_lun },
- modify_lu => { cmd => $modify_lun },
- add_view => { cmd => $add_view },
- list_view => { cmd => $list_view },
- list_lu => { cmd => $list_lun },
- };
- die "unknown command '$method'" unless exists $cmdmap->{$method};
- return $cmdmap->{$method};
- };
- sub run_lun_command {
- my ($scfg, $timeout, $method, @params) = @_;
- $get_config->($scfg);
- my $cmdmap = $get_lun_cmd_map->($method);
- my $msg = $cmdmap->{cmd}->($scfg, $timeout, $method, @params);
- return $msg;
- }
- sub get_base {
- return '/dev';
- }
- 1;
|