]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
bump version to 3.3-12
[qemu-server.git] / PVE / QemuServer.pm
index f914db5484ca82878daf70a1d9706f2cead43b2e..9f3a2f39701920534aa110b98bfeae64dbcefb8f 100644 (file)
@@ -174,7 +174,7 @@ my $confdesc = {
         optional => 1,
         type => 'boolean',
         description => "Allow hotplug for disk and network device",
-        default => 0,
+        default => 1,
     },
     reboot => {
        optional => 1,
@@ -312,12 +312,12 @@ EODESC
        description => "Enable/disable Numa.",
        default => 0,
     },
-    maxcpus => {
+    vcpus => {
        optional => 1,
        type => 'integer',
-       description => "Maximum cpus for hotplug.",
+       description => "Number of hotplugged vcpus.",
        minimum => 1,
-       default => 1,
+       default => 0,
     },
     acpi => {
        optional => 1,
@@ -494,7 +494,7 @@ my $MAX_NUMA = 8;
 my $numadesc = {
     optional => 1,
     type => 'string', format => 'pve-qm-numanode',
-    typetext => "cpus=<id[-id],memory=<mb>[[,hostnodes=<id[-id]>][,policy=<preferred|bind|interleave>]]",
+    typetext => "cpus=<id[-id],memory=<mb>[[,hostnodes=<id[-id]>] [,policy=<preferred|bind|interleave>]]",
     description => "numa topology",
 };
 PVE::JSONSchema::register_standard_option("pve-qm-numanode", $numadesc);
@@ -504,13 +504,14 @@ for (my $i = 0; $i < $MAX_NUMA; $i++)  {
 }
 
 my $nic_model_list = ['rtl8139', 'ne2k_pci', 'e1000',  'pcnet',  'virtio',
-                     'ne2k_isa', 'i82551', 'i82557b', 'i82559er', 'vmxnet3'];
+                     'ne2k_isa', 'i82551', 'i82557b', 'i82559er', 'vmxnet3',
+                     'e1000-82540em', 'e1000-82544gc', 'e1000-82545em'];
 my $nic_model_list_txt = join(' ', sort @$nic_model_list);
 
 my $netdesc = {
     optional => 1,
     type => 'string', format => 'pve-qm-net',
-    typetext => "MODEL=XX:XX:XX:XX:XX:XX [,bridge=<dev>][,queues=<nbqueues>][,rate=<mbps>][,tag=<vlanid>][,firewall=0|1]",
+    typetext => "MODEL=XX:XX:XX:XX:XX:XX [,bridge=<dev>][,queues=<nbqueues>][,rate=<mbps>] [,tag=<vlanid>][,firewall=0|1],link_down=0|1]",
     description => <<EODESCR,
 Specify network devices.
 
@@ -1236,11 +1237,8 @@ sub print_netdevice_full {
          $device = 'virtio-net-pci';
      };
 
-    # qemu > 0.15 always try to boot from network - we disable that by
-    # not loading the pxe rom file
-    my $extra = ($bootorder !~ m/n/) ? "romfile=," : '';
     my $pciaddr = print_pci_addr("$netid", $bridges);
-    my $tmpstr = "$device,${extra}mac=$net->{macaddr},netdev=$netid$pciaddr,id=$netid";
+    my $tmpstr = "$device,mac=$net->{macaddr},netdev=$netid$pciaddr,id=$netid";
     if ($net->{queues} && $net->{queues} > 1 && $net->{model} eq 'virtio'){
        #Consider we have N queues, the number of vectors needed is 2*N + 2 (plus one config interrupt and control vq)
        my $vectors = $net->{queues} * 2 + 2;
@@ -1363,7 +1361,7 @@ sub parse_net {
 
     foreach my $kvp (split(/,/, $data)) {
 
-       if ($kvp =~ m/^(ne2k_pci|e1000|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er|vmxnet3)(=([0-9a-f]{2}(:[0-9a-f]{2}){5}))?$/i) {
+       if ($kvp =~ m/^(ne2k_pci|e1000|e1000-82540em|e1000-82544gc|e1000-82545em|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er|vmxnet3)(=([0-9a-f]{2}(:[0-9a-f]{2}){5}))?$/i) {
            my $model = lc($1);
            my $mac = defined($3) ? uc($3) : PVE::Tools::random_ether_addr();
            $res->{model} = $model;
@@ -1376,8 +1374,10 @@ sub parse_net {
            $res->{rate} = $1;
         } elsif ($kvp =~ m/^tag=(\d+)$/) {
             $res->{tag} = $1;
-        } elsif ($kvp =~ m/^firewall=(\d+)$/) {
+        } elsif ($kvp =~ m/^firewall=([01])$/) {
            $res->{firewall} = $1;
+       } elsif ($kvp =~ m/^link_down=([01])$/) {
+           $res->{link_down} = $1;
        } else {
            return undef;
        }
@@ -1397,7 +1397,8 @@ sub print_net {
     $res .= ",bridge=$net->{bridge}" if $net->{bridge};
     $res .= ",rate=$net->{rate}" if $net->{rate};
     $res .= ",tag=$net->{tag}" if $net->{tag};
-    $res .= ",firewall=$net->{firewall}" if $net->{firewall};
+    $res .= ",firewall=1" if $net->{firewall};
+    $res .= ",link_down=1" if $net->{link_down};
 
     return $res;
 }
@@ -2028,10 +2029,6 @@ sub write_vm_config {
        delete $conf->{smp};
     }
 
-    if ($conf->{maxcpus} && $conf->{sockets}) {
-       delete $conf->{sockets};
-    }
-
     my $used_volids = {};
 
     my $cleanup_config = sub {
@@ -2758,19 +2755,17 @@ sub config_to_command {
     $sockets = $conf->{sockets} if  $conf->{sockets};
 
     my $cores = $conf->{cores} || 1;
-    my $maxcpus = $conf->{maxcpus} if $conf->{maxcpus};
 
-    my $total_cores = $sockets * $cores;
-    my $allowed_cores = $cpuinfo->{cpus};
+    my $maxcpus = $sockets * $cores;
 
-    die "MAX $allowed_cores cores allowed per VM on this node\n"
-       if ($allowed_cores < $total_cores);
+    my $vcpus = $conf->{vcpus} ? $conf->{vcpus} : $maxcpus;
 
-    if ($maxcpus) {
-       push @$cmd, '-smp', "cpus=$cores,maxcpus=$maxcpus";
-    } else {
-       push @$cmd, '-smp', "sockets=$sockets,cores=$cores";
-    }
+    my $allowed_vcpus = $cpuinfo->{cpus};
+
+    die "MAX $maxcpus vcpus allowed per VM on this node\n"
+       if ($allowed_vcpus < $maxcpus);
+
+    push @$cmd, '-smp', "$vcpus,sockets=$sockets,cores=$cores,maxcpus=$maxcpus";
 
     push @$cmd, '-nodefaults';
 
@@ -2783,7 +2778,7 @@ sub config_to_command {
        $i++;
     }
 
-    push @$cmd, '-boot', "menu=on";
+    push @$cmd, '-boot', "menu=on,strict=on,reboot-timeout=1000";
 
     push @$cmd, '-no-acpi' if defined($conf->{acpi}) && $conf->{acpi} == 0;
 
@@ -3153,111 +3148,131 @@ sub vm_devices_list {
     return $devices;
 }
 
+sub hotplug_enabled {
+    my ($conf) = @_;
+
+    my $default = $confdesc->{'hotplug'}->{default};
+
+    return defined($conf->{hotplug}) ? $conf->{hotplug} : $default;
+}
+
 sub vm_deviceplug {
     my ($storecfg, $conf, $vmid, $deviceid, $device) = @_;
 
-    return 1 if !check_running($vmid);
+    die "internal error" if !hotplug_enabled($conf);
 
     my $q35 = machine_type_is_q35($conf);
 
-    return 1 if !$conf->{hotplug};
-
     my $devices_list = vm_devices_list($vmid);
     return 1 if defined($devices_list->{$deviceid});
 
+    qemu_add_pci_bridge($storecfg, $conf, $vmid, $deviceid); # add PCI bridge if we need it for the device
+
     if ($deviceid eq 'tablet') {
+
        qemu_deviceadd($vmid, print_tabletdevice_full($conf));
-       return 1;
-    }
 
-    qemu_add_pci_bridge($storecfg, $conf, $vmid, $deviceid); # add PCI bridge if we need it for the device
+    } elsif ($deviceid =~ m/^(virtio)(\d+)$/) {
 
-    if ($deviceid =~ m/^(virtio)(\d+)$/) {
-        return undef if !qemu_driveadd($storecfg, $vmid, $device);
+        qemu_driveadd($storecfg, $vmid, $device);
         my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device);
+
         qemu_deviceadd($vmid, $devicefull);
-        if(!qemu_deviceaddverify($vmid, $deviceid)) {
-           qemu_drivedel($vmid, $deviceid);
-           return undef;
+       eval { qemu_deviceaddverify($vmid, $deviceid); };
+       if (my $err = $@) {
+           eval { qemu_drivedel($vmid, $deviceid); };
+           warn $@ if $@;
+           die $err;
         }
-    }
 
-    if ($deviceid =~ m/^(scsihw)(\d+)$/) {
+    } elsif ($deviceid =~ m/^(scsihw)(\d+)$/) {
+
         my $scsihw = defined($conf->{scsihw}) ? $conf->{scsihw} : "lsi";
         my $pciaddr = print_pci_addr($deviceid);
         my $devicefull = "$scsihw,id=$deviceid$pciaddr";
+
         qemu_deviceadd($vmid, $devicefull);
-        return undef if(!qemu_deviceaddverify($vmid, $deviceid));
-    }
+        qemu_deviceaddverify($vmid, $deviceid);
 
-    if ($deviceid =~ m/^(scsi)(\d+)$/) {
-        return undef if !qemu_findorcreatescsihw($storecfg,$conf, $vmid, $device);
-        return undef if !qemu_driveadd($storecfg, $vmid, $device);
-        my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device);
-        if(!qemu_deviceadd($vmid, $devicefull)) {
-           qemu_drivedel($vmid, $deviceid);
-           return undef;
+    } elsif ($deviceid =~ m/^(scsi)(\d+)$/) {
+
+        qemu_findorcreatescsihw($storecfg,$conf, $vmid, $device);
+        qemu_driveadd($storecfg, $vmid, $device);
+        
+       my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device);
+       eval { qemu_deviceadd($vmid, $devicefull); };
+       if (my $err = $@) {
+           eval { qemu_drivedel($vmid, $deviceid); };
+           warn $@ if $@;
+           die $err;
         }
-    }
 
-    if ($deviceid =~ m/^(net)(\d+)$/) {
+    } elsif ($deviceid =~ m/^(net)(\d+)$/) {
+
         return undef if !qemu_netdevadd($vmid, $conf, $device, $deviceid);
         my $netdevicefull = print_netdevice_full($vmid, $conf, $device, $deviceid);
         qemu_deviceadd($vmid, $netdevicefull);
-        if(!qemu_deviceaddverify($vmid, $deviceid)) {
-           qemu_netdevdel($vmid, $deviceid);
-           return undef;
+        eval { qemu_deviceaddverify($vmid, $deviceid); };
+       if (my $err = $@) {
+           eval { qemu_netdevdel($vmid, $deviceid); };
+           warn $@ if $@;
+           die $err;
         }
-    }
 
+    } elsif (!$q35 && $deviceid =~ m/^(pci\.)(\d+)$/) {
 
-    if (!$q35 && $deviceid =~ m/^(pci\.)(\d+)$/) {
        my $bridgeid = $2;
        my $pciaddr = print_pci_addr($deviceid);
        my $devicefull = "pci-bridge,id=pci.$bridgeid,chassis_nr=$bridgeid$pciaddr";
+       
        qemu_deviceadd($vmid, $devicefull);
-       return undef if !qemu_deviceaddverify($vmid, $deviceid);
+       qemu_deviceaddverify($vmid, $deviceid);
+
+    } else {
+       die "can't hotplug device '$deviceid'\n";       
     }
 
     return 1;
 }
 
+# fixme: this should raise exceptions on error!
 sub vm_deviceunplug {
     my ($vmid, $conf, $deviceid) = @_;
 
-    return 1 if !check_running ($vmid);
-
-    return 1 if !$conf->{hotplug};
+    die "internal error" if !hotplug_enabled($conf);
 
     my $devices_list = vm_devices_list($vmid);
     return 1 if !defined($devices_list->{$deviceid});
 
+    die "can't unplug bootdisk" if $conf->{bootdisk} && $conf->{bootdisk} eq $deviceid;
+
     if ($deviceid eq 'tablet') {
+
        qemu_devicedel($vmid, $deviceid);
-       return 1;
-    }
 
-    die "can't unplug bootdisk" if $conf->{bootdisk} && $conf->{bootdisk} eq $deviceid;
+    } elsif ($deviceid =~ m/^(virtio)(\d+)$/) {
 
-    if ($deviceid =~ m/^(virtio)(\d+)$/) {
         qemu_devicedel($vmid, $deviceid);
-        return undef if !qemu_devicedelverify($vmid, $deviceid);
-        return undef if !qemu_drivedel($vmid, $deviceid);
-    }
-
-    if ($deviceid =~ m/^(lsi)(\d+)$/) {
-        return undef if !qemu_devicedel($vmid, $deviceid);
-    }
+        qemu_devicedelverify($vmid, $deviceid);
+        qemu_drivedel($vmid, $deviceid);
+   
+    } elsif ($deviceid =~ m/^(lsi)(\d+)$/) {
+    
+       qemu_devicedel($vmid, $deviceid);
+    
+    } elsif ($deviceid =~ m/^(scsi)(\d+)$/) {
 
-    if ($deviceid =~ m/^(scsi)(\d+)$/) {
-        return undef if !qemu_devicedel($vmid, $deviceid);
-        return undef if !qemu_drivedel($vmid, $deviceid);
-    }
+        qemu_devicedel($vmid, $deviceid);
+        qemu_drivedel($vmid, $deviceid);
+    
+    } elsif ($deviceid =~ m/^(net)(\d+)$/) {
 
-    if ($deviceid =~ m/^(net)(\d+)$/) {
         qemu_devicedel($vmid, $deviceid);
-        return undef if !qemu_devicedelverify($vmid, $deviceid);
-        return undef if !qemu_netdevdel($vmid, $deviceid);
+        qemu_devicedelverify($vmid, $deviceid);
+        qemu_netdevdel($vmid, $deviceid);
+
+    } else {
+       die "can't unplug device '$deviceid'\n";
     }
 
     return 1;
@@ -3270,26 +3285,24 @@ sub qemu_deviceadd {
     my %options =  split(/[=,]/, $devicefull);
 
     vm_mon_cmd($vmid, "device_add" , %options);
-    return 1;
 }
 
 sub qemu_devicedel {
-    my($vmid, $deviceid) = @_;
+    my ($vmid, $deviceid) = @_;
+
     my $ret = vm_mon_cmd($vmid, "device_del", id => $deviceid);
-    return 1;
 }
 
 sub qemu_driveadd {
-    my($storecfg, $vmid, $device) = @_;
+    my ($storecfg, $vmid, $device) = @_;
 
     my $drive = print_drive_full($storecfg, $vmid, $device);
     my $ret = vm_human_monitor_command($vmid, "drive_add auto $drive");
+
     # If the command succeeds qemu prints: "OK"
-    if ($ret !~ m/OK/s) {
-        syslog("err", "adding drive failed: $ret");
-        return undef;
-    }
-    return 1;
+    return 1 if $ret =~ m/OK/s;
+
+    die "adding drive failed: $ret\n";
 }
 
 sub qemu_drivedel {
@@ -3297,40 +3310,41 @@ sub qemu_drivedel {
 
     my $ret = vm_human_monitor_command($vmid, "drive_del drive-$deviceid");
     $ret =~ s/^\s+//;
-    if ($ret =~ m/Device \'.*?\' not found/s) {
-        # NB: device not found errors mean the drive was auto-deleted and we ignore the error
-    }
-    elsif ($ret ne "") {
-      syslog("err", "deleting drive $deviceid failed : $ret");
-      return undef;
-    }
-    return 1;
+    
+    return 1 if $ret eq "";
+  
+    # NB: device not found errors mean the drive was auto-deleted and we ignore the error
+    return 1 if $ret =~ m/Device \'.*?\' not found/s; 
+    
+    die "deleting drive $deviceid failed : $ret\n";
 }
 
 sub qemu_deviceaddverify {
-    my ($vmid,$deviceid) = @_;
+    my ($vmid, $deviceid) = @_;
 
     for (my $i = 0; $i <= 5; $i++) {
          my $devices_list = vm_devices_list($vmid);
          return 1 if defined($devices_list->{$deviceid});
          sleep 1;
     }
-    syslog("err", "error on hotplug device $deviceid");
-    return undef;
+
+    die "error on hotplug device '$deviceid'\n";
 }
 
 
 sub qemu_devicedelverify {
-    my ($vmid,$deviceid) = @_;
+    my ($vmid, $deviceid) = @_;
+
+    # need to verify that the device is correctly removed as device_del 
+    # is async and empty return is not reliable
 
-    #need to verify the device is correctly remove as device_del is async and empty return is not reliable
     for (my $i = 0; $i <= 5; $i++) {
          my $devices_list = vm_devices_list($vmid);
          return 1 if !defined($devices_list->{$deviceid});
          sleep 1;
     }
-    syslog("err", "error on hot-unplugging device $deviceid");
-    return undef;
+
+    die "error on hot-unplugging device '$deviceid'\n";
 }
 
 sub qemu_findorcreatescsihw {
@@ -3342,8 +3356,9 @@ sub qemu_findorcreatescsihw {
     my $devices_list = vm_devices_list($vmid);
 
     if(!defined($devices_list->{$scsihwid})) {
-       return undef if !vm_deviceplug($storecfg, $conf, $vmid, $scsihwid);
+       vm_deviceplug($storecfg, $conf, $vmid, $scsihwid);
     }
+
     return 1;
 }
 
@@ -3359,18 +3374,25 @@ sub qemu_add_pci_bridge {
     while (my ($k, $v) = each %$bridges) {
        $bridgeid = $k;
     }
-    return if !defined($bridgeid) || $bridgeid < 1;
+    return if !defined($bridgeid) || $bridgeid < 1;
 
     my $bridge = "pci.$bridgeid";
     my $devices_list = vm_devices_list($vmid);
 
     if (!defined($devices_list->{$bridge})) {
-       return undef if !vm_deviceplug($storecfg, $conf, $vmid, $bridge);
+       vm_deviceplug($storecfg, $conf, $vmid, $bridge);
     }
 
     return 1;
 }
 
+sub qemu_set_link_status {
+    my ($vmid, $device, $up) = @_;
+
+    vm_mon_cmd($vmid, "set_link", name => $device, 
+              up => $up ? JSON::true : JSON::false);
+}
+
 sub qemu_netdevadd {
     my ($vmid, $conf, $device, $deviceid) = @_;
 
@@ -3385,31 +3407,31 @@ sub qemu_netdevdel {
     my ($vmid, $deviceid) = @_;
 
     vm_mon_cmd($vmid, "netdev_del", id => $deviceid);
-    return 1;
 }
 
 sub qemu_cpu_hotplug {
-    my ($vmid, $conf, $cores) = @_;
+    my ($vmid, $conf, $vcpus) = @_;
 
-    my $sockets = $conf->{sockets} || 1;
-    die "cpu hotplug only works with one socket\n"
-       if $sockets > 1;
+    my $sockets = 1;
+    $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
+    $sockets = $conf->{sockets} if  $conf->{sockets};
+    my $cores = $conf->{cores} || 1;
+    my $maxcpus = $sockets * $cores;
 
-    die "maxcpus is not defined\n"
-       if !$conf->{maxcpus};
+    $vcpus = $maxcpus if !$vcpus;
 
-    die "you can't add more cores than maxcpus\n"
-       if $cores > $conf->{maxcpus};
+    die "you can't add more vcpus than maxcpus\n"
+       if $vcpus > $maxcpus;
 
-    my $currentcores = $conf->{cores} || 1;
+    my $currentvcpus = $conf->{vcpus} || $maxcpus;
     die "online cpu unplug is not yet possible\n"
-       if $cores < $currentcores;
+       if $vcpus < $currentvcpus;
 
-    my $currentrunningcores = vm_mon_cmd($vmid, "query-cpus");
-    die "cores number if running vm is different than configuration\n"
-       if scalar(@{$currentrunningcores}) != $currentcores;
+    my $currentrunningvcpus = vm_mon_cmd($vmid, "query-cpus");
+    die "vcpus in running vm is different than configuration\n"
+       if scalar(@{$currentrunningvcpus}) != $currentvcpus;
 
-    for (my $i = $currentcores; $i < $cores; $i++) {
+    for (my $i = $currentvcpus; $i < $vcpus; $i++) {
        vm_mon_cmd($vmid, "cpu-add", id => int($i));
     }
 }
@@ -3593,6 +3615,14 @@ sub set_migration_caps {
     vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => $cap_ref);
 }
 
+my $fast_plug_option = {
+    'name' => 1,
+    'hotplug' => 1,
+    'onboot' => 1, 
+    'shares' => 1,
+    'startup' => 1,
+};
+
 # hotplug changes in [PENDING]
 # $selection hash can be used to only apply specified options, for
 # example: { cores => 1 } (only apply changed 'cores')
@@ -3613,7 +3643,7 @@ sub vmconfig_hotplug_pending {
 
     my $changes = 0;
     foreach my $opt (keys %{$conf->{pending}}) { # add/change
-       if ($opt eq 'name' || $opt eq 'hotplug' || $opt eq 'onboot' || $opt eq 'shares') {
+       if ($fast_plug_option->{$opt}) {
            $conf->{$opt} = $conf->{pending}->{$opt};
            delete $conf->{pending}->{$opt};
            $changes = 1;
@@ -3625,7 +3655,7 @@ sub vmconfig_hotplug_pending {
        $conf = load_config($vmid); # update/reload
     }
 
-    my $hotplug = defined($conf->{hotplug}) ? $conf->{hotplug} : $defaults->{hotplug};
+    my $hotplug = hotplug_enabled($conf);
 
     my @delete = PVE::Tools::split_list($conf->{pending}->{delete});
     foreach my $opt (@delete) {
@@ -3638,9 +3668,21 @@ sub vmconfig_hotplug_pending {
                } else {
                    vm_deviceunplug($vmid, $conf, $opt);
                }
-           } elsif ($opt eq 'cores') {
+           } elsif ($opt eq 'vcpus') {
                die "skip\n" if !$hotplug;
-               qemu_cpu_hotplug($vmid, $conf, 1);
+               qemu_cpu_hotplug($vmid, $conf, undef);
+            } elsif ($opt eq 'balloon') {
+               # enable balloon device is not hotpluggable
+               die "skip\n" if !defined($conf->{balloon}) || $conf->{balloon};
+           } elsif ($fast_plug_option->{$opt}) {
+               # do nothing
+           } elsif ($opt =~ m/^net(\d+)$/) {
+               die "skip\n" if !$hotplug;
+               vm_deviceunplug($vmid, $conf, $opt);
+           } elsif (valid_drivename($opt)) {
+               die "skip\n" if !$hotplug || $opt =~ m/(ide|sata)(\d+)/;
+               vm_deviceunplug($vmid, $conf, $opt);
+               vmconfig_register_unused_drive($storecfg, $vmid, $conf, parse_drive($opt, $conf->{$opt}));
            } else {
                die "skip\n";
            }
@@ -3667,14 +3709,26 @@ sub vmconfig_hotplug_pending {
                } elsif ($value == 0) {
                    vm_deviceunplug($vmid, $conf, $opt);
                }
-           } elsif ($opt eq 'cores') {
+           } elsif ($opt eq 'vcpus') {
                die "skip\n" if !$hotplug;
                qemu_cpu_hotplug($vmid, $conf, $value);
            } elsif ($opt eq 'balloon') {
-               die "skip\n" if !(defined($conf->{shares}) && ($conf->{shares} == 0));
+               # enable/disable balloning device is not hotpluggable
+               my $old_balloon_enabled =  !!(!defined($conf->{balloon}) || $conf->{balloon});
+               my $new_balloon_enabled =  !!(!defined($conf->{pending}->{balloon}) || $conf->{pending}->{balloon});            
+               die "skip\n" if $old_balloon_enabled != $new_balloon_enabled;
+
                # allow manual ballooning if shares is set to zero
-               my $balloon = $conf->{pending}->{balloon} || $conf->{memory} || $defaults->{memory};
-               vm_mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
+               if (!(defined($conf->{shares}) && ($conf->{shares} == 0))) {
+                   my $balloon = $conf->{pending}->{balloon} || $conf->{memory} || $defaults->{memory};
+                   vm_mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
+               }
+           } elsif ($opt =~ m/^net(\d+)$/) { 
+               # some changes can be done without hotplug
+               vmconfig_update_net($storecfg, $conf, $vmid, $opt, $value);
+           } elsif (valid_drivename($opt)) {
+               # some changes can be done without hotplug
+               vmconfig_update_disk($storecfg, $conf, $vmid, $opt, $value, 1);
            } else {
                die "skip\n";  # skip non-hot-pluggable options
            }
@@ -3735,6 +3789,164 @@ sub vmconfig_apply_pending {
     }
 }
 
+my $safe_num_ne = sub {
+    my ($a, $b) = @_;
+
+    return 0 if !defined($a) && !defined($b);
+    return 1 if !defined($a);
+    return 1 if !defined($b);
+
+    return $a != $b;
+};
+
+my $safe_string_ne = sub {
+    my ($a, $b) = @_;
+
+    return 0 if !defined($a) && !defined($b);
+    return 1 if !defined($a);
+    return 1 if !defined($b);
+
+    return $a ne $b;
+};
+
+sub vmconfig_update_net {
+    my ($storecfg, $conf, $vmid, $opt, $value) = @_;
+
+    my $newnet = parse_net($value);
+
+    my $hotplug = hotplug_enabled($conf);
+
+    if ($conf->{$opt}) {
+       my $oldnet = parse_net($conf->{$opt});
+
+       if (&$safe_string_ne($oldnet->{model}, $newnet->{model}) ||
+           &$safe_string_ne($oldnet->{macaddr}, $newnet->{macaddr}) ||
+           &$safe_num_ne($oldnet->{queues}, $newnet->{queues}) ||
+           !($newnet->{bridge} && $oldnet->{bridge})) { # bridge/nat mode change
+
+            # for non online change, we try to hot-unplug
+           die "skip\n" if !$hotplug;
+           vm_deviceunplug($vmid, $conf, $opt);
+       } else {
+
+           die "internal error" if $opt !~ m/net(\d+)/;
+           my $iface = "tap${vmid}i$1";
+               
+           if (&$safe_num_ne($oldnet->{rate}, $newnet->{rate})) {
+               PVE::Network::tap_rate_limit($iface, $newnet->{rate});
+           }
+
+           if (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
+               &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
+               &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
+               PVE::Network::tap_unplug($iface);
+               PVE::Network::tap_plug($iface, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
+           }
+
+           if (&$safe_string_ne($oldnet->{link_down}, $newnet->{link_down})) {
+               qemu_set_link_status($vmid, $opt, !$newnet->{link_down});
+           }
+
+           return 1;
+       }
+    }
+    
+    if ($hotplug) {
+       vm_deviceplug($storecfg, $conf, $vmid, $opt, $newnet);
+    } else {
+       die "skip\n";
+    }
+}
+
+sub vmconfig_update_disk {
+    my ($storecfg, $conf, $vmid, $opt, $value, $force) = @_;
+
+    # fixme: do we need force?
+
+    my $drive = parse_drive($opt, $value);
+
+    my $hotplug = hotplug_enabled($conf);
+
+    if ($conf->{$opt}) {
+
+       if (my $old_drive = parse_drive($opt, $conf->{$opt}))  {
+
+           my $media = $drive->{media} || 'disk';
+           my $oldmedia = $old_drive->{media} || 'disk';
+           die "unable to change media type\n" if $media ne $oldmedia;
+
+           if (!drive_is_cdrom($old_drive)) {
+
+               if ($drive->{file} ne $old_drive->{file}) {  
+
+                   die "skip\n" if !$hotplug;
+
+                   # unplug and register as unused
+                   vm_deviceunplug($vmid, $conf, $opt);
+                   vmconfig_register_unused_drive($storecfg, $vmid, $conf, $old_drive)
+       
+               } else {
+                   # update existing disk
+
+                   # skip non hotpluggable value
+                   if (&$safe_num_ne($drive->{discard}, $old_drive->{discard}) || 
+                       &$safe_string_ne($drive->{cache}, $old_drive->{cache})) {
+                       die "skip\n";
+                   }
+
+                   # apply throttle
+                   if (&$safe_num_ne($drive->{mbps}, $old_drive->{mbps}) ||
+                       &$safe_num_ne($drive->{mbps_rd}, $old_drive->{mbps_rd}) ||
+                       &$safe_num_ne($drive->{mbps_wr}, $old_drive->{mbps_wr}) ||
+                       &$safe_num_ne($drive->{iops}, $old_drive->{iops}) ||
+                       &$safe_num_ne($drive->{iops_rd}, $old_drive->{iops_rd}) ||
+                       &$safe_num_ne($drive->{iops_wr}, $old_drive->{iops_wr}) ||
+                       &$safe_num_ne($drive->{mbps_max}, $old_drive->{mbps_max}) ||
+                       &$safe_num_ne($drive->{mbps_rd_max}, $old_drive->{mbps_rd_max}) ||
+                       &$safe_num_ne($drive->{mbps_wr_max}, $old_drive->{mbps_wr_max}) ||
+                       &$safe_num_ne($drive->{iops_max}, $old_drive->{iops_max}) ||
+                       &$safe_num_ne($drive->{iops_rd_max}, $old_drive->{iops_rd_max}) ||
+                       &$safe_num_ne($drive->{iops_wr_max}, $old_drive->{iops_wr_max})) {
+                       
+                       qemu_block_set_io_throttle($vmid,"drive-$opt",
+                                                  ($drive->{mbps} || 0)*1024*1024,
+                                                  ($drive->{mbps_rd} || 0)*1024*1024,
+                                                  ($drive->{mbps_wr} || 0)*1024*1024,
+                                                  $drive->{iops} || 0,
+                                                  $drive->{iops_rd} || 0,
+                                                  $drive->{iops_wr} || 0,
+                                                  ($drive->{mbps_max} || 0)*1024*1024,
+                                                  ($drive->{mbps_rd_max} || 0)*1024*1024,
+                                                  ($drive->{mbps_wr_max} || 0)*1024*1024,
+                                                  $drive->{iops_max} || 0,
+                                                  $drive->{iops_rd_max} || 0,
+                                                  $drive->{iops_wr_max} || 0);
+
+                   }
+                   
+                   return 1;
+               }
+           }
+       }
+    }
+
+    if (drive_is_cdrom($drive)) { # cdrom
+
+       if ($drive->{file} eq 'none') {
+           vm_mon_cmd($vmid, "eject",force => JSON::true,device => "drive-$opt");
+       } else {
+           my $path = get_iso_path($storecfg, $vmid, $drive->{file});
+           vm_mon_cmd($vmid, "eject", force => JSON::true,device => "drive-$opt"); # force eject if locked
+           vm_mon_cmd($vmid, "change", device => "drive-$opt",target => "$path") if $path;
+       }
+
+    } else { 
+       die "skip\n" if !$hotplug || $opt =~ m/(ide|sata)(\d+)/;   
+       # hotplug new disks
+       vm_deviceplug($storecfg, $conf, $vmid, $opt, $drive);
+    }
+}
+
 sub vm_start {
     my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused, $forcemachine, $spice_ticket) = @_;
 
@@ -3841,6 +4053,12 @@ sub vm_start {
                            property => "guest-stats-polling-interval",
                            value => 2);
            }
+
+           foreach my $opt (keys %$conf) {
+               next if $opt !~  m/^net\d+$/;
+               my $nicconf = parse_net($conf->{$opt});
+               qemu_set_link_status($vmid, $opt, 0) if $nicconf->{link_down};
+           }
        }
     });
 }
@@ -3950,7 +4168,7 @@ sub get_vm_volumes {
 }
 
 sub vm_stop_cleanup {
-    my ($storecfg, $vmid, $conf, $keepActive) = @_;
+    my ($storecfg, $vmid, $conf, $keepActive, $apply_pending_changes) = @_;
 
     eval {
        fairsched_rmnod($vmid); # try to destroy group
@@ -3959,10 +4177,12 @@ sub vm_stop_cleanup {
            my $vollist = get_vm_volumes($conf);
            PVE::Storage::deactivate_volumes($storecfg, $vollist);
        }
-
+       
        foreach my $ext (qw(mon qmp pid vnc qga)) {
            unlink "/var/run/qemu-server/${vmid}.$ext";
        }
+       
+       vmconfig_apply_pending($vmid, $conf, $storecfg) if $apply_pending_changes;
     };
     warn $@ if $@; # avoid errors - just warn
 }
@@ -3979,7 +4199,7 @@ sub vm_stop {
        my $pid = check_running($vmid, $nocheck, $migratedfrom);
        kill 15, $pid if $pid;
        my $conf = load_config($vmid, $migratedfrom);
-       vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive);
+       vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 0);
        return;
     }
 
@@ -4002,7 +4222,7 @@ sub vm_stop {
 
        eval {
            if ($shutdown) {
-               if (!$nocheck && $conf->{agent}) {
+               if (defined($conf) && $conf->{agent}) {
                    vm_qmp_command($vmid, { execute => "guest-shutdown" }, $nocheck);
                } else {
                    vm_qmp_command($vmid, { execute => "system_powerdown" }, $nocheck);
@@ -4028,7 +4248,7 @@ sub vm_stop {
                    die "VM quit/powerdown failed - got timeout\n";
                }
            } else {
-               vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive) if $conf;
+               vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 1) if $conf;
                return;
            }
        } else {
@@ -4055,7 +4275,7 @@ sub vm_stop {
            sleep 1;
        }
 
-       vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive) if $conf;
+       vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 1) if $conf;
    });
 }