]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/API2/Qemu.pm
vmconfig_cleanup_pending: new method to clenup setting in [PENDING]
[qemu-server.git] / PVE / API2 / Qemu.pm
index 7c87833f1b5e033d2f9d8e34aefc14714256a679..405ca383663b211e10343695111f8ceb35ac69f8 100644 (file)
@@ -4,6 +4,7 @@ use strict;
 use warnings;
 use Cwd 'abs_path';
 use Net::SSLeay;
+use UUID;
 
 use PVE::Cluster qw (cfs_read_file cfs_write_file);;
 use PVE::SafeSyslog;
@@ -36,6 +37,21 @@ my $resolve_cdrom_alias = sub {
     }
 };
 
+my $test_deallocate_drive = sub {
+    my ($storecfg, $vmid, $key, $drive, $force) = @_;
+
+    if (!PVE::QemuServer::drive_is_cdrom($drive)) {
+       my $volid = $drive->{file};
+       if ( PVE::QemuServer::vm_is_volid_owner($storecfg, $vmid, $volid)) {
+           if ($force || $key =~ m/^unused/) {
+               my $sid = PVE::Storage::parse_volume_id($volid);
+               return $sid;
+           }
+       }
+    }
+
+    return undef;
+};
 
 my $check_storage_access = sub {
    my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
@@ -192,7 +208,7 @@ my $check_vm_modify_config_perm = sub {
        } elsif ($opt eq 'args' || $opt eq 'lock') {
            die "only root can set '$opt' config\n";
        } elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
-                $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
+                $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
        } elsif ($opt =~ m/^net\d+$/) {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
@@ -242,7 +258,7 @@ __PACKAGE__->register_method({
            next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
 
            my $data = $vmstatus->{$vmid};
-           $data->{vmid} = $vmid;
+           $data->{vmid} = int($vmid);
            push @$res, $data;
        }
 
@@ -431,6 +447,14 @@ __PACKAGE__->register_method({
                        $conf->{bootdisk} = $firstdisk;
                    }
 
+                   # auto generate uuid if user did not specify smbios1 option
+                   if (!$conf->{smbios1}) {
+                       my ($uuid, $uuid_str);
+                       UUID::generate($uuid);
+                       UUID::unparse($uuid, $uuid_str);
+                       $conf->{smbios1} = "uuid=$uuid_str";
+                   }
+
                    PVE::QemuServer::update_config_nolock($vmid, $conf);
 
                };
@@ -503,7 +527,7 @@ __PACKAGE__->register_method({
     }});
 
 __PACKAGE__->register_method ({
-    subclass => "PVE::API2::Firewall::VM",  
+    subclass => "PVE::API2::Firewall::VM",
     path => '{vmid}/firewall',
 });
 
@@ -630,43 +654,13 @@ __PACKAGE__->register_method({
        return $conf;
     }});
 
