X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FStorage%2FPlugin.pm;h=e075a1dee3becab190ba818656ad93bca0d20ec0;hb=c2fc9fbe1d6eaa6e190e4c80248d3434cb2f4f87;hp=7bde330c6a0a96a4e0c6fa234ccf0299ebb576f2;hpb=e1f6cb39047226a4ea1a48f438854d1105a61bbe;p=pve-storage.git diff --git a/PVE/Storage/Plugin.pm b/PVE/Storage/Plugin.pm index 7bde330..e075a1d 100644 --- a/PVE/Storage/Plugin.pm +++ b/PVE/Storage/Plugin.pm @@ -2,16 +2,37 @@ package PVE::Storage::Plugin; use strict; use warnings; + use File::chdir; use File::Path; +use File::Basename; + use PVE::Tools qw(run_command); use PVE::JSONSchema qw(get_standard_option); use PVE::Cluster qw(cfs_register_file); -use Data::Dumper; - use base qw(PVE::SectionConfig); +our @COMMON_TAR_FLAGS = qw( + --one-file-system + -p --sparse --numeric-owner --acls + --xattrs --xattrs-include=user.* --xattrs-include=security.capability + --warning=no-file-ignored --warning=no-xattr-write +); + +our @SHARED_STORAGE = ( + 'iscsi', + 'nfs', + 'cifs', + 'rbd', + 'cephfs', + 'iscsidirect', + 'glusterfs', + 'zfs', + 'drbd'); + +our $MAX_VOLUMES_PER_GUEST = 1024; + cfs_register_file ('storage.cfg', sub { __PACKAGE__->parse_config(@_); }, sub { __PACKAGE__->write_config(@_); }); @@ -305,7 +326,7 @@ sub parse_config { priority => 0, # force first entry path => '/var/lib/vz', maxfiles => 0, - content => { images => 1, rootdir => 1, vztmpl => 1, iso => 1}, + content => { images => 1, rootdir => 1, vztmpl => 1, iso => 1, snippets => 1}, }; } @@ -324,8 +345,7 @@ sub parse_config { if ($def->{content}) { $d->{content} = $def->{content}->[1] if !$d->{content}; } - - if ($type eq 'iscsi' || $type eq 'nfs' || $type eq 'rbd' || $type eq 'sheepdog' || $type eq 'iscsidirect' || $type eq 'glusterfs' || $type eq 'zfs' || $type eq 'drbd') { + if (grep { $_ eq $type } @SHARED_STORAGE) { $d->{shared} = 1; } } @@ -335,6 +355,26 @@ sub parse_config { # Storage implementation +# called during addition of storage (before the new storage config got written) +# die to abort additon if there are (grave) problems +# NOTE: runs in a storage config *locked* context +sub on_add_hook { + my ($class, $storeid, $scfg, %param) = @_; + + # do nothing by default +} + +# called during deletion of storage (before the new storage config got written) +# and if the activate check on addition fails, to cleanup all storage traces +# which on_add_hook may have created. +# die to abort deletion if there are (very grave) problems +# NOTE: runs in a storage config *locked* context +sub on_delete_hook { + my ($class, $storeid, $scfg) = @_; + + # do nothing by default +} + sub cluster_lock_storage { my ($class, $storeid, $shared, $timeout, $func, @param) = @_; @@ -387,6 +427,8 @@ sub parse_volname { return ('backup', $fn, $2); } return ('backup', $fn); + } elsif ($volname =~ m!^snippets/([^/]+)$!) { + return ('snippets', $1); } die "unable to parse directory volume name '$volname'\n"; @@ -398,6 +440,7 @@ my $vtype_subdirs = { iso => 'template/iso', vztmpl => 'template/cache', backup => 'dump', + snippets => 'snippets', }; sub get_subdir { @@ -485,24 +528,59 @@ sub create_base { return $newvolname; } -my $find_free_diskname = sub { - my ($imgdir, $vmid, $fmt) = @_; +my $get_vm_disk_number = sub { + my ($disk_name, $scfg, $vmid, $suffix) = @_; + + my $disk_regex = qr/(vm|base)-$vmid-disk-(\d+)$suffix/; + + my $type = $scfg->{type}; + my $def = { %{$defaultData->{plugindata}->{$type}} }; + + my $valid = $def->{format}[0]; + if ($valid->{subvol}) { + $disk_regex = qr/(vm|base|subvol|basevol)-$vmid-disk-(\d+)/; + } + + if ($disk_name =~ m/$disk_regex/) { + return $2; + } + + return undef; +}; + +sub get_next_vm_diskname { + my ($disk_list, $storeid, $vmid, $fmt, $scfg, $add_fmt_suffix) = @_; + + $fmt //= ''; + my $prefix = ($fmt eq 'subvol') ? 'subvol' : 'vm'; + my $suffix = $add_fmt_suffix ? ".$fmt" : ''; my $disk_ids = {}; - PVE::Tools::dir_glob_foreach($imgdir, - qr!(vm|base)-$vmid-disk-(\d+)\..*!, - sub { - my ($fn, $type, $disk) = @_; - $disk_ids->{$disk} = 1; - }); - - for (my $i = 1; $i < 100; $i++) { + foreach my $disk (@$disk_list) { + my $disknum = $get_vm_disk_number->($disk, $scfg, $vmid, $suffix); + $disk_ids->{$disknum} = 1 if defined($disknum); + } + + for (my $i = 0; $i < $MAX_VOLUMES_PER_GUEST; $i++) { if (!$disk_ids->{$i}) { - return "vm-$vmid-disk-$i.$fmt"; + return "$prefix-$vmid-disk-$i$suffix"; } } - die "unable to allocate a new image name for VM $vmid in '$imgdir'\n"; + die "unable to allocate an image name for VM $vmid in storage '$storeid'\n" +} + +my $find_free_diskname = sub { + my ($imgdir, $vmid, $fmt, $scfg) = @_; + + my $disk_list = []; + + if (defined(my $dh = IO::Dir->new($imgdir))) { + @$disk_list = $dh->read(); + $dh->close(); + } + + return get_next_vm_diskname($disk_list, $imgdir, $vmid, $fmt, $scfg, 1); }; sub clone_image { @@ -527,7 +605,7 @@ sub clone_image { mkpath $imagedir; - my $name = &$find_free_diskname($imagedir, $vmid, "qcow2"); + my $name = $find_free_diskname->($imagedir, $vmid, "qcow2", $scfg); warn "clone $volname: $vtype, $name, $vmid to $name (base=../$basevmid/$basename)\n"; @@ -559,7 +637,7 @@ sub alloc_image { mkpath $imagedir; - $name = &$find_free_diskname($imagedir, $vmid, $fmt) if !$name; + $name = $find_free_diskname->($imagedir, $vmid, $fmt, $scfg) if !$name; my (undef, $tmpfmt) = parse_name_dir($name); @@ -586,7 +664,12 @@ sub alloc_image { push @$cmd, '-f', $fmt, $path, "${size}K"; - run_command($cmd, errmsg => "unable to create image"); + eval { run_command($cmd, errmsg => "unable to create image"); }; + if ($@) { + unlink $path; + rmdir $imagedir; + die "$@"; + } } return "$vmid/$name"; @@ -606,14 +689,18 @@ sub free_image { if (defined($format) && ($format eq 'subvol')) { File::Path::remove_tree($path); } else { - - if (! -f $path) { + if (!(-f $path || -l $path)) { warn "disk image '$path' does not exists\n"; return undef; } unlink($path) || die "unlink '$path' failed - $!\n"; } + + # try to cleanup directory to not clutter storage with empty $vmid dirs if + # all images from a guest got deleted + my $dir = dirname($path); + rmdir($dir); return undef; } @@ -735,6 +822,12 @@ sub volume_snapshot_delete { return undef; } +sub storage_can_replicate { + my ($class, $scfg, $storeid, $format) = @_; + + return 0; +} + sub volume_has_feature { my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_; @@ -809,6 +902,83 @@ sub list_images { return $res; } +# list templates ($tt = ) +my $get_subdir_files = sub { + my ($sid, $path, $tt, $vmid) = @_; + + my $res = []; + + foreach my $fn (<$path/*>) { + + next if -d $fn; + + my $info; + + if ($tt eq 'iso') { + next if $fn !~ m!/([^/]+\.iso)$!i; + + $info = { volid => "$sid:iso/$1", format => 'iso' }; + + } elsif ($tt eq 'vztmpl') { + next if $fn !~ m!/([^/]+\.tar\.([gx]z))$!; + + $info = { volid => "$sid:vztmpl/$1", format => "t$2" }; + + } elsif ($tt eq 'backup') { + next if $fn !~ m!/([^/]+\.(tar|tar\.gz|tar\.lzo|tgz|vma|vma\.gz|vma\.lzo))$!; + next if defined($vmid) && $fn !~ m/\S+-$vmid-\S+/; + + $info = { volid => "$sid:backup/$1", format => $2 }; + + } elsif ($tt eq 'snippets') { + + $info = { + volid => "$sid:snippets/". basename($fn), + format => 'snippet', + }; + } + + $info->{size} = -s $fn // 0; + + push @$res, $info; + } + + return $res; +}; + +sub list_volumes { + my ($class, $storeid, $scfg, $vmid, $content_types) = @_; + + my $res = []; + + foreach my $ct (@$content_types) { + my $data; + + my $path = $class->get_subdir($scfg, $ct); + + if ($ct eq 'images') { + $data = $class->list_images($storeid, $scfg, $vmid); + } elsif ($ct eq 'iso' && !defined($vmid)) { + $data = $get_subdir_files->($storeid, $path, 'iso'); + } elsif ($ct eq 'vztmpl'&& !defined($vmid)) { + $data = $get_subdir_files->($storeid, $path, 'vztmpl'); + } elsif ($ct eq 'backup') { + $data = $get_subdir_files->($storeid, $path, 'backup', $vmid); + } elsif ($ct eq 'snippets') { + $data = $get_subdir_files->($storeid, $path, 'snippets'); + } + + next if !$data; + + foreach my $item (@$data) { + $item->{content} = $ct; + push @$res, $item; + } + } + + return $res; +} + sub status { my ($class, $storeid, $scfg, $cache) = @_; @@ -840,8 +1010,13 @@ sub activate_storage { die "storage definintion has no path\n" if !$path; - die "unable to activate storage '$storeid' - " . - "directory '$path' does not exist\n" if ! -d $path; + # this path test may hang indefinitely on unresponsive mounts + my $timeout = 2; + if (! PVE::Tools::run_fork_with_timeout($timeout, sub {-d $path})) { + die "unable to activate storage '$storeid' - " . + "directory '$path' does not exist or is unreachable\n"; + } + return if defined($scfg->{mkdir}) && !$scfg->{mkdir}; @@ -863,6 +1038,19 @@ sub deactivate_storage { # do nothing by default } +sub map_volume { + my ($class, $storeid, $scfg, $volname, $snapname) = @_; + + my ($path) = $class->path($scfg, $volname, $storeid, $snapname); + return $path; +} + +sub unmap_volume { + my ($class, $storeid, $scfg, $volname, $snapname) = @_; + + return 1; +} + sub activate_volume { my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_; @@ -909,8 +1097,11 @@ sub check_connection { # files which are already in qcow2 format, or via `qemu-img convert`. # Note that these formats are only valid with $with_snapshots being true. # tar+size: (subvolumes only) -# A GNU tar stream with the inner contents of the subvolume put into the -# 'subvol/' directory. +# A GNU tar stream containing just the inner contents of the subvolume. +# This does not distinguish between the contents of a privileged or +# unprivileged container. In other words, this is from the root user +# namespace's point of view with no uid-mapping in effect. +# As produced via `tar -C vm-100-disk-1.subvol -cpf TheOutputFile.dat .` # Plugins may reuse these helpers. Changes to the header format should be # reflected by changes to the function prototypes. @@ -955,7 +1146,7 @@ sub volume_export { } elsif ($format eq 'tar+size') { goto unsupported if $file_format ne 'subvol'; write_common_header($fh, $size); - run_command(['tar', '--xform=s,^\./,subvol/,S', '-cf', '-', '-C', $file, '.'], + run_command(['tar', @COMMON_TAR_FLAGS, '-cf', '-', '-C', $file, '.'], output => '>&'.fileno($fh)); return; } @@ -1023,7 +1214,7 @@ sub volume_import { run_command(['dd', "of=$file", 'conv=sparse', 'bs=64k'], input => '<&'.fileno($fh)); } elsif ($data_format eq 'tar') { - run_command(['tar', '-C', $file, '--xform=s,^subvol/,./,S', '-xf', '-'], + run_command(['tar', @COMMON_TAR_FLAGS, '-C', $file, '-xf', '-'], input => '<&'.fileno($fh)); } else { die "volume import format '$format' not available for $class";