qemu-server/PVE/QemuServer/Memory.pm
Alexandre Derumier 0567a4d572 move memory config generation to QemuServer::Memory::config
Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
2016-05-23 10:04:31 +02:00

245 lines
7.3 KiB
Perl

package PVE::QemuServer::Memory;
use strict;
use warnings;
use PVE::QemuServer;
my $MAX_NUMA = 8;
my $MAX_MEM = 4194304;
my $STATICMEM = 1024;
sub foreach_dimm{
my ($conf, $vmid, $memory, $sockets, $func) = @_;
my $dimm_id = 0;
my $current_size = 1024;
my $dimm_size = 512;
return if $current_size == $memory;
for (my $j = 0; $j < 8; $j++) {
for (my $i = 0; $i < 32; $i++) {
my $name = "dimm${dimm_id}";
$dimm_id++;
my $numanode = $i % $sockets;
$current_size += $dimm_size;
&$func($conf, $vmid, $name, $dimm_size, $numanode, $current_size, $memory);
return $current_size if $current_size >= $memory;
}
$dimm_size *= 2;
}
}
sub foreach_reverse_dimm {
my ($conf, $vmid, $memory, $sockets, $func) = @_;
my $dimm_id = 253;
my $current_size = 4177920;
my $dimm_size = 65536;
return if $current_size == $memory;
for (my $j = 0; $j < 8; $j++) {
for (my $i = 0; $i < 32; $i++) {
my $name = "dimm${dimm_id}";
$dimm_id--;
my $numanode = $i % $sockets;
$current_size -= $dimm_size;
&$func($conf, $vmid, $name, $dimm_size, $numanode, $current_size, $memory);
return $current_size if $current_size <= $memory;
}
$dimm_size /= 2;
}
}
sub qemu_memory_hotplug {
my ($vmid, $conf, $defaults, $opt, $value) = @_;
return $value if !check_running($vmid);
my $memory = $conf->{memory} || $defaults->{memory};
$value = $defaults->{memory} if !$value;
return $value if $value == $memory;
my $static_memory = $STATICMEM;
my $dimm_memory = $memory - $static_memory;
die "memory can't be lower than $static_memory MB" if $value < $static_memory;
die "you cannot add more memory than $MAX_MEM MB!\n" if $memory > $MAX_MEM;
my $sockets = 1;
$sockets = $conf->{sockets} if $conf->{sockets};
if($value > $memory) {
foreach_dimm($conf, $vmid, $value, $sockets, sub {
my ($conf, $vmid, $name, $dimm_size, $numanode, $current_size, $memory) = @_;
return if $current_size <= $conf->{memory};
eval { PVE::QemuServer::vm_mon_cmd($vmid, "object-add", 'qom-type' => "memory-backend-ram", id => "mem-$name", props => { size => int($dimm_size*1024*1024) } ) };
if (my $err = $@) {
eval { PVE::QemuServer::qemu_objectdel($vmid, "mem-$name"); };
die $err;
}
eval { PVE::QemuServer::vm_mon_cmd($vmid, "device_add", driver => "pc-dimm", id => "$name", memdev => "mem-$name", node => $numanode) };
if (my $err = $@) {
eval { PVE::QemuServer::qemu_objectdel($vmid, "mem-$name"); };
die $err;
}
#update conf after each succesful module hotplug
$conf->{memory} = $current_size;
PVE::QemuConfig->write_config($vmid, $conf);
});
} else {
foreach_reverse_dimm($conf, $vmid, $value, $sockets, sub {
my ($conf, $vmid, $name, $dimm_size, $numanode, $current_size, $memory) = @_;
return if $current_size >= $conf->{memory};
print "try to unplug memory dimm $name\n";
my $retry = 0;
while (1) {
eval { PVE::QemuServer::qemu_devicedel($vmid, $name) };
sleep 3;
my $dimm_list = qemu_dimm_list($vmid);
last if !$dimm_list->{$name};
raise_param_exc({ $name => "error unplug memory module" }) if $retry > 5;
$retry++;
}
#update conf after each succesful module unplug
$conf->{memory} = $current_size;
eval { PVE::QemuServer::qemu_objectdel($vmid, "mem-$name"); };
PVE::QemuConfig->write_config($vmid, $conf);
});
}
}
sub qemu_dimm_list {
my ($vmid) = @_;
my $dimmarray = PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "query-memory-devices");
my $dimms = {};
foreach my $dimm (@$dimmarray) {
$dimms->{$dimm->{data}->{id}}->{id} = $dimm->{data}->{id};
$dimms->{$dimm->{data}->{id}}->{node} = $dimm->{data}->{node};
$dimms->{$dimm->{data}->{id}}->{addr} = $dimm->{data}->{addr};
$dimms->{$dimm->{data}->{id}}->{size} = $dimm->{data}->{size};
$dimms->{$dimm->{data}->{id}}->{slot} = $dimm->{data}->{slot};
}
return $dimms;
}
sub config {
my ($conf, $vmid, $sockets, $cores, $defaults, $hotplug_features, $cmd) = @_;
my $memory = $conf->{memory} || $defaults->{memory};
my $static_memory = 0;
my $dimm_memory = 0;
if ($hotplug_features->{memory}) {
die "NUMA need to be enabled for memory hotplug\n" if !$conf->{numa};
die "Total memory is bigger than ${MAX_MEM}MB\n" if $memory > $MAX_MEM;
$static_memory = $STATICMEM;
die "minimum memory must be ${static_memory}MB\n" if($memory < $static_memory);
$dimm_memory = $memory - $static_memory;
push @$cmd, '-m', "size=${static_memory},slots=255,maxmem=${MAX_MEM}M";
} else {
$static_memory = $memory;
push @$cmd, '-m', $static_memory;
}
if ($conf->{numa}) {
my $numa_totalmemory = undef;
for (my $i = 0; $i < $MAX_NUMA; $i++) {
next if !$conf->{"numa$i"};
my $numa = PVE::QemuServer::parse_numa($conf->{"numa$i"});
next if !$numa;
# memory
die "missing NUMA node$i memory value\n" if !$numa->{memory};
my $numa_memory = $numa->{memory};
$numa_totalmemory += $numa_memory;
my $numa_object = "memory-backend-ram,id=ram-node$i,size=${numa_memory}M";
# cpus
my $cpulists = $numa->{cpus};
die "missing NUMA node$i cpus\n" if !defined($cpulists);
my $cpus = join(',', map {
my ($start, $end) = @$_;
defined($end) ? "$start-$end" : $start
} @$cpulists);
# hostnodes
my $hostnodelists = $numa->{hostnodes};
if (defined($hostnodelists)) {
my $hostnodes;
foreach my $hostnoderange (@$hostnodelists) {
my ($start, $end) = @$hostnoderange;
$hostnodes .= ',' if $hostnodes;
$hostnodes .= $start;
$hostnodes .= "-$end" if defined($end);
$end //= $start;
for (my $i = $start; $i <= $end; ++$i ) {
die "host NUMA node$i don't exist\n" if ! -d "/sys/devices/system/node/node$i/";
}
}
# policy
my $policy = $numa->{policy};
die "you need to define a policy for hostnode $hostnodes\n" if !$policy;
$numa_object .= ",host-nodes=$hostnodes,policy=$policy";
}
push @$cmd, '-object', $numa_object;
push @$cmd, '-numa', "node,nodeid=$i,cpus=$cpus,memdev=ram-node$i";
}
die "total memory for NUMA nodes must be equal to vm static memory\n"
if $numa_totalmemory && $numa_totalmemory != $static_memory;
#if no custom tology, we split memory and cores across numa nodes
if(!$numa_totalmemory) {
my $numa_memory = ($static_memory / $sockets) . "M";
for (my $i = 0; $i < $sockets; $i++) {
my $cpustart = ($cores * $i);
my $cpuend = ($cpustart + $cores - 1) if $cores && $cores > 1;
my $cpus = $cpustart;
$cpus .= "-$cpuend" if $cpuend;
push @$cmd, '-object', "memory-backend-ram,size=$numa_memory,id=ram-node$i";
push @$cmd, '-numa', "node,nodeid=$i,cpus=$cpus,memdev=ram-node$i";
}
}
}
if ($hotplug_features->{memory}) {
foreach_dimm($conf, $vmid, $memory, $sockets, sub {
my ($conf, $vmid, $name, $dimm_size, $numanode, $current_size, $memory) = @_;
push @$cmd, "-object" , "memory-backend-ram,id=mem-$name,size=${dimm_size}M";
push @$cmd, "-device", "pc-dimm,id=$name,memdev=mem-$name,node=$numanode";
#if dimm_memory is not aligned to dimm map
if($current_size > $memory) {
$conf->{memory} = $current_size;
PVE::QemuConfig->write_config($vmid, $conf);
}
});
}
}
1;