X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FAPI2%2FQemu.pm;h=b28373e0f53b141b673e3f4fa2a3d730672d1fab;hb=c3ddb94dc024dd166cd1211a4611d0c0e93de0da;hp=eef149b81e1abb0c0904e9c76855cccea8651942;hpb=b04ea5845346a8b13711abc03b58d86f2d6afb5f;p=qemu-server.git diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index eef149b..b28373e 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -9,6 +9,7 @@ use IO::Socket::IP; use URI::Escape; use PVE::Cluster qw (cfs_read_file cfs_write_file);; +use PVE::RRD; use PVE::SafeSyslog; use PVE::Tools qw(extract_param); use PVE::Exception qw(raise raise_param_exc raise_perm_exc); @@ -19,6 +20,7 @@ use PVE::ReplicationConfig; use PVE::GuestHelpers; use PVE::QemuConfig; use PVE::QemuServer; +use PVE::QemuServer::Monitor qw(mon_cmd); use PVE::QemuMigrate; use PVE::RPCEnvironment; use PVE::AccessControl; @@ -28,6 +30,8 @@ use PVE::Firewall; use PVE::API2::Firewall::VM; use PVE::API2::Qemu::Agent; use PVE::VZDump::Plugin; +use PVE::DataCenterConfig; +use PVE::SSHInfo; BEGIN { if (!$ENV{PVE_GENERATING_DOCS}) { @@ -66,7 +70,7 @@ my $check_storage_access = sub { my $volid = $drive->{file}; my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1); - if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || $volname eq 'cloudinit')) { + if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) { # nothing to check } elsif ($isCDROM && ($volid eq 'cdrom')) { $rpcenv->check($authuser, "/", ['Sys.Console']); @@ -143,8 +147,8 @@ my $create_disks = sub { if (!$volid || $volid eq 'none' || $volid eq 'cdrom') { delete $disk->{size}; - $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk); - } elsif ($volname eq 'cloudinit') { + $res->{$ds} = PVE::QemuServer::print_drive($disk); + } elsif (defined($volname) && $volname eq 'cloudinit') { $storeid = $storeid // $default_storage; die "no storage ID specified (and no default storage)\n" if !$storeid; my $scfg = PVE::Storage::storage_config($storecfg, $storeid); @@ -165,7 +169,7 @@ my $create_disks = sub { $disk->{media} = 'cdrom'; push @$vollist, $volid; delete $disk->{format}; # no longer needed - $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk); + $res->{$ds} = PVE::QemuServer::print_drive($disk); } elsif ($volid =~ $NEW_DISK_RE) { my ($storeid, $size) = ($2 || $default_storage, $3); die "no storage ID specified (and no default storage)\n" if !$storeid; @@ -184,7 +188,7 @@ my $create_disks = sub { $disk->{file} = $volid; $disk->{size} = PVE::Tools::convert_size($size, 'kb' => 'b'); delete $disk->{format}; # no longer needed - $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk); + $res->{$ds} = PVE::QemuServer::print_drive($disk); } else { PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $volid); @@ -207,7 +211,7 @@ my $create_disks = sub { $disk->{size} = $size; } - $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk); + $res->{$ds} = PVE::QemuServer::print_drive($disk); } }; @@ -279,6 +283,7 @@ my $generaloptions = { 'startup' => 1, 'tdf' => 1, 'template' => 1, + 'tags' => 1, }; my $vmpoweroptions = { @@ -310,6 +315,7 @@ my $check_vm_modify_config_perm = sub { # some checks (e.g., disk, serial port, usb) need to be done somewhere # else, as there the permission can be value dependend next if PVE::QemuServer::is_valid_drivename($opt); + next if $opt eq 'vmstate'; next if $opt eq 'cdrom'; next if $opt =~ m/^(?:unused|serial|usb)\d+$/; @@ -463,31 +469,20 @@ __PACKAGE__->register_method({ 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 $archive = extract_param($param, 'archive'); my $is_restore = !!$archive; - my $storage = extract_param($param, 'storage'); - + my $bwlimit = extract_param($param, 'bwlimit'); my $force = extract_param($param, 'force'); - - my $unique = extract_param($param, 'unique'); - my $pool = extract_param($param, 'pool'); - - my $bwlimit = extract_param($param, 'bwlimit'); - my $start_after_create = extract_param($param, 'start'); - - my $filename = PVE::QemuConfig->config_file($vmid); - - my $storecfg = PVE::Storage::config(); + my $storage = extract_param($param, 'storage'); + my $unique = extract_param($param, 'unique'); if (defined(my $ssh_keys = $param->{sshkeys})) { $ssh_keys = URI::Escape::uri_unescape($ssh_keys); @@ -496,6 +491,9 @@ __PACKAGE__->register_method({ PVE::Cluster::check_cfs_quorum(); + my $filename = PVE::QemuConfig->config_file($vmid); + my $storecfg = PVE::Storage::config(); + if (defined($pool)) { $rpcenv->check_pool_exist($pool); } @@ -527,7 +525,7 @@ __PACKAGE__->register_method({ raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive; PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive); - $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive); + $param->{$opt} = PVE::QemuServer::print_drive($drive); } } @@ -596,7 +594,7 @@ __PACKAGE__->register_method({ my $conf = $param; - my ($arch, undef) = PVE::QemuServer::get_basic_machine_info($conf); + my $arch = PVE::QemuServer::get_vm_arch($conf); eval { @@ -772,7 +770,7 @@ __PACKAGE__->register_method({ code => sub { my ($param) = @_; - return PVE::Cluster::create_rrd_graph( + return PVE::RRD::create_rrd_graph( "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{ds}, $param->{cf}); @@ -815,7 +813,7 @@ __PACKAGE__->register_method({ code => sub { my ($param) = @_; - return PVE::Cluster::create_rrd_data( + return PVE::RRD::create_rrd_data( "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf}); }}); @@ -1065,7 +1063,7 @@ my $update_vm_api = sub { 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); + $param->{$opt} = PVE::QemuServer::print_drive($drive); } elsif ($opt =~ m/^net(\d+)$/) { # add macaddr my $net = PVE::QemuServer::parse_net($param->{$opt}); @@ -1093,6 +1091,15 @@ my $update_vm_api = sub { die "checksum missmatch (file change by other user?)\n" if $digest && $digest ne $conf->{digest}; + # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?! + if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) { + if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') { + delete $conf->{lock}; # for check lock check, not written out + push @delete, 'lock'; # this is the real deal to write it out + } + push @delete, 'runningmachine' if $conf->{runningmachine}; + } + PVE::QemuConfig->check_lock($conf) if !$skiplock; foreach my $opt (keys %$revert) { @@ -1143,6 +1150,14 @@ my $update_vm_api = sub { delete $conf->{$opt}; PVE::QemuConfig->write_config($vmid, $conf); } + } elsif ($opt eq 'vmstate') { + PVE::QemuConfig->check_protection($conf, "can't remove vmstate '$val'"); + # the user needs Disk and PowerMgmt privileges to remove the vmstate + $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk', 'VM.PowerMgmt' ]); + if (PVE::QemuServer::try_deallocate_drive($storecfg, $vmid, $conf, $opt, { file => $val }, $rpcenv, $authuser, 1)) { + delete $conf->{$opt}; + PVE::QemuConfig->write_config($vmid, $conf); + } } elsif (PVE::QemuServer::is_valid_drivename($opt)) { PVE::QemuConfig->check_protection($conf, "can't remove drive '$opt'"); $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']); @@ -1177,7 +1192,7 @@ my $update_vm_api = sub { $conf = PVE::QemuConfig->load_config($vmid); # update/reload next if defined($conf->{pending}->{$opt}) && ($param->{$opt} eq $conf->{pending}->{$opt}); # skip if nothing changed - my ($arch, undef) = PVE::QemuServer::get_basic_machine_info($conf); + my $arch = PVE::QemuServer::get_vm_arch($conf); if (PVE::QemuServer::is_valid_drivename($opt)) { my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt}); @@ -1561,9 +1576,9 @@ __PACKAGE__->register_method({ if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) { (undef, $family) = PVE::Cluster::remote_node_ip($node); - my $sshinfo = PVE::Cluster::get_ssh_info($node); + my $sshinfo = PVE::SSHInfo::get_ssh_info($node); # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure - $remcmd = PVE::Cluster::ssh_info_to_command($sshinfo, $use_serial ? '-t' : '-T'); + $remcmd = PVE::SSHInfo::ssh_info_to_command($sshinfo, $use_serial ? '-t' : '-T'); } else { $family = PVE::Tools::get_host_address_family($node); } @@ -1701,8 +1716,8 @@ __PACKAGE__->register_method({ if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) { (undef, $family) = PVE::Cluster::remote_node_ip($node); - my $sshinfo = PVE::Cluster::get_ssh_info($node); - $remcmd = PVE::Cluster::ssh_info_to_command($sshinfo, '-t'); + my $sshinfo = PVE::SSHInfo::get_ssh_info($node); + $remcmd = PVE::SSHInfo::ssh_info_to_command($sshinfo, '-t'); push @$remcmd, '--'; } else { $family = PVE::Tools::get_host_address_family($node); @@ -1834,8 +1849,8 @@ __PACKAGE__->register_method({ 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"); + mon_cmd($vmid, "set_password", protocol => 'spice', password => $ticket); + mon_cmd($vmid, "expire_password", protocol => 'spice', time => "+30"); return $remote_viewer_config; }}); @@ -1971,7 +1986,7 @@ __PACKAGE__->register_method({ description => "CIDR of the (sub) network that is used for migration.", optional => 1, }, - machine => get_standard_option('pve-qm-machine'), + machine => get_standard_option('pve-qemu-machine'), targetstorage => { description => "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)", type => 'string', @@ -2119,7 +2134,7 @@ __PACKAGE__->register_method({ print "Requesting HA stop for VM $vmid\n"; - my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped']; + my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0']; PVE::Tools::run_command($cmd); return; }; @@ -2260,7 +2275,8 @@ __PACKAGE__->register_method({ # checking the qmp status here to get feedback to the gui/cli/api # and the status query should not take too long my $qmpstatus = eval { - PVE::QemuServer::vm_qmp_command($vmid, { execute => "query-status" }, 0); + PVE::QemuConfig::assert_config_exists_on_node($vmid); + mon_cmd($vmid, "query-status"); }; my $err = $@ if $@; @@ -2275,12 +2291,13 @@ __PACKAGE__->register_method({ if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') { + my $timeout = $param->{timeout} // 60; my $hacmd = sub { my $upid = shift; print "Requesting HA stop for VM $vmid\n"; - my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'stopped']; + my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"]; PVE::Tools::run_command($cmd); return; }; @@ -2340,7 +2357,8 @@ __PACKAGE__->register_method({ my $vmid = extract_param($param, 'vmid'); my $qmpstatus = eval { - PVE::QemuServer::vm_qmp_command($vmid, { execute => "query-status" }, 0); + PVE::QemuConfig::assert_config_exists_on_node($vmid); + mon_cmd($vmid, "query-status"); }; my $err = $@ if $@; @@ -2805,10 +2823,10 @@ __PACKAGE__->register_method({ } elsif (PVE::QemuServer::is_valid_drivename($opt)) { my $drive = PVE::QemuServer::parse_drive($opt, $value); die "unable to parse drive options for '$opt'\n" if !$drive; - if (PVE::QemuServer::drive_is_cdrom($drive)) { + if (PVE::QemuServer::drive_is_cdrom($drive, 1)) { $newconf->{$opt} = $value; # simply copy configuration } else { - if ($full) { + if ($full || PVE::QemuServer::drive_is_cloudinit($drive)) { 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; @@ -2887,7 +2905,7 @@ __PACKAGE__->register_method({ $newid, $storage, $format, $fullclone->{$opt}, $newvollist, $jobs, $skipcomplete, $oldconf->{agent}, $clonelimit); - $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive); + $newconf->{$opt} = PVE::QemuServer::print_drive($newdrive); PVE::QemuConfig->write_config($newid, $newconf); $i++; @@ -3081,7 +3099,7 @@ __PACKAGE__->register_method({ my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef, $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit); - $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive); + $conf->{$disk} = PVE::QemuServer::print_drive($newdrive); PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete}; @@ -3092,7 +3110,7 @@ __PACKAGE__->register_method({ PVE::QemuConfig->write_config($vmid, $conf); if ($running && PVE::QemuServer::parse_guest_agent($conf)->{fstrim_cloned_disks} && PVE::QemuServer::qga_check_running($vmid)) { - eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fstrim"); }; + eval { mon_cmd($vmid, "guest-fstrim"); }; } eval { @@ -3306,7 +3324,7 @@ __PACKAGE__->register_method({ targetstorage => get_standard_option('pve-storage-id', { description => "Default target storage.", optional => 1, - completion => \&PVE::QemuServer::complete_storage, + completion => \&PVE::QemuServer::complete_migration_storage, }), bwlimit => { description => "Override I/O bandwidth limit (in KiB/s).", @@ -3360,7 +3378,7 @@ __PACKAGE__->register_method({ if (PVE::QemuServer::check_running($vmid)) { die "can't migrate running VM without --online\n" if !$param->{online}; } else { - warn "VM isn't running. Doing offline migration instead\n." if $param->{online}; + warn "VM isn't running. Doing offline migration instead.\n" if $param->{online}; $param->{online} = 0; } @@ -3448,7 +3466,7 @@ __PACKAGE__->register_method({ my $res = ''; eval { - $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command}); + $res = PVE::QemuServer::Monitor::hmp_cmd($vmid, $param->{command}); }; $res = "ERROR: $@" if $@; @@ -3571,7 +3589,7 @@ __PACKAGE__->register_method({ PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize); $drive->{size} = $newsize; - $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive); + $conf->{$disk} = PVE::QemuServer::print_drive($drive); PVE::QemuConfig->write_config($vmid, $conf); }; @@ -3715,6 +3733,9 @@ __PACKAGE__->register_method({ die "unable to use snapshot name 'current' (reserved name)\n" if $snapname eq 'current'; + die "unable to use snapshot name 'pending' (reserved name)\n" + if lc($snapname) eq 'pending'; + my $realcmd = sub { PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname"); PVE::QemuConfig->snapshot_create($vmid, $snapname, $param->{vmstate},