-my $vm_is_volid_owner = sub {
-    my ($storecfg, $vmid, $volid) =@_;
-
-    if ($volid !~  m|^/|) {
-       my ($path, $owner);
-       eval { ($path, $owner) = PVE::Storage::path($storecfg, $volid); };
-       if ($owner && ($owner == $vmid)) {
-           return 1;
-       }
-    }
-
-    return undef;
-};
-
-my $test_deallocate_drive = sub {
-    my ($storecfg, $vmid, $key, $drive, $force) = @_;
-
-    if (!PVE::QemuServer::drive_is_cdrom($drive)) {
-       my $volid = $drive->{file};
-       if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
-           if ($force || $key =~ m/^unused/) {
-               my $sid = PVE::Storage::parse_volume_id($volid);
-               return $sid;
-           }
-       }
-    }
-
-    return undef;
-};
-
 my $delete_drive = sub {
     my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
 
     if (!PVE::QemuServer::drive_is_cdrom($drive)) {
        my $volid = $drive->{file};
 
-       if (&$vm_is_volid_owner($storecfg, $vmid, $volid)) {
+       if (PVE::QemuServer::vm_is_volid_owner($storecfg, $vmid, $volid)) {
            if ($force || $key =~ m/^unused/) {
                eval {
                    # check if the disk is really unused
@@ -952,8 +946,8 @@ my $update_vm_api  = sub {
        PVE::QemuServer::check_lock($conf) if !$skiplock;
 
        if ($param->{memory} || defined($param->{balloon})) {
-           my $maxmem = $param->{memory} || $conf->{memory} || $defaults->{memory};
-           my $balloon = defined($param->{balloon}) ?  $param->{balloon} : $conf->{balloon};
+           my $maxmem = $param->{memory} || $conf->{pending}->{memory} || $conf->{memory} || $defaults->{memory};
+           my $balloon = defined($param->{balloon}) ? $param->{balloon} : $conf->{pending}->{balloon} || $conf->{balloon};
 
            die "balloon value too large (must be smaller than assigned memory)\n"
                if $balloon && $balloon > $maxmem;
@@ -965,13 +959,68 @@ my $update_vm_api  = sub {
 
            print "update VM $vmid: " . join (' ', @paramarr) . "\n";
 
-           foreach my $opt (@delete) { # delete
+           # write updates to pending section
+
+           foreach my $opt (@delete) {
                $conf = PVE::QemuServer::load_config($vmid); # update/reload
-               &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force);
+               if ($opt =~ m/^unused/) {
+                   $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
+                   my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
+                   if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, $force)) {
+                       $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
+                       &$delete_drive($conf, $storecfg, $vmid, $opt, $drive);
+                       PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+                   }
+               } elsif (PVE::QemuServer::valid_drivename($opt)) {
+                   $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
+                   PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $conf, PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt}))
+                       if defined($conf->{pending}->{$opt});
+                   PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt);
+                   PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+               } else {
+                   PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt);
+                   PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+               }
+           }
+
+           foreach my $opt (keys %$param) { # add/change
+               $conf = PVE::QemuServer::load_config($vmid); # update/reload
+               next if defined($conf->{pending}->{$opt}) && ($param->{$opt} eq $conf->{pending}->{$opt}); # skip if nothing changed
+
+               if (PVE::QemuServer::valid_drivename($opt)) {
+                   my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
+                   if (PVE::QemuServer::drive_is_cdrom($drive)) { # CDROM
+                       $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
+                   } else {
+                       $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
+                   }
+                   PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $conf, PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt}))
+                       if defined($conf->{pending}->{$opt});
+
+                   &$create_disks($rpcenv, $authuser, $conf->{pending}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
+               } else {
+                   $conf->{pending}->{$opt} = $param->{$opt};
+               }
+               PVE::QemuServer::vmconfig_undelete_pending_option($conf, $opt);
+               PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
            }
 
+           # remove pending changes when nothing changed
+           $conf = PVE::QemuServer::load_config($vmid); # update/reload
+           my $changes = PVE::QemuServer::vmconfig_cleanup_pending($conf);
+           PVE::QemuServer::update_config_nolock($vmid, $conf, 1) if $changes;
+
+           return if !scalar(keys %{$conf->{pending}});
+
            my $running = PVE::QemuServer::check_running($vmid);
 
