[pve-devel] [PATCH v4 container 3/5] refactor locking & add full clones
Wolfgang Bumiller
w.bumiller at proxmox.com
Tue Oct 10 10:15:01 CEST 2017
---
src/PVE/API2/LXC.pm | 212 ++++++++++++++++++++++++++++------------------------
1 file changed, 113 insertions(+), 99 deletions(-)
diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm
index 0397224..2d84f0b 100644
--- a/src/PVE/API2/LXC.pm
+++ b/src/PVE/API2/LXC.pm
@@ -1191,6 +1191,9 @@ __PACKAGE__->register_method({
my $storage = extract_param($param, 'storage');
+ die "Full clone requires a target storage.\n"
+ if $param->{full} && !$storage;
+
my $localnode = PVE::INotify::nodename();
my $storecfg = PVE::Storage::config();
@@ -1202,142 +1205,153 @@ __PACKAGE__->register_method({
PVE::Cluster::check_cfs_quorum();
- my $running = PVE::LXC::check_running($vmid) || 0;
-
- my $clonefn = sub {
-
- # do all tests after lock
- # we also try to do all tests before we fork the worker
- my $conf = PVE::LXC::Config->load_config($vmid);
-
- PVE::LXC::Config->check_lock($conf);
+ my $conffile;
+ my $newconf = {};
+ my $mountpoints = {};
+ my $fullclone = {};
+ my $vollist = [];
- my $verify_running = PVE::LXC::check_running($vmid) || 0;
-
- die "unexpected state change\n" if $verify_running != $running;
+ PVE::LXC::Config->lock_config($vmid, sub {
+ my $src_conf = PVE::LXC::Config->set_lock($vmid, 'disk');
+ eval {
+ die "snapshot '$snapname' does not exist\n"
+ if $snapname && !defined($src_conf->{snapshots}->{$snapname});
- die "snapshot '$snapname' does not exist\n"
- if $snapname && !defined( $conf->{snapshots}->{$snapname});
+ my $running = PVE::LXC::check_running($vmid) || 0;
- my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
+ my $src_conf = $snapname ? $src_conf->{snapshots}->{$snapname} : $src_conf;
- my $conffile = PVE::LXC::Config->config_file($newid);
- die "unable to create CT $newid: config file already exists\n"
- if -f $conffile;
+ $conffile = PVE::LXC::Config->config_file($newid);
+ die "unable to create CT $newid: config file already exists\n"
+ if -f $conffile;
- my $newconf = { lock => 'clone' };
- my $mountpoints = {};
- my $fullclone = {};
- my $vollist = [];
+ foreach my $opt (keys %$src_conf) {
+ next if $opt =~ m/^unused\d+$/;
- foreach my $opt (keys %$oldconf) {
- my $value = $oldconf->{$opt};
+ my $value = $src_conf->{$opt};
- # no need to copy unused images, because VMID(owner) changes anyways
- next if $opt =~ m/^unused\d+$/;
+ if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
+ my $mp = $opt eq 'rootfs' ?
+ PVE::LXC::Config->parse_ct_rootfs($value) :
+ PVE::LXC::Config->parse_ct_mountpoint($value);
- if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
- my $mp = $opt eq 'rootfs' ?
- PVE::LXC::Config->parse_ct_rootfs($value) :
- PVE::LXC::Config->parse_ct_mountpoint($value);
+ if ($mp->{type} eq 'volume') {
+ my $volid = $mp->{volume};
+ if ($param->{full}) {
+ die "Cannot do full clones on a running container without snapshots\n"
+ if $running && !defined($snapname);
+ $fullclone->{$opt} = 1;
+ } else {
+ # not full means clone instead of copy
+ die "Linked clone feature for '$volid' is not available\n"
+ if !PVE::Storage::volume_has_feature($storecfg, 'clone', $volid, $snapname, $running);
+ }
- if ($mp->{type} eq 'volume') {
- my $volid = $mp->{volume};
- if ($param->{full}) {
- die "fixme: full clone not implemented";
+ $mountpoints->{$opt} = $mp;
+ push @$vollist, $volid;
- die "Full clone feature for '$volid' is not available\n"
- if !PVE::Storage::volume_has_feature($storecfg, 'copy', $volid, $snapname, $running);
- $fullclone->{$opt} = 1;
} else {
- # not full means clone instead of copy
- die "Linked clone feature for '$volid' is not available\n"
- if !PVE::Storage::volume_has_feature($storecfg, 'clone', $volid, $snapname, $running);
+ # TODO: allow bind mounts?
+ die "unable to clone mountpint '$opt' (type $mp->{type})\n";
}
-
- $mountpoints->{$opt} = $mp;
- push @$vollist, $volid;
-
} else {
- # TODO: allow bind mounts?
- die "unable to clone mountpint '$opt' (type $mp->{type})\n";
+ # copy everything else
+ $newconf->{$opt} = $value;
}
+ }
- } else {
- # copy everything else
- $newconf->{$opt} = $value;
+ # Replace the 'disk' lock with a 'create' lock.
+ $newconf->{lock} = 'create';
+
+ delete $newconf->{template};
+ if ($param->{hostname}) {
+ $newconf->{hostname} = $param->{hostname};
}
- }
- delete $newconf->{template};
- if ($param->{hostname}) {
- $newconf->{hostname} = $param->{hostname};
- }
+ if ($param->{description}) {
+ $newconf->{description} = $param->{description};
+ }
- if ($param->{description}) {
- $newconf->{description} = $param->{description};
+ # create empty/temp config - this fails if CT already exists on other node
+ PVE::LXC::Config->write_config($newid, $newconf);
+ };
+ if (my $err = $@) {
+ eval { PVE::LXC::Config->remove_lock($vmid, 'disk') };
+ warn $@ if $@;
+ die $err;
}
+ });
- # create empty/temp config - this fails if CT already exists on other node
- PVE::Tools::file_set_contents($conffile, "# ctclone temporary file\nlock: clone\n");
+ my $update_conf = sub {
+ my ($key, $value) = @_;
+ return PVE::LXC::Config->lock_config($newid, sub {
+ my $conf = PVE::LXC::Config->load_config($newid);
+ die "Lost 'create' config lock, aborting.\n"
+ if !PVE::LXC::Config->has_lock($conf, 'create');
+ $conf->{$key} = $value;
+ PVE::LXC::Config->write_config($newid, $conf);
+ });
+ };
- my $realcmd = sub {
- my $upid = shift;
+ my $realcmd = sub {
+ my ($upid) = @_;
- my $newvollist = [];
+ my $newvollist = [];
- eval {
- local $SIG{INT} =
- local $SIG{TERM} =
- local $SIG{QUIT} =
- local $SIG{HUP} = sub { die "interrupted by signal\n"; };
-
- PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
+ eval {
+ local $SIG{INT} =
+ local $SIG{TERM} =
+ local $SIG{QUIT} =
+ local $SIG{HUP} = sub { die "interrupted by signal\n"; };
- foreach my $opt (keys %$mountpoints) {
- my $mp = $mountpoints->{$opt};
- my $volid = $mp->{volume};
+ PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
- if ($fullclone->{$opt}) {
- die "fixme: full clone not implemented\n";
- } else {
- print "create linked clone of mount point $opt ($volid)\n";
- my $newvolid = PVE::Storage::vdisk_clone($storecfg, $volid, $newid, $snapname);
- push @$newvollist, $newvolid;
- $mp->{volume} = $newvolid;
+ foreach my $opt (keys %$mountpoints) {
+ my $mp = $mountpoints->{$opt};
+ my $volid = $mp->{volume};
- $newconf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp, $opt eq 'rootfs');
- PVE::LXC::Config->write_config($newid, $newconf);
- }
+ my $newvolid;
+ if ($fullclone->{$opt}) {
+ print "create full clone of mountpoint $opt ($volid)\n";
+ $newvolid = PVE::LXC::copy_volume($mp, $newid, $storage, $storecfg, $newconf, $snapname);
+ } else {
+ print "create linked clone of mount point $opt ($volid)\n";
+ $newvolid = PVE::Storage::vdisk_clone($storecfg, $volid, $newid, $snapname);
}
- delete $newconf->{lock};
- PVE::LXC::Config->write_config($newid, $newconf);
+ push @$newvollist, $newvolid;
+ $mp->{volume} = $newvolid;
- PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
- };
- if (my $err = $@) {
- unlink $conffile;
-
- sleep 1; # some storage like rbd need to wait before release volume - really?
-
- foreach my $volid (@$newvollist) {
- eval { PVE::Storage::vdisk_free($storecfg, $volid); };
- warn $@ if $@;
- }
- die "clone failed: $err";
+ $update_conf->($opt, PVE::LXC::Config->print_ct_mountpoint($mp, $opt eq 'rootfs'));
}
- return;
+ PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
+ PVE::LXC::Config->remove_lock($newid, 'create');
};
+ my $err = $@;
- PVE::Firewall::clone_vmfw_conf($vmid, $newid);
+ # Unlock the source config in any case:
+ eval { PVE::LXC::Config->remove_lock($vmid, 'disk') };
+ warn $@ if $@;
- return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
+ if ($err) {
+ # Now cleanup the config & disks:
+ unlink $conffile;
+ sleep 1; # some storages like rbd need to wait before release volume - really?
+
+ foreach my $volid (@$newvollist) {
+ eval { PVE::Storage::vdisk_free($storecfg, $volid); };
+ warn $@ if $@;
+ }
+ die "clone failed: $err";
+ }
+
+ return;
};
- return PVE::LXC::Config->lock_config($vmid, $clonefn);
+ PVE::Firewall::clone_vmfw_conf($vmid, $newid);
+ return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
}});
--
2.11.0
More information about the pve-devel
mailing list