Skip to content

Commit 16f0739

Browse files
committed
kvm: Properly report available memory to Management Server
The KVM Agent had two mechanisms for reporting its capabilities and memory to the Management Server. On startup it would ask libvirt the amount of Memory the Host has and subtract and add the reserved and overcommit memory. When the HostStats were however reported to the Management Server these two configured values on the Agent were no longer reported in the statistics thus showing all the available memory in the Agent/Host to the Management Server. This commit unifies this by using the same logic on Agent Startup and during statistics reporting. memory=3069636608, reservedMemory=1073741824 This was reported by a 4GB Hypervisor with this setting: host.reserved.mem.mb=1024 The GUI (thus API) would then show: Memory Total 2.86 GB This way the Agent properly 'lies' to the Management Server about its capabilities in terms of Memory. This is very helpful if you want to overprovision or undercommit machines for various reasons. Overcommitting can be done when KSM or ZSwap or a fast SWAP device is installed in the machine. Underprovisioning is done when the Host might run other tasks then a KVM hypervisor, for example when it runs in a hyperconverged setup with Ceph. In addition internally many values have been changed from a Double to a Long and also store the amount of bytes instead of Kilobytes. Signed-off-by: Wido den Hollander <[email protected]>
1 parent 5e48c0b commit 16f0739

File tree

7 files changed

+231
-110
lines changed

7 files changed

+231
-110
lines changed

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java

Lines changed: 9 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919
import java.io.BufferedReader;
2020
import java.io.File;
2121
import java.io.FileNotFoundException;
22-
import java.io.FileReader;
2322
import java.io.IOException;
24-
import java.io.Reader;
2523
import java.io.StringReader;
2624
import java.net.InetAddress;
2725
import java.net.URI;
@@ -52,11 +50,11 @@
5250
import org.apache.cloudstack.storage.to.VolumeObjectTO;
5351
import org.apache.cloudstack.utils.hypervisor.HypervisorUtils;
5452
import org.apache.cloudstack.utils.linux.CPUStat;
53+
import org.apache.cloudstack.utils.linux.HostInfo;
5554
import org.apache.cloudstack.utils.linux.MemStat;
5655
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
5756
import org.apache.cloudstack.utils.security.KeyStoreUtils;
5857
import org.apache.commons.io.FileUtils;
59-
import org.apache.commons.io.IOUtils;
6058
import org.apache.commons.lang.ArrayUtils;
6159
import org.apache.commons.lang.math.NumberUtils;
6260
import org.apache.log4j.Logger;
@@ -306,9 +304,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
306304

307305
protected int _cmdsTimeout;
308306
protected int _stopTimeout;
309-
protected CPUStat _cpuStat = new CPUStat();
310-
protected MemStat _memStat = new MemStat();
311-
312307
private final LibvirtUtilitiesHelper libvirtUtilitiesHelper = new LibvirtUtilitiesHelper();
313308

314309
@Override
@@ -383,11 +378,11 @@ public LibvirtUtilitiesHelper getLibvirtUtilitiesHelper() {
383378
}
384379

385380
public CPUStat getCPUStat() {
386-
return _cpuStat;
381+
return new CPUStat();
387382
}
388383

389384
public MemStat getMemStat() {
390-
return _memStat;
385+
return new MemStat(_dom0MinMem, _dom0OvercommitMem);
391386
}
392387

