X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FAPI2%2FQemu.pm;h=191ffa6d75cc6d8c3a802f903ce15344b4780fa6;hb=3a11fadb4107f0d79e8006d19f7a26df2ee23cf6;hp=8930313361b720b74ae23a908af009a62caaf9a9;hpb=d7fd6a445b85b55bc56f42ab24ef734838eb4f84;p=qemu-server.git diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 89303133..191ffa6d 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -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) = @_; @@ -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,78 @@ my $update_vm_api = sub { print "update VM $vmid: " . join (' ', @paramarr) . "\n"; - foreach my $opt (@delete) { # delete + # write updates to pending section + + my $modified = {}; # record what $option we modify + + foreach my $opt (@delete) { + $modified->{$opt} = 1; $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 + $modified->{$opt} = 1; + $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 + + if ($running) { + my $errors = {}; + PVE::QemuServer::vmconfig_hotplug_pending($vmid, $conf, $storecfg, $modified, $errors); + raise_param_exc($errors) if scalar(keys %$errors); + } else { + 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 +1054,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}); } @@ -1420,7 +1479,7 @@ __PACKAGE__->register_method({ # check is done by verifying the VNC ticket (inside VNC protocol). my $port = $param->{port}; - + return { port => $port }; }}); @@ -1459,12 +1518,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; }}); @@ -1665,7 +1724,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', @@ -1709,7 +1768,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; @@ -2119,7 +2178,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', { @@ -2253,10 +2311,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}; @@ -2267,6 +2329,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}) { @@ -2464,9 +2534,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 $@; @@ -2715,9 +2785,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']); @@ -2836,11 +2903,6 @@ __PACKAGE__->register_method({ type => 'boolean', description => "Save the vmstate", }, - freezefs => { - optional => 1, - type => 'boolean', - description => "Freeze the filesystem", - }, description => { optional => 1, type => 'string', @@ -2870,8 +2932,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);