ZFSPluginPlus.pm 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. package PVE::Storage::Custom::Shared::ZFSPluginPlus;
  2. use PVE::Storage::Custom::LunCmd::SCST;
  3. use Data::Dumper;
  4. use PVE::Tools qw(run_command);
  5. # inherit on the ZFSPlugin
  6. #use base qw(PVE::Storage::ZFSPlugin);
  7. @ISA = qw(PVE::Storage::ZFSPlugin);
  8. my @ssh_opts = ('-o', 'BatchMode=yes');
  9. my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
  10. my $id_rsa_path = '/etc/pve/priv/zfs';
  11. # plugin configuration
  12. # we want to allow copy of snap, currently dies at line 377 in ZFSPlugin.pm
  13. sub volume_has_feature {
  14. my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
  15. my $features = {
  16. snapshot => { current => 1, snap => 1},
  17. clone => { base => 1},
  18. template => { current => 1},
  19. copy => { base => 1, current => 1, snap=>1}, #added snap
  20. };
  21. my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
  22. $class->parse_volname($volname);
  23. my $key = undef;
  24. if ($snapname) {
  25. $key = 'snap';
  26. } else {
  27. $key = $isBase ? 'base' : 'current';
  28. }
  29. return 1 if $features->{$feature}->{$key};
  30. return undef;
  31. }
  32. #
  33. # + added port, chap-auth and transport (iser)
  34. # + allow access to materialized snapshots
  35. #
  36. sub path {
  37. my ($class, $scfg, $volname, $storeid, $snapname) = @_;
  38. # die "direct access to snapshots not implemented"
  39. # if defined($snapname);
  40. my ($vtype, $name, $vmid) = $class->parse_volname($volname);
  41. if(defined($snapname)) {
  42. $name = materialize_snapshot($class,$scfg,$volname,$snapname);
  43. }
  44. my $target = $scfg->{target};
  45. my $portal = $scfg->{portal};
  46. my $guid = $class->zfs_get_lu_name($scfg, $name);
  47. my $lun = $class->zfs_get_lun_number($scfg, $guid);
  48. my $transport = $scfg->{transport};
  49. if($transport eq 'rdma') {
  50. $transport = 'iser';
  51. } else {
  52. $transport = 'iscsi';
  53. }
  54. my $authString = $scfg->{chap_user} && $scfg->{chap_pass}
  55. ? "$scfg->{chap_user}%$scfg->{chap_pass}@" : '';
  56. my $port = $scfg->{port} ? ":$scfg->{port}" : '';
  57. my $path = "$transport://$authString$portal$port/$target/$lun";
  58. return ($path, $vmid, $vtype);
  59. }
  60. my $zfs_get_base = sub {
  61. my ($scfg) = @_;
  62. if ($scfg->{iscsiprovider} eq 'comstar') {
  63. return PVE::Storage::LunCmd::Comstar::get_base;
  64. } elsif ($scfg->{iscsiprovider} eq 'istgt') {
  65. return PVE::Storage::LunCmd::Istgt::get_base;
  66. } elsif ($scfg->{iscsiprovider} eq 'iet') {
  67. return PVE::Storage::LunCmd::Iet::get_base;
  68. } elsif ($scfg->{iscsiprovider} eq 'scstzfs') {
  69. return PVE::Storage::Custom::LunCmd::SCST::get_base;
  70. } elsif ($scfg->{iscsiprovider} eq 'freenas') {
  71. return PVE::Storage::Custom::LunCmd::FreeNas::get_base;
  72. } else {
  73. # $zfs_unknown_scsi_provider->($scfg->{iscsiprovider});
  74. }
  75. };
  76. sub zfs_get_lu_name {
  77. my ($class, $scfg, $zvol) = @_;
  78. my $base = $zfs_get_base->($scfg);
  79. my $object = ($zvol =~ /^.+\/.+/) ? "$base/$zvol" : "$base/$scfg->{pool}/$zvol";
  80. my $lu_name = $class->zfs_request($scfg, undef, 'list_lu', $object);
  81. return $lu_name if $lu_name;
  82. die "Could not find lu_name for zvol $zvol base $base object $object";
  83. }
  84. #
  85. # Allow Activation of Snap via ZFS send/receive
  86. #
  87. sub activate_volume {
  88. my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
  89. if($snapname) {
  90. materialize_snapshot($class,$scfg,$volname,$snapname);
  91. }
  92. return 1;
  93. }
  94. #
  95. # Allow Deactivation of Snap-Volume
  96. #
  97. sub deactivate_volume {
  98. my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
  99. if($snapname) {
  100. $class->dematerialize_snapshot($scfg,$volname,$snapname);
  101. }
  102. return 1;
  103. }
  104. #
  105. # materializes a snapshot of volname by zfs send/revc and creates an iscsi LUN
  106. #
  107. sub materialize_snapshot {
  108. my ($class, $scfg, $volname, $snapshot) = @_;
  109. my $srcVol = get_snapshot_path($class,$scfg,$volname,$snapshot);
  110. my $snapTmpVol = get_snapshot_tmp_name($class,$scfg,$volname,$snapshot);
  111. my $base = $class->zfs_get_base($scfg);
  112. my $object = ($zvol =~ /^.+\/.+/) ? "$base/$snapTmpVol" : "$base/$scfg->{pool}/$snapTmpVol";
  113. my $lu_name = $class->zfs_request($scfg, undef, 'list_lu', $object);
  114. if ($lu_name) {
  115. return $snapTmpVol;
  116. }
  117. # create the clone if not present
  118. my $cloneExists = $class->zfs_volume_exists($scfg,"$scfg->{pool}/$snapTmpVol");
  119. if(!$cloneExists) {
  120. $class->zfs_request($scfg, undef,
  121. 'clone', $srcVol, "$scfg->{pool}/$snapTmpVol"
  122. );
  123. }
  124. # and add it to the iscsi-target
  125. my $guid = $class->zfs_create_lu($scfg, $snapTmpVol);
  126. $class->zfs_add_lun_mapping_entry($scfg, $snapTmpVol, $guid);
  127. return $snapTmpVol;
  128. }
  129. #
  130. # removes lun and zvol for snapshot of volname
  131. #
  132. sub dematerialize_snapshot {
  133. my ($class, $scfg, $volname, $snapname) = @_;
  134. my $snapTmpVol = $class->get_snapshot_tmp_name($scfg,$volname,$snapname);
  135. # remove iscsi-target if present
  136. my $base = $class->zfs_get_base($scfg);
  137. my $object = ($zvol =~ /^.+\/.+/) ? "$base/$snapTmpVol" : "$base/$scfg->{pool}/$snapTmpVol";
  138. my $lu_name = $class->zfs_request($scfg, undef, 'list_lu', $object);
  139. if ($lu_name) {
  140. $class->free_image(undef, $scfg, $snapTmpVol, undef);
  141. }
  142. # destroy clone
  143. $class->zfs_request($scfg,undef,
  144. 'destroy', "$scfg->{pool}/$snapTmpVol"
  145. );
  146. return 1;
  147. }
  148. #
  149. # Create a copy of snapshot from srcVol and provide as dstVol
  150. #
  151. sub volume_snapshot_copy {
  152. my($class, $scfg, $srcVol, $snapshot, $dstVol) = @_;
  153. my $materializedPath = $class->materialize_snapshot($scfg, $srcVol, $snapshot);
  154. # remove iscsi-target of tmp-snap when present
  155. my $src_guid = $class->zfs_get_lu_name($scfg, $materializedPath);
  156. if( $src_guid ) {
  157. $class->zfs_delete_lu($scfg, $materializedPath);
  158. }
  159. # free the image in case it is exported
  160. my $dst_guid = $class->zfs_get_lu_name($scfg, $dstVol);
  161. if( $dst_guid ) {
  162. $class->zfs_delete_lu($scfg, $dstVol);
  163. }
  164. # zfs-destroy a present volume
  165. $class->zfs_request($scfg, undef,
  166. 'destroy','-r',"$scfg->{pool}/$dstVol"
  167. );
  168. # create current snap on clone
  169. $class->zfs_request($scfg,undef,
  170. 'snapshot',"$scfg->{pool}/$materializedPath\@now"
  171. );
  172. # zfs send/rcv from snap of clone
  173. $class->zfs_request($scfg, 86400,
  174. "send", '-v', "$scfg->{pool}/$materializedPath\@now", '|',
  175. 'zfs', 'recv', '-F', "$scfg->{pool}/$dstVol"
  176. );
  177. # rollback to @now on dstVol
  178. $class->zfs_request($scfg, undef,
  179. 'rollback', "$scfg->{pool}/$dstVol\@now"
  180. );
  181. # destroy snap @now on dstVol
  182. $class->zfs_request($scfg,undef,
  183. 'destroy', "$scfg->{pool}/$dstVol\@now"
  184. );
  185. # destroy tmp snap @now on clone
  186. $class->zfs_request($scfg,undef,
  187. 'destroy', "$scfg->{pool}/$materializedPath\@now"
  188. );
  189. # attach iscsi-target for dstVol
  190. my ($vtype, $dst_name, $vmid) = $class->parse_volname($dstVol);
  191. my $dst_guid = $class->zfs_create_lu($scfg, $dst_name);
  192. $class->zfs_add_lun_mapping_entry($scfg, $dst_name, $dst_guid);
  193. }
  194. sub volume_copy {
  195. my($class, $scfg, $srcVol, $dstVol) = @_;
  196. my $snap = 'volume-copy-base-tmp';
  197. # test if tmp-snapshot exists and should be deleted
  198. if ( $class->volume_has_snapshot($scfg, "$scfg->{pool}/$srcVol", $snap) ) {
  199. $class->zfs_request($scfg, undef, 'destroy','-R', "$scfg->{pool}/$srcVol\@$snap");
  200. }
  201. # create a tmp snap from base
  202. $class->zfs_request($scfg, undef, 'snapshot', "$scfg->{pool}/$srcVol\@$snap");
  203. # volume_snapshot_copy
  204. $class->volume_snapshot_copy($scfg,$srcVol,$snap,$dstVol);
  205. # remove the tmp snapshot, including the clone (-R)
  206. $class->zfs_request($scfg, undef, 'destroy','-R', "$scfg->{pool}/$srcVol\@$snap");
  207. }
  208. #
  209. # Test if snapshot exists in volume
  210. #
  211. sub volume_has_snapshot {
  212. my ($class, $scfg, $volname, $snapname) = @_;
  213. my $fullSnapName = "$volname\@$snapname";
  214. my @params = ('-t', 'snapshot', '-o', 'name', '-s', 'creation');
  215. my $text = $class->zfs_request($scfg, undef, 'list', @params);
  216. my @snapshots = split(/\n/, $text);
  217. foreach (@snapshots) {
  218. if( $_ eq $fullSnapName) {
  219. return 1;
  220. }
  221. }
  222. }
  223. #
  224. # test if a zvol exists
  225. #
  226. sub zfs_volume_exists {
  227. my ($class, $scfg, $volname) = @_;
  228. my $volumes_text = $class->zfs_request($scfg, 10,
  229. 'list', '-o', 'name', '-t', 'volume', '-Hr'
  230. );
  231. my @lines = split /\n/, $volumes_text;
  232. foreach my $zvol (@lines) {
  233. return 1 if $zvol eq $volname;
  234. }
  235. }
  236. #
  237. # creates tmp-name for zvol a snapshot will be / has been created for volname
  238. #
  239. sub get_snapshot_tmp_name {
  240. my ($class, $scfg, $volname, $snapshot) = @_;
  241. return $volname.'-clone-at-'.$snapshot;
  242. }
  243. #
  244. # returns zvol name of snapshot for volname
  245. #
  246. sub get_snapshot_path {
  247. my ($class, $scfg, $volname, $snapshot) = @_;
  248. return "$scfg->{pool}/$volname\@$snapshot";
  249. }
  250. # Other parts of Storage implementation is identical to ZFSPlugin and therefore not changed
  251. 1;