]> git.proxmox.com Git - pve-storage.git/blobdiff - PVE/Storage/Plugin.pm
plugin: add volume_snapshot_info function
[pve-storage.git] / PVE / Storage / Plugin.pm
index 158e3698242143729362d6246a22b916a00f02fa..d4b3615b7b3937e7e68ea020ed79635a3e819303 100644 (file)
@@ -41,6 +41,19 @@ our @SHARED_STORAGE = (
     'pbs',
 );
 
+our $QCOW2_PREALLOCATION = {
+    off => 1,
+    metadata => 1,
+    falloc => 1,
+    full => 1,
+};
+
+our $RAW_PREALLOCATION = {
+    off => 1,
+    falloc => 1,
+    full => 1,
+};
+
 our $MAX_VOLUMES_PER_GUEST = 1024;
 
 cfs_register_file ('storage.cfg',
@@ -93,15 +106,17 @@ PVE::JSONSchema::register_format('prune-backups', $prune_backups_format, \&valid
 sub validate_prune_backups {
     my ($prune_backups) = @_;
 
-    my $keep_all = delete $prune_backups->{'keep-all'};
+    my $res = { $prune_backups->%* };
 
-    if (!scalar(grep {$_ > 0} values %{$prune_backups})) {
-       $prune_backups = { 'keep-all' => 1 };
+    my $keep_all = delete $res->{'keep-all'};
+
+    if (scalar(grep { $_ > 0 } values %{$res}) == 0) {
+       $res = { 'keep-all' => 1 };
     } elsif ($keep_all) {
        die "keep-all cannot be set together with other options.\n";
     }
 
-    return $prune_backups;
+    return $res;
 }
 register_standard_option('prune-backups', {
     description => "The retention options with shorter intervals are processed first " .
@@ -133,7 +148,8 @@ my $defaultData = {
            optional => 1,
        },
        maxfiles => {
-           description => "Maximal number of backup files per VM. Use '0' for unlimted.",
+           description => "Deprecated: use 'prune-backups' instead. " .
+               "Maximal number of backup files per VM. Use '0' for unlimited.",
            type => 'integer',
            minimum => 0,
            optional => 1,
@@ -149,6 +165,13 @@ my $defaultData = {
            type => 'string', format => 'pve-storage-format',
            optional => 1,
        },
+       preallocation => {
+           description => "Preallocation mode for raw and qcow2 images. " .
+               "Using 'metadata' on raw images results in preallocation=off.",
+           type => 'string', enum => ['off', 'metadata', 'falloc', 'full'],
+           default => 'metadata',
+           optional => 1,
+       },
     },
 };
 
@@ -344,6 +367,10 @@ sub decode_value {
            die "unable to combine 'none' with other content types\n";
        }
 
+       if (scalar(keys $res->%*) == 0 && !$valid_content->{none}) {
+           die "storage does not support content type 'none'\n";
+       }
+
        return $res;
     } elsif ($key eq 'format') {
        my $valid_formats = $def->{format}->[0];
@@ -402,8 +429,15 @@ sub parse_config {
            type => 'dir',
            priority => 0, # force first entry
            path => '/var/lib/vz',
-           maxfiles => 0,
-           content => { images => 1, rootdir => 1, vztmpl => 1, iso => 1, snippets => 1},
+           'prune-backups' => 'keep-all=1',
+           content => {
+               backup => 1,
+               images => 1,
+               iso => 1,
+               rootdir => 1,
+               snippets => 1,
+               vztmpl => 1,
+           },
        };
     }
 
@@ -430,10 +464,35 @@ sub parse_config {
     return $cfg;
 }
 
+sub preallocation_cmd_option {
+    my ($scfg, $fmt) = @_;
+
+    my $prealloc = $scfg->{preallocation};
+
+    if ($fmt eq 'qcow2') {
+       $prealloc = $prealloc // 'metadata';
+
+       die "preallocation mode '$prealloc' not supported by format '$fmt'\n"
+           if !$QCOW2_PREALLOCATION->{$prealloc};
+
+       return "preallocation=$prealloc";
+    } elsif ($fmt eq 'raw') {
+       $prealloc = $prealloc // 'off';
+       $prealloc = 'off' if $prealloc eq 'metadata';
+
+       die "preallocation mode '$prealloc' not supported by format '$fmt'\n"
+           if !$RAW_PREALLOCATION->{$prealloc};
+
+       return "preallocation=$prealloc";
+    }
+
+    return;
+}
+
 # Storage implementation
 
 # called during addition of storage (before the new storage config got written)
-# die to abort additon if there are (grave) problems
+# die to abort addition if there are (grave) problems
 # NOTE: runs in a storage config *locked* context
 sub on_add_hook {
     my ($class, $storeid, $scfg, %param) = @_;
@@ -506,7 +565,7 @@ sub parse_volname {
        return ('images', $name, $vmid, undef, undef, $isBase, $format);
     } elsif ($volname =~ m!^iso/([^/]+$PVE::Storage::iso_extension_re)$!) {
        return ('iso', $1);
-    } elsif ($volname =~ m!^vztmpl/([^/]+\.tar\.[gx]z)$!) {
+    } elsif ($volname =~ m!^vztmpl/([^/]+$PVE::Storage::vztmpl_extension_re)$!) {
        return ('vztmpl', $1);
     } elsif ($volname =~ m!^rootdir/(\d+)$!) {
        return ('rootdir', $1, $1);
@@ -541,7 +600,7 @@ sub get_subdir {
 
     my $path = $scfg->{path};
 
-    die "storage definintion has no path\n" if !$path;
+    die "storage definition has no path\n" if !$path;
 
     my $subdir = $vtype_subdirs->{$vtype};
 
@@ -677,7 +736,7 @@ sub clone_image {
     my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
 
     # this only works for file based storage types
-    die "storage definintion has no path\n" if !$scfg->{path};
+    die "storage definition has no path\n" if !$scfg->{path};
 
     my ($vtype, $basename, $basevmid, undef, undef, $isBase, $format) =
        $class->parse_volname($volname);
@@ -695,7 +754,7 @@ sub clone_image {
 
     mkpath $imagedir;
 
-    my $name = $class->find_free_diskname($imagedir, $scfg, $vmid, "qcow2", 1);
+    my $name = $class->find_free_diskname($storeid, $scfg, $vmid, "qcow2", 1);
 
     warn "clone $volname: $vtype, $name, $vmid to $name (base=../$basevmid/$basename)\n";
 
@@ -708,7 +767,7 @@ sub clone_image {
        local $CWD = $imagedir;
 
        my $cmd = ['/usr/bin/qemu-img', 'create', '-b', "../$basevmid/$basename",
-                  '-f', 'qcow2', $path];
+                  '-F', $format, '-f', 'qcow2', $path];
 
        run_command($cmd);
     };
@@ -727,7 +786,7 @@ sub alloc_image {
 
     mkpath $imagedir;
 
-    $name = $class->find_free_diskname($imagedir, $scfg, $vmid, $fmt, 1) if !$name;
+    $name = $class->find_free_diskname($storeid, $scfg, $vmid, $fmt, 1) if !$name;
 
     my (undef, $tmpfmt) = parse_name_dir($name);
 
@@ -750,7 +809,8 @@ sub alloc_image {
     } else {
        my $cmd = ['/usr/bin/qemu-img', 'create'];
 
-       push @$cmd, '-o', 'preallocation=metadata' if $fmt eq 'qcow2';
+       my $prealloc_opt = preallocation_cmd_option($scfg, $fmt);
+       push @$cmd, '-o', $prealloc_opt if defined($prealloc_opt);
 
        push @$cmd, '-f', $fmt, $path, "${size}K";
 
@@ -821,10 +881,19 @@ sub file_size_info {
     warn $@ if $@;
 
     my $info = eval { decode_json($json) };
-    warn "could not parse qemu-img info command output for '$filename'\n" if $@;
+    if (my $err = $@) {
+       warn "could not parse qemu-img info command output for '$filename' - $err\n";
+       return wantarray ? (undef, undef, undef, undef, $st->ctime) : undef;
+    }
 
     my ($size, $format, $used, $parent) = $info->@{qw(virtual-size format actual-size backing-filename)};
 
+    ($size) = ($size =~ /^(\d+)$/) or die "size '$size' not an integer\n"; # untaint
+    ($used) = ($used =~ /^(\d+)$/) or die "used '$used' not an integer\n"; # untaint
+    ($format) = ($format =~ /^(\S+)$/) or die "format '$format' includes whitespace\n"; # untaint
+    if (defined($parent)) {
+       ($parent) = ($parent =~ /^(\S+)$/) or die "parent '$parent' includes whitespace\n"; # untaint
+    }
     return wantarray ? ($size, $format, $used, $parent, $st->ctime) : $size;
 }
 
@@ -879,8 +948,11 @@ sub volume_snapshot {
     return undef;
 }
 
+# Asserts that a rollback to $snap on $volname is possible.
+# If certain snapshots are preventing the rollback and $blockers is an array
+# reference, the snapshot names can be pushed onto $blockers prior to dying.
 sub volume_rollback_is_possible {
-    my ($class, $scfg, $storeid, $volname, $snap) = @_;
+    my ($class, $scfg, $storeid, $volname, $snap, $blockers) = @_;
 
     return 1;
 }
@@ -1029,7 +1101,7 @@ my $get_subdir_files = sub {
            $info = { volid => "$sid:iso/$1", format => 'iso' };
 
        } elsif ($tt eq 'vztmpl') {
-           next if $fn !~ m!/([^/]+\.tar\.([gx]z))$!;
+           next if $fn !~ m!/([^/]+$PVE::Storage::vztmpl_extension_re)$!;
 
            $info = { volid => "$sid:vztmpl/$1", format => "t$2" };
 
@@ -1130,7 +1202,7 @@ sub status {
 
     my $path = $scfg->{path};
 
-    die "storage definintion has no path\n" if !$path;
+    die "storage definition has no path\n" if !$path;
 
     my $timeout = 2;
     my $res = PVE::Tools::df($path, $timeout);
@@ -1140,6 +1212,16 @@ sub status {
     return ($res->{total}, $res->{avail}, $res->{used}, 1);
 }
 
+# Returns a hash with the snapshot names as keys and the following data:
+# id        - Unique id to distinguish different snapshots even if the have the same name.
+# timestamp - Creation time of the snapshot (seconds since epoch).
+# Returns an empty hash if the volume does not exist.
+sub volume_snapshot_info {
+    my ($class, $scfg, $storeid, $volname) = @_;
+
+    die "volume_snapshot_info is not implemented for $class";
+}
+
 sub volume_snapshot_list {
     my ($class, $scfg, $storeid, $volname) = @_;
 
@@ -1154,7 +1236,7 @@ sub activate_storage {
 
     my $path = $scfg->{path};
 
-    die "storage definintion has no path\n" if !$path;
+    die "storage definition has no path\n" if !$path;
 
     # this path test may hang indefinitely on unresponsive mounts
     my $timeout = 2;
@@ -1249,8 +1331,9 @@ sub prune_backups {
        $prune_entry->{vmid} = $backup_vmid if defined($backup_vmid);
 
        if ($archive_info->{is_std_name}) {
-           die "internal error - got no vmid\n" if !defined($backup_vmid);
-           die "internal error - got wrong vmid\n" if defined($vmid) && $backup_vmid ne $vmid;
+           die "internal error - got no VMID\n" if !defined($backup_vmid);
+           die "internal error - got wrong VMID '$backup_vmid' != '$vmid'\n"
+               if defined($vmid) && $backup_vmid ne $vmid;
 
            $prune_entry->{ctime} = $archive_info->{ctime};
            my $group = "$backup_type/$backup_vmid";
@@ -1329,7 +1412,6 @@ sub read_common_header($) {
     sysread($fh, my $size, 8);
     $size = unpack('Q<', $size);
     die "import: no size found in export header, aborting.\n" if !defined($size);
-    die "import: got a bad size (not a multiple of 1K), aborting.\n" if ($size&1023);
     # Size is in bytes!
     return $size;
 }
@@ -1389,7 +1471,7 @@ sub volume_export_formats {
 
 # Import data from a stream, creating a new or replacing or adding to an existing volume.
 sub volume_import {
-    my ($class, $scfg, $storeid, $fh, $volname, $format, $base_snapshot, $with_snapshots, $allow_rename) = @_;
+    my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots, $allow_rename) = @_;
 
     die "volume import format '$format' not available for $class\n"
        if $format !~ /^(raw|tar|qcow2|vmdk)\+size$/;
@@ -1449,7 +1531,7 @@ sub volume_import {
 }
 
 sub volume_import_formats {
-    my ($class, $scfg, $storeid, $volname, $base_snapshot, $with_snapshots) = @_;
+    my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_;
     if ($scfg->{path} && !defined($base_snapshot)) {
        my $format = ($class->parse_volname($volname))[6];
        if ($with_snapshots) {