]> git.proxmox.com Git - pve-storage.git/blobdiff - PVE/Storage/Plugin.pm
Don't remove and recreate lun when changing a volume
[pve-storage.git] / PVE / Storage / Plugin.pm
index 7516f4eaa6378ced727abf9b59a4fe3ba52d5449..88da2318b69d75eb34edd3a5027a83b569b9259a 100644 (file)
@@ -5,11 +5,14 @@ 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 JSON;
+
 use base qw(PVE::SectionConfig);
 
 our @COMMON_TAR_FLAGS = qw(
@@ -25,7 +28,6 @@ our @SHARED_STORAGE = (
     'cifs',
     'rbd',
     'cephfs',
-    'sheepdog',
     'iscsidirect',
     'glusterfs',
     'zfs',
@@ -326,7 +328,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},
        };
     }
 
@@ -415,7 +417,7 @@ sub parse_volname {
        my ($vmid, $name) = ($1, $2);
        my (undef, $format, $isBase) = parse_name_dir($name);
        return ('images', $name, $vmid, undef, undef, $isBase, $format);
-    } elsif ($volname =~ m!^iso/([^/]+\.[Ii][Ss][Oo])$!) {
+    } elsif ($volname =~ m!^iso/([^/]+$PVE::Storage::iso_extension_re)$!) {
        return ('iso', $1);
     } elsif ($volname =~ m!^vztmpl/([^/]+\.tar\.[gx]z)$!) {
        return ('vztmpl', $1);
@@ -427,6 +429,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";
@@ -438,6 +442,7 @@ my $vtype_subdirs = {
     iso => 'template/iso',
     vztmpl => 'template/cache',
     backup => 'dump',
+    snippets => 'snippets',
 };
 
 sub get_subdir {
@@ -525,41 +530,40 @@ sub create_base {
     return $newvolname;
 }
 
-sub is_valid_vm_diskname {
-    my ($disk_name, $scfg, $vmid, $fmt, $add_fmt_suffix) = @_;
-
-    $vmid = qr/\d+/ if !defined($vmid);
+my $get_vm_disk_number = sub {
+    my ($disk_name, $scfg, $vmid, $suffix) = @_;
 
-    my $suffix = (defined($fmt) && $add_fmt_suffix) ? ".$fmt" : '';
+    my $disk_regex = qr/(vm|base)-$vmid-disk-(\d+)$suffix/;
 
     my $type = $scfg->{type};
-    my $def = $defaultData->{plugindata}->{$type};
-    my $valid_formats = $def->{format}[0];
+    my $def = { %{$defaultData->{plugindata}->{$type}} };
 
-    my $disk_regex = qr/(vm|base)-$vmid-disk-(\d+)$suffix/;
-    $disk_regex = qr/(vm|base|subvol|basevol)-$vmid-disk-(\d+)/
-       if $valid_formats->{subvol};
+    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 wantarray ? (1, $2) : 1;
+    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) = @_;
 
-    my $disk_ids = {};
-    my ($match, $disknum);
-    foreach my $disk (@$disk_list) {
-       ($match, $disknum) = is_valid_vm_diskname($disk,  $scfg, $vmid, $fmt, $add_fmt_suffix);
-       $disk_ids->{$disknum} = 1 if $match;
-    }
-
     $fmt //= '';
     my $prefix = ($fmt eq 'subvol') ? 'subvol' : 'vm';
     my $suffix = $add_fmt_suffix ? ".$fmt" : '';
 
-    for (my $i = 1; $i < $MAX_VOLUMES_PER_GUEST; $i++) {
+    my $disk_ids = {};
+    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 "$prefix-$vmid-disk-$i$suffix";
        }
@@ -569,23 +573,16 @@ sub get_next_vm_diskname {
 }
 
 my $find_free_diskname = sub {
-    my ($imgdir, $vmid, $fmt) = @_;
+    my ($imgdir, $vmid, $fmt, $scfg) = @_;
 
-    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++) {
-       if (!$disk_ids->{$i}) {
-           return "vm-$vmid-disk-$i.$fmt";
-       }
+    my $disk_list = [];
+
+    if (defined(my $dh = IO::Dir->new($imgdir))) {
+       @$disk_list = $dh->read();
+       $dh->close();
     }
 
-    die "unable to allocate a new image name for VM $vmid in '$imgdir'\n";
+    return  get_next_vm_diskname($disk_list, $imgdir, $vmid, $fmt, $scfg, 1);
 };
 
 sub clone_image {
@@ -610,7 +607,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";
 
@@ -642,7 +639,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);
 
@@ -656,7 +653,7 @@ sub alloc_image {
     if ($fmt eq 'subvol') {
        # only allow this if size = 0, so that user knows what he is doing
        die "storage does not support subvol quotas\n" if $size != 0;
-       
+
        my $old_umask = umask(0022);
        my $err;
        mkdir($path) or $err = "unable to create subvol '$path' - $!\n";
@@ -666,12 +663,17 @@ sub alloc_image {
        my $cmd = ['/usr/bin/qemu-img', 'create'];
 
        push @$cmd, '-o', 'preallocation=metadata' if $fmt eq 'qcow2';
-       
+
        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";
 }
 
@@ -689,15 +691,19 @@ 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;
 }
 
@@ -707,36 +713,21 @@ sub file_size_info {
     if (-d $filename) {
        return wantarray ? (0, 'subvol', 0, undef) : 1;
     }
-    
-    my $cmd = ['/usr/bin/qemu-img', 'info', $filename];
-
-    my $format;
-    my $parent;
-    my $size = 0;
-    my $used = 0;
 
+    my $json = '';
     eval {
-       run_command($cmd, timeout => $timeout, outfunc => sub {
-           my $line = shift;
-           if ($line =~ m/^file format:\s+(\S+)\s*$/) {
-               $format = $1;
-           } elsif ($line =~ m/^backing file:\s(\S+)\s/) {
-               $parent = $1;
-           } elsif ($line =~ m/^virtual size:\s\S+\s+\((\d+)\s+bytes\)$/) {
-               $size = int($1);
-           } elsif ($line =~ m/^disk size:\s+(\d+(.\d+)?)([KMGT])\s*$/) {
-               $used = $1;
-               my $u = $3;
-
-               $used *= 1024 if $u eq 'K';
-               $used *= (1024*1024) if $u eq 'M';
-               $used *= (1024*1024*1024) if $u eq 'G';
-               $used *= (1024*1024*1024*1024) if $u eq 'T';
-
-               $used = int($used);
-           }
-       });
+       run_command(['/usr/bin/qemu-img', 'info', '--output=json', $filename],
+           timeout => $timeout,
+           outfunc => sub { $json .= shift },
+           errfunc => sub { warn "$_[0]\n" }
+       );
     };
+    warn $@ if $@;
+
+    my $info = eval { decode_json($json) };
+    warn "could not parse qemu-img info command output for '$filename'\n" if $@;
+
+    my ($size, $format, $used, $parent) = $info->@{qw(virtual-size format actual-size backing-filename)};
 
     return wantarray ? ($size, $format, $used, $parent) : $size;
 }
@@ -781,9 +772,9 @@ sub volume_snapshot {
 }
 
 sub volume_rollback_is_possible {
-    my ($class, $scfg, $storeid, $volname, $snap) = @_; 
+    my ($class, $scfg, $storeid, $volname, $snap) = @_;
 
-    return 1; 
+    return 1;
 }
 
 sub volume_snapshot_rollback {
@@ -898,6 +889,85 @@ sub list_images {
     return $res;
 }
 
+# list templates ($tt = <iso|vztmpl|backup|snippets>)
+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!/([^/]+$PVE::Storage::iso_extension_re)$!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 defined($vmid) && $fn !~  m/\S+-$vmid-\S+/;
+           next if $fn !~ m!/([^/]+\.(tar|tar\.gz|tar\.lzo|tgz|vma|vma\.gz|vma\.lzo))$!;
+
+           $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;
+
+       if ($ct eq 'images' || $ct eq 'rootdir') {
+           $data = $class->list_images($storeid, $scfg, $vmid);
+       } elsif ($scfg->{path}) {
+           my $path = $class->get_subdir($scfg, $ct);
+
+           if ($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) = @_;
 
@@ -957,6 +1027,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) = @_;