[pve-devel] [PATCH] memory hotplug patch v6

Alexandre Derumier aderumier at odiso.com
Mon Jan 12 14:24:38 CET 2015


This patch allow to hotplug|define pc dimm devices memory

Minimum size is 128M
Maximum devices is 255

(tested under debian and windows).

vmid.conf
---------
dimm0: size=128[,numanode=0]
dimm1: size=512,numanode=1
dimm255: size=1024
memory: 1024
maxmemory: 8192

total running memory is : memory + dimmX.

dimmX devices are not seen by os at start, so need to have "memory" enough big to load kernel
numanode is optionnal, default is node=0.
If numa node don't exist, qemu simply allocate the dimm without error

hotplug
----
qm set <vmid> -dimm0 128,[numanode=0]

unplug (not yet implemented in qemu)
------
qm set <vmid> -delete dimm0

linux guest
-----------
-acpi hotplug module should be loaded in guest
-need a recent kernel. (tested with 3.10)

can be enable automaticaly, adding:

/lib/udev/rules.d/80-hotplug-cpu-mem.rules
SUBSYSTEM=="cpu", ACTION=="add", TEST=="online", ATTR{online}=="0", \
 ATTR{online}="1"

SUBSYSTEM=="memory", ACTION=="add", TEST=="state", ATTR{state}=="offline", \
 ATTR{state}="online"

windows guest
-------------
windows guest need numa:1 (windows limitation)

tested with:

- windows 2012 standard
- windows 2008 enterprise/datacenter

Signed-off-by: Alexandre Derumier <aderumier at odiso.com>
---
 PVE/QemuServer.pm |  143 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 141 insertions(+), 2 deletions(-)

diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index 087c508..b73c058 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -210,6 +210,12 @@ my $confdesc = {
 	minimum => 16,
 	default => 512,
     },
