SCST.pm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. package PVE::Storage::Custom::LunCmd::SCST;
  2. # iscsi storage running SCST on Linux
  3. #
  4. #
  5. #
  6. # https://pve.proxmox.com/wiki/Storage:_ZFS_over_iSCSI
  7. #
  8. #
  9. # mkdir /etc/pve/priv/zfs
  10. # ssh-keygen -f /etc/pve/priv/zfs/<IP>_id_rsa
  11. #
  12. # ssh-copy-id -i /etc/pve/priv/zfs/<IP>_id_rsa.pub root@<IP>
  13. #
  14. # ssh -i /etc/pve/priv/zfs/<IP>_id_rsa root@<IP>
  15. #
  16. # On one of the proxmox nodes:
  17. # 1) Login as root
  18. # 2) ssh-copy-id <ip_of_iscsi_storage>
  19. #
  20. #
  21. # On SCST LUN0 is ignored ...
  22. # make sure it is always configured as a dummy-device
  23. #
  24. use strict;
  25. use warnings;
  26. use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach);
  27. use Data::Dumper;
  28. use PVE::SafeSyslog;
  29. use POSIX qw(strftime);
  30. use File::Basename;
  31. sub get_base;
  32. # A logical unit can max have 16864 LUNs
  33. # http://manpages.ubuntu.com/manpages/precise/man5/ietd.conf.5.html
  34. #my $MAX_LUNS = 16864;
  35. my $MAX_LUNS = 20; # more handy for development / dumping used luns
  36. my $SETTINGS = undef;
  37. my @ssh_opts = ('-o', 'BatchMode=yes');
  38. my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
  39. my @scp_cmd = ('/usr/bin/scp', @ssh_opts);
  40. my $id_rsa_path = '/etc/pve/priv/zfs';
  41. my $scstadm = '/usr/local/sbin/scstadmin';
  42. my $debugmsg = sub {
  43. my ($mtype, $msg, $logfd) = @_;
  44. chomp $msg;
  45. return if !$msg;
  46. # my $pre = $debugstattxt->{$mtype} || $debugstattxt->{'err'};
  47. my $pre = "SCST-ZFS";
  48. my $timestr = strftime ("%b %d %H:%M:%S", CORE::localtime);
  49. syslog ($mtype eq 'info' ? 'info' : 'err', "$pre $msg");
  50. foreach my $line (split (/\n/, $msg)) {
  51. print STDERR "$pre $line\n";
  52. print $logfd "$timestr $pre $line\n" if $logfd;
  53. }
  54. };
  55. my $execute_command = sub {
  56. my ($scfg, $exec, $timeout, $method, @params) = @_;
  57. my $msg = '';
  58. my $err = undef;
  59. my $target;
  60. my $cmd;
  61. my $res = ();
  62. $timeout = 10 if !$timeout;
  63. my $output = sub {
  64. my $line = shift;
  65. #$debugmsg->('info','#READ '.$line);
  66. $msg .= "$line\n";
  67. };
  68. my $errfunc = sub {
  69. my $line = shift;
  70. #$debugmsg->('info','#ERR '.$line);
  71. $err .= "$line";
  72. };
  73. if ($exec eq 'scp') {
  74. $target = 'root@[' . $scfg->{portal} . ']';
  75. $cmd = [@scp_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", '--', $method, "$target:$params[0]"];
  76. } else {
  77. $target = 'root@' . $scfg->{portal};
  78. $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, '--', $method, @params];
  79. }
  80. eval {
  81. run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
  82. };
  83. if ($@) {
  84. $res = {
  85. result => 0,
  86. msg => $err,
  87. }
  88. } else {
  89. $res = {
  90. result => 1,
  91. msg => $msg,
  92. }
  93. }
  94. return $res;
  95. };
  96. my $get_target_path = sub {
  97. my ($scfg) = @_;
  98. if(defined $scfg->{ini_group} and length $scfg->{ini_group}) {
  99. return "/sys/kernel/scst_tgt/targets/iscsi/$scfg->{target}/ini_groups/$scfg->{ini_group}/luns/";
  100. } else {
  101. return "/sys/kernel/scst_tgt/targets/iscsi/$scfg->{target}/luns/";
  102. }
  103. };
  104. #
  105. # Read config via SCST's sysfs, for the target in question
  106. # no need for a parser with scst as we don't parse an actual config-file
  107. #
  108. # $SETTINS should list all LUN's
  109. # what more ?
  110. #
  111. my $read_configured_luns = sub {
  112. my ($scfg, $timeout) = @_;
  113. my $msg = '';
  114. my $err = undef;
  115. my $target;
  116. my $targetPath;
  117. my $output = sub {
  118. my $line = shift;
  119. $msg .= "$line\n";
  120. };
  121. my $errfunc = sub {
  122. my $line = shift;
  123. $err .= "$line";
  124. };
  125. $timeout = 10 if !$timeout;
  126. $target = 'root@' . $scfg->{portal};
  127. $targetPath = $get_target_path->($scfg);
  128. # esos find has no posix-extended and can't prinf !
  129. # my @listLunParams = (
  130. # $targetPath,
  131. # ,'-maxdepth','1'
  132. # ,'-regextype','posix-extended'
  133. # ,'-regex',"\'^.*/luns/\([0-9]+\)\$\'"
  134. # ,'-printf',"'%f\n'"
  135. # );
  136. my $findRegexp = "\'^.*luns/[0-9][0-9]\?[0-9]\?[0-9]\?\$'";
  137. if( defined $scfg->{esos} && $scfg->{esos} eq "1") {
  138. $findRegexp = "\'^.*luns/[0-9][0-9]\\?[0-9]\\?[0-9]\\?\$'";
  139. }
  140. my @listLunParams = (
  141. $targetPath,
  142. ,'-maxdepth','1'
  143. ,'-regex', $findRegexp
  144. ,'-print'
  145. );
  146. $debugmsg->('info',"find $targetPath -maxdepth 1 -regex $findRegexp -print");
  147. my $res = $execute_command->($scfg,'ssh',$timeout,'find',@listLunParams);
  148. die $res->{msg} unless $res->{result};
  149. my @lunsList = split "\n", $res->{msg};
  150. my $currentLun = undef;
  151. foreach (@lunsList) {
  152. $currentLun = basename($_); # was genau kommt da ?
  153. if($currentLun != 0) {
  154. my $conf = undef;
  155. $conf->{include} = 1;
  156. $conf->{lun} = $currentLun;
  157. my $pathRes = $execute_command->($scfg, 'ssh', undef,
  158. 'cat', "$targetPath$currentLun/device/filename");
  159. die $pathRes->{msg} unless $pathRes->{result};
  160. my @lunPath = split "\n", $pathRes->{msg};
  161. $conf->{Path} = $lunPath[0];
  162. push @{$SETTINGS->{luns}}, $conf;
  163. } else {
  164. # this is LUN 0 with vdisk_nullio
  165. my $conf = undef;
  166. $conf->{include} = 1;
  167. $conf->{lun} = $currentLun;
  168. $conf->{Path} = '/dev/null';
  169. push @{$SETTINGS->{luns}}, $conf;
  170. }
  171. $SETTINGS->{used}->{$currentLun} = 1;
  172. }
  173. return $res->{msg};
  174. };
  175. #
  176. # read initial config, build settings and read luns
  177. #
  178. my $get_config = sub {
  179. my ($scfg) = @_;
  180. if(! defined $SETTINGS ) {
  181. $SETTINGS->{target} = $scfg->{target};
  182. $read_configured_luns->($scfg, undef);
  183. }
  184. };
  185. my $clear_config = sub {
  186. my ($scfg) = @_;
  187. $SETTINGS = undef;
  188. };
  189. #
  190. # Write-out current config of running scst to /etc/scst.conf on Storage
  191. # Makes use of use scstadmin --write_config
  192. #
  193. my $update_config = sub {
  194. my ($scfg) = @_;
  195. my $file = "/etc/scst.conf";
  196. my @params = ('--write_config',$file);
  197. my $res = $execute_command->($scfg, 'ssh',undef,'scstadmin', @params);
  198. die $res->{msg} unless $res->{result};
  199. if( defined $scfg->{esos} && $scfg->{esos} eq "1") {
  200. my $syncEsosRes = $execute_command->($scfg,'ssh',undef,'/usr/local/sbin/conf_sync.sh');
  201. die $syncEsosRes->{msg} unless $syncEsosRes->{result};
  202. }
  203. };
  204. my $get_target_tid = sub {
  205. my ($scfg) = @_;
  206. return 0;
  207. # what exactly is output of this ?
  208. # first * is iscsi
  209. # find /sys/kernel/scst_tgt/targets/*/iqn*/enabled 2> /dev/null
  210. # example-result for a enabled target:
  211. # /sys/kernel/scst_tgt/targets/iscsi/iqn.2016-06-02.modula-shop-systems.de:tgt/enabled
  212. my $proc = '/sys/kernel/scst_tgt/targets/iscsi/iqn*/enabled 2> /dev/null';
  213. my $tid = undef;
  214. my @params = ($proc);
  215. my $res = $execute_command->($scfg, 'ssh', undef, 'find', @params);
  216. die $res->{msg} unless $res->{result};
  217. my @cfg = split "\n", $res->{msg};
  218. foreach (@cfg) {
  219. # $debugmsg->('info','target is : '.$scfg->{target}.' $_'. $_);
  220. # gives:
  221. # SCST-ZFS target is :
  222. # iqn.2016-06-02.modula-shop-systems.de:tgt
  223. # $_/sys/kernel/scst_tgt/targets/iscsi/iqn.2016-06-02.modula-shop-systems.de:tgt/enabled
  224. if ($_ =~ /^\s*tid:(\d+)\s+name:([\w\-\:\.]+)\s*$/) {
  225. if ($2 && $2 eq $scfg->{target}) {
  226. $tid = $1;
  227. last;
  228. }
  229. }
  230. }
  231. return $tid;
  232. };
  233. #
  234. # Gets next free LUN-Number to be created
  235. #
  236. my $get_free_lun = sub {
  237. my $usedLuns = ();
  238. my $i=0;
  239. # LUN0 on SCST is reserved:
  240. $usedLuns->{$i} = 1;
  241. for ($i = 1; $i < $MAX_LUNS; $i++) {
  242. $usedLuns->{$i} = 0;
  243. }
  244. foreach my $lun (@{$SETTINGS->{luns}}) {
  245. $usedLuns->{$lun->{lun}} = 1;
  246. }
  247. $SETTINGS->{used} = $usedLuns;
  248. for ($i = 0; $i < $MAX_LUNS; $i++) {
  249. if(!$SETTINGS->{used}->{$i}) {
  250. #if(!$usedLuns->{$i}) {
  251. # $SETTINGS->{used}->{$i} = 1;
  252. # $debugmsg->('info',"get_lu_name result $i");
  253. return $i;
  254. }
  255. }
  256. };
  257. #
  258. # Removes LUN from Settings and free`s the slot / LUN Number
  259. #
  260. my $free_lu_name = sub {
  261. my ($lu_name) = @_;
  262. my $updated_luns;
  263. foreach my $lun (@{$SETTINGS->{luns}}) {
  264. if ($lun->{lun} != $lu_name) {
  265. push @$updated_luns, $lun;
  266. }
  267. }
  268. $SETTINGS->{luns} = $updated_luns;
  269. $SETTINGS->{used}->{$lu_name} = undef;
  270. };
  271. #
  272. # Creates a stub for a new LUN
  273. #
  274. my $make_lun = sub {
  275. my ($scfg, $path) = @_;
  276. die 'Maximum number of LUNs per target is $MAX_LUNS' if scalar @{$SETTINGS->{luns}} >= $MAX_LUNS;
  277. $get_config->($scfg);
  278. my $lun = $get_free_lun->();
  279. my $conf = {
  280. lun => $lun,
  281. Path => $path,
  282. Type => 'blockio',
  283. include => 1,
  284. };
  285. push @{$SETTINGS->{luns}}, $conf;
  286. return $conf;
  287. };
  288. my $list_view = sub {
  289. my ($scfg, $timeout, $method, @params) = @_;
  290. my $lun = undef;
  291. my $object = $params[0];
  292. foreach my $lun (@{$SETTINGS->{luns}}) {
  293. next unless $lun->{include} == 1;
  294. if ($lun->{Path} =~ /^$object$/) {
  295. return $lun->{lun} if (defined($lun->{lun}));
  296. die "$lun->{Path}: Missing LUN";
  297. }
  298. }
  299. return $lun;
  300. };
  301. #
  302. # Returns Path of LUN when present, undef when Lun is not found
  303. # Params: [0] - Path of LUN to search
  304. #
  305. my $list_lun = sub {
  306. my ($scfg, $timeout, $method, @params) = @_;
  307. my $name = undef;
  308. my $searchLun = $params[0];
  309. $clear_config->($scfg);
  310. $get_config->($scfg);
  311. foreach my $lun (@{$SETTINGS->{luns}}) {
  312. next unless $lun->{include} == 1;
  313. if ($lun->{Path} =~ /^$searchLun$/) {
  314. return $lun->{Path};
  315. }
  316. }
  317. return $name;
  318. };
  319. #
  320. # Creates a new LUN on scst target
  321. #
  322. # First: crate device on scst
  323. # Second: add the device to target with LUN
  324. #
  325. my $create_lun = sub {
  326. my ($scfg, $timeout, $method, @params) = @_;
  327. $clear_config->($scfg);
  328. $get_config->($scfg);
  329. if ($list_lun->($scfg, $timeout, $method, @params)) {
  330. die "$params[0]: LUN exists";
  331. }
  332. my $lun = $params[0];
  333. $lun = $make_lun->($scfg, $lun);
  334. my $tid = $get_target_tid->($scfg);
  335. my $path = "Path=$lun->{Path},Type=$lun->{Type}";
  336. # $debugmsg->('info', "CMD: --op new --tid=$tid --lun=$lun->{lun} --params $path");
  337. # add device in scst
  338. # echo "add_device disk1 filename=/disk1; blocksize=1" >/sys/kernel/scst_tgt/handlers/vdisk_fileio/mgmt
  339. #
  340. # add the device to target at lun:
  341. # echo "add disk1 0" >/sys/kernel/scst_tgt/targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/mgmt
  342. my $scstDiskName = basename($lun->{Path});
  343. my @paramsAddDevice = ('"','add_device',$scstDiskName,"filename=$lun->{Path}".'"',">/sys/kernel/scst_tgt/handlers/vdisk_blockio/mgmt");
  344. my $resAddDevice = $execute_command->($scfg, 'ssh', $timeout, 'echo', @paramsAddDevice);
  345. my $targetPath;
  346. $targetPath = $get_target_path->($scfg);
  347. my @paramsAddLun = (
  348. '"','add ',$scstDiskName,$lun->{lun},'"',
  349. ">$targetPath/mgmt"
  350. );
  351. my $resAddLun = $execute_command->($scfg, 'ssh', $timeout, 'echo', @paramsAddLun);
  352. do {
  353. my @paramsRemoveDevice = (
  354. "\"del_device $scstDiskName\"",
  355. ">/sys/kernel/scst_tgt/handlers/vdisk_blockio/mgmt"
  356. );
  357. my $resRemoveDevice = $execute_command->($scfg, 'ssh', $timeout, 'echo', @paramsRemoveDevice);
  358. $free_lu_name->($lun->{lun});
  359. $update_config->($scfg);
  360. die "Could not create LUN: $lun->{lun} PATH: $lun->{Path} NAME: $scstDiskName: ".$resAddLun->{msg};
  361. } unless $resAddLun->{result};
  362. # make config persist here ?
  363. $update_config->($scfg);
  364. $clear_config->($scfg);
  365. return $resAddLun->{msg};
  366. };
  367. my $delete_lun = sub {
  368. my ($scfg, $timeout, $method, @params) = @_;
  369. my $res = {msg => undef};
  370. $get_config->($scfg);
  371. my $path = $params[0];
  372. my $targetPath;
  373. $targetPath = $get_target_path->($scfg);
  374. # my $tid = $get_target_tid->($scfg);
  375. # $debugmsg->('info',"Delete LUN $path");
  376. foreach my $lun (@{$SETTINGS->{luns}}) {
  377. if ($lun->{Path} eq $path) {
  378. # $debugmsg->('info',"DO DELETE: $lun->{Path} $lun->{lun}");
  379. # Delete LUN
  380. @params = (
  381. "\"del $lun->{lun}\"",
  382. ">$targetPath/mgmt"
  383. );
  384. $res = $execute_command->($scfg, 'ssh', $timeout, 'echo', @params);
  385. if ($res->{result}) {
  386. $debugmsg->('info',"[OK - delete LUN]: $lun->{Path} : $res->{msg} -> call: free_lu_name: $lun->{lun}");
  387. # Delete Device
  388. my $scstDiskName = basename($lun->{Path});
  389. @params = (
  390. "\"del_device $scstDiskName\"",
  391. ">/sys/kernel/scst_tgt/handlers/vdisk_blockio/mgmt"
  392. );
  393. $res = $execute_command->($scfg, 'ssh', $timeout, 'echo', @params);
  394. if ($res->{result}) {
  395. $free_lu_name->($lun->{lun});
  396. $update_config->($scfg);
  397. $clear_config->($scfg);
  398. $get_config->($scfg);
  399. last;
  400. } else {
  401. die $res->{msg};
  402. }
  403. } else {
  404. die $res->{msg};
  405. }
  406. }
  407. }
  408. return $res->{msg};
  409. };
  410. my $import_lun = sub {
  411. my ($scfg, $timeout, $method, @params) = @_;
  412. return $create_lun->($scfg, $timeout, $method, @params);
  413. };
  414. my $modify_lun = sub {
  415. my ($scfg, $timeout, $method, @params) = @_;
  416. my $res;
  417. my $scstDevice = basename($params[1]);
  418. @params = ('echo 1',">/sys/kernel/scst_tgt/devices/$scstDevice/resync_size");
  419. $res = $execute_command->($scfg, 'ssh', $timeout, @params);
  420. die $res->{msg} unless $res->{result};
  421. return $res->{msg};
  422. };
  423. my $add_view = sub {
  424. my ($scfg, $timeout, $method, @params) = @_;
  425. return '';
  426. };
  427. my $get_lun_cmd_map = sub {
  428. my ($method) = @_;
  429. my $cmdmap = {
  430. create_lu => { cmd => $create_lun },
  431. delete_lu => { cmd => $delete_lun },
  432. import_lu => { cmd => $import_lun },
  433. modify_lu => { cmd => $modify_lun },
  434. add_view => { cmd => $add_view },
  435. list_view => { cmd => $list_view },
  436. list_lu => { cmd => $list_lun },
  437. };
  438. die "unknown command '$method'" unless exists $cmdmap->{$method};
  439. return $cmdmap->{$method};
  440. };
  441. sub run_lun_command {
  442. my ($scfg, $timeout, $method, @params) = @_;
  443. $get_config->($scfg);
  444. my $cmdmap = $get_lun_cmd_map->($method);
  445. my $msg = $cmdmap->{cmd}->($scfg, $timeout, $method, @params);
  446. return $msg;
  447. }
  448. sub get_base {
  449. return '/dev';
  450. }
  451. 1;