]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/API2/Qemu.pm
API/clone: do not overwrite global signal handlers
[qemu-server.git] / PVE / API2 / Qemu.pm
index 091f9e10fc110918a8a3605a0a232659e9814ffa..747172e21adbb73e23f3d45d7ac4316119755ccc 100644 (file)
@@ -15,6 +15,8 @@ use PVE::Exception qw(raise raise_param_exc raise_perm_exc);
 use PVE::Storage;
 use PVE::JSONSchema qw(get_standard_option);
 use PVE::RESTHandler;
+use PVE::ReplicationConfig;
+use PVE::GuestHelpers;
 use PVE::QemuConfig;
 use PVE::QemuServer;
 use PVE::QemuMigrate;
@@ -24,7 +26,6 @@ use PVE::INotify;
 use PVE::Network;
 use PVE::Firewall;
 use PVE::API2::Firewall::VM;
-use PVE::ReplicationTools;
 
 BEGIN {
     if (!$ENV{PVE_GENERATING_DOCS}) {
@@ -51,6 +52,7 @@ my $resolve_cdrom_alias = sub {
     }
 };
 
+my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
 my $check_storage_access = sub {
    my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
 
@@ -65,10 +67,13 @@ my $check_storage_access = sub {
            # nothing to check
        } elsif ($isCDROM && ($volid eq 'cdrom')) {
            $rpcenv->check($authuser, "/", ['Sys.Console']);
-       } elsif (!$isCDROM && ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/)) {
+       } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
            my ($storeid, $size) = ($2 || $default_storage, $3);
            die "no storage ID specified (and no default storage)\n" if !$storeid;
            $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
+           my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+           raise_param_exc({ storage => "storage '$storeid' does not support vm images"})
+               if !$scfg->{content}->{images};
        } else {
            PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid);
        }
@@ -120,7 +125,8 @@ my $create_disks = sub {
     my $vollist = [];
 
     my $res = {};
-    PVE::QemuServer::foreach_drive($settings, sub {
+
+    my $code = sub {
        my ($ds, $disk) = @_;
 
        my $volid = $disk->{file};
@@ -128,7 +134,7 @@ my $create_disks = sub {
        if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
            delete $disk->{size};
            $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
-       } elsif ($volid =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) {
+       } elsif ($volid =~ $NEW_DISK_RE) {
            my ($storeid, $size) = ($2 || $default_storage, $3);
            die "no storage ID specified (and no default storage)\n" if !$storeid;
            my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);
@@ -191,7 +197,9 @@ my $create_disks = sub {
 
            $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
        }
-    });
+    };
+
+    eval { PVE::QemuServer::foreach_drive($settings, $code); };
 
     # free allocated images on error
     if (my $err = $@) {
@@ -521,6 +529,9 @@ __PACKAGE__->register_method({
                PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
            };
 
+           # ensure no old replication state are exists
+           PVE::ReplicationState::delete_guest_states($vmid);
+
            return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
        };
 
@@ -529,6 +540,9 @@ __PACKAGE__->register_method({
            # test after locking
            PVE::Cluster::check_vmid_unused($vmid);
 
+           # ensure no old replication state are exists
+           PVE::ReplicationState::delete_guest_states($vmid);
+
            my $realcmd = sub {
 
                my $vollist = [];
@@ -894,7 +908,7 @@ my $update_vm_api  = sub {
     my $background_delay = extract_param($param, 'background_delay');
 
     my @paramarr = (); # used for log message
-    foreach my $key (keys %$param) {
+    foreach my $key (sort keys %$param) {
        push @paramarr, "-$key", $param->{$key};
     }
 
@@ -950,12 +964,35 @@ my $update_vm_api  = sub {
        push @delete, $opt;
     }
 
+    my $repl_conf = PVE::ReplicationConfig->new();
+    my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
+    my $check_replication = sub {
+       my ($drive) = @_;
+       return if !$is_replicated;
+       my $volid = $drive->{file};
+       return if !$volid || !($drive->{replicate}//1);
+       return if PVE::QemuServer::drive_is_cdrom($drive);
+       my ($storeid, $format);
+       if ($volid =~ $NEW_DISK_RE) {
+           $storeid = $2;
+           $format = $drive->{format} || PVE::Storage::storage_default_format($storecfg, $storeid);
+       } else {
+           ($storeid, undef) = PVE::Storage::parse_volume_id($volid, 1);
+           $format = (PVE::Storage::parse_volname($storecfg, $volid))[6];
+       }
+       return if PVE::Storage::storage_can_replicate($storecfg, $storeid, $format);
+       my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+       return if $scfg->{shared};
+       die "cannot add non-replicatable volume to a replicated VM\n";
+    };
+
     foreach my $opt (keys %$param) {
        if (PVE::QemuServer::is_valid_drivename($opt)) {
            # cleanup drive path
            my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
            raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
            PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
+           $check_replication->($drive);
            $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
        } elsif ($opt =~ m/^net(\d+)$/) {
            # add macaddr
@@ -1008,7 +1045,7 @@ my $update_vm_api  = sub {
            foreach my $opt (@delete) {
                $modified->{$opt} = 1;
                $conf = PVE::QemuConfig->load_config($vmid); # update/reload
-               if (!defined($conf->{$opt})) {
+               if (!defined($conf->{$opt}) && !defined($conf->{pending}->{$opt})) {
                    warn "cannot delete '$opt' - not set in current configuration!\n";
                    $modified->{$opt} = 0;
                    next;
@@ -1051,12 +1088,6 @@ my $update_vm_api  = sub {
                        if defined($conf->{pending}->{$opt});
 
                    &$create_disks($rpcenv, $authuser, $conf->{pending}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
-               } elsif ($opt eq "replicate") {
-                   die "Not all volumes are syncable, please check your config\n"
-                       if !PVE::ReplicationTools::check_guest_volumes_syncable($conf, 'qemu');
-                   my $repl = PVE::JSONSchema::check_format('pve-replicate', $param->{opt});
-                   PVE::Cluster::check_node_exists($repl->{target});
-                   $conf->{$opt} = $param->{$opt};
                } else {
                    $conf->{pending}->{$opt} = $param->{$opt};
                }
@@ -1283,6 +1314,10 @@ __PACKAGE__->register_method({
        die "unable to remove VM $vmid - used in HA resources\n"
            if PVE::HA::Config::vm_is_ha_managed($vmid);
 
+       # do not allow destroy if there are replication jobs
+       my $repl_conf = PVE::ReplicationConfig->new();
+       $repl_conf->check_for_existing_jobs($vmid);
+
        # early tests (repeat after locking)
        die "VM $vmid is running - destroy failed\n"
            if PVE::QemuServer::check_running($vmid);
@@ -1431,6 +1466,7 @@ __PACKAGE__->register_method({
                $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
 
                my $sock = IO::Socket::IP->new(
+                   ReuseAddr => 1,
                    Listen => 1,
                    LocalPort => $port,
                    Proto => 'tcp',
@@ -1753,7 +1789,7 @@ __PACKAGE__->register_method({
 
                my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
 
-               print "Executing HA start for VM $vmid\n";
+               print "Requesting HA start for VM $vmid\n";
 
                PVE::Tools::run_command($cmd);
 
@@ -1850,7 +1886,7 @@ __PACKAGE__->register_method({
 
                my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
 
-               print "Executing HA stop for VM $vmid\n";
+               print "Requesting HA stop for VM $vmid\n";
 
                PVE::Tools::run_command($cmd);
 
@@ -2020,7 +2056,7 @@ __PACKAGE__->register_method({
 
                my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
 
-               print "Executing HA stop for VM $vmid\n";
+               print "Requesting HA stop for VM $vmid\n";
 
                PVE::Tools::run_command($cmd);
 
@@ -2445,12 +2481,12 @@ __PACKAGE__->register_method({
                        $newconf->{$opt} = $value; # simply copy configuration
                    } else {
                        if ($param->{full}) {
-                           die "Full clone feature is not available"
+                           die "Full clone feature is not supported for drive '$opt'\n"
                                if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
                            $fullclone->{$opt} = 1;
                        } else {
                            # not full means clone instead of copy
-                           die "Linked clone feature is not available"
+                           die "Linked clone feature is not supported for drive '$opt'\n"
                                if !PVE::Storage::volume_has_feature($storecfg, 'clone', $drive->{file}, $snapname, $running);
                        }
                        $drives->{$opt} = $drive;
@@ -2496,7 +2532,10 @@ __PACKAGE__->register_method({
                my $jobs = {};
 
                eval {
-                   local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; };
+                   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);
 
@@ -2843,7 +2882,7 @@ __PACKAGE__->register_method({
 
                my $cmd = ['ha-manager', 'migrate', $service, $target];
 
-               print "Executing HA migrate for VM $vmid to node $target\n";
+               print "Requesting HA migration for VM $vmid to node $target\n";
 
                PVE::Tools::run_command($cmd);
 
@@ -2855,12 +2894,14 @@ __PACKAGE__->register_method({
        } else {
 
            my $realcmd = sub {
-               my $upid = shift;
-
                PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
            };
 
-           return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
+           my $worker = sub {
+               return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
+           };
+
+           return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
        }
 
     }});
@@ -3387,7 +3428,12 @@ __PACKAGE__->register_method({
            PVE::QemuConfig->snapshot_rollback($vmid, $snapname);
        };
 
-       return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
+       my $worker = sub {
+           # hold migration lock, this makes sure that nobody create replication snapshots
+           return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
+       };
+
+       return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
     }});
 
 __PACKAGE__->register_method({