]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
fix #4435: devices list: avoid error for undefined value
[qemu-server.git] / PVE / QemuServer.pm
index b54ec69ea1bb484e2f714a8c16d63f652ad73fff..4292da74c8bb99fe794e619ed9b09a26aeb0d3fd 100644 (file)
@@ -116,7 +116,7 @@ PVE::JSONSchema::register_standard_option('pve-qm-stateuri', {
 });
 
 PVE::JSONSchema::register_standard_option('pve-qemu-machine', {
-       description => "Specifies the Qemu machine type.",
+       description => "Specifies the QEMU machine type.",
        type => 'string',
        pattern => '(pc|pc(-i440fx)?-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|q35|pc-q35-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|virt(?:-\d+(\.\d+)+)?(\+pve\d+)?)',
        maxLength => 40,
@@ -150,7 +150,7 @@ PVE::JSONSchema::register_format('pve-qm-watchdog', $watchdog_fmt);
 
 my $agent_fmt = {
     enabled => {
-       description => "Enable/disable communication with a Qemu Guest Agent (QGA) running in the VM.",
+       description => "Enable/disable communication with a QEMU Guest Agent (QGA) running in the VM.",
        type => 'boolean',
        default => 0,
        default_key => 1,
@@ -481,7 +481,7 @@ EODESC
     },
     agent => {
        optional => 1,
-       description => "Enable/disable communication with the Qemu Guest Agent and its properties.",
+       description => "Enable/disable communication with the QEMU Guest Agent and its properties.",
        type => 'string',
        format => $agent_fmt,
     },
@@ -2788,6 +2788,12 @@ sub check_local_storage_availability {
 sub check_running {
     my ($vmid, $nocheck, $node) = @_;
 
+    # $nocheck is set when called during a migration, in which case the config
+    # file might still or already reside on the *other* node
+    # - because rename has already happened, and current node is source
+    # - because rename hasn't happened yet, and current node is target
+    # - because rename has happened, current node is target, but hasn't yet
+    # processed it yet
     PVE::QemuConfig::assert_config_exists_on_node($vmid, $node) if !$nocheck;
     return PVE::QemuServer::Helpers::vm_running_locally($vmid);
 }
@@ -2813,7 +2819,7 @@ sub vzlist {
 our $vmstatus_return_properties = {
     vmid => get_standard_option('pve-vmid'),
     status => {
-       description => "Qemu process status.",
+       description => "QEMU process status.",
        type => 'string',
        enum => ['stopped', 'running'],
     },
@@ -2835,7 +2841,7 @@ our $vmstatus_return_properties = {
        optional => 1,
     },
     qmpstatus => {
-       description => "Qemu QMP agent status.",
+       description => "QEMU QMP agent status.",
        type => 'string',
        optional => 1,
     },
@@ -3508,6 +3514,47 @@ my sub should_disable_smm {
        $vga->{type} && $vga->{type} =~ m/^(serial\d+|none)$/;
 }
 
+my sub print_ovmf_drive_commandlines {
+    my ($conf, $storecfg, $vmid, $arch, $q35, $version_guard) = @_;
+
+    my $d = $conf->{efidisk0} ? parse_drive('efidisk0', $conf->{efidisk0}) : undef;
+
+    my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch, $d, $q35);
+    die "uefi base image '$ovmf_code' not found\n" if ! -f $ovmf_code;
+
+    my $var_drive_str = "if=pflash,unit=1,id=drive-efidisk0";
+    if ($d) {
+       my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1);
+       my ($path, $format) = $d->@{'file', 'format'};
+       if ($storeid) {
+           $path = PVE::Storage::path($storecfg, $d->{file});
+           if (!defined($format)) {
+               my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+               $format = qemu_img_format($scfg, $volname);
+           }
+       } elsif (!defined($format)) {
+           die "efidisk format must be specified\n";
+       }
+       # SPI flash does lots of read-modify-write OPs, without writeback this gets really slow #3329
+       if ($path =~ m/^rbd:/) {
+           $var_drive_str .= ',cache=writeback';
+           $path .= ':rbd_cache_policy=writeback'; # avoid write-around, we *need* to cache writes too
+       }
+       $var_drive_str .= ",format=$format,file=$path";
+
+       $var_drive_str .= ",size=" . (-s $ovmf_vars) if $format eq 'raw' && $version_guard->(4, 1, 2);
+       $var_drive_str .= ',readonly=on' if drive_is_read_only($conf, $d);
+    } else {
+       log_warn("no efidisk configured! Using temporary efivars disk.");
+       my $path = "/tmp/$vmid-ovmf.fd";
+       PVE::Tools::file_copy($ovmf_vars, $path, -s $ovmf_vars);
+       $var_drive_str .= ",format=raw,file=$path";
+       $var_drive_str .= ",size=" . (-s $ovmf_vars) if $version_guard->(4, 1, 2);
+    }
+
+    return ("if=pflash,unit=0,format=raw,readonly=on,file=$ovmf_code", $var_drive_str);
+}
+
 sub config_to_command {
     my ($storecfg, $vmid, $conf, $defaults, $forcemachine, $forcecpu,
         $pbs_backing) = @_;
@@ -3627,54 +3674,10 @@ sub config_to_command {
     }
 
     if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
-       my $d;
-       if (my $efidisk = $conf->{efidisk0}) {
-           $d = parse_drive('efidisk0', $efidisk);
-       }
-
-       my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch, $d, $q35);
-       die "uefi base image '$ovmf_code' not found\n" if ! -f $ovmf_code;
-
-       my ($path, $format);
-       my $read_only_str = '';
-       if ($d) {
-           my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1);
-           $format = $d->{format};
-           if ($storeid) {
-               $path = PVE::Storage::path($storecfg, $d->{file});
-               if (!defined($format)) {
-                   my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
-                   $format = qemu_img_format($scfg, $volname);
-               }
-           } else {
-               $path = $d->{file};
-               die "efidisk format must be specified\n"
-                   if !defined($format);
-           }
-
-           $read_only_str = ',readonly=on' if drive_is_read_only($conf, $d);
-       } else {
-           log_warn("no efidisk configured! Using temporary efivars disk.");
-           $path = "/tmp/$vmid-ovmf.fd";
-           PVE::Tools::file_copy($ovmf_vars, $path, -s $ovmf_vars);
-           $format = 'raw';
-       }
-
-       my $size_str = "";
-
-       if ($format eq 'raw' && $version_guard->(4, 1, 2)) {
-           $size_str = ",size=" . (-s $ovmf_vars);
-       }
-
-       # SPI flash does lots of read-modify-write OPs, without writeback this gets really slow #3329
-       my $cache = "";
-       if ($path =~ m/^rbd:/) {
-               $cache = ',cache=writeback';
-               $path .= ':rbd_cache_policy=writeback'; # avoid write-around, we *need* to cache writes too
-       }
-
-       push @$cmd, '-drive', "if=pflash,unit=0,format=raw,readonly=on,file=$ovmf_code";
-       push @$cmd, '-drive', "if=pflash,unit=1$cache,format=$format,id=drive-efidisk0$size_str,file=${path}${read_only_str}";
+       my ($code_drive_str, $var_drive_str) =
+           print_ovmf_drive_commandlines($conf, $storecfg, $vmid, $arch, $q35, $version_guard);
+       push $cmd->@*, '-drive', $code_drive_str;
+       push $cmd->@*, '-drive', $var_drive_str;
     }
 
     if ($q35) { # tell QEMU to load q35 config early
@@ -3747,7 +3750,7 @@ sub config_to_command {
        if ($path eq 'socket') {
            my $socket = "/var/run/qemu-server/${vmid}.serial$i";
            push @$devices, '-chardev', "socket,id=serial$i,path=$socket,server=on,wait=off";
-           # On aarch64, serial0 is the UART device. Qemu only allows
+           # On aarch64, serial0 is the UART device. QEMU only allows
            # connecting UART devices via the '-serial' command line, as
            # the device has a fixed slot on the hardware...
            if ($arch eq 'aarch64' && $i == 0) {
@@ -4191,7 +4194,7 @@ sub vm_devices_list {
        my $to_check = [];
        for my $d (@$devices_to_check) {
            $devices->{$d->{'qdev_id'}} = 1 if $d->{'qdev_id'};
-           next if !$d->{'pci_bridge'};
+           next if !$d->{'pci_bridge'} || !$d->{'pci_bridge'}->{devices};
 
            $devices->{$d->{'qdev_id'}} += scalar(@{$d->{'pci_bridge'}->{devices}});
            push @$to_check, @{$d->{'pci_bridge'}->{devices}};
@@ -5211,7 +5214,7 @@ sub vmconfig_delete_or_detach_drive {
 
 
 sub vmconfig_apply_pending {
-    my ($vmid, $conf, $storecfg, $errors) = @_;
+    my ($vmid, $conf, $storecfg, $errors, $skip_cloud_init) = @_;
 
     return if !scalar(keys %{$conf->{pending}});
 
@@ -5244,7 +5247,7 @@ sub vmconfig_apply_pending {
 
     PVE::QemuConfig->cleanup_pending($conf);
 
-    my $generate_cloudnit = undef;
+    my $generate_cloudinit = $skip_cloud_init ? 0 : undef;
 
     foreach my $opt (keys %{$conf->{pending}}) { # add/change
        next if $opt eq 'delete'; # just to be sure
@@ -5259,7 +5262,7 @@ sub vmconfig_apply_pending {
 
            if (is_valid_drivename($opt)) {
                my $drive = parse_drive($opt, $conf->{pending}->{$opt});
-               $generate_cloudnit = 1 if drive_is_cloudinit($drive);
+               $generate_cloudinit //= 1 if drive_is_cloudinit($drive);
            }
 
            $conf->{$opt} = delete $conf->{pending}->{$opt};
@@ -5268,7 +5271,7 @@ sub vmconfig_apply_pending {
 
     # write all changes at once to avoid unnecessary i/o
     PVE::QemuConfig->write_config($vmid, $conf);
-    if ($generate_cloudnit) {
+    if ($generate_cloudinit) {
        if (PVE::QemuServer::Cloudinit::apply_cloudinit_config($conf, $vmid)) {
            # After successful generation and if there were changes to be applied, update the
            # config to drop the {cloudinit} entry.
@@ -5621,7 +5624,7 @@ sub vm_start {
 #   statefile => 'tcp', 'unix' for migration or path/volid for RAM state
 #   skiplock => 0/1, skip checking for config lock
 #   skiptemplate => 0/1, skip checking whether VM is template
-#   forcemachine => to force Qemu machine (rollback/migration)
+#   forcemachine => to force QEMU machine (rollback/migration)
 #   forcecpu => a QEMU '-cpu' argument string to override get_cpu_options
 #   timeout => in seconds
 #   paused => start VM in paused state (backup)
@@ -5688,7 +5691,10 @@ sub vm_start_nolock {
     my $defaults = load_defaults();
 
     # set environment variable useful inside network script
-    $ENV{PVE_MIGRATED_FROM} = $migratedfrom if $migratedfrom;
+    # for remote migration the config is available on the target node!
+    if (!$migrate_opts->{remote_node}) {
+       $ENV{PVE_MIGRATED_FROM} = $migratedfrom;
+    }
 
     PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-start', 1);
 
@@ -5735,10 +5741,10 @@ sub vm_start_nolock {
        return $migration_ip;
     };
 
-    my $migrate_uri;
     if ($statefile) {
        if ($statefile eq 'tcp') {
-           my $localip = "localhost";
+           my $migrate = $res->{migrate} = { proto => 'tcp' };
+           $migrate->{addr} = "localhost";
            my $datacenterconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
            my $nodename = nodename();
 
@@ -5751,26 +5757,26 @@ sub vm_start_nolock {
            }
 
            if ($migration_type eq 'insecure') {
-               $localip = $get_migration_ip->($nodename);
-               $localip = "[$localip]" if Net::IP::ip_is_ipv6($localip);
+               $migrate->{addr} = $get_migration_ip->($nodename);
+               $migrate->{addr} = "[$migrate->{addr}]" if Net::IP::ip_is_ipv6($migrate->{addr});
            }
 
            my $pfamily = PVE::Tools::get_host_address_family($nodename);
-           my $migrate_port = PVE::Tools::next_migrate_port($pfamily);
-           $migrate_uri = "tcp:${localip}:${migrate_port}";
-           push @$cmd, '-incoming', $migrate_uri;
+           $migrate->{port} = PVE::Tools::next_migrate_port($pfamily);
+           $migrate->{uri} = "tcp:$migrate->{addr}:$migrate->{port}";
+           push @$cmd, '-incoming', $migrate->{uri};
            push @$cmd, '-S';
 
        } elsif ($statefile eq 'unix') {
            # should be default for secure migrations as a ssh TCP forward
            # tunnel is not deterministic reliable ready and fails regurarly
            # to set up in time, so use UNIX socket forwards
-           my $socket_addr = "/run/qemu-server/$vmid.migrate";
-           unlink $socket_addr;
+           my $migrate = $res->{migrate} = { proto => 'unix' };
+           $migrate->{addr} = "/run/qemu-server/$vmid.migrate";
+           unlink $migrate->{addr};
 
-           $migrate_uri = "unix:$socket_addr";
-
-           push @$cmd, '-incoming', $migrate_uri;
+           $migrate->{uri} = "unix:$migrate->{addr}";
+           push @$cmd, '-incoming', $migrate->{uri};
            push @$cmd, '-S';
 
        } elsif (-e $statefile) {
@@ -5925,10 +5931,9 @@ sub vm_start_nolock {
     eval { PVE::QemuServer::PCI::reserve_pci_usage($pci_id_list, $vmid, undef, $pid) };
     warn $@ if $@;
 
-    print "migration listens on $migrate_uri\n" if $migrate_uri;
-    $res->{migrate_uri} = $migrate_uri;
-
-    if ($statefile && $statefile ne 'tcp' && $statefile ne 'unix')  {
+    if (defined($res->{migrate})) {
+       print "migration listens on $res->{migrate}->{uri}\n";
+    } elsif ($statefile) {
        eval { mon_cmd($vmid, "cont"); };
        warn $@ if $@;
     }
@@ -5939,10 +5944,11 @@ sub vm_start_nolock {
 
        my $migrate_storage_uri;
        # nbd_protocol_version > 0 for unix socket support
-       if ($nbd_protocol_version > 0 && $migration_type eq 'secure') {
+       if ($nbd_protocol_version > 0 && ($migration_type eq 'secure' || $migration_type eq 'websocket')) {
            my $socket_path = "/run/qemu-server/$vmid\_nbd.migrate";
            mon_cmd($vmid, "nbd-server-start", addr => { type => 'unix', data => { path => $socket_path } } );
            $migrate_storage_uri = "nbd:unix:$socket_path";
+           $res->{migrate}->{unix_sockets} = [$socket_path];
        } else {
            my $nodename = nodename();
            my $localip = $get_migration_ip->($nodename);
@@ -5960,8 +5966,6 @@ sub vm_start_nolock {
            $migrate_storage_uri = "nbd:${localip}:${storage_migrate_port}";
        }
 
-       $res->{migrate_storage_uri} = $migrate_storage_uri;
-
        foreach my $opt (sort keys %$nbd) {
            my $drivestr = $nbd->{$opt}->{drivestr};
            my $volid = $nbd->{$opt}->{volid};
@@ -6358,6 +6362,9 @@ sub vm_suspend {
     }
 }
 
+# $nocheck is set when called as part of a migration - in this context the
+# location of the config file (source or target node) is not deterministic,
+# since migration cannot wait for pmxcfs to process the rename
 sub vm_resume {
     my ($vmid, $skiplock, $nocheck) = @_;
 
@@ -6365,7 +6372,23 @@ sub vm_resume {
        my $res = mon_cmd($vmid, 'query-status');
        my $resume_cmd = 'cont';
        my $reset = 0;
-       my $conf = PVE::QemuConfig->load_config($vmid);
+       my $conf;
+       if ($nocheck) {
+           $conf = eval { PVE::QemuConfig->load_config($vmid) }; # try on target node
+           if ($@) {
+               my $vmlist = PVE::Cluster::get_vmlist();
+               if (exists($vmlist->{ids}->{$vmid})) {
+                   my $node = $vmlist->{ids}->{$vmid}->{node};
+                   $conf = eval { PVE::QemuConfig->load_config($vmid, $node) }; # try on source node
+               }
+               if (!$conf) {
+                   PVE::Cluster::cfs_update(); # vmlist was wrong, invalidate cache
+                   $conf = PVE::QemuConfig->load_config($vmid); # last try on target node again
+               }
+           }
+       } else {
+           $conf = PVE::QemuConfig->load_config($vmid);
+       }
 
        if ($res->{status}) {
            return if $res->{status} eq 'running'; # job done, go home
@@ -6374,7 +6397,6 @@ sub vm_resume {
        }
 
        if (!$nocheck) {
-
            PVE::QemuConfig->check_lock($conf)
                if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
        }
@@ -7530,7 +7552,7 @@ sub qga_check_running {
 
     eval { mon_cmd($vmid, "guest-ping", timeout => 3); };
     if ($@) {
-       warn "Qemu Guest Agent is not running - $@" if !$nowarn;
+       warn "QEMU Guest Agent is not running - $@" if !$nowarn;
        return 0;
     }
     return 1;
@@ -8447,9 +8469,10 @@ sub add_nets_bridge_fdb {
            next;
        }
 
+       my $bridge = $net->{bridge};
        if ($have_sdn) {
-           PVE::Network::SDN::Zones::add_bridge_fdb($iface, $mac, $net->{bridge}, $net->{firewall});
-       } else {
+           PVE::Network::SDN::Zones::add_bridge_fdb($iface, $mac, $bridge, $net->{firewall});
+       } elsif (-d "/sys/class/net/$bridge/bridge") { # avoid fdb management with OVS for now
            PVE::Network::add_bridge_fdb($iface, $mac, $net->{firewall});
        }
     }
@@ -8465,9 +8488,10 @@ sub del_nets_bridge_fdb {
        my $net = parse_net($conf->{$opt}) or next;
        my $mac = $net->{macaddr} or next;
 
+       my $bridge = $net->{bridge};
        if ($have_sdn) {
-           PVE::Network::SDN::Zones::del_bridge_fdb($iface, $mac, $net->{bridge}, $net->{firewall});
-       } else {
+           PVE::Network::SDN::Zones::del_bridge_fdb($iface, $mac, $bridge, $net->{firewall});
+       } elsif (-d "/sys/class/net/$bridge/bridge") { # avoid fdb management with OVS for now
            PVE::Network::del_bridge_fdb($iface, $mac, $net->{firewall});
        }
     }