X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FQemuServer.pm;h=c346af2cc1c5105d7288cca52acab0b0990dc898;hb=d5535a00c45b63fe4245abbd5949371560464e99;hp=ff593a6ecd394e055ecd6313139698b951aaae35;hpb=8aa2ed7c859ade2cf67f69936207f88fd6ab02eb;p=qemu-server.git diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index ff593a6..c346af2 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -21,6 +21,7 @@ use JSON; use Fcntl; use PVE::SafeSyslog; use Storable qw(dclone); +use MIME::Base64; use PVE::Exception qw(raise raise_param_exc); use PVE::Storage; use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach $IPV6RE); @@ -54,7 +55,7 @@ my $OVMF = { ], }; -my $qemu_snap_storage = {rbd => 1, sheepdog => 1}; +my $qemu_snap_storage = { rbd => 1 }; my $cpuinfo = PVE::ProcFSTools::read_cpuinfo(); @@ -166,7 +167,7 @@ my $cpu_vendor_list = { max => 'default', }; -my $cpu_flag = qr/[+-](pcid|spec-ctrl|ibpb|ssbd|virt-ssbd|amd-ssbd|amd-no-ssb|pdpe1gb)/; +my $cpu_flag = qr/[+-](pcid|spec-ctrl|ibpb|ssbd|virt-ssbd|amd-ssbd|amd-no-ssb|pdpe1gb|md-clear)/; my $cpu_fmt = { cputype => { @@ -192,7 +193,7 @@ my $cpu_fmt = { flags => { description => "List of additional CPU flags separated by ';'." . " Use '+FLAG' to enable, '-FLAG' to disable a flag." - . " Currently supported flags: 'pcid', 'spec-ctrl', 'ibpb', 'ssbd', 'virt-ssbd', 'amd-ssbd', 'amd-no-ssb', 'pdpe1gb'.", + . " Currently supported flags: 'pcid', 'spec-ctrl', 'ibpb', 'ssbd', 'virt-ssbd', 'amd-ssbd', 'amd-no-ssb', 'pdpe1gb', 'md-clear'.", format_description => '+FLAG[;-FLAG...]', type => 'string', pattern => qr/$cpu_flag(;$cpu_flag)*/, @@ -266,6 +267,21 @@ my $ivshmem_fmt = { }, }; +my $audio_fmt = { + device => { + type => 'string', + enum => [qw(ich9-intel-hda intel-hda AC97)], + description => "Configure an audio device." + }, + driver => { + type => 'string', + enum => ['spice'], + default => 'spice', + optional => 1, + description => "Driver backend for the audio device." + }, +}; + my $confdesc = { onboot => { optional => 1, @@ -591,7 +607,7 @@ EODESCR smbios1 => { description => "Specify SMBIOS type 1 fields.", type => 'string', format => 'pve-qm-smbios1', - maxLength => 256, + maxLength => 512, optional => 1, }, protection => { @@ -636,7 +652,13 @@ EODESCR format => $ivshmem_fmt, description => "Inter-VM shared memory. Useful for direct communication between VMs, or to the host.", optional => 1, - } + }, + audio0 => { + type => 'string', + format => $audio_fmt, + description => "Configure a audio device, useful in combination with QXL/Spice.", + optional => 1 + }, }; my $cicustom_fmt = { @@ -2358,7 +2380,7 @@ sub vmconfig_cleanup_pending { return $changes; } -# smbios: [manufacturer=str][,product=str][,version=str][,serial=str][,uuid=uuid][,sku=str][,family=str] +# smbios: [manufacturer=str][,product=str][,version=str][,serial=str][,uuid=uuid][,sku=str][,family=str][,base64=bool] my $smbios1_fmt = { uuid => { type => 'string', @@ -2369,46 +2391,51 @@ my $smbios1_fmt = { }, version => { type => 'string', - pattern => '\S+', - format_description => 'string', + pattern => '[A-Za-z0-9+\/]+={0,2}', + format_description => 'Base64 encoded string', description => "Set SMBIOS1 version.", optional => 1, }, serial => { type => 'string', - pattern => '\S+', - format_description => 'string', + pattern => '[A-Za-z0-9+\/]+={0,2}', + format_description => 'Base64 encoded string', description => "Set SMBIOS1 serial number.", optional => 1, }, manufacturer => { type => 'string', - pattern => '\S+', - format_description => 'string', + pattern => '[A-Za-z0-9+\/]+={0,2}', + format_description => 'Base64 encoded string', description => "Set SMBIOS1 manufacturer.", optional => 1, }, product => { type => 'string', - pattern => '\S+', - format_description => 'string', + pattern => '[A-Za-z0-9+\/]+={0,2}', + format_description => 'Base64 encoded string', description => "Set SMBIOS1 product ID.", optional => 1, }, sku => { type => 'string', - pattern => '\S+', - format_description => 'string', + pattern => '[A-Za-z0-9+\/]+={0,2}', + format_description => 'Base64 encoded string', description => "Set SMBIOS1 SKU string.", optional => 1, }, family => { type => 'string', - pattern => '\S+', - format_description => 'string', + pattern => '[A-Za-z0-9+\/]+={0,2}', + format_description => 'Base64 encoded string', description => "Set SMBIOS1 family string.", optional => 1, }, + base64 => { + type => 'boolean', + description => 'Flag to indicate that the SMBIOS values are base64 encoded', + optional => 1, + }, }; sub parse_smbios1 { @@ -2918,6 +2945,45 @@ sub shared_nodes { return $nodehash } +sub check_local_storage_availability { + my ($conf, $storecfg) = @_; + + my $nodelist = PVE::Cluster::get_nodelist(); + my $nodehash = { map { $_ => {} } @$nodelist }; + + foreach_drive($conf, sub { + my ($ds, $drive) = @_; + + my $volid = $drive->{file}; + return if !$volid; + + my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1); + if ($storeid) { + my $scfg = PVE::Storage::storage_config($storecfg, $storeid); + + if ($scfg->{disable}) { + foreach my $node (keys %$nodehash) { + $nodehash->{$node}->{unavailable_storages}->{$storeid} = 1; + } + } elsif (my $avail = $scfg->{nodes}) { + foreach my $node (keys %$nodehash) { + if (!$avail->{$node}) { + $nodehash->{$node}->{unavailable_storages}->{$storeid} = 1; + } + } + } + } + }); + + foreach my $node (values %$nodehash) { + if (my $unavail = $node->{unavailable_storages}) { + $node->{unavailable_storages} = [ sort keys %$unavail ]; + } + } + + return $nodehash +} + sub check_cmdline { my ($pidfile, $pid) = @_; @@ -3295,7 +3361,7 @@ sub foreach_volid { my $volhash = {}; my $test_volid = sub { - my ($volid, $is_cdrom, $replicate, $shared, $snapname) = @_; + my ($volid, $is_cdrom, $replicate, $shared, $snapname, $size) = @_; return if !$volid; @@ -3313,11 +3379,12 @@ sub foreach_volid { $volhash->{$volid}->{referenced_in_snapshot}->{$snapname} = 1 if defined($snapname); + $volhash->{$volid}->{size} = $size if $size; }; foreach_drive($conf, sub { my ($ds, $drive) = @_; - $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, undef); + $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, undef, $drive->{size}); }); foreach my $snapname (keys %{$conf->{snapshots}}) { @@ -3346,6 +3413,24 @@ sub conf_has_serial { return 0; } +sub conf_has_audio { + my ($conf, $id) = @_; + + $id //= 0; + my $audio = $conf->{"audio$id"}; + return undef if !defined($audio); + + my $audioproperties = PVE::JSONSchema::parse_property_string($audio_fmt, $audio); + my $audiodriver = $audioproperties->{driver} // 'spice'; + + return { + dev => $audioproperties->{device}, + dev_id => "audio$id", + backend => $audiodriver, + backend_id => "$audiodriver-backend${id}", + }; +} + sub vga_conf_has_spice { my ($vga) = @_; @@ -3524,7 +3609,26 @@ sub config_to_command { push @$cmd, '-daemonize'; if ($conf->{smbios1}) { - push @$cmd, '-smbios', "type=1,$conf->{smbios1}"; + my $smbios_conf = parse_smbios1($conf->{smbios1}); + if ($smbios_conf->{base64}) { + # Do not pass base64 flag to qemu + delete $smbios_conf->{base64}; + my $smbios_string = ""; + foreach my $key (keys %$smbios_conf) { + my $value; + if ($key eq "uuid") { + $value = $smbios_conf->{uuid} + } else { + $value = decode_base64($smbios_conf->{$key}); + } + # qemu accepts any binary data, only commas need escaping by double comma + $value =~ s/,/,,/g; + $smbios_string .= "," . $key . "=" . $value if $value; + } + push @$cmd, '-smbios', "type=1" . $smbios_string; + } else { + push @$cmd, '-smbios', "type=1,$conf->{smbios1}"; + } } if ($conf->{vmgenid}) { @@ -3563,6 +3667,15 @@ sub config_to_command { push @$cmd, '-drive', "if=pflash,unit=1,format=$format,id=drive-efidisk0,file=$path"; } + # load q35 config + if ($q35) { + # we use different pcie-port hardware for qemu >= 4.0 for passthrough + if (qemu_machine_feature_enabled($machine_type, $kvmver, 4, 0)) { + push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35-4.0.cfg'; + } else { + push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35.cfg'; + } + } # add usb controllers my @usbcontrollers = PVE::QemuServer::USB::get_usb_controllers($conf, $bridges, $arch, $machine_type, $usbdesc->{format}, $MAX_USB_DEVICES); @@ -3706,6 +3819,23 @@ sub config_to_command { } } + if (my $audio = conf_has_audio($conf)) { + + my $audiopciaddr = print_pci_addr("audio0", $bridges, $arch, $machine_type); + + my $id = $audio->{dev_id}; + if ($audio->{dev} eq 'AC97') { + push @$devices, '-device', "AC97,id=${id}${audiopciaddr}"; + } elsif ($audio->{dev} =~ /intel\-hda$/) { + push @$devices, '-device', "$audio->{dev},id=${id}${audiopciaddr}"; + push @$devices, '-device', "hda-micro,id=${id}-codec0,bus=${id}.0,cad=0"; + push @$devices, '-device', "hda-duplex,id=${id}-codec1,bus=${id}.0,cad=1"; + } else { + die "unkown audio device '$audio->{dev}', implement me!"; + } + + push @$devices, '-audiodev', "$audio->{backend},id=$audio->{backend_id}"; + } my $sockets = 1; $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused @@ -3754,7 +3884,7 @@ sub config_to_command { if ($vga->{type} && $vga->{type} !~ m/^serial\d+$/ && $vga->{type} ne 'none'){ push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_type, undef, $qxlnum, $bridges); my $socket = vnc_socket($vmid); - push @$cmd, '-vnc', "unix:$socket,x509,password"; + push @$cmd, '-vnc', "unix:$socket,password"; } else { push @$cmd, '-vga', 'none' if $vga->{type} eq 'none'; push @$cmd, '-nographic'; @@ -3789,7 +3919,7 @@ sub config_to_command { push @$machineFlags, "type=${machine_type}"; } - if ($conf->{startdate}) { + if (($conf->{startdate}) && ($conf->{startdate} ne 'now')) { push @$rtcFlags, "base=$conf->{startdate}"; } elsif ($useLocaltime) { push @$rtcFlags, 'base=localtime'; @@ -3803,11 +3933,6 @@ sub config_to_command { push @$cmd, '-k', $conf->{keyboard} if defined($conf->{keyboard}); - # enable sound - #my $soundhw = $conf->{soundhw} || $defaults->{soundhw}; - #push @$cmd, '-soundhw', 'es1370'; - #push @$cmd, '-soundhw', $soundhw if $soundhw; - if (parse_guest_agent($conf)->{enabled}) { my $qgasocket = qmp_socket($vmid, 1); my $pciaddr = print_pci_addr("qga0", $bridges, $arch, $machine_type); @@ -5310,12 +5435,13 @@ sub vm_start { PVE::Storage::activate_volumes($storecfg, $vollist); - if (-d "/sys/fs/cgroup/systemd/qemu.slice/$vmid.scope") { - eval { - run_command(['/bin/systemctl', 'stop', "$vmid.scope"], - outfunc => sub {}, errfunc => sub {}); - }; - } + eval { + run_command(['/bin/systemctl', 'stop', "$vmid.scope"], + outfunc => sub {}, errfunc => sub {}); + }; + # Issues with the above 'stop' not being fully completed are extremely rare, a very low + # timeout should be more than enough here... + PVE::Systemd::wait_for_unit_removed("$vmid.scope", 5); my $cpuunits = defined($conf->{cpuunits}) ? $conf->{cpuunits} : $defaults->{cpuunits}; @@ -5459,9 +5585,8 @@ sub vm_qmp_command { my $res; my $timeout; - if ($cmd->{arguments} && $cmd->{arguments}->{timeout}) { - $timeout = $cmd->{arguments}->{timeout}; - delete $cmd->{arguments}->{timeout}; + if ($cmd->{arguments}) { + $timeout = delete $cmd->{arguments}->{timeout}; } eval { @@ -5486,8 +5611,6 @@ sub vm_qmp_command { sub vm_human_monitor_command { my ($vmid, $cmdline) = @_; - my $res; - my $cmd = { execute => 'human-monitor-command', arguments => { 'command-line' => $cmdline}, @@ -5620,12 +5743,13 @@ sub vm_stop { PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-stop'); } - $timeout = 60 if !defined($timeout); - eval { if ($shutdown) { if (defined($conf) && parse_guest_agent($conf)->{enabled}) { - vm_qmp_command($vmid, { execute => "guest-shutdown" }, $nocheck); + vm_qmp_command($vmid, { + execute => "guest-shutdown", + arguments => { timeout => $timeout } + }, $nocheck); } else { vm_qmp_command($vmid, { execute => "system_powerdown" }, $nocheck); } @@ -5636,6 +5760,8 @@ sub vm_stop { my $err = $@; if (!$err) { + $timeout = 60 if !defined($timeout); + my $count = 0; while (($count < $timeout) && check_running($vmid, $nocheck)) { $count++; @@ -5794,7 +5920,8 @@ sub vm_sendkey { my $conf = PVE::QemuConfig->load_config($vmid); # there is no qmp command, so we use the human monitor command - vm_human_monitor_command($vmid, "sendkey $key"); + my $res = vm_human_monitor_command($vmid, "sendkey $key"); + die $res if $res ne ''; }); } @@ -7145,6 +7272,13 @@ sub add_hyperv_enlightenments { push @$cpuFlags , 'hv_synic'; push @$cpuFlags , 'hv_stimer'; } + + if (qemu_machine_feature_enabled ($machine_type, $kvmver, 3, 1)) { + push @$cpuFlags , 'hv_tlbflush'; + push @$cpuFlags , 'hv_ipi'; + # FIXME: AMD does not supports this currently, only add with special flag?? + #push @$cpuFlags , 'hv_evmcs'; + } } }