+    maxmemory => {
+       optional => 1,
+       type => 'integer',
+       description => "Maximum memory in MB for hotplug.",
+       minimum => 128,
+    },
     balloon => {
         optional => 1,
         type => 'integer',
@@ -490,6 +496,7 @@ my $MAX_HOSTPCI_DEVICES = 4;
 my $MAX_SERIAL_PORTS = 4;
 my $MAX_PARALLEL_PORTS = 3;
 my $MAX_NUMA = 8;
+my $MAX_DIMMS = 255;
 
 my $numadesc = {
     optional => 1,
@@ -539,6 +546,18 @@ for (my $i = 0; $i < $MAX_NETS; $i++)  {
     $confdesc->{"net$i"} = $netdesc;
 }
 
+my $dimmdesc = {
+    optional => 1,
+    type => 'string',
+    typetext => "<MB>[,numanode=<id>]",
+    description => "Hotpluggable memory DIMM modules"
+};
+PVE::JSONSchema::register_standard_option("pve-qm-dimm", $dimmdesc);
+
+for (my $i = 0; $i < $MAX_DIMMS; $i++)  {
+    $confdesc->{"dimm$i"} = $dimmdesc;
+}
+
 my $drivename_hash;
 
 my $idedesc = {
@@ -1316,6 +1335,25 @@ sub parse_numa {
     return $res;
 }
 
+sub parse_dimm {
+    my ($data) = @_;
+
+    my $res = {};
+
+    foreach my $kvp (split(/,/, $data)) {
+
+	if ($kvp =~ m/^(\d+)$/) {
+	    $res->{size} = $1;
+	} elsif ($kvp =~ m/^numanode=(\d)$/) {
+	    $res->{numanode} = $1;
+	} else {
+	    return undef;
+	}
+    }
+
+    return $res;
+}
+
 sub parse_hostpci {
     my ($value) = @_;
 
@@ -1592,6 +1630,17 @@ sub verify_numa {
     die "unable to parse numa options\n";
 }
 
+PVE::JSONSchema::register_format('pve-qm-dimm', \&verify_dimm);
+sub verify_dimm {
+    my ($value, $noerr) = @_;
+
+    return $value if parse_dimm($value);
+
+    return undef if $noerr;
+
+    die "unable to parse dimm options\n";
+}
+
 PVE::JSONSchema::register_format('pve-qm-net', \&verify_net);
 sub verify_net {
     my ($value, $noerr) = @_;
@@ -2850,8 +2899,27 @@ sub config_to_command {
     # push @$cmd, '-cpu', "$cpu,enforce";
     push @$cmd, '-cpu', $cpu;
 
-    my $memory =  $conf->{memory} || $defaults->{memory};
-    push @$cmd, '-m', $memory;
+    my $memory = $conf->{memory} || $defaults->{memory};
+    my $totalmemory = $memory;
+    for (my $i = 0; $i < $MAX_DIMMS; $i++)  {
+	next if !exists $conf->{"dimm$i"};
+	$conf->{"dimm$i"} =~ m/(\d+)/ or next;
+        my $dimm = parse_dimm($conf->{"dimm$i"});
+	my $dimmsize = $dimm->{size};
+	my $numanode = $dimm->{numanode} ? $dimm->{numanode} : 0;
+
+	die "dimm size should be a multiple of 128!\n" if ($dimmsize % 128 != 0);
+	push @$cmd, "-object" , "memory-backend-ram,id=mem$i,size=".int(${dimmsize}*1024*1024);
+	push @$cmd, "-device", "pc-dimm,id=dimm$i,memdev=mem$i,node=$numanode";
+	$totalmemory += $dimmsize;
+    }
+
+    if ($conf->{maxmemory}) {
+	die "Total memory is bigger than maxmemory\n" if $totalmemory > $conf->{maxmemory};
+	push @$cmd, '-m', "size=".$memory.",slots=$MAX_DIMMS,maxmem=".$conf->{maxmemory}."M";
+    } else {
+	push @$cmd, '-m', $memory;
+    }
 
     if ($conf->{numa}) {
 
@@ -3461,6 +3529,58 @@ sub qemu_cpu_hotplug {
     }
 }
 
+sub qemu_memory_hotplug {
+    my ($vmid, $conf, $opt, $value) = @_;
+
+    return if !check_running($vmid);
+ 
+    my $dimm = parse_dimm($value);
+    my $size = $dimm->{size};
+    my $numanode = $dimm->{numanode} ? $dimm->{numanode} : 0;
+
+    die "maxmemory is not defined" if !$conf->{maxmemory};
+
+    die "dimm already exist" if $conf->{$opt};
+
+    die "dimm size should be a multiple of 128!\n" if ($size % 128 != 0);
+
+    my $defaults = load_defaults();
+    my $memory = $conf->{memory} || $defaults->{memory};
+    my $totalmemory = $memory;
+    for (my $i = 0; $i < $MAX_DIMMS; $i++)  {
+	next if !exists $conf->{"dimm$i"};
+	$conf->{"dimm$i"} =~ m/(\d+)/ or next;
+	my $dimmsize = $1;
+	$totalmemory += $dimmsize;
+    }
+    $totalmemory += $size;
+
+    die "you cannot add more memory than maxmemory!\n" if $totalmemory > $conf->{'maxmemory'};
+
+    eval { vm_mon_cmd($vmid, "object-add", 'qom-type' => "memory-backend-ram", id => "mem$opt", props => { size => int($size*1024*1024) } ) };
+    if (my $err = $@) {
+        eval { qemu_objectdel($vmid, "mem$opt"); };
+        die $err;
+    }
+
+    eval { vm_mon_cmd($vmid, "device_add", driver => "pc-dimm", id => "$opt", memdev => "mem$opt", node => $numanode) };
+    if (my $err = $@) {
+        eval { qemu_objectdel($vmid, "mem$opt"); };
+        die $err;
+    }
+
+}
+
+sub qemu_memory_unplug {
+    my ($vmid, $conf, $opt) = @_;
+
+    return if !check_running($vmid);
+
+    die "memory unplug is not yet implemented";
+
+    vm_mon_cmd($vmid, "device_del", id => $opt);
+}
+
 sub qemu_block_set_io_throttle {
     my ($vmid, $deviceid, $bps, $bps_rd, $bps_wr, $iops, $iops_rd, $iops_wr) = @_;
 
@@ -3708,6 +3828,9 @@ sub vmconfig_hotplug_pending {
 		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}));
+	    } elsif ($opt =~ m/^dimm(\d+)$/) { #dimms
+		die "skip\n" if !$conf->{maxmemory} && !$conf->{hotplug};
+		qemu_memory_unplug($vmid, $conf, $opt);
 	    } else {
 		die "skip\n";
 	    }
@@ -3754,6 +3877,9 @@ sub vmconfig_hotplug_pending {
 	    } elsif (valid_drivename($opt)) {
 		# some changes can be done without hotplug
 		vmconfig_update_disk($storecfg, $conf, $vmid, $opt, $value, 1);
+	    } elsif ($opt =~ m/^dimm(\d+)$/) { #dimms
+		die "skip\n" if !$conf->{maxmemory} && !$conf->{hotplug};
+		qemu_memory_hotplug($vmid, $conf, $opt, $value);
 	    } else {
 		die "skip\n";  # skip non-hot-pluggable options
 	    }
@@ -5964,4 +6090,17 @@ sub lspci {
     return $devices;
 }
 
+sub totalmem {
+    my ($conf, $defaults) = @_;
+
+    my $totalmem = 0;
+    $totalmem = $conf->{memory} || $defaults->{memory};
+
+    foreach my $opt (keys %$conf) {
+	next if $opt !~ m/^dimm(\d+)$/;
+	$totalmem += $conf->{$opt};
+    }
+    return $totalmem;
+}
+
 1;
-- 
1.7.10.4



More information about the pve-devel mailing list