package PVE::Storage::Custom::Shared::ZFSPluginPlus; use PVE::Storage::Custom::LunCmd::SCST; use Data::Dumper; use PVE::Tools qw(run_command); # inherit on the ZFSPlugin #use base qw(PVE::Storage::ZFSPlugin); @ISA = qw(PVE::Storage::ZFSPlugin); my @ssh_opts = ('-o', 'BatchMode=yes'); my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts); my $id_rsa_path = '/etc/pve/priv/zfs'; # plugin configuration # we want to allow copy of snap, currently dies at line 377 in ZFSPlugin.pm sub volume_has_feature { my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_; my $features = { snapshot => { current => 1, snap => 1}, clone => { base => 1}, template => { current => 1}, copy => { base => 1, current => 1, snap=>1}, #added snap }; my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname); my $key = undef; if ($snapname) { $key = 'snap'; } else { $key = $isBase ? 'base' : 'current'; } return 1 if $features->{$feature}->{$key}; return undef; } # # + added port, chap-auth and transport (iser) # + allow access to materialized snapshots # sub path { my ($class, $scfg, $volname, $storeid, $snapname) = @_; # die "direct access to snapshots not implemented" # if defined($snapname); my ($vtype, $name, $vmid) = $class->parse_volname($volname); if(defined($snapname)) { $name = materialize_snapshot($class,$scfg,$volname,$snapname); } my $target = $scfg->{target}; my $portal = $scfg->{portal}; my $guid = $class->zfs_get_lu_name($scfg, $name); my $lun = $class->zfs_get_lun_number($scfg, $guid); my $transport = $scfg->{transport}; if($transport eq 'rdma') { $transport = 'iser'; } else { $transport = 'iscsi'; } my $authString = $scfg->{chap_user} && $scfg->{chap_pass} ? "$scfg->{chap_user}%$scfg->{chap_pass}@" : ''; my $port = $scfg->{port} ? ":$scfg->{port}" : ''; my $path = "$transport://$authString$portal$port/$target/$lun"; return ($path, $vmid, $vtype); } my $zfs_get_base = sub { my ($scfg) = @_; if ($scfg->{iscsiprovider} eq 'comstar') { return PVE::Storage::LunCmd::Comstar::get_base; } elsif ($scfg->{iscsiprovider} eq 'istgt') { return PVE::Storage::LunCmd::Istgt::get_base; } elsif ($scfg->{iscsiprovider} eq 'iet') { return PVE::Storage::LunCmd::Iet::get_base; } elsif ($scfg->{iscsiprovider} eq 'scstzfs') { return PVE::Storage::Custom::LunCmd::SCST::get_base; } elsif ($scfg->{iscsiprovider} eq 'freenas') { return PVE::Storage::Custom::LunCmd::FreeNas::get_base; } else { # $zfs_unknown_scsi_provider->($scfg->{iscsiprovider}); } }; sub zfs_get_lu_name { my ($class, $scfg, $zvol) = @_; my $base = $zfs_get_base->($scfg); my $object = ($zvol =~ /^.+\/.+/) ? "$base/$zvol" : "$base/$scfg->{pool}/$zvol"; my $lu_name = $class->zfs_request($scfg, undef, 'list_lu', $object); return $lu_name if $lu_name; die "Could not find lu_name for zvol $zvol base $base object $object"; } # # Allow Activation of Snap via ZFS send/receive # sub activate_volume { my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_; if($snapname) { materialize_snapshot($class,$scfg,$volname,$snapname); } return 1; } # # Allow Deactivation of Snap-Volume # sub deactivate_volume { my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_; if($snapname) { $class->dematerialize_snapshot($scfg,$volname,$snapname); } return 1; } # # materializes a snapshot of volname by zfs send/revc and creates an iscsi LUN # sub materialize_snapshot { my ($class, $scfg, $volname, $snapshot) = @_; my $srcVol = get_snapshot_path($class,$scfg,$volname,$snapshot); my $snapTmpVol = get_snapshot_tmp_name($class,$scfg,$volname,$snapshot); my $base = $class->zfs_get_base($scfg); my $object = ($zvol =~ /^.+\/.+/) ? "$base/$snapTmpVol" : "$base/$scfg->{pool}/$snapTmpVol"; my $lu_name = $class->zfs_request($scfg, undef, 'list_lu', $object); if ($lu_name) { return $snapTmpVol; } # create the clone if not present my $cloneExists = $class->zfs_volume_exists($scfg,"$scfg->{pool}/$snapTmpVol"); if(!$cloneExists) { $class->zfs_request($scfg, undef, 'clone', $srcVol, "$scfg->{pool}/$snapTmpVol" ); } # and add it to the iscsi-target my $guid = $class->zfs_create_lu($scfg, $snapTmpVol); $class->zfs_add_lun_mapping_entry($scfg, $snapTmpVol, $guid); return $snapTmpVol; } # # removes lun and zvol for snapshot of volname # sub dematerialize_snapshot { my ($class, $scfg, $volname, $snapname) = @_; my $snapTmpVol = $class->get_snapshot_tmp_name($scfg,$volname,$snapname); # remove iscsi-target if present my $base = $class->zfs_get_base($scfg); my $object = ($zvol =~ /^.+\/.+/) ? "$base/$snapTmpVol" : "$base/$scfg->{pool}/$snapTmpVol"; my $lu_name = $class->zfs_request($scfg, undef, 'list_lu', $object); if ($lu_name) { $class->free_image(undef, $scfg, $snapTmpVol, undef); } # destroy clone $class->zfs_request($scfg,undef, 'destroy', "$scfg->{pool}/$snapTmpVol" ); return 1; } # # Create a copy of snapshot from srcVol and provide as dstVol # sub volume_snapshot_copy { my($class, $scfg, $srcVol, $snapshot, $dstVol) = @_; my $materializedPath = $class->materialize_snapshot($scfg, $srcVol, $snapshot); # remove iscsi-target of tmp-snap when present my $src_guid = $class->zfs_get_lu_name($scfg, $materializedPath); if( $src_guid ) { $class->zfs_delete_lu($scfg, $materializedPath); } # free the image in case it is exported my $dst_guid = $class->zfs_get_lu_name($scfg, $dstVol); if( $dst_guid ) { $class->zfs_delete_lu($scfg, $dstVol); } # zfs-destroy a present volume $class->zfs_request($scfg, undef, 'destroy','-r',"$scfg->{pool}/$dstVol" ); # create current snap on clone $class->zfs_request($scfg,undef, 'snapshot',"$scfg->{pool}/$materializedPath\@now" ); # zfs send/rcv from snap of clone $class->zfs_request($scfg, 86400, "send", '-v', "$scfg->{pool}/$materializedPath\@now", '|', 'zfs', 'recv', '-F', "$scfg->{pool}/$dstVol" ); # rollback to @now on dstVol $class->zfs_request($scfg, undef, 'rollback', "$scfg->{pool}/$dstVol\@now" ); # destroy snap @now on dstVol $class->zfs_request($scfg,undef, 'destroy', "$scfg->{pool}/$dstVol\@now" ); # destroy tmp snap @now on clone $class->zfs_request($scfg,undef, 'destroy', "$scfg->{pool}/$materializedPath\@now" ); # attach iscsi-target for dstVol my ($vtype, $dst_name, $vmid) = $class->parse_volname($dstVol); my $dst_guid = $class->zfs_create_lu($scfg, $dst_name); $class->zfs_add_lun_mapping_entry($scfg, $dst_name, $dst_guid); } sub volume_copy { my($class, $scfg, $srcVol, $dstVol) = @_; my $snap = 'volume-copy-base-tmp'; # test if tmp-snapshot exists and should be deleted if ( $class->volume_has_snapshot($scfg, "$scfg->{pool}/$srcVol", $snap) ) { $class->zfs_request($scfg, undef, 'destroy','-R', "$scfg->{pool}/$srcVol\@$snap"); } # create a tmp snap from base $class->zfs_request($scfg, undef, 'snapshot', "$scfg->{pool}/$srcVol\@$snap"); # volume_snapshot_copy $class->volume_snapshot_copy($scfg,$srcVol,$snap,$dstVol); # remove the tmp snapshot, including the clone (-R) $class->zfs_request($scfg, undef, 'destroy','-R', "$scfg->{pool}/$srcVol\@$snap"); } # # Test if snapshot exists in volume # sub volume_has_snapshot { my ($class, $scfg, $volname, $snapname) = @_; my $fullSnapName = "$volname\@$snapname"; my @params = ('-t', 'snapshot', '-o', 'name', '-s', 'creation'); my $text = $class->zfs_request($scfg, undef, 'list', @params); my @snapshots = split(/\n/, $text); foreach (@snapshots) { if( $_ eq $fullSnapName) { return 1; } } } # # test if a zvol exists # sub zfs_volume_exists { my ($class, $scfg, $volname) = @_; my $volumes_text = $class->zfs_request($scfg, 10, 'list', '-o', 'name', '-t', 'volume', '-Hr' ); my @lines = split /\n/, $volumes_text; foreach my $zvol (@lines) { return 1 if $zvol eq $volname; } } # # creates tmp-name for zvol a snapshot will be / has been created for volname # sub get_snapshot_tmp_name { my ($class, $scfg, $volname, $snapshot) = @_; return $volname.'-clone-at-'.$snapshot; } # # returns zvol name of snapshot for volname # sub get_snapshot_path { my ($class, $scfg, $volname, $snapshot) = @_; return "$scfg->{pool}/$volname\@$snapshot"; } # Other parts of Storage implementation is identical to ZFSPlugin and therefore not changed 1;