From 1843632c24daf4767249c04249a6c37c224f6e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Flauzino?= Date: Wed, 9 Nov 2022 14:00:12 -0300 Subject: [PATCH] Fix memory stats for KVM (#6358) Co-authored-by: joseflauzino --- agent/conf/agent.properties | 4 + .../src/main/java/com/cloud/agent/Agent.java | 3 +- .../agent/properties/AgentProperties.java | 15 ++ .../resource/LibvirtComputingResource.java | 87 +++++++++- .../kvm/resource/LibvirtDomainXMLParser.java | 27 +++ .../hypervisor/kvm/resource/LibvirtVMDef.java | 60 ++++++- .../LibvirtComputingResourceTest.java | 154 +++++++++++++++++- .../resource/LibvirtDomainXMLParserTest.java | 6 + .../kvm/resource/LibvirtVMDefTest.java | 23 +++ 9 files changed, 375 insertions(+), 4 deletions(-) diff --git a/agent/conf/agent.properties b/agent/conf/agent.properties index b877e97afab..e6d8844bda2 100644 --- a/agent/conf/agent.properties +++ b/agent/conf/agent.properties @@ -202,6 +202,10 @@ hypervisor.type=kvm # Disable memory ballooning on vm guests for overcommit, by default overcommit # feature enables balloon and sets currentMemory to a minimum value. # +# The time interval (in seconds) at which the balloon driver will get memory stats updates. +# This is equivalent to Libvirt's --period parameter when using the dommemstat command. +# vm.memballoon.stats.period=0 +# # vm.diskactivity.checkenabled=false # Set to true to check disk activity on VM's disks before starting a VM. This only applies # to QCOW2 files, and ensures that there is no other running instance accessing diff --git a/agent/src/main/java/com/cloud/agent/Agent.java b/agent/src/main/java/com/cloud/agent/Agent.java index 21faaa2a1d4..b0028c148fb 100644 --- a/agent/src/main/java/com/cloud/agent/Agent.java +++ b/agent/src/main/java/com/cloud/agent/Agent.java @@ -100,7 +100,7 @@ import com.cloud.utils.script.Script; * **/ public class Agent implements HandlerFactory, IAgentControl { - private static final Logger s_logger = Logger.getLogger(Agent.class.getName()); + protected static Logger s_logger = Logger.getLogger(Agent.class); public enum ExitStatus { Normal(0), // Normal status = 0. @@ -303,6 +303,7 @@ public class Agent implements HandlerFactory, IAgentControl { } _shell.updateConnectedHost(); scavengeOldAgentObjects(); + } public void stop(final String reason, final String detail) { diff --git a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java index 657876c13ac..9f84bb572db 100644 --- a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java +++ b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java @@ -54,6 +54,21 @@ public class AgentProperties{ */ public static final Property ENABLE_MANUALLY_SETTING_CPU_TOPOLOGY_ON_KVM_VM = new Property("enable.manually.setting.cpu.topology.on.kvm.vm", true); + /** + * Disables memory ballooning on VM guests for overcommit.
+ * By default overcommit feature enables balloon and sets currentMemory to a minimum value.
+ * Data type: Boolean.
+ * Default value: false + */ + public static final Property VM_MEMBALLOON_DISABLE = new Property<>("vm.memballoon.disable", false); + + /** + * The time interval (in seconds) at which the balloon driver will get memory stats updates. This is equivalent to Libvirt's --period parameter when using the dommemstat command. + * Data type: Integer.
+ * Default value: 0 + */ + public static final Property VM_MEMBALLOON_STATS_PERIOD = new Property<>("vm.memballoon.stats.period", 0); + public static class Property { private final String name; private final T defaultValue; diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 59eaa6b64d8..bcfff1b6a32 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -147,6 +147,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestResourceDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InputDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.MemBalloonDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef.RngBackendModel; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SCSIDef; @@ -221,7 +222,7 @@ import static com.cloud.host.Host.HOST_VOLUME_ENCRYPTION; * pool | the parent of the storage pool hierarchy * } **/ public class LibvirtComputingResource extends ServerResourceBase implements ServerResource, VirtualRouterDeployer { - private static final Logger s_logger = Logger.getLogger(LibvirtComputingResource.class); + protected static Logger s_logger = Logger.getLogger(LibvirtComputingResource.class); private static final String CONFIG_VALUES_SEPARATOR = ","; @@ -448,6 +449,15 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv protected Boolean enableManuallySettingCpuTopologyOnKvmVm = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.ENABLE_MANUALLY_SETTING_CPU_TOPOLOGY_ON_KVM_VM); + protected LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser(); + + /** + * Virsh command to set the memory balloon stats period.

+ * 1st parameter: the VM ID or name;
+ * 2nd parameter: the period (in seconds). + */ + private static final String COMMAND_SET_MEM_BALLOON_STATS_PERIOD = "virsh dommemstat %s --period %s --live"; + protected long getHypervisorLibvirtVersion() { return _hypervisorLibvirtVersion; } @@ -1298,9 +1308,84 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv s_logger.info("iscsi session clean up is disabled"); } + setupMemoryBalloonStatsPeriod(conn); + return true; } + /** + * Gets the ID list of the VMs to set memory balloon stats period. + * @param conn the Libvirt connection. + * @return the list of VM IDs. + */ + protected List getVmsToSetMemoryBalloonStatsPeriod(Connect conn) { + List vmIdList = new ArrayList<>(); + Integer[] vmIds = null; + try { + vmIds = ArrayUtils.toObject(conn.listDomains()); + } catch (final LibvirtException e) { + s_logger.error("Unable to get the list of Libvirt domains on this host.", e); + return vmIdList; + } + vmIdList.addAll(Arrays.asList(vmIds)); + s_logger.debug(String.format("We have found a total of [%s] VMs (Libvirt domains) on this host: [%s].", vmIdList.size(), vmIdList.toString())); + + if (vmIdList.isEmpty()) { + s_logger.info("Skipping the memory balloon stats period setting, since there are no VMs (active Libvirt domains) on this host."); + } + return vmIdList; + } + + /** + * Gets the current VM balloon stats period from the agent.properties file. + * @return the current VM balloon stats period. + */ + protected Integer getCurrentVmBalloonStatsPeriod() { + if (Boolean.TRUE.equals(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MEMBALLOON_DISABLE))) { + s_logger.info(String.format("The [%s] property is set to 'true', so the memory balloon stats period will be set to 0 for all VMs.", + AgentProperties.VM_MEMBALLOON_DISABLE.getName())); + return 0; + } + Integer vmBalloonStatsPeriod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MEMBALLOON_STATS_PERIOD); + if (vmBalloonStatsPeriod == 0) { + s_logger.info(String.format("The [%s] property is set to '0', this prevents memory statistics from being displayed correctly. " + + "Adjust (increase) the value of this parameter to correct this.", AgentProperties.VM_MEMBALLOON_STATS_PERIOD.getName())); + } + return vmBalloonStatsPeriod; + } + + /** + * Sets the balloon driver of each VM to get the memory stats at the time interval defined in the agent.properties file. + * @param conn the Libvirt connection. + */ + protected void setupMemoryBalloonStatsPeriod(Connect conn) { + List vmIdList = getVmsToSetMemoryBalloonStatsPeriod(conn); + Integer currentVmBalloonStatsPeriod = getCurrentVmBalloonStatsPeriod(); + for (Integer vmId : vmIdList) { + Domain dm = null; + try { + dm = conn.domainLookupByID(vmId); + parser.parseDomainXML(dm.getXMLDesc(0)); + MemBalloonDef memBalloon = parser.getMemBalloon(); + if (!MemBalloonDef.MemBalloonModel.VIRTIO.equals(memBalloon.getMemBalloonModel())) { + s_logger.debug(String.format("Skipping the memory balloon stats period setting for the VM (Libvirt Domain) with ID [%s] and name [%s] because this VM has no memory" + + " balloon.", vmId, dm.getName())); + } + String setMemBalloonStatsPeriodCommand = String.format(COMMAND_SET_MEM_BALLOON_STATS_PERIOD, vmId, currentVmBalloonStatsPeriod); + String setMemBalloonStatsPeriodResult = Script.runSimpleBashScript(setMemBalloonStatsPeriodCommand); + if (StringUtils.isNotBlank(setMemBalloonStatsPeriodResult)) { + s_logger.error(String.format("Unable to set up memory balloon stats period for VM (Libvirt Domain) with ID [%s] due to an error when running the [%s] " + + "command. Output: [%s].", vmId, setMemBalloonStatsPeriodCommand, setMemBalloonStatsPeriodResult)); + continue; + } + s_logger.debug(String.format("The memory balloon stats period [%s] has been set successfully for the VM (Libvirt Domain) with ID [%s] and name [%s].", + currentVmBalloonStatsPeriod, vmId, dm.getName())); + } catch (final LibvirtException e) { + s_logger.warn("Failed to set up memory balloon stats period." + e.getMessage()); + } + } + } + private void enableSSLForKvmAgent(final Map params) { final File keyStoreFile = PropertiesUtil.findConfigFile(KeyStoreUtils.KS_FILENAME); if (keyStoreFile == null) { diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java index da9b2233c8d..d28ace6d576 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java @@ -41,6 +41,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef.NicModel; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.MemBalloonDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef.RngBackendModel; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef; @@ -50,6 +51,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef.WatchDogModel; public class LibvirtDomainXMLParser { private static final Logger s_logger = Logger.getLogger(LibvirtDomainXMLParser.class); private final List interfaces = new ArrayList(); + private MemBalloonDef memBalloonDef = new MemBalloonDef(); private final List diskDefs = new ArrayList(); private final List rngDefs = new ArrayList(); private final List channels = new ArrayList(); @@ -206,6 +208,8 @@ public class LibvirtDomainXMLParser { diskDefs.add(def); } + memBalloonDef = parseMemBalloonTag(devices); + NodeList nics = devices.getElementsByTagName("interface"); for (int i = 0; i < nics.getLength(); i++) { Element nic = (Element)nics.item(i); @@ -342,6 +346,25 @@ public class LibvirtDomainXMLParser { return false; } + /** + * Parse the memballoon tag. + * @param devices the devices tag. + * @return the MemBalloonDef. + */ + private MemBalloonDef parseMemBalloonTag(Element devices) { + MemBalloonDef def = new MemBalloonDef(); + NodeList memBalloons = devices.getElementsByTagName("memballoon"); + if (memBalloons != null && memBalloons.getLength() != 0) { + Element memBalloon = (Element)memBalloons.item(0); + String model = memBalloon.getAttribute("model"); + if (model.equalsIgnoreCase("virtio")) { + String statsPeriod = getAttrValue("stats", "period", memBalloon); + def.defVirtioMemBalloon(statsPeriod); + } + } + return def; + } + private static String getTagValue(String tag, Element eElement) { NodeList tagNodeList = eElement.getElementsByTagName(tag); if (tagNodeList == null || tagNodeList.getLength() == 0) { @@ -372,6 +395,10 @@ public class LibvirtDomainXMLParser { return interfaces; } + public MemBalloonDef getMemBalloon() { + return memBalloonDef; + } + public List getDisks() { return diskDefs; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java index 2c1e362fc63..e8af21cc681 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java @@ -27,6 +27,9 @@ import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; +import com.cloud.agent.properties.AgentProperties; +import com.cloud.agent.properties.AgentPropertiesFileHandler; + public class LibvirtVMDef { private static final Logger s_logger = Logger.getLogger(LibvirtVMDef.class); @@ -236,6 +239,7 @@ public class LibvirtVMDef { private int vcpu = -1; private int maxVcpu = -1; private boolean memoryBalloning = false; + private int memoryBalloonStatsPeriod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MEMBALLOON_STATS_PERIOD); public void setMemorySize(long mem) { this.memory = mem; @@ -276,7 +280,14 @@ public class LibvirtVMDef { response.append(String.format(" \n", this.maxVcpu - 1, this.currentMemory)); } - response.append(String.format("\n\n\n", this.memoryBalloning ? "virtio" : "none")); + MemBalloonDef memBalloonDef = new MemBalloonDef(); + if (this.memoryBalloning) { + memBalloonDef.defVirtioMemBalloon(String.valueOf(memoryBalloonStatsPeriod)); + } else { + memBalloonDef.defNoneMemBalloon(); + } + response.append(String.format("%n%s%n%n", memBalloonDef.toString())); + response.append(String.format("%s\n", this.vcpu, this.maxVcpu)); return response.toString(); } @@ -1172,6 +1183,53 @@ public class LibvirtVMDef { } } + public static class MemBalloonDef { + private MemBalloonModel memBalloonModel; + private String memBalloonStatsPeriod; + + public enum MemBalloonModel { + NONE("none"), VIRTIO("virtio"); + String model; + + MemBalloonModel(String model) { + this.model = model; + } + + @Override + public String toString() { + return model; + } + } + + public void defNoneMemBalloon() { + memBalloonModel = MemBalloonModel.NONE; + } + + public void defVirtioMemBalloon(String period) { + memBalloonModel = MemBalloonModel.VIRTIO; + memBalloonStatsPeriod = period; + } + + public MemBalloonModel getMemBalloonModel() { + return memBalloonModel; + } + + public String getMemBalloonStatsPeriod() { + return memBalloonStatsPeriod; + } + + @Override + public String toString() { + StringBuilder memBalloonBuilder = new StringBuilder(); + memBalloonBuilder.append("\n"); + if (StringUtils.isNotBlank(memBalloonStatsPeriod)) { + memBalloonBuilder.append("\n"); + } + memBalloonBuilder.append(""); + return memBalloonBuilder.toString(); + } + } + public static class InterfaceDef { public enum GuestNetType { BRIDGE("bridge"), DIRECT("direct"), NETWORK("network"), USER("user"), ETHERNET("ethernet"), INTERNAL("internal"), VHOSTUSER("vhostuser"); diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java index f3fb1df74a0..c9c288f4951 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java @@ -61,8 +61,10 @@ import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; import org.apache.cloudstack.utils.linux.CPUStat; import org.apache.cloudstack.utils.linux.MemStat; import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat; +import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.SystemUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; import org.joda.time.Duration; import org.junit.Assert; import org.junit.Before; @@ -164,6 +166,8 @@ import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.StorageFilerTO; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.agent.api.to.VolumeTO; +import com.cloud.agent.properties.AgentProperties; +import com.cloud.agent.properties.AgentPropertiesFileHandler; import com.cloud.agent.resource.virtualnetwork.VirtualRoutingResource; import com.cloud.exception.InternalErrorException; import com.cloud.hypervisor.Hypervisor.HypervisorType; @@ -181,6 +185,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestDef.GuestType; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestResourceDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InputDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.MemBalloonDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SCSIDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SerialDef; @@ -213,7 +218,7 @@ import com.cloud.vm.VirtualMachine.PowerState; import com.cloud.vm.VirtualMachine.Type; @RunWith(PowerMockRunner.class) -@PrepareForTest(value = {MemStat.class, SshHelper.class}) +@PrepareForTest(value = {MemStat.class, SshHelper.class, AgentPropertiesFileHandler.class, AgentProperties.class, Script.class}) @PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*"}) public class LibvirtComputingResourceTest { @@ -223,6 +228,13 @@ public class LibvirtComputingResourceTest { VirtualMachineTO vmTO; @Mock LibvirtVMDef vmDef; + @Mock + Logger loggerMock; + @Mock + Connect connMock; + @Mock + LibvirtDomainXMLParser parserMock; + @Spy private LibvirtComputingResource libvirtComputingResourceSpy = Mockito.spy(LibvirtComputingResource.class); @@ -245,8 +257,10 @@ public class LibvirtComputingResourceTest { @Before public void setup() throws Exception { libvirtComputingResourceSpy._qemuSocketsPath = new File("/var/run/qemu"); + libvirtComputingResourceSpy.parser = parserMock; Scanner scanner = new Scanner(memInfo); PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scanner); + LibvirtComputingResource.s_logger = loggerMock; } /** @@ -5928,4 +5942,142 @@ public class LibvirtComputingResourceTest { configLocalStorageTests(params); } + @Test + public void getVmsToSetMemoryBalloonStatsPeriodTestLibvirtError() throws LibvirtException { + Mockito.when(connMock.listDomains()).thenThrow(LibvirtException.class); + + List result = libvirtComputingResourceSpy.getVmsToSetMemoryBalloonStatsPeriod(connMock); + + Mockito.verify(loggerMock).error(Mockito.anyString(), Mockito.any()); + Assert.assertTrue(result.isEmpty()); + } + + @Test + public void getVmsToSetMemoryBalloonStatsPeriodTestWithNoVMs() throws LibvirtException { + Mockito.when(connMock.listDomains()).thenReturn(new int[0]); + + List result = libvirtComputingResourceSpy.getVmsToSetMemoryBalloonStatsPeriod(connMock); + + Mockito.verify(loggerMock).info("Skipping the memory balloon stats period setting, since there are no VMs (active Libvirt domains) on this host."); + Assert.assertTrue(result.isEmpty()); + } + + @Test + public void getVmsToSetMemoryBalloonStatsPeriodTestWhenSuccessfullyGetVmIds() throws LibvirtException { + int[] fakeList = new int[]{1}; + List expected = Arrays.asList(ArrayUtils.toObject(fakeList)); + Mockito.when(connMock.listDomains()).thenReturn(fakeList); + + List result = libvirtComputingResourceSpy.getVmsToSetMemoryBalloonStatsPeriod(connMock); + + Assert.assertEquals(expected, result); + } + + @Test + public void getCurrentVmBalloonStatsPeriodTestWhenMemBalloonIsDisabled() { + Integer expected = 0; + PowerMockito.mockStatic(AgentPropertiesFileHandler.class); + PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_DISABLE))).thenReturn(true); + + Integer result = libvirtComputingResourceSpy.getCurrentVmBalloonStatsPeriod(); + + Assert.assertEquals(expected, result); + } + + @Test + public void getCurrentVmBalloonStatsPeriodTestWhenStatsPeriodIsZero() { + Integer expected = 0; + PowerMockito.mockStatic(AgentPropertiesFileHandler.class); + PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_DISABLE))).thenReturn(false); + PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_STATS_PERIOD))).thenReturn(0); + + Integer result = libvirtComputingResourceSpy.getCurrentVmBalloonStatsPeriod(); + + Mockito.verify(loggerMock).info(String.format("The [%s] property is set to '0', this prevents memory statistics from being displayed correctly. " + + "Adjust (increase) the value of this parameter to correct this.", AgentProperties.VM_MEMBALLOON_STATS_PERIOD.getName())); + Assert.assertEquals(expected, result); + } + + @Test + public void getCurrentVmBalloonStatsPeriodTestSuccess() { + Integer expected = 60; + PowerMockito.mockStatic(AgentPropertiesFileHandler.class); + PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_DISABLE))).thenReturn(false); + PowerMockito.when(AgentPropertiesFileHandler.getPropertyValue(Mockito.eq(AgentProperties.VM_MEMBALLOON_STATS_PERIOD))).thenReturn(60); + + Integer result = libvirtComputingResourceSpy.getCurrentVmBalloonStatsPeriod(); + + Assert.assertEquals(expected, result); + } + + private void prepareMocksToSetupMemoryBalloonStatsPeriod(Integer currentVmBalloonStatsPeriod) throws LibvirtException { + Integer[] fakeList = ArrayUtils.toObject(new int[]{1}); + Mockito.doReturn(Arrays.asList(fakeList)).when(libvirtComputingResourceSpy).getVmsToSetMemoryBalloonStatsPeriod(connMock); + Mockito.doReturn(currentVmBalloonStatsPeriod).when(libvirtComputingResourceSpy).getCurrentVmBalloonStatsPeriod(); + Mockito.when(domainMock.getXMLDesc(Mockito.anyInt())).thenReturn(""); + Mockito.when(domainMock.getName()).thenReturn("fake-VM-name"); + Mockito.when(connMock.domainLookupByID(1)).thenReturn(domainMock); + } + + @Test + public void setupMemoryBalloonStatsPeriodTestMemBalloonPropertyDisabled() throws LibvirtException { + prepareMocksToSetupMemoryBalloonStatsPeriod(0); + MemBalloonDef memBalloonDef = new MemBalloonDef(); + memBalloonDef.defVirtioMemBalloon("60"); + Mockito.when(parserMock.parseDomainXML(Mockito.anyString())).thenReturn(true); + Mockito.when(parserMock.getMemBalloon()).thenReturn(memBalloonDef); + PowerMockito.mockStatic(Script.class); + PowerMockito.when(Script.runSimpleBashScript(Mockito.any())).thenReturn(null); + + libvirtComputingResourceSpy.setupMemoryBalloonStatsPeriod(connMock); + + Mockito.verify(loggerMock).debug("The memory balloon stats period [0] has been set successfully for the VM (Libvirt Domain) with ID [1] and name [fake-VM-name]."); + } + + @Test + public void setupMemoryBalloonStatsPeriodTestErrorWhenSetNewPeriod() throws LibvirtException { + prepareMocksToSetupMemoryBalloonStatsPeriod(60); + MemBalloonDef memBalloonDef = new MemBalloonDef(); + memBalloonDef.defVirtioMemBalloon("0"); + Mockito.when(parserMock.parseDomainXML(Mockito.anyString())).thenReturn(true); + Mockito.when(parserMock.getMemBalloon()).thenReturn(memBalloonDef); + PowerMockito.mockStatic(Script.class); + PowerMockito.when(Script.runSimpleBashScript(Mockito.eq("virsh dommemstat 1 --period 60 --live"))).thenReturn("some-fake-error"); + + libvirtComputingResourceSpy.setupMemoryBalloonStatsPeriod(connMock); + + Mockito.verify(loggerMock).error("Unable to set up memory balloon stats period for VM (Libvirt Domain) with ID [1] due to an error when running the [virsh " + + "dommemstat 1 --period 60 --live] command. Output: [some-fake-error]."); + } + + @Test + public void setupMemoryBalloonStatsPeriodTestSetNewPeriodSuccessfully() throws LibvirtException { + prepareMocksToSetupMemoryBalloonStatsPeriod(60); + MemBalloonDef memBalloonDef = new MemBalloonDef(); + memBalloonDef.defVirtioMemBalloon("0"); + Mockito.when(parserMock.parseDomainXML(Mockito.anyString())).thenReturn(true); + Mockito.when(parserMock.getMemBalloon()).thenReturn(memBalloonDef); + PowerMockito.mockStatic(Script.class); + PowerMockito.when(Script.runSimpleBashScript(Mockito.eq("virsh dommemstat 1 --period 60 --live"))).thenReturn(null); + + libvirtComputingResourceSpy.setupMemoryBalloonStatsPeriod(connMock); + + PowerMockito.verifyStatic(Script.class); + Script.runSimpleBashScript("virsh dommemstat 1 --period 60 --live"); + Mockito.verify(loggerMock, Mockito.never()).error(Mockito.anyString()); + } + + @Test + public void setupMemoryBalloonStatsPeriodTestSkipVm() throws LibvirtException { + prepareMocksToSetupMemoryBalloonStatsPeriod(60); + MemBalloonDef memBalloonDef = new MemBalloonDef(); + memBalloonDef.defNoneMemBalloon(); + Mockito.when(parserMock.parseDomainXML(Mockito.anyString())).thenReturn(true); + Mockito.when(parserMock.getMemBalloon()).thenReturn(memBalloonDef); + + libvirtComputingResourceSpy.setupMemoryBalloonStatsPeriod(connMock); + + Mockito.verify(loggerMock).debug("Skipping the memory balloon stats period setting for the VM (Libvirt Domain) with ID [1] and name [fake-VM-name] because this" + + " VM has no memory balloon."); + } } diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java index ccab4b01c33..5dc8e1fcca0 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java @@ -25,6 +25,7 @@ import java.util.List; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.MemBalloonDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef; @@ -180,6 +181,7 @@ public class LibvirtDomainXMLParserTest extends TestCase { "
" + "" + "" + + "" + "" + "
" + "" + @@ -238,6 +240,10 @@ public class LibvirtDomainXMLParserTest extends TestCase { assertEquals("vnet" + i, ifs.get(i).getDevName()); } + MemBalloonDef memBalloon = parser.getMemBalloon(); + assertEquals(MemBalloonDef.MemBalloonModel.VIRTIO, memBalloon.getMemBalloonModel()); + assertEquals("60", memBalloon.getMemBalloonStatsPeriod()); + List rngs = parser.getRngs(); assertEquals("/dev/random", rngs.get(0).getPath()); assertEquals(RngDef.RngBackendModel.RANDOM, rngs.get(0).getRngBackendModel()); diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java index 9da1f5ea75b..ec59265c832 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java @@ -29,6 +29,7 @@ import junit.framework.TestCase; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.MemBalloonDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SCSIDef; import org.apache.cloudstack.utils.linux.MemStat; import org.apache.cloudstack.utils.qemu.QemuObject; @@ -358,6 +359,28 @@ public class LibvirtVMDefTest extends TestCase { assertEquals(xmlDef, expectedXml); } + @Test + public void memBalloonDefTestNone() { + String expectedXml = "\n"; + MemBalloonDef memBalloonDef = new MemBalloonDef(); + memBalloonDef.defNoneMemBalloon(); + + String xmlDef = memBalloonDef.toString(); + + assertEquals(xmlDef, expectedXml); + } + + @Test + public void memBalloonDefTestVirtio() { + String expectedXml = "\n\n"; + MemBalloonDef memBalloonDef = new MemBalloonDef(); + memBalloonDef.defVirtioMemBalloon("60"); + + String xmlDef = memBalloonDef.toString(); + + assertEquals(xmlDef, expectedXml); + } + @Test public void testHypervEnlightDef() { LibvirtVMDef.FeaturesDef featuresDef = new LibvirtVMDef.FeaturesDef();