393388
public VirtualRoutingResource getVirtRouterResource() {
@@ -852,7 +847,7 @@ public boolean configure(final String name, final Map<String, Object> params) th
852847

853848
value = (String)params.get("host.reserved.mem.mb");
854849
// Reserve 1GB unless admin overrides
855-
_dom0MinMem = NumbersUtil.parseInt(value, 1024) * 1024 * 1024L;
850+
_dom0MinMem = NumbersUtil.parseInt(value, 1024) * 1024* 1024L;
856851

857852
value = (String)params.get("host.overcommit.mem.mb");
858853
// Support overcommit memory for host if host uses ZSWAP, KSM and other memory
@@ -2601,12 +2596,14 @@ private Map<String, String> getVersionStrings() {
26012596
@Override
26022597
public StartupCommand[] initialize() {
26032598

2604-
final List<Object> info = getHostInfo();
2599+
final HostInfo info = new HostInfo(_dom0MinMem, _dom0OvercommitMem);
2600+
2601+
final String capabilities = String.join(",", info.getCapabilities());
26052602

26062603
final StartupRoutingCommand cmd =
2607-
new StartupRoutingCommand((Integer)info.get(0), (Long)info.get(1), (Long)info.get(2), (Long)info.get(4), (String)info.get(3), _hypervisorType,
2604+
new StartupRoutingCommand(info.getCpus(), info.getCpuSpeed(), info.getTotalMemory(), info.getReservedMemory(), capabilities, _hypervisorType,
26082605
RouterPrivateIpStrategy.HostLocal);
2609-
cmd.setCpuSockets((Integer)info.get(5));
2606+
cmd.setCpuSockets(info.getCpuSockets());
26102607
fillNetworkInformation(cmd);
26112608
_privateIp = cmd.getPrivateIpAddress();
26122609
cmd.getHostDetails().putAll(getVersionStrings());
@@ -2826,71 +2823,6 @@ private HashMap<String, HostVmStateReportEntry> getHostVmStateReport(final Conne
28262823
return vmStates;
28272824
}
28282825

2829-
protected List<Object> getHostInfo() {
2830-
final ArrayList<Object> info = new ArrayList<Object>();
2831-
long speed = 0;
2832-
long cpus = 0;
2833-
long ram = 0;
2834-
int cpuSockets = 0;
2835-
String cap = null;
2836-
try {
2837-
final Connect conn = LibvirtConnection.getConnection();
2838-
final NodeInfo hosts = conn.nodeInfo();
2839-
speed = getCpuSpeed(hosts);
2840-
2841-
/*
2842-
* Some CPUs report a single socket and multiple NUMA cells.
2843-
* We need to multiply them to get the correct socket count.
2844-
*/
2845-
cpuSockets = hosts.sockets;
2846-
if (hosts.nodes > 0) {
2847-
cpuSockets = hosts.sockets * hosts.nodes;
2848-
}
2849-
cpus = hosts.cpus;
2850-
ram = hosts.memory * 1024L;
2851-
final LibvirtCapXMLParser parser = new LibvirtCapXMLParser();
2852-
parser.parseCapabilitiesXML(conn.getCapabilities());
2853-
final ArrayList<String> oss = parser.getGuestOsType();
2854-
for (final String s : oss) {
2855-
/*
2856-
* Even host supports guest os type more than hvm, we only
2857-
* report hvm to management server
2858-
*/
2859-
if (s.equalsIgnoreCase("hvm")) {
2860-
cap = "hvm";
2861-
}
2862-
}
2863-
} catch (final LibvirtException e) {
2864-
s_logger.trace("Ignoring libvirt error.", e);
2865-
}
2866-
2867-
if (isSnapshotSupported()) {
2868-
cap = cap + ",snapshot";
2869-
}
2870-
2871-
info.add((int)cpus);
2872-
info.add(speed);
2873-
// Report system's RAM as actual RAM minus host OS reserved RAM
2874-
ram = ram - _dom0MinMem + _dom0OvercommitMem;
2875-
info.add(ram);
2876-
info.add(cap);
2877-
info.add(_dom0MinMem);
2878-
info.add(cpuSockets);
2879-
s_logger.debug("cpus=" + cpus + ", speed=" + speed + ", ram=" + ram + ", _dom0MinMem=" + _dom0MinMem + ", _dom0OvercommitMem=" + _dom0OvercommitMem + ", cpu sockets=" + cpuSockets);
2880-
2881-
return info;
2882-
}
2883-
2884-
protected static long getCpuSpeed(final NodeInfo nodeInfo) {
2885-
try (final Reader reader = new FileReader(
2886-
"/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq")) {
2887-
return Long.parseLong(IOUtils.toString(reader).trim()) / 1000;
2888-
} catch (IOException | NumberFormatException e) {
2889-
s_logger.warn("Could not read cpuinfo_max_freq");
2890-
return nodeInfo.mhz;
2891-
}
2892-
}
2893-
28942826
public String rebootVM(final Connect conn, final String vmName) {
28952827
Domain dm = null;
28962828
String msg = null;

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetHostStatsCommandWrapper.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,10 @@ public Answer execute(final GetHostStatsCommand command, final LibvirtComputingR
4242
MemStat memStat = libvirtComputingResource.getMemStat();
4343

4444
final double cpuUtil = cpuStat.getCpuUsedPercent();
45-
memStat.refresh();
46-
double totMem = memStat.getTotal();
47-
double freeMem = memStat.getAvailable();
4845

4946
final Pair<Double, Double> nicStats = libvirtComputingResource.getNicStats(libvirtComputingResource.getPublicBridgeName());
5047

51-
final HostStatsEntry hostStats = new HostStatsEntry(command.getHostId(), cpuUtil, nicStats.first() / 1024, nicStats.second() / 1024, "host", totMem, freeMem, 0, 0);
48+
final HostStatsEntry hostStats = new HostStatsEntry(command.getHostId(), cpuUtil, nicStats.first() / 1024, nicStats.second() / 1024, "host", memStat.getTotal() / 1024, memStat.getAvailable() / 1024, 0, 0);
5249
return new GetHostStatsAnswer(command, hostStats);
5350
}
5451
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.utils.linux;
18+
19+
import com.cloud.hypervisor.kvm.resource.LibvirtCapXMLParser;
20+
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
21+
import org.apache.commons.io.IOUtils;
22+
import org.apache.log4j.Logger;
23+
import org.libvirt.Connect;
24+
import org.libvirt.LibvirtException;
25+
import org.libvirt.NodeInfo;
26+
27+
import java.io.FileReader;
28+
import java.io.IOException;
29+
import java.io.Reader;
30+
import java.util.ArrayList;
31+
import java.util.List;
32+
33+
public class HostInfo {
34+
35+
private static final Logger s_logger = Logger.getLogger(HostInfo.class);
36+
37+
private int cpus;
38+
private int cpusockets;
39+
private long cpuSpeed;
40+
private long totalMemory;
41+
private long reservedMemory;
42+
private long overCommitMemory;
43+
private List<String> capabilities = new ArrayList<>();
44+
45+
public HostInfo(long reservedMemory, long overCommitMemory) {
46+
this.reservedMemory = reservedMemory;
47+
this.overCommitMemory = overCommitMemory;
48+
this.getHostInfoFromLibvirt();
49+
this.totalMemory = new MemStat(this.getReservedMemory(), this.getOverCommitMemory()).getTotal();
50+
}
51+
52+
public int getCpus() {
53+
return this.cpus;
54+
}
55+
56+
public int getCpuSockets() {
57+
return this.cpusockets;
58+
}
59+
60+
public long getCpuSpeed() {
61+
return this.cpuSpeed;
62+
}
63+
64+
public long getTotalMemory() {
65+
return this.totalMemory;
66+
}
67+
68+
public long getReservedMemory() {
69+
return this.reservedMemory;
70+
}
71+
72+
public long getOverCommitMemory() {
73+
return this.overCommitMemory;
74+
}
75+
76+
public List<String> getCapabilities() {
77+
return this.capabilities;
78+
}
79+
80+
protected static long getCpuSpeed(final NodeInfo nodeInfo) {
81+
try (final Reader reader = new FileReader(
82+
"/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq")) {
83+
return Long.parseLong(IOUtils.toString(reader).trim()) / 1000;
84+
} catch (IOException | NumberFormatException e) {
85+
s_logger.info("Could not read cpuinfo_max_freq, falling back on libvirt");
86+
return nodeInfo.mhz;
87+
}
88+
}
89+
90+
private void getHostInfoFromLibvirt() {
91+
try {
92+
final Connect conn = LibvirtConnection.getConnection();
93+
final NodeInfo hosts = conn.nodeInfo();
94+
this.cpuSpeed = getCpuSpeed(hosts);
95+
96+
/*
97+
* Some CPUs report a single socket and multiple NUMA cells.
98+
* We need to multiply them to get the correct socket count.
99+
*/
100+
this.cpusockets = hosts.sockets;
101+
if (hosts.nodes > 0) {
102+
this.cpusockets = hosts.sockets * hosts.nodes;
103+
}
104+
this.cpus = hosts.cpus;
105+
106+
final LibvirtCapXMLParser parser = new LibvirtCapXMLParser();
107+
parser.parseCapabilitiesXML(conn.getCapabilities());
108+
final ArrayList<String> oss = parser.getGuestOsType();
109+
for (final String s : oss) {
110+
/*
111+
* Even host supports guest os type more than hvm, we only
112+
* report hvm to management server
113+
*/
114+
String hvmCapability = "hvm";
115+
if (s.equalsIgnoreCase(hvmCapability)) {
116+
if (!this.capabilities.contains(hvmCapability)) {
117+
this.capabilities.add(hvmCapability);
118+
}
119+
}
120+
}
121+
122+
/*
123+
Any modern Qemu/KVM supports snapshots
124+
We used to check if this was supported, but that is no longer required
125+
*/
126+
this.capabilities.add("snapshot");
127+
conn.close();
128+
} catch (final LibvirtException e) {
129+
s_logger.error("Caught libvirt exception while fetching host information", e);
130+
}
131+
}
132+
}

plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/linux/MemStat.java

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,47 @@
2222
import java.util.Map;
2323
import java.util.Scanner;
2424

25+
2526
public class MemStat {
27+
/*
28+
Gather Memory Statistics of the current node by opening /proc/meminfo
29+
which contains the memory information in KiloBytes.
30+
31+
Convert this all to bytes and return Long as a type with the information
32+
in bytes
33+
*/
2634
protected final static String MEMINFO_FILE = "/proc/meminfo";
2735
protected final static String FREE_KEY = "MemFree";
2836
protected final static String CACHE_KEY = "Cached";
2937
protected final static String TOTAL_KEY = "MemTotal";
38+
long reservedMemory;
39+
long overCommitMemory;
3040

31-
private final Map<String, Double> _memStats = new HashMap<String, Double>();
41+
private final Map<String, Long> _memStats = new HashMap<>();
3242

3343
public MemStat() {
44+
this(0,0);
45+
}
46+
47+
public MemStat(long reservedMemory, long overCommitMemory) {
48+
this.reservedMemory = reservedMemory;
49+
this.overCommitMemory = overCommitMemory;
50+
this.refresh();
3451
}
3552

36-
public Double getTotal() {
37-
return _memStats.get(TOTAL_KEY);
53+
public long getTotal() {
54+
return _memStats.get(TOTAL_KEY) - reservedMemory + overCommitMemory;
3855
}
3956

40-
public Double getAvailable() {
57+
public long getAvailable() {
4158
return getFree() + getCache();
4259
}
4360

44-
public Double getFree() {
45-
return _memStats.get(FREE_KEY);
61+
public long getFree() {
62+
return _memStats.get(FREE_KEY) - reservedMemory + overCommitMemory;
4663
}
4764

48-
public Double getCache() {
65+
public long getCache() {
4966
return _memStats.get(CACHE_KEY);
5067
}
5168

@@ -63,7 +80,7 @@ protected void parseFromScanner(Scanner scanner) {
6380
while(scanner.hasNext()) {
6481
String[] stats = scanner.next().split("\\:\\s+");
6582
if (stats.length == 2) {
66-
_memStats.put(stats[0], Double.valueOf(stats[1].replaceAll("\\s+\\w+","")));
83+
_memStats.put(stats[0], Long.valueOf(stats[1].replaceAll("\\s+\\w+","")) * 1024L);
6784
}
6885
}
6986
}

plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -520,13 +520,6 @@ public List<DiskDef> getDisks(final Connect conn, final String vmName) {
520520
Assert.assertTrue(vmStat.getTargetMemoryKBs() >= vmStat.getMemoryKBs());
521521
}
522522

523-
@Test
524-
public void getCpuSpeed() {
525-
Assume.assumeTrue(SystemUtils.IS_OS_LINUX);
526-
final NodeInfo nodeInfo = Mockito.mock(NodeInfo.class);
527-
LibvirtComputingResource.getCpuSpeed(nodeInfo);
528-
}
529-
530523
/*
531524
* New Tests
532525
*/
@@ -974,8 +967,8 @@ public void testGetHostStatsCommand() {
974967
when(libvirtComputingResource.getMemStat()).thenReturn(memStat);
975968
when(libvirtComputingResource.getNicStats(Mockito.anyString())).thenReturn(new Pair<Double, Double>(1.0d, 1.0d));
976969
when(cpuStat.getCpuUsedPercent()).thenReturn(0.5d);
977-
when(memStat.getAvailable()).thenReturn(1500.5d);
978-
when(memStat.getTotal()).thenReturn(15000d);
970+
when(memStat.getAvailable()).thenReturn(1500L);
971+
when(memStat.getTotal()).thenReturn(15000L);
979972

980973
final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
981974
assertNotNull(wrapper);

0 commit comments

Comments
 (0)