[pve-devel] [PATCH v2 pve-container 3/4] support resizing of owned container disks

Wolfgang Bumiller w.bumiller at proxmox.com
Thu Sep 3 15:45:53 CEST 2015


---
 src/PVE/API2/LXC/Config.pm | 145 +++++++++++++++++++++++++++++++++++++++++++++
 src/PVE/LXC.pm             |  36 ++++++++++-
 src/PVE/VZDump/LXC.pm      |  18 ++----
 src/pct                    |   2 +
 4 files changed, 184 insertions(+), 17 deletions(-)

diff --git a/src/PVE/API2/LXC/Config.pm b/src/PVE/API2/LXC/Config.pm
index 95eafaa..e8b9ad0 100644
--- a/src/PVE/API2/LXC/Config.pm
+++ b/src/PVE/API2/LXC/Config.pm
@@ -149,4 +149,149 @@ __PACKAGE__->register_method({
 	return undef;
     }});
 
+__PACKAGE__->register_method({
+    name => 'resize_vm',
+    path => '{vmid}/resize',
+    method => 'PUT',
+    protected => 1,
+    proxyto => 'node',
+    description => "Resize a container mountpoint.",
+    permissions => {
+	check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
+    },
+    parameters => {
+    	additionalProperties => 0,
+	properties => PVE::LXC::json_config_properties(
+	    {
+		node => get_standard_option('pve-node'),
+		vmid => get_standard_option('pve-vmid'),
+		disk => {
+		    type => 'string',
+		    description => "The disk you want to resize.",
+		    enum => [PVE::LXC::mountpoint_names()],
+		},
+		size => {
+		    type => 'string',
+		    pattern => '\+?\d+(\.\d+)?[KMGT]?',
+		    description => "The new size. With the '+' sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.",
+		},
+		digest => {
+		    type => 'string',
+		    description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
+		    maxLength => 40,
+		    optional => 1,
+		}
+	    }),
+    },
+    returns => { type => 'null'},
+    code => sub {
+	my ($param) = @_;
+
+	my $rpcenv = PVE::RPCEnvironment::get();
+
+	my $authuser = $rpcenv->get_user();
+
+	my $node = extract_param($param, 'node');
+
+	my $vmid = extract_param($param, 'vmid');
+
+	my $digest = extract_param($param, 'digest');
+
+	my $sizestr = extract_param($param, 'size');
+	die "invalid size string" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
+	my ($ext, $newsize, $unit) = ($1, $2, $4);
+
+	die "no options specified\n" if !scalar(keys %$param);
+
+	PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
+
+	my $storage_cfg = cfs_read_file("storage.cfg");
+
+	my $code = sub {
+
+	    my $conf = PVE::LXC::load_config($vmid);
+	    PVE::LXC::check_lock($conf);
+
+	    PVE::Tools::assert_if_modified($digest, $conf->{digest});
+
+	    my $running = PVE::LXC::check_running($vmid);
+
+	    my $disk = $param->{disk};
+	    my $mp = PVE::LXC::parse_ct_mountpoint($conf->{$disk});
+	    my $volid = $mp->{volume};
+
+	    my (undef, undef, $owner, undef, undef, undef, $format) =
+		PVE::Storage::parse_volname($storage_cfg, $volid);
+
+	    die "can't resize mountpoint owned by another container ($owner)"
+		if $vmid != $owner;
+
+	    die "can't resize volume: $disk if snapshot exists\n" 
+		if %{$conf->{snapshots}} && $format eq 'qcow2';
+
+	    my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
+
+	    $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
+
+	    my $size = PVE::Storage::volume_size_info($storage_cfg, $volid, 5);
+	    # FIXME: also in PVE::API2::Qemu (PVE::Tools candidate?)
+	    if ($unit) {
+		if ($unit eq 'K') {
+		    $newsize = $newsize * 1024;
+		} elsif ($unit eq 'M') {
+		    $newsize = $newsize * 1024 * 1024;
+		} elsif ($unit eq 'G') {
+		    $newsize = $newsize * 1024 * 1024 * 1024;
+		} elsif ($unit eq 'T') {
+		    $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
+		}
+	    }
+	    $newsize += $size if $ext;
+	    $newsize = int($newsize);
+
+	    die "unable to skrink disk size\n" if $newsize < $size;
+
+	    return if $size == $newsize;
+
+            PVE::Cluster::log_msg('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
+
+	    # FIXME: volume_resize doesn't do anything if $running=1?
+	    PVE::Storage::volume_resize($storage_cfg, $volid, $newsize, 0);
+
+	    $mp->{size} = $newsize/1024; # kB
+	    $conf->{$disk} = PVE::LXC::print_ct_mountpoint($mp, $disk eq 'rootfs');
+
+	    PVE::LXC::write_config($vmid, $conf);
+
+	    if ($format eq 'raw') {
+		my $path = PVE::Storage::path($storage_cfg, $volid, undef);
+		if ($running) {
+		    # NOTE: do not use loopdevices_list as losetup's paths get scrambled after a container started
+		    # (possibly due to namespaces?)
+		    $path = PVE::LXC::query_loopdev($path);
+		    die "internal error: CT running but mountpoint not attached to a loop device"
+			if !$path; # fixme: zvols and other storages?
+		    PVE::Tools::run_command(['losetup', '--set-capacity', $path]);
+
+		    # In order for resize2fs to know that we need online-resizing a mountpoint needs
+		    # to be visible to it in its namespace.
+		    # To not interfere with the rest of the system we unshare the current mount namespace,
+		    # mount over /tmp and then run resize2fs.
+
+		    # interestingly we don't need to e2fsck on mounted systems...
+		    my $quoted = PVE::Tools::shellquote($path);
+		    my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
+		    PVE::Tools::run_command(['unshare', '-m', '--', 'sh', '-c', $cmd]);
+		} else {
+		    PVE::Tools::run_command(['e2fsck', '-f', '-y', $path]);
+		    PVE::Tools::run_command(['resize2fs', $path]);
+		}
+	    }
+	};
+
+	PVE::LXC::lock_container($vmid, undef, $code);
+
+	return undef;
+    }});
+
 1;
diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm
index 5bc08a6..c9dec51 100644
--- a/src/PVE/LXC.pm
+++ b/src/PVE/LXC.pm
@@ -1848,6 +1848,20 @@ sub blockdevices_list {
     return $bdevs;
 }
 
+sub query_loopdev {
+    my ($path) = @_;
+    my $found;
+    my $parser = sub {
+	my $line = shift;
+	if ($line =~ m@^(/dev/loop\d+):@) {
+	    $found = $1;
+	}
+    };
+    my $cmd = ['losetup', '--associated', $path];
+    PVE::Tools::run_command($cmd, outfunc => $parser);
+    return $found;
+}
+
 sub find_loopdev {
     my ($loopdevs, $path) = @_;
 
@@ -1910,6 +1924,18 @@ sub attach_loops {
     return $loopdevs;
 }
 
+sub detach_loop_devices {
+    my ($devs, $noerr) = @_;
+    foreach my $dev (@$devs) {
+	my $cmd = ['losetup', '-d', $dev];
+	if ($noerr) {
+	    eval { PVE::Tools::run_command($cmd, errfunc => sub {}); };
+	} else {
+	    PVE::Tools::run_command($cmd);
+	}
+    }
+}
+
 sub detach_loops {
     my ($storage_cfg, $vollist, $snapname) = @_;
 
@@ -1933,9 +1959,9 @@ sub detach_loops {
 }
 
 sub umount_all {
-    my ($vmid, $storage_cfg, $conf, $noerr, $loopdevs) = @_;
+    my ($vmid, $storage_cfg, $conf, $noerr, $fixed_loopdevs) = @_;
 
-    $loopdevs ||= loopdevices_list();
+    my $loopdevs = $fixed_loopdevs || loopdevices_list();
 
     my $rootdir = "/var/lib/lxc/$vmid/rootfs";
     my $volid_list = get_vm_volumes($conf);
@@ -1964,7 +1990,11 @@ sub umount_all {
 	}
     });
 
-    PVE::LXC::detach_loops($storage_cfg, $volid_list);
+    if ($fixed_loopdevs) {
+	detach_loop_devices([keys %$fixed_loopdevs], 1);
+    } else {
+	detach_loops($storage_cfg, $volid_list);
+    }
 }
 
 sub mount_all {
diff --git a/src/PVE/VZDump/LXC.pm b/src/PVE/VZDump/LXC.pm
index b99cd62..9edcd11 100644
--- a/src/PVE/VZDump/LXC.pm
+++ b/src/PVE/VZDump/LXC.pm
@@ -130,8 +130,7 @@ sub prepare {
 	&$check_mountpoint_empty($mountpoint);
 
 	my $volid_list = [$diskinfo->{volid}];
-	$task->{cleanup}->{detach_loops} = $volid_list;
-	my $loopdevs = PVE::LXC::attach_loops($self->{storecfg}, $volid_list);
+	my $loopdevs = $task->{cleanup}->{loopdevs} = PVE::LXC::attach_loops($self->{storecfg}, $volid_list);
 	my $mp = { volume => $diskinfo->{volid}, mp => "/" };
 	PVE::LXC::mountpoint_mount($mp, $mountpoint, $self->{storecfg}, $loopdevs);
 	$diskinfo->{dir} = $diskinfo->{mountpoint} = $mountpoint;
@@ -177,8 +176,7 @@ sub snapshot {
     # my $volid_list = PVE::LXC::get_vm_volumes($snapconf);
     my $volid_list = [$diskinfo->{volid}];
 
-    $task->{cleanup}->{detach_loops} = $volid_list;
-    my $loopdevs = PVE::LXC::attach_loops($self->{storecfg}, $volid_list, 'vzdump');
+    my $loopdevs = $task->{cleanup}->{loopdevs} = PVE::LXC::attach_loops($self->{storecfg}, $volid_list, 'vzdump');
 
     my $mountpoint = $default_mount_point;
 	
@@ -293,16 +291,8 @@ sub cleanup {
 	PVE::Tools::run_command(['umount', '-l', '-d', $mountpoint]);
     };
 
-    if (my $volid_list = $task->{cleanup}->{detach_vzdump_snapshot_loops}) {
-	PVE::LXC::detach_loops($self->{storecfg}, $volid_list, 'vzdump');
-    }
-
-    if (my $volid_list = $task->{cleanup}->{detach_loops}) {
-	if ($task->{cleanup}->{remove_snapshot}) {
-	    PVE::LXC::detach_loops($self->{storecfg}, $volid_list, 'vzdump');
-	} else {
-	    PVE::LXC::detach_loops($self->{storecfg}, $volid_list);
-	}
+    if (my $looplist = $task->{cleanup}->{loopdevs}) {
+	PVE::LXC::detach_loop_devices([keys %$looplist], 1);
     }
 
     if ($task->{cleanup}->{remove_snapshot}) {
diff --git a/src/pct b/src/pct
index 9c78d5a..350b558 100755
--- a/src/pct
+++ b/src/pct
@@ -110,6 +110,8 @@ my $cmddef = {
 		    }
 		}],
     set => [ 'PVE::API2::LXC::Config', 'update_vm', ['vmid'], { node => $nodename }],
+
+    resize => [ "PVE::API2::LXC::Config", 'resize_vm', ['vmid', 'disk', 'size'], { node => $nodename } ],
     
     create => [ 'PVE::API2::LXC', 'create_vm', ['vmid', 'ostemplate'], { node => $nodename }, $upid_exit ],
     restore => [ 'PVE::API2::LXC', 'create_vm', ['vmid', 'ostemplate'], { node => $nodename, restore => 1 }, $upid_exit ],
-- 
2.1.4




More information about the pve-devel mailing list