X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FStorage.pm;h=a36842bc19fc0c6f09b4128ca8b2dd3a28e2f509;hb=af50c2e67101c8242da5a9837387c6e6a13510b6;hp=bd6e15e9f1f9f05dec9a6cbcd88391705ff08212;hpb=343ca2570c3972f0fa1086b020bc9ab731f27b11;p=pve-storage.git diff --git a/PVE/Storage.pm b/PVE/Storage.pm index bd6e15e..a36842b 100755 --- a/PVE/Storage.pm +++ b/PVE/Storage.pm @@ -37,8 +37,8 @@ use PVE::Storage::ISCSIDirectPlugin; use PVE::Storage::GlusterfsPlugin; use PVE::Storage::ZFSPoolPlugin; use PVE::Storage::ZFSPlugin; -use PVE::Storage::DRBDPlugin; use PVE::Storage::PBSPlugin; +use PVE::Storage::BTRFSPlugin; # Storage API version. Increment it on changes in storage API interface. use constant APIVER => 8; @@ -60,8 +60,8 @@ PVE::Storage::ISCSIDirectPlugin->register(); PVE::Storage::GlusterfsPlugin->register(); PVE::Storage::ZFSPoolPlugin->register(); PVE::Storage::ZFSPlugin->register(); -PVE::Storage::DRBDPlugin->register(); PVE::Storage::PBSPlugin->register(); +PVE::Storage::BTRFSPlugin->register(); # load third-party plugins if ( -d '/usr/share/perl5/PVE/Storage/Custom' ) { @@ -75,10 +75,8 @@ if ( -d '/usr/share/perl5/PVE/Storage/Custom' ) { require $file; # Check perl interface: - die "not derived from PVE::Storage::Plugin\n" - if !$modname->isa('PVE::Storage::Plugin'); - die "does not provide an api() method\n" - if !$modname->can('api'); + die "not derived from PVE::Storage::Plugin\n" if !$modname->isa('PVE::Storage::Plugin'); + die "does not provide an api() method\n" if !$modname->can('api'); # Check storage API version and that file is really storage plugin. my $version = $modname->api(); die "implements an API version newer than current ($version > " . APIVER . ")\n" @@ -86,11 +84,11 @@ if ( -d '/usr/share/perl5/PVE/Storage/Custom' ) { my $min_version = (APIVER - APIAGE); die "API version too old, please update the plugin ($version < $min_version)\n" if $version < $min_version; + # all OK, do import and register (i.e., "use") import $file; $modname->register(); - # If we got this far and the API version is not the same, make some - # noise: + # If we got this far and the API version is not the same, make some noise: warn "Plugin \"$modname\" is implementing an older storage API, an upgrade is recommended\n" if $version != APIVER; }; @@ -103,10 +101,10 @@ if ( -d '/usr/share/perl5/PVE/Storage/Custom' ) { # initialize all plugins PVE::Storage::Plugin->init(); -my $UDEVADM = '/sbin/udevadm'; - our $iso_extension_re = qr/\.(?:iso|img)/i; +our $vztmpl_extension_re = qr/\.tar\.([gx]z)/i; + # PVE::Storage utility functions sub config { @@ -129,6 +127,28 @@ sub lock_storage_config { } } +# FIXME remove maxfiles for PVE 8.0 or PVE 9.0 +my $convert_maxfiles_to_prune_backups = sub { + my ($scfg) = @_; + + return if !$scfg; + + my $maxfiles = delete $scfg->{maxfiles}; + + if (!defined($scfg->{'prune-backups'}) && defined($maxfiles)) { + my $prune_backups; + if ($maxfiles) { + $prune_backups = { 'keep-last' => $maxfiles }; + } else { # maxfiles 0 means no limit + $prune_backups = { 'keep-all' => 1 }; + } + $scfg->{'prune-backups'} = PVE::JSONSchema::print_property_string( + $prune_backups, + 'prune-backups' + ); + } +}; + sub storage_config { my ($cfg, $storeid, $noerr) = @_; @@ -138,6 +158,8 @@ sub storage_config { die "storage '$storeid' does not exist\n" if (!$noerr && !$scfg); + $convert_maxfiles_to_prune_backups->($scfg); + return $scfg; } @@ -172,7 +194,7 @@ sub storage_check_enabled { # storage_can_replicate: # return true if storage supports replication -# (volumes alocated with vdisk_alloc() has replication feature) +# (volumes allocated with vdisk_alloc() has replication feature) sub storage_can_replicate { my ($cfg, $storeid, $format) = @_; @@ -193,6 +215,26 @@ sub file_size_info { return PVE::Storage::Plugin::file_size_info($filename, $timeout); } +sub get_volume_notes { + my ($cfg, $volid, $timeout) = @_; + + my ($storeid, $volname) = parse_volume_id($volid); + my $scfg = storage_config($cfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); + + return $plugin->get_volume_notes($scfg, $storeid, $volname, $timeout); +} + +sub update_volume_notes { + my ($cfg, $volid, $notes, $timeout) = @_; + + my ($storeid, $volname) = parse_volume_id($volid); + my $scfg = storage_config($cfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); + + $plugin->update_volume_notes($scfg, $storeid, $volname, $notes, $timeout); +} + sub volume_size_info { my ($cfg, $volid, $timeout) = @_; @@ -454,8 +496,15 @@ sub check_volume_access { return undef; } -my $volume_is_base_and_used__no_lock = sub { - my ($scfg, $storeid, $plugin, $volname) = @_; +# NOTE: this check does not work for LVM-thin, where the clone -> base +# reference is not encoded in the volume ID. +# see note in PVE::Storage::LvmThinPlugin for details. +sub volume_is_base_and_used { + my ($cfg, $volid) = @_; + + my ($storeid, $volname) = parse_volume_id($volid); + my $scfg = storage_config($cfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); my ($vtype, $name, $vmid, undef, undef, $isBase, undef) = $plugin->parse_volname($volname); @@ -478,21 +527,6 @@ my $volume_is_base_and_used__no_lock = sub { } } return 0; -}; - -# NOTE: this check does not work for LVM-thin, where the clone -> base -# reference is not encoded in the volume ID. -# see note in PVE::Storage::LvmThinPlugin for details. -sub volume_is_base_and_used { - my ($cfg, $volid) = @_; - - my ($storeid, $volname) = parse_volume_id($volid); - my $scfg = storage_config($cfg, $storeid); - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); - - $plugin->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub { - return &$volume_is_base_and_used__no_lock($scfg, $storeid, $plugin, $volname); - }); } # try to map a filesystem path to a volume identifier @@ -543,7 +577,7 @@ sub path_to_volume_id { } elsif ($path =~ m!^$isodir/([^/]+$iso_extension_re)$!) { my $name = $1; return ('iso', "$sid:iso/$name"); - } elsif ($path =~ m!^$tmpldir/([^/]+\.tar\.gz)$!) { + } elsif ($path =~ m!^$tmpldir/([^/]+$vztmpl_extension_re)$!) { my $name = $1; return ('vztmpl', "$sid:vztmpl/$name"); } elsif ($path =~ m!^$privatedir/(\d+)$!) { @@ -575,22 +609,22 @@ sub path { } sub abs_filesystem_path { - my ($cfg, $volid) = @_; + my ($cfg, $volid, $allow_blockdev) = @_; my $path; if (parse_volume_id ($volid, 1)) { activate_volumes($cfg, [ $volid ]); $path = PVE::Storage::path($cfg, $volid); } else { - if (-f $volid) { + if (-f $volid || ($allow_blockdev && -b $volid)) { my $abspath = abs_path($volid); if ($abspath && $abspath =~ m|^(/.+)$|) { $path = $1; # untaint any path } } } - - die "can't find file '$volid'\n" if !($path && -f $path); + die "can't find file '$volid'\n" + if !($path && (-f $path || ($allow_blockdev && -b $path))); return $path; } @@ -724,8 +758,14 @@ sub storage_migrate { or die "receive command failed: $!\n"; close($input); - my ($ip) = <$info> =~ /^($PVE::Tools::IPRE)$/ or die "no tunnel IP received\n"; - my ($port) = <$info> =~ /^(\d+)$/ or die "no tunnel port received\n"; + my $try_ip = <$info> // ''; + my ($ip) = $try_ip =~ /^($PVE::Tools::IPRE)$/ # untaint + or die "no tunnel IP received, got '$try_ip'\n"; + + my $try_port = <$info> // ''; + my ($port) = $try_port =~ /^(\d+)$/ # untaint + or die "no tunnel port received, got '$try_port'\n"; + my $socket = IO::Socket::IP->new(PeerHost => $ip, PeerPort => $port, Type => SOCK_STREAM) or die "failed to connect to tunnel at $ip:$port\n"; # we won't be reading from the socket @@ -876,7 +916,7 @@ sub vdisk_free { $plugin->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub { # LVM-thin allows deletion of still referenced base volumes! die "base volume '$volname' is still in use by linked clones\n" - if &$volume_is_base_and_used__no_lock($scfg, $storeid, $plugin, $volname); + if volume_is_base_and_used($cfg, $volid); my (undef, undef, undef, undef, undef, $isBase, $format) = $plugin->parse_volname($volname); @@ -892,13 +932,13 @@ sub vdisk_free { } sub vdisk_list { - my ($cfg, $storeid, $vmid, $vollist) = @_; + my ($cfg, $storeid, $vmid, $vollist, $ctype) = @_; my $ids = $cfg->{ids}; storage_check_enabled($cfg, $storeid) if ($storeid); - my $res = {}; + my $res = $storeid ? { $storeid => [] } : {}; # prepare/activate/refresh all storages @@ -915,6 +955,7 @@ sub vdisk_list { next if $storeid && $storeid ne $sid; next if !storage_check_enabled($cfg, $sid, undef, 1); my $content = $ids->{$sid}->{content}; + next if defined($ctype) && !$content->{$ctype}; next if !($content->{rootdir} || $content->{images}); push @$storage_list, $sid; } @@ -924,9 +965,8 @@ sub vdisk_list { activate_storage_list($cfg, $storage_list, $cache); - foreach my $sid (keys %$ids) { + for my $sid ($storage_list->@*) { next if $storeid && $storeid ne $sid; - next if !storage_check_enabled($cfg, $sid, undef, 1); my $scfg = $ids->{$sid}; my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); @@ -1031,8 +1071,7 @@ sub activate_storage { # only call udevsettle if there are events if ($newseq > $cache->{uevent_seqnum}) { - my $timeout = 30; - system ("$UDEVADM settle --timeout=$timeout"); # ignore errors + system ("udevadm settle --timeout=30"); # ignore errors $cache->{uevent_seqnum} = $newseq; } @@ -1411,7 +1450,8 @@ sub decompressor_info { die "ERROR: archive format not defined\n" if !defined($decompressor->{$format}); - my $decomp = $decompressor->{$format}->{$comp} if $comp; + my $decomp; + $decomp = $decompressor->{$format}->{$comp} if $comp; my $info = { format => $format, @@ -1586,6 +1626,8 @@ sub prune_backups { $keep = PVE::JSONSchema::parse_property_string('prune-backups', $scfg->{'prune-backups'}); } + activate_storage($cfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); return $plugin->prune_backups($scfg, $storeid, $keep, $vmid, $type, $dryrun, $logfunc); } @@ -1601,13 +1643,14 @@ my $prune_mark = sub { foreach my $prune_entry (@{$prune_entries}) { my $mark = $prune_entry->{mark}; my $id = $id_func->($prune_entry->{ctime}); + $already_included->{$id} = 1 if defined($mark) && $mark eq 'keep'; + } - next if $already_included->{$id}; + foreach my $prune_entry (@{$prune_entries}) { + my $mark = $prune_entry->{mark}; + my $id = $id_func->($prune_entry->{ctime}); - if (defined($mark)) { - $already_included->{$id} = 1 if $mark eq 'keep'; - next; - } + next if defined($mark) || $already_included->{$id}; if (!$newly_included->{$id}) { last if scalar(keys %{$newly_included}) >= $keep_count; @@ -1622,7 +1665,10 @@ my $prune_mark = sub { sub prune_mark_backup_group { my ($backup_group, $keep) = @_; - if (!scalar(grep {$_ > 0} values %{$keep})) { + my $keep_all = delete $keep->{'keep-all'}; + + if ($keep_all || !scalar(grep {$_ > 0} values %{$keep})) { + $keep = { 'keep-all' => 1 } if $keep_all; foreach my $prune_entry (@{$backup_group}) { $prune_entry->{mark} = 'keep'; } @@ -1648,8 +1694,8 @@ sub prune_mark_backup_group { $prune_mark->($prune_list, $keep->{'keep-weekly'}, sub { my ($ctime) = @_; my ($sec, $min, $hour, $day, $month, $year) = localtime($ctime); - my $iso_week = int(strftime("%V", $sec, $min, $hour, $day, $month - 1, $year - 1900)); - my $iso_week_year = int(strftime("%G", $sec, $min, $hour, $day, $month - 1, $year - 1900)); + my $iso_week = int(strftime("%V", $sec, $min, $hour, $day, $month, $year)); + my $iso_week_year = int(strftime("%G", $sec, $min, $hour, $day, $month, $year)); return "$iso_week/$iso_week_year"; }); $prune_mark->($prune_list, $keep->{'keep-monthly'}, sub { @@ -1804,7 +1850,7 @@ sub get_bandwidth_limit { my ($operation, $storage_list, $override) = @_; # called for each limit (global, per-storage) with the 'default' and the - # $operation limit and should udpate $override for every limit affecting + # $operation limit and should update $override for every limit affecting # us. my $use_global_limits = 0; my $apply_limit = sub {