+           # apply pending changes
+
+           $conf = PVE::QemuServer::load_config($vmid); # update/reload
+           PVE::QemuServer::vmconfig_apply_pending($vmid, $conf, $storecfg, $running);
+
+           return; # TODO: remove old code below
+
            foreach my $opt (keys %$param) { # add/change
 
                $conf = PVE::QemuServer::load_config($vmid); # update/reload
@@ -995,7 +1044,7 @@ my $update_vm_api  = sub {
                    } elsif($opt eq 'tablet' && $param->{$opt} == 0){
                        PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
                    }
-               
+
                    if($opt eq 'cores' && $conf->{maxcpus}){
                        PVE::QemuServer::qemu_cpu_hotplug($vmid, $conf, $param->{$opt});
                    }
@@ -1371,14 +1420,20 @@ __PACKAGE__->register_method({
     path => '{vmid}/vncwebsocket',
     method => 'GET',
     permissions => {
+       description => "You also need to pass a valid ticket (vncticket).",
        check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
     },
-    description => "Opens a weksocket for VNV traffic.",
+    description => "Opens a weksocket for VNC traffic.",
     parameters => {
        additionalProperties => 0,
        properties => {
            node => get_standard_option('pve-node'),
            vmid => get_standard_option('pve-vmid'),
+           vncticket => {
+               description => "Ticket from previous call to vncproxy.",
+               type => 'string',
+               maxLength => 512,
+           },
            port => {
                description => "Port number returned by previous vncproxy call.",
                type => 'integer',
@@ -1403,6 +1458,10 @@ __PACKAGE__->register_method({
        my $vmid = $param->{vmid};
        my $node = $param->{node};
 
+       my $authpath = "/vms/$vmid";
+
+       PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
+
        my $conf = PVE::QemuServer::load_config($vmid, $node); # VM exists ?
 
        # Note: VNC ports are acessible from outside, so we do not gain any
@@ -1410,7 +1469,7 @@ __PACKAGE__->register_method({
        # check is done by verifying the VNC ticket (inside VNC protocol).
 
        my $port = $param->{port};
-       
+
        return { port => $port };
     }});
 
@@ -1449,12 +1508,12 @@ __PACKAGE__->register_method({
 
        my $port = PVE::QemuServer::spice_port($vmid);
 
-       my ($ticket, undef, $remote_viewer_config) = 
+       my ($ticket, undef, $remote_viewer_config) =
            PVE::AccessControl::remote_viewer_config($authuser, $vmid, $node, $proxy, $title, $port);
-       
+
        PVE::QemuServer::vm_mon_cmd($vmid, "set_password", protocol => 'spice', password => $ticket);
        PVE::QemuServer::vm_mon_cmd($vmid, "expire_password", protocol => 'spice', time => "+30");
-       
+
        return $remote_viewer_config;
     }});
 
@@ -1655,7 +1714,7 @@ __PACKAGE__->register_method({
            node => get_standard_option('pve-node'),
            vmid => get_standard_option('pve-vmid'),
            skiplock => get_standard_option('skiplock'),
-           migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
+           migratedfrom => get_standard_option('pve-node', { optional => 1 }),
            timeout => {
                description => "Wait maximal timeout seconds.",
                type => 'integer',
@@ -1699,7 +1758,7 @@ __PACKAGE__->register_method({
 
        my $storecfg = PVE::Storage::config();
 
-       if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
+       if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type} ne 'ha') && !defined($migratedfrom)) {
 
            my $hacmd = sub {
                my $upid = shift;
@@ -2109,7 +2168,6 @@ __PACKAGE__->register_method({
                description => "Add the new VM to the specified pool.",
            },
             snapname => get_standard_option('pve-snapshot-name', {
-               requires => 'full',
                optional => 1,
             }),
            storage => get_standard_option('pve-storage-id', {
@@ -2243,10 +2301,14 @@ __PACKAGE__->register_method({
                    if (PVE::QemuServer::drive_is_cdrom($drive)) {
                        $newconf->{$opt} = $value; # simply copy configuration
                    } else {
-                       if ($param->{full} || !PVE::Storage::volume_is_base($storecfg,  $drive->{file})) {
+                       if ($param->{full}) {
                            die "Full clone feature is not available"
                                if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
                            $drive->{full} = 1;
+                       } else {
+                           # not full means clone instead of copy
+                           die "Linked clone feature is not available"
+                               if !PVE::Storage::volume_has_feature($storecfg, 'clone', $drive->{file}, $snapname, $running);
                        }
                        $drives->{$opt} = $drive;
                        push @$vollist, $drive->{file};
@@ -2257,6 +2319,14 @@ __PACKAGE__->register_method({
                }
            }
 
+            # auto generate a new uuid
+            my ($uuid, $uuid_str);
+            UUID::generate($uuid);
+            UUID::unparse($uuid, $uuid_str);
+           my $smbios1 = PVE::QemuServer::parse_smbios1($newconf->{smbios1} || '');
+           $smbios1->{uuid} = $uuid_str;
+           $newconf->{smbios1} = PVE::QemuServer::print_smbios1($smbios1);
+
            delete $newconf->{template};
 
            if ($param->{name}) {
@@ -2454,9 +2524,9 @@ __PACKAGE__->register_method({
 
                    PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
 
-                   eval { 
+                   eval {
                        # try to deactivate volumes - avoid lvm LVs to be active on several nodes
-                       PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ]) 
+                       PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ])
                            if !$running;
                    };
                    warn $@ if $@;
@@ -2705,9 +2775,6 @@ __PACKAGE__->register_method({
 
            die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
 
-           die "you can't online resize a virtio windows bootdisk\n"
-               if PVE::QemuServer::check_running($vmid) && $conf->{bootdisk} eq $disk && $conf->{ostype} =~ m/^w/ && $disk =~ m/^virtio/;
-
            my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
 
            $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
@@ -2826,11 +2893,6 @@ __PACKAGE__->register_method({
                type => 'boolean',
                description => "Save the vmstate",
            },
-           freezefs => {
-               optional => 1,
-               type => 'boolean',
-               description => "Freeze the filesystem",
-           },
            description => {
                optional => 1,
                type => 'string',
@@ -2860,8 +2922,8 @@ __PACKAGE__->register_method({
 
        my $realcmd = sub {
            PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
-           PVE::QemuServer::snapshot_create($vmid, $snapname, $param->{vmstate},
-                                            $param->{freezefs}, $param->{description});
+           PVE::QemuServer::snapshot_create($vmid, $snapname, $param->{vmstate}, 
+                                            $param->{description});
        };
 
        return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);