X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FStorage.pm;h=cd74a3ade79cd039fc93b0b9e17820f0d4a95976;hb=5c009b733ebfd4c8f1b9c989435850e7bf4e07a5;hp=cb34139ac421ac4f901f7c559d198c41a3d46bcf;hpb=3af60e62fa43568729cc90a8244e13210bc5579a;p=pve-storage.git diff --git a/PVE/Storage.pm b/PVE/Storage.pm index cb34139..cd74a3a 100755 --- a/PVE/Storage.pm +++ b/PVE/Storage.pm @@ -13,14 +13,15 @@ use IPC::Open2; use Cwd 'abs_path'; use Getopt::Long qw(GetOptionsFromArray); use Socket; -use Digest::SHA1; +use Digest::SHA; use Net::Ping; -use PVE::Tools qw(run_command file_read_firstline trim); +use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach); use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file); use PVE::Exception qw(raise_param_exc); use PVE::JSONSchema; use PVE::INotify; +use PVE::RPCEnvironment; my $ISCSIADM = '/usr/bin/iscsiadm'; my $UDEVADM = '/sbin/udevadm'; @@ -78,36 +79,6 @@ sub load_stable_scsi_paths { return $stable_paths; } -sub dir_glob_regex { - my ($dir, $regex) = @_; - - my $dh = IO::Dir->new ($dir); - return wantarray ? () : undef if !$dh; - - while (defined(my $tmp = $dh->read)) { - if (my @res = $tmp =~ m/^($regex)$/) { - $dh->close; - return wantarray ? @res : $tmp; - } - } - $dh->close; - - return wantarray ? () : undef; -} - -sub dir_glob_foreach { - my ($dir, $regex, $func) = @_; - - my $dh = IO::Dir->new ($dir); - if (defined $dh) { - while (defined(my $tmp = $dh->read)) { - if (my @res = $tmp =~ m/^($regex)$/) { - &$func (@res); - } - } - } -} - sub read_proc_mounts { local $/; # enable slurp mode @@ -137,6 +108,7 @@ my $confvars = { path => 'path', shared => 'bool', disable => 'bool', + saferemove => 'bool', format => 'format', content => 'content', server => 'server', @@ -147,6 +119,7 @@ my $confvars = { target => 'target', nodes => 'nodes', options => 'options', + maxfiles => 'natural', }; my $required_config = { @@ -169,6 +142,7 @@ my $default_config = { nodes => 0, shared => 0, disable => 0, + maxfiles => 0, content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1, none => 1 }, { images => 1, rootdir => 1 }], format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ], @@ -181,6 +155,7 @@ my $default_config = { server => 1, export => 1, options => 0, + maxfiles => 0, content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1}, { images => 1 }], format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ], @@ -191,6 +166,7 @@ my $default_config = { nodes => 0, shared => 0, disable => 0, + saferemove => 0, content => [ {images => 1}, { images => 1 }], base => 1, }, @@ -354,6 +330,10 @@ sub check_type { return parse_lvm_name ($value, $noerr); } elsif ($ct eq 'portal') { return verify_portal($value, $noerr); + } elsif ($ct eq 'natural') { + return int($value) if $value =~ m/^\d+$/; + return undef if $noerr; + die "type check ('natural') failed - got '$value'\n"; } elsif ($ct eq 'nodes') { my $res = {}; @@ -416,7 +396,7 @@ sub parse_config { my $ids = {}; - my $digest = Digest::SHA1::sha1_hex(defined($raw) ? $raw : ''); + my $digest = Digest::SHA::sha1_hex(defined($raw) ? $raw : ''); my $pri = 0; @@ -431,7 +411,7 @@ sub parse_config { my $type = $1; my $ignore = 0; - if (!parse_storage_id ($storeid, 1)) { + if (!PVE::JSONSchema::parse_storage_id($storeid, 1)) { $ignore = 1; warn "ignoring storage '$storeid' - (illegal characters)\n"; } elsif (!$default_config->{$type}) { @@ -478,7 +458,8 @@ sub parse_config { $ids->{local} = { type => 'dir', priority => $pri++, - path => '/var/lib/vz', + path => '/var/lib/vz', + maxfiles => 0, content => { images => 1, rootdir => 1, vztmpl => 1, iso => 1}, }; } @@ -607,7 +588,7 @@ sub storage_check_node { if ($scfg->{nodes}) { $node = PVE::INotify::nodename() if !$node || ($node eq 'localhost'); if (!$scfg->{nodes}->{$node}) { - die "storage '$storeid' is not available on node '$node'" if !$noerr; + die "storage '$storeid' is not available on node '$node'\n" if !$noerr; return undef; } } @@ -708,9 +689,9 @@ sub write_config { foreach my $k (keys %$def) { next if defined ($done_hash->{$k}); - if (defined (my $v = $ids->{$storeid}->{$k})) { - $data .= sprint_config_line ($k, $v); - } + my $v = $ids->{$storeid}->{$k}; + next if !defined($v); + $data .= sprint_config_line ($k, $v); } $out .= "$data\n"; @@ -916,7 +897,7 @@ sub iscsi_device_list { $res->{$target}->{$volid} = { 'format' => 'raw', - 'size' => int($size / 2), + 'size' => int($size * 512), 'vmid' => 0, # not assigned to any vm 'channel' => int($channel), 'id' => int($id), @@ -933,23 +914,6 @@ sub iscsi_device_list { # library implementation - -PVE::JSONSchema::register_format('pve-storage-id', \&parse_storage_id); -sub parse_storage_id { - my ($storeid, $noerr) = @_; - - if ($storeid !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) { - return undef if $noerr; - die "storage ID '$storeid' contains illegal characters\n"; - } - return $storeid; -} - -PVE::JSONSchema::register_standard_option('pve-storage-id', { - description => "The storage identifier.", - type => 'string', format => 'pve-storage-id', -}); - PVE::JSONSchema::register_format('pve-storage-vgname', \&parse_lvm_name); sub parse_lvm_name { my ($name, $noerr) = @_; @@ -1004,8 +968,12 @@ sub parse_volname_dir { return ('vztmpl', $1); } elsif ($volname =~ m!^rootdir/(\d+)$!) { return ('rootdir', $1, $1); - } elsif ($volname =~ m!^backup/([^/]+(\.tar|\.tgz))$!) { - return ('backup', $1); + } elsif ($volname =~ m!^backup/([^/]+(\.(tar|tar\.gz|tar\.lzo|tgz)))$!) { + my $fn = $1; + if ($fn =~ m/^vzdump-(openvz|qemu)-(\d+)-.+/) { + return ('backup', $fn, $2); + } + return ('backup', $fn); } die "unable to parse directory volume name '$volname'\n"; } @@ -1050,7 +1018,9 @@ sub path_to_volume_id { return (''); } - $path = abs_path ($path); + # Note: abs_path() return undef if $path doesn not exist + # for example when nfs storage is not mounted + $path = abs_path($path) || $path; foreach my $sid (keys %$ids) { my $type = $ids->{$sid}->{type}; @@ -1075,7 +1045,7 @@ sub path_to_volume_id { } elsif ($path =~ m!^$privatedir/(\d+)$!) { my $vmid = $1; return ('rootdir', "$sid:rootdir/$vmid"); - } elsif ($path =~ m!^$backupdir/([^/]+\.(tar|tgz))$!) { + } elsif ($path =~ m!^$backupdir/([^/]+\.(tar|tar\.gz|tar\.lzo|tgz))$!) { my $name = $1; return ('iso', "$sid:backup/$name"); } @@ -1094,9 +1064,11 @@ sub path { my $path; my $owner; + my $vtype = 'image'; if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') { - my ($vtype, $name, $vmid) = parse_volname_dir ($volname); + my ($name, $vmid); + ($vtype, $name, $vmid) = parse_volname_dir ($volname); $owner = $vmid; my $imagedir = get_image_dir($cfg, $storeid, $vmid); @@ -1135,7 +1107,7 @@ sub path { die "unknown storage type '$scfg->{type}'"; } - return wantarray ? ($path, $owner) : $path; + return wantarray ? ($path, $owner, $vtype) : $path; } sub storage_migrate { @@ -1227,9 +1199,9 @@ sub vdisk_alloc { die "no storage id specified\n" if !$storeid; - parse_storage_id ($storeid); + PVE::JSONSchema::parse_storage_id($storeid); - my $scfg = storage_config ($cfg, $storeid); + my $scfg = storage_config($cfg, $storeid); die "no VMID specified\n" if !$vmid; @@ -1271,7 +1243,7 @@ sub vdisk_alloc { my $path = "$imagedir/$name"; - die "disk image '$path' already exists\n" if -f $path; + die "disk image '$path' already exists\n" if -e $path; run_command("/usr/bin/qemu-img create -f $fmt '$path' ${size}K", errmsg => "unable to create image"); @@ -1333,6 +1305,11 @@ sub vdisk_free { activate_storage ($cfg, $storeid); + # we need to zero out LVM data for security reasons + # and to allow thin provisioning + + my $vg; + # lock shared storage cluster_lock_storage($storeid, $scfg->{shared}, undef, sub { @@ -1346,17 +1323,43 @@ sub vdisk_free { } } elsif ($scfg->{type} eq 'lvm') { - my $vg = $scfg->{vgname}; - - my $cmd = ['/sbin/lvremove', '-f', "$vg/$volname"]; + if ($scfg->{saferemove}) { + # avoid long running task, so we only rename here + $vg = $scfg->{vgname}; + my $cmd = ['/sbin/lvrename', $vg, $volname, "del-$volname"]; + run_command($cmd, errmsg => "lvrename '$vg/$volname' error"); + } else { + my $tmpvg = $scfg->{vgname}; + my $cmd = ['/sbin/lvremove', '-f', "$tmpvg/$volname"]; + run_command($cmd, errmsg => "lvremove '$tmpvg/$volname' error"); + } - run_command($cmd, errmsg => "lvremove '$vg/$volname' error"); } elsif ($scfg->{type} eq 'iscsi') { die "can't free space in iscsi storage\n"; } else { die "unknown storage type '$scfg->{type}'"; } }); + + return if !$vg; + + my $zero_out_worker = sub { + print "zero-out data on image $volname\n"; + my $cmd = ['dd', "if=/dev/zero", "of=/dev/$vg/del-$volname", "bs=1M"]; + eval { run_command($cmd, errmsg => "zero out failed"); }; + warn $@ if $@; + + cluster_lock_storage($storeid, $scfg->{shared}, undef, sub { + my $cmd = ['/sbin/lvremove', '-f', "$vg/del-$volname"]; + run_command($cmd, errmsg => "lvremove '$vg/del-$volname' error"); + }); + print "successfully removed volume $volname\n"; + }; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + $rpcenv->fork_worker('imgdel', undef, $authuser, $zero_out_worker); } # lvm utility functions @@ -1561,7 +1564,7 @@ sub template_list { $info = { volid => "$sid:vztmpl/$1", format => 'tgz' }; } elsif ($tt eq 'backup') { - next if $fn !~ m!/([^/]+\.(tar|tgz))$!; + next if $fn !~ m!/([^/]+\.(tar|tar\.gz|tar\.lzo|tgz))$!; $info = { volid => "$sid:backup/$1", format => $2 }; } @@ -1761,7 +1764,7 @@ sub nfs_is_mounted { $mountdata = read_proc_mounts() if !$mountdata; - if ($mountdata =~ m/^$source\s$mountpoint\snfs/m) { + if ($mountdata =~ m|^$source/?\s$mountpoint\snfs|m) { return $mountpoint; } @@ -1966,7 +1969,7 @@ sub activate_volumes { # check is volume exists if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') { - die "volume '$volid' does not exist\n" if ! -f $path; + die "volume '$volid' does not exist\n" if ! -e $path; } else { die "volume '$volid' does not exist\n" if ! -b $path; } @@ -1978,8 +1981,6 @@ sub deactivate_volumes { return if !($vollist && scalar(@$vollist)); - my $lvs = lvm_lvs (); - my @errlist = (); foreach my $volid (@$vollist) { my ($storeid, $volname) = parse_volume_id ($volid); @@ -1987,16 +1988,14 @@ sub deactivate_volumes { my $scfg = storage_config ($cfg, $storeid); if ($scfg->{type} eq 'lvm') { - my ($name) = parse_volname_lvm ($volname); - - if ($lvs->{$scfg->{vgname}}->{$name}) { - my $path = path ($cfg, $volid); - my $cmd = ['/sbin/lvchange', '-aln', $path]; - eval { run_command($cmd, errmsg => "can't deactivate LV '$volid'"); }; - if (my $err = $@) { - warn $err; - push @errlist, $volid; - } + my $path = path ($cfg, $volid); + next if ! -b $path; + + my $cmd = ['/sbin/lvchange', '-aln', $path]; + eval { run_command($cmd, errmsg => "can't deactivate LV '$volid'"); }; + if (my $err = $@) { + warn $err; + push @errlist, $volid; } } }