From 919c9797cc86dd135edc669840793c4e8a7a53b1 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 6 May 2025 14:12:30 +0530 Subject: [PATCH 1/7] server: prevent duplicate HA works and alerts (#10624) Signed-off-by: Abhishek Kumar --- .../cloud/agent/manager/AgentManagerImpl.java | 18 +++++++--- .../cloud/ha/HighAvailabilityManagerImpl.java | 35 +++++++++++++++---- .../com/cloud/ha/dao/HighAvailabilityDao.java | 2 ++ .../cloud/ha/dao/HighAvailabilityDaoImpl.java | 14 +++++++- .../ha/HighAvailabilityManagerImplTest.java | 2 -- 5 files changed, 56 insertions(+), 15 deletions(-) diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java index d2e86fbc4b9..4b2578d20c4 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java @@ -38,9 +38,6 @@ import java.util.concurrent.locks.ReentrantLock; import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.configuration.Config; -import com.cloud.utils.NumbersUtil; -import com.cloud.utils.db.GlobalLock; import org.apache.cloudstack.agent.lb.IndirectAgentLB; import org.apache.cloudstack.ca.CAManager; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; @@ -54,6 +51,7 @@ import org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDao; import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.apache.log4j.MDC; @@ -82,6 +80,7 @@ import com.cloud.agent.api.UnsupportedAnswer; import com.cloud.agent.transport.Request; import com.cloud.agent.transport.Response; import com.cloud.alert.AlertManager; +import com.cloud.configuration.Config; import com.cloud.configuration.ManagementServiceConfiguration; import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenterVO; @@ -105,11 +104,13 @@ import com.cloud.resource.Discoverer; import com.cloud.resource.ResourceManager; import com.cloud.resource.ResourceState; import com.cloud.resource.ServerResource; +import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.DB; import com.cloud.utils.db.EntityManager; +import com.cloud.utils.db.GlobalLock; import com.cloud.utils.db.QueryBuilder; import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.TransactionLegacy; @@ -124,7 +125,6 @@ import com.cloud.utils.nio.Link; import com.cloud.utils.nio.NioServer; import com.cloud.utils.nio.Task; import com.cloud.utils.time.InaccurateClock; -import org.apache.commons.lang3.StringUtils; /** * Implementation of the Agent Manager. This class controls the connection to the agents. @@ -208,6 +208,11 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl protected final ConfigKey CheckTxnBeforeSending = new ConfigKey("Developer", Boolean.class, "check.txn.before.sending.agent.commands", "false", "This parameter allows developers to enable a check to see if a transaction wraps commands that are sent to the resource. This is not to be enabled on production systems.", true); + public static final List HOST_DOWN_ALERT_UNSUPPORTED_HOST_TYPES = Arrays.asList( + Host.Type.SecondaryStorage, + Host.Type.ConsoleProxy + ); + @Override public boolean configure(final String name, final Map params) throws ConfigurationException { @@ -901,7 +906,10 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl if (determinedState == Status.Down) { final String message = "Host is down: " + host.getId() + "-" + host.getName() + ". Starting HA on the VMs"; s_logger.error(message); - if (host.getType() != Host.Type.SecondaryStorage && host.getType() != Host.Type.ConsoleProxy) { + if (Status.Down.equals(host.getStatus())) { + s_logger.debug(String.format("Skipping sending alert for %s as it already in %s state", + host, host.getStatus())); + } else if (!HOST_DOWN_ALERT_UNSUPPORTED_HOST_TYPES.contains(host.getType())) { _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(), host.getPodId(), "Host down, " + host.getId(), message); } event = Status.Event.HostDown; diff --git a/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java b/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java index aa43e6b9161..b78b135adf9 100644 --- a/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java +++ b/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java @@ -21,6 +21,7 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -41,6 +42,7 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.managed.context.ManagedContext; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.management.ManagementServerHost; +import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; import org.apache.log4j.NDC; @@ -71,7 +73,6 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.VpcVirtualNetworkApplianceService; import com.cloud.resource.ResourceManager; import com.cloud.server.ManagementServer; -import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.StorageManager; @@ -223,6 +224,18 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur long _timeBetweenCleanups; String _haTag = null; + private boolean vmHasPendingHAJob(final List pendingHaWorks, final VMInstanceVO vm) { + Optional item = pendingHaWorks.stream() + .filter(h -> h.getInstanceId() == vm.getId()) + .reduce((first, second) -> second); + if (item.isPresent() && (item.get().getTimesTried() < _maxRetries || + !item.get().canScheduleNew(_timeBetweenFailures))) { + s_logger.debug(String.format("Skipping HA on %s as there is already a running HA job for it", vm)); + return true; + } + return false; + } + protected HighAvailabilityManagerImpl() { } @@ -265,28 +278,37 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur s_logger.warn("Scheduling restart for VMs on host " + host.getId() + "-" + host.getName()); final List vms = _instanceDao.listByHostId(host.getId()); + final List pendingHaWorks = _haDao.listPendingHAWorkForHost(host.getId()); final DataCenterVO dcVO = _dcDao.findById(host.getDataCenterId()); // send an email alert that the host is down StringBuilder sb = null; List reorderedVMList = new ArrayList(); - if ((vms != null) && !vms.isEmpty()) { + int skippedHAVms = 0; + if (CollectionUtils.isNotEmpty(vms)) { sb = new StringBuilder(); sb.append(" Starting HA on the following VMs:"); // collect list of vm names for the alert email - for (int i = 0; i < vms.size(); i++) { - VMInstanceVO vm = vms.get(i); + for (VMInstanceVO vm : vms) { + if (vmHasPendingHAJob(pendingHaWorks, vm)) { + skippedHAVms++; + continue; + } if (vm.getType() == VirtualMachine.Type.User) { reorderedVMList.add(vm); } else { reorderedVMList.add(0, vm); } if (vm.isHaEnabled()) { - sb.append(" " + vm.getHostName()); + sb.append(" ").append(vm.getHostName()); } } } - + if (reorderedVMList.isEmpty() && skippedHAVms > 0 && skippedHAVms == vms.size()) { + s_logger.debug(String.format( + "Skipping sending alert for %s as it is suspected to be a duplicate of a recent alert", host)); + return; + } // send an email alert that the host is down, include VMs HostPodVO podVO = _podDao.findById(host.getPodId()); String hostDesc = "name: " + host.getName() + " (id:" + host.getId() + "), availability zone: " + dcVO.getName() + ", pod: " + podVO.getName(); @@ -294,7 +316,6 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur "Host [" + hostDesc + "] is down." + ((sb != null) ? sb.toString() : "")); for (VMInstanceVO vm : reorderedVMList) { - ServiceOfferingVO vmOffering = _serviceOfferingDao.findById(vm.getServiceOfferingId()); if (_itMgr.isRootVolumeOnLocalStorage(vm.getId())) { if (s_logger.isDebugEnabled()){ s_logger.debug("Skipping HA on vm " + vm + ", because it uses local storage. Its fate is tied to the host."); diff --git a/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDao.java b/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDao.java index e8a3e17f805..395b74e0464 100644 --- a/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDao.java +++ b/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDao.java @@ -85,4 +85,6 @@ public interface HighAvailabilityDao extends GenericDao { List listPendingHaWorkForVm(long vmId); List listPendingMigrationsForVm(long vmId); + + List listPendingHAWorkForHost(long hostId); } diff --git a/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDaoImpl.java b/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDaoImpl.java index c7284053fb2..7057ad84a42 100644 --- a/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDaoImpl.java +++ b/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDaoImpl.java @@ -19,7 +19,6 @@ package com.cloud.ha.dao; import java.util.Date; import java.util.List; - import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -260,4 +259,17 @@ public class HighAvailabilityDaoImpl extends GenericDaoBase impl return update(vo, sc); } + + @Override + public List listPendingHAWorkForHost(long hostId) { + SearchBuilder sb = createSearchBuilder(); + sb.and("hostId", sb.entity().getHostId(), Op.EQ); + sb.and("type", sb.entity().getWorkType(), Op.EQ); + sb.and("step", sb.entity().getStep(), Op.NIN); + SearchCriteria sc = sb.create(); + sc.setParameters("hostId", hostId); + sc.setParameters("type", WorkType.HA); + sc.setParameters("step", Step.Done, Step.Cancelled, Step.Error); + return listBy(sc); + } } diff --git a/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java b/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java index 53e5a26849f..f542aee5d6d 100644 --- a/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java +++ b/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java @@ -62,7 +62,6 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.VpcVirtualNetworkApplianceService; import com.cloud.resource.ResourceManager; import com.cloud.server.ManagementServer; -import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.StorageManager; import com.cloud.storage.dao.GuestOSCategoryDao; @@ -214,7 +213,6 @@ public class HighAvailabilityManagerImplTest { Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(DataCenterVO.class)); Mockito.when(_haDao.findPreviousHA(Mockito.anyLong())).thenReturn(Arrays.asList(Mockito.mock(HaWorkVO.class))); Mockito.when(_haDao.persist((HaWorkVO)Mockito.anyObject())).thenReturn(Mockito.mock(HaWorkVO.class)); - Mockito.when(_serviceOfferingDao.findById(vm1.getServiceOfferingId())).thenReturn(Mockito.mock(ServiceOfferingVO.class)); highAvailabilityManager.scheduleRestartForVmsOnHost(hostVO, true); } From f0838cdd30984818919c12663dec6effa26483c7 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Thu, 8 May 2025 12:18:14 +0530 Subject: [PATCH 2/7] [VMware] Update vlans with proper range before creating port group for dvSwitch (#10708) --- .../java/com/cloud/utils/StringUtils.java | 92 ++++++++++++++++++- .../vmware/mo/HypervisorHostHelper.java | 22 +++-- .../vmware/mo/HypervisorHostHelperTest.java | 56 ++++++++++- 3 files changed, 159 insertions(+), 11 deletions(-) diff --git a/utils/src/main/java/com/cloud/utils/StringUtils.java b/utils/src/main/java/com/cloud/utils/StringUtils.java index 01b6f833271..c6116123b40 100644 --- a/utils/src/main/java/com/cloud/utils/StringUtils.java +++ b/utils/src/main/java/com/cloud/utils/StringUtils.java @@ -28,6 +28,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -90,7 +92,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { /** * Converts a List of tags to a comma separated list - * @param tags + * @param tagsList * @return String containing a comma separated list of tags */ @@ -304,4 +306,92 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { return mapResult; } + + /** + * Converts the comma separated numbers to ranges for any consecutive numbers in the input with numbers (and ranges) + * Eg: "198,200-203,299,300,301,303,304,305,306,307,308,311,197" to "197-198,200-203,299-301,303-308,311" + * @param inputNumbersAndRanges + * @return String containing a converted ranges for any consecutive numbers + */ + public static String numbersToRange(String inputNumbersAndRanges) { + Set numberSet = new TreeSet<>(); + for (String inputNumber : inputNumbersAndRanges.split(",")) { + inputNumber = inputNumber.trim(); + if (inputNumber.contains("-")) { + String[] range = inputNumber.split("-"); + if (range.length == 2 && range[0] != null && range[1] != null) { + int start = NumbersUtil.parseInt(range[0], 0); + int end = NumbersUtil.parseInt(range[1], 0); + for (int i = start; i <= end; i++) { + numberSet.add(i); + } + } + } else { + numberSet.add(NumbersUtil.parseInt(inputNumber, 0)); + } + } + + StringBuilder result = new StringBuilder(); + if (!numberSet.isEmpty()) { + List numbers = new ArrayList<>(numberSet); + int startNumber = numbers.get(0); + int endNumber = startNumber; + + for (int i = 1; i < numbers.size(); i++) { + if (numbers.get(i) == endNumber + 1) { + endNumber = numbers.get(i); + } else { + appendRange(result, startNumber, endNumber); + startNumber = endNumber = numbers.get(i); + } + } + appendRange(result, startNumber, endNumber); + } + + return result.toString(); + } + + private static void appendRange(StringBuilder sb, int startNumber, int endNumber) { + if (sb.length() > 0) { + sb.append(","); + } + if (startNumber == endNumber) { + sb.append(startNumber); + } else { + sb.append(startNumber).append("-").append(endNumber); + } + } + + /** + * Converts the comma separated numbers and ranges to numbers + * Eg: "197-198,200-203,299-301,303-308,311" to "197,198,200,201,202,203,299,300,301,303,304,305,306,307,308,311" + * @param inputNumbersAndRanges + * @return String containing a converted numbers + */ + public static String rangeToNumbers(String inputNumbersAndRanges) { + Set numberSet = new TreeSet<>(); + for (String inputNumber : inputNumbersAndRanges.split(",")) { + inputNumber = inputNumber.trim(); + if (inputNumber.contains("-")) { + String[] range = inputNumber.split("-"); + int startNumber = Integer.parseInt(range[0]); + int endNumber = Integer.parseInt(range[1]); + for (int i = startNumber; i <= endNumber; i++) { + numberSet.add(i); + } + } else { + numberSet.add(Integer.parseInt(inputNumber)); + } + } + + StringBuilder result = new StringBuilder(); + for (int number : numberSet) { + if (result.length() > 0) { + result.append(","); + } + result.append(number); + } + + return result.toString(); + } } diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java index 44965e9321b..e43af246696 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java @@ -274,16 +274,18 @@ public class HypervisorHostHelper { } } - public static String composeCloudNetworkName(String prefix, String vlanId, String svlanId, Integer networkRateMbps, String vSwitchName) { + public static String composeCloudNetworkName(String prefix, String vlanId, String svlanId, Integer networkRateMbps, String vSwitchName, VirtualSwitchType vSwitchType) { StringBuffer sb = new StringBuffer(prefix); if (vlanId == null || UNTAGGED_VLAN_NAME.equalsIgnoreCase(vlanId)) { sb.append(".untagged"); } else { + if (vSwitchType != VirtualSwitchType.StandardVirtualSwitch && StringUtils.containsAny(vlanId, ",-")) { + vlanId = com.cloud.utils.StringUtils.numbersToRange(vlanId); + } sb.append(".").append(vlanId); if (svlanId != null) { sb.append(".").append("s" + svlanId); } - } if (networkRateMbps != null && networkRateMbps.intValue() > 0) @@ -293,7 +295,12 @@ public class HypervisorHostHelper { sb.append(".").append(VersioningContants.PORTGROUP_NAMING_VERSION); sb.append("-").append(vSwitchName); - return sb.toString(); + String networkName = sb.toString(); + if (networkName.length() > 80) { + // the maximum limit for a vSwitch name is 80 chars, applies to both standard and distributed virtual switches. + s_logger.warn(String.format("The network name: %s for the vSwitch %s of type %s, exceeds 80 chars", networkName, vSwitchName, vSwitchType)); + } + return networkName; } public static Map getValidatedVsmCredentials(VmwareContext context) throws Exception { @@ -595,7 +602,7 @@ public class HypervisorHostHelper { if (vlanId != null) { vlanId = vlanId.replace("vlan://", ""); } - networkName = composeCloudNetworkName(namePrefix, vlanId, secondaryvlanId, networkRateMbps, physicalNetwork); + networkName = composeCloudNetworkName(namePrefix, vlanId, secondaryvlanId, networkRateMbps, physicalNetwork, vSwitchType); if (vlanId != null && !UNTAGGED_VLAN_NAME.equalsIgnoreCase(vlanId) && !StringUtils.containsAny(vlanId, ",-")) { createGCTag = true; @@ -1167,8 +1174,9 @@ public class HypervisorHostHelper { if (vlanId == null && vlanRange != null && !vlanRange.isEmpty()) { s_logger.debug("Creating dvSwitch port vlan-trunk spec with range: " + vlanRange); VmwareDistributedVirtualSwitchTrunkVlanSpec trunkVlanSpec = new VmwareDistributedVirtualSwitchTrunkVlanSpec(); - for (final String vlanRangePart : vlanRange.split(",")) { - if (vlanRangePart == null || vlanRange.isEmpty()) { + String vlanRangeUpdated = com.cloud.utils.StringUtils.numbersToRange(vlanRange); + for (final String vlanRangePart : vlanRangeUpdated.split(",")) { + if (vlanRangePart == null || vlanRangePart.isEmpty()) { continue; } final NumericRange numericRange = new NumericRange(); @@ -1320,7 +1328,7 @@ public class HypervisorHostHelper { // No doubt about this, depending on vid=null to avoid lots of code below vid = null; } else { - networkName = composeCloudNetworkName(namePrefix, vlanId, null, networkRateMbps, vSwitchName); + networkName = composeCloudNetworkName(namePrefix, vlanId, null, networkRateMbps, vSwitchName, VirtualSwitchType.StandardVirtualSwitch); if (vlanId != null && !UNTAGGED_VLAN_NAME.equalsIgnoreCase(vlanId)) { createGCTag = true; diff --git a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelperTest.java b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelperTest.java index 1c888a05748..05fb9c9e6d3 100644 --- a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelperTest.java +++ b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelperTest.java @@ -557,7 +557,7 @@ public class HypervisorHostHelperTest { networkRateMbps = 200; prefix = "cloud.public"; vSwitchName = "vSwitch0"; - String cloudNetworkName = HypervisorHostHelper.composeCloudNetworkName(prefix, vlanId, svlanId, networkRateMbps, vSwitchName); + String cloudNetworkName = HypervisorHostHelper.composeCloudNetworkName(prefix, vlanId, svlanId, networkRateMbps, vSwitchName, VirtualSwitchType.StandardVirtualSwitch); assertEquals("cloud.public.100.200.1-vSwitch0", cloudNetworkName); } @@ -567,7 +567,7 @@ public class HypervisorHostHelperTest { networkRateMbps = null; prefix = "cloud.storage"; vSwitchName = "vSwitch1"; - String cloudNetworkName = HypervisorHostHelper.composeCloudNetworkName(prefix, vlanId, svlanId, networkRateMbps, vSwitchName); + String cloudNetworkName = HypervisorHostHelper.composeCloudNetworkName(prefix, vlanId, svlanId, networkRateMbps, vSwitchName, VirtualSwitchType.StandardVirtualSwitch); assertEquals("cloud.storage.untagged.0.1-vSwitch1", cloudNetworkName); } @@ -578,10 +578,60 @@ public class HypervisorHostHelperTest { networkRateMbps = 512; prefix = "cloud.guest"; vSwitchName = "vSwitch2"; - String cloudNetworkName = HypervisorHostHelper.composeCloudNetworkName(prefix, vlanId, svlanId, networkRateMbps, vSwitchName); + String cloudNetworkName = HypervisorHostHelper.composeCloudNetworkName(prefix, vlanId, svlanId, networkRateMbps, vSwitchName, VirtualSwitchType.StandardVirtualSwitch); assertEquals("cloud.guest.400.s123.512.1-vSwitch2", cloudNetworkName); } + @Test + public void testComposeCloudNetworkNameVlanRangeGuestTrafficDvSwitch() { + vlanId = "400-500"; + networkRateMbps = 512; + prefix = "cloud.guest"; + vSwitchName = "dvSwitch0"; + String cloudNetworkName = HypervisorHostHelper.composeCloudNetworkName(prefix, vlanId, null, networkRateMbps, vSwitchName, VirtualSwitchType.VMwareDistributedVirtualSwitch); + assertEquals("cloud.guest.400-500.512.1-dvSwitch0", cloudNetworkName); + } + + @Test + public void testComposeCloudNetworkNameVlanNumbersGuestTrafficDvSwitch() { + vlanId = "3001,3002,3003,3004,3005,3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020"; + networkRateMbps = 512; + prefix = "cloud.guest"; + vSwitchName = "dvSwitch0"; + String cloudNetworkName = HypervisorHostHelper.composeCloudNetworkName(prefix, vlanId, null, networkRateMbps, vSwitchName, VirtualSwitchType.VMwareDistributedVirtualSwitch); + assertEquals("cloud.guest.3001-3020.512.1-dvSwitch0", cloudNetworkName); + } + + @Test + public void testComposeCloudNetworkNameVlanNumbersAndRangeGuestTrafficDvSwitch() { + vlanId = "3001,3004-3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3020"; + networkRateMbps = 512; + prefix = "cloud.guest"; + vSwitchName = "dvSwitch0"; + String cloudNetworkName = HypervisorHostHelper.composeCloudNetworkName(prefix, vlanId, null, networkRateMbps, vSwitchName, VirtualSwitchType.VMwareDistributedVirtualSwitch); + assertEquals("cloud.guest.3001,3004-3018,3020.512.1-dvSwitch0", cloudNetworkName); + } + + @Test + public void testComposeCloudNetworkNameUnorderedVlanNumbersAndRangeGuestTrafficDvSwitch() { + vlanId = "3018,3020,3011,3012,3004-3006,3007,3001,3008,3009,3010,3013,3014,3015,3016,3017"; + networkRateMbps = 512; + prefix = "cloud.guest"; + vSwitchName = "dvSwitch0"; + String cloudNetworkName = HypervisorHostHelper.composeCloudNetworkName(prefix, vlanId, null, networkRateMbps, vSwitchName, VirtualSwitchType.VMwareDistributedVirtualSwitch); + assertEquals("cloud.guest.3001,3004-3018,3020.512.1-dvSwitch0", cloudNetworkName); + } + + @Test + public void testComposeCloudNetworkNameOverlappingVlanNumbersAndRangeGuestTrafficDvSwitch() { + vlanId = "3018,3020,3011,3012,3004-3006,3007,3001,3008,3009,3010,3013,3014,3015,3016,3017,3005-3008"; + networkRateMbps = 512; + prefix = "cloud.guest"; + vSwitchName = "dvSwitch0"; + String cloudNetworkName = HypervisorHostHelper.composeCloudNetworkName(prefix, vlanId, null, networkRateMbps, vSwitchName, VirtualSwitchType.VMwareDistributedVirtualSwitch); + assertEquals("cloud.guest.3001,3004-3018,3020.512.1-dvSwitch0", cloudNetworkName); + } + @Test public void testOvfDomRewriter() { final String ovfString = "" + From 696bc50f3ba3d44b70316f4f50c6d447a0038815 Mon Sep 17 00:00:00 2001 From: Fabricio Duarte Date: Fri, 9 May 2025 04:49:14 -0300 Subject: [PATCH 3/7] Backport #9888 to 4.19: Fix Usage inconsistencies (#10712) --- .../com/cloud/usage/dao/UsageNetworksDao.java | 2 + .../cloud/usage/dao/UsageNetworksDaoImpl.java | 27 ++++- .../java/com/cloud/usage/dao/UsageVpcDao.java | 4 + .../com/cloud/usage/dao/UsageVpcDaoImpl.java | 28 ++++- .../com/cloud/usage/UsageManagerImpl.java | 104 ++++++++++++++---- 5 files changed, 130 insertions(+), 35 deletions(-) diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDao.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDao.java index 9be4c49b55e..4d84caaad44 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDao.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDao.java @@ -28,4 +28,6 @@ public interface UsageNetworksDao extends GenericDao { void remove(long networkId, Date removed); List getUsageRecords(Long accountId, Date startDate, Date endDate); + + List listAll(long networkId); } diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDaoImpl.java index 8829414f3f2..6b132228985 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDaoImpl.java @@ -16,15 +16,16 @@ // under the License. package com.cloud.usage.dao; -import com.cloud.network.Network; import com.cloud.usage.UsageNetworksVO; import com.cloud.utils.DateUtil; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; +import javax.annotation.PostConstruct; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; @@ -39,6 +40,14 @@ public class UsageNetworksDaoImpl extends GenericDaoBase " account_id = ? AND ((removed IS NULL AND created <= ?) OR (created BETWEEN ? AND ?) OR (removed BETWEEN ? AND ?) " + " OR ((created <= ?) AND (removed >= ?)))"; + private SearchBuilder usageNetworksSearch; + + @PostConstruct + public void init() { + usageNetworksSearch = createSearchBuilder(); + usageNetworksSearch.and("networkId", usageNetworksSearch.entity().getNetworkId(), SearchCriteria.Op.EQ); + usageNetworksSearch.done(); + } @Override public void update(long networkId, long newNetworkOffering, String state) { @@ -68,11 +77,10 @@ public class UsageNetworksDaoImpl extends GenericDaoBase SearchCriteria sc = this.createSearchCriteria(); sc.addAnd("networkId", SearchCriteria.Op.EQ, networkId); sc.addAnd("removed", SearchCriteria.Op.NULL); - UsageNetworksVO vo = findOneBy(sc); - if (vo != null) { - vo.setRemoved(removed); - vo.setState(Network.State.Destroy.name()); - update(vo.getId(), vo); + List usageNetworksVOs = listBy(sc); + for (UsageNetworksVO entry : usageNetworksVOs) { + entry.setRemoved(removed); + update(entry.getId(), entry); } } catch (final Exception e) { txn.rollback(); @@ -131,4 +139,11 @@ public class UsageNetworksDaoImpl extends GenericDaoBase return usageRecords; } + + @Override + public List listAll(long networkId) { + SearchCriteria sc = usageNetworksSearch.create(); + sc.setParameters("networkId", networkId); + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDao.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDao.java index a1514aba4ca..5167bf88c48 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDao.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDao.java @@ -24,6 +24,10 @@ import java.util.List; public interface UsageVpcDao extends GenericDao { void update(UsageVpcVO usage); + void remove(long vpcId, Date removed); + List getUsageRecords(Long accountId, Date startDate, Date endDate); + + List listAll(long vpcId); } diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDaoImpl.java index a42b96486a5..e7ebf706cf3 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDaoImpl.java @@ -16,15 +16,16 @@ // under the License. package com.cloud.usage.dao; -import com.cloud.network.vpc.Vpc; import com.cloud.usage.UsageVpcVO; import com.cloud.utils.DateUtil; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; +import javax.annotation.PostConstruct; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; @@ -39,6 +40,15 @@ public class UsageVpcDaoImpl extends GenericDaoBase implements " account_id = ? AND ((removed IS NULL AND created <= ?) OR (created BETWEEN ? AND ?) OR (removed BETWEEN ? AND ?) " + " OR ((created <= ?) AND (removed >= ?)))"; + private SearchBuilder usageVpcSearch; + + @PostConstruct + public void init() { + usageVpcSearch = createSearchBuilder(); + usageVpcSearch.and("vpcId", usageVpcSearch.entity().getVpcId(), SearchCriteria.Op.EQ); + usageVpcSearch.done(); + } + @Override public void update(UsageVpcVO usage) { TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.USAGE_DB); @@ -66,11 +76,10 @@ public class UsageVpcDaoImpl extends GenericDaoBase implements SearchCriteria sc = this.createSearchCriteria(); sc.addAnd("vpcId", SearchCriteria.Op.EQ, vpcId); sc.addAnd("removed", SearchCriteria.Op.NULL); - UsageVpcVO vo = findOneBy(sc); - if (vo != null) { - vo.setRemoved(removed); - vo.setState(Vpc.State.Inactive.name()); - update(vo.getId(), vo); + List usageVpcVOs = listBy(sc); + for (UsageVpcVO entry : usageVpcVOs) { + entry.setRemoved(removed); + update(entry.getId(), entry); } } catch (final Exception e) { txn.rollback(); @@ -128,4 +137,11 @@ public class UsageVpcDaoImpl extends GenericDaoBase implements return usageRecords; } + + @Override + public List listAll(long vpcId) { + SearchCriteria sc = usageVpcSearch.create(); + sc.setParameters("vpcId", vpcId); + return listBy(sc); + } } diff --git a/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java b/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java index 63624cdc3c0..cc129a9ec5e 100644 --- a/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java +++ b/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java @@ -1540,7 +1540,7 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna //For volumes which are 'attached' successfully, set the 'deleted' column in the usage_storage table, //so that the secondary storage should stop accounting and only primary will be accounted. SearchCriteria sc = _usageStorageDao.createSearchCriteria(); - sc.addAnd("id", SearchCriteria.Op.EQ, volId); + sc.addAnd("entityId", SearchCriteria.Op.EQ, volId); sc.addAnd("storageType", SearchCriteria.Op.EQ, StorageTypes.VOLUME); List volumesVOs = _usageStorageDao.search(sc, null); if (volumesVOs != null) { @@ -1595,7 +1595,8 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna //For Upload event add an entry to the usage_storage table. SearchCriteria sc = _usageStorageDao.createSearchCriteria(); sc.addAnd("accountId", SearchCriteria.Op.EQ, event.getAccountId()); - sc.addAnd("id", SearchCriteria.Op.EQ, volId); + sc.addAnd("entityId", SearchCriteria.Op.EQ, volId); + sc.addAnd("storageType", SearchCriteria.Op.EQ, StorageTypes.VOLUME); sc.addAnd("deleted", SearchCriteria.Op.NULL); List volumesVOs = _usageStorageDao.search(sc, null); @@ -1772,7 +1773,7 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna } else if (EventTypes.EVENT_LOAD_BALANCER_DELETE.equals(event.getType())) { SearchCriteria sc = _usageLoadBalancerPolicyDao.createSearchCriteria(); sc.addAnd("accountId", SearchCriteria.Op.EQ, event.getAccountId()); - sc.addAnd("id", SearchCriteria.Op.EQ, id); + sc.addAnd("lbId", SearchCriteria.Op.EQ, id); sc.addAnd("deleted", SearchCriteria.Op.NULL); List lbVOs = _usageLoadBalancerPolicyDao.search(sc, null); if (lbVOs.size() > 1) { @@ -1806,7 +1807,7 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna } else if (EventTypes.EVENT_NET_RULE_DELETE.equals(event.getType())) { SearchCriteria sc = _usagePortForwardingRuleDao.createSearchCriteria(); sc.addAnd("accountId", SearchCriteria.Op.EQ, event.getAccountId()); - sc.addAnd("id", SearchCriteria.Op.EQ, id); + sc.addAnd("pfId", SearchCriteria.Op.EQ, id); sc.addAnd("deleted", SearchCriteria.Op.NULL); List pfVOs = _usagePortForwardingRuleDao.search(sc, null); if (pfVOs.size() > 1) { @@ -2104,7 +2105,7 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna } else if (EventTypes.EVENT_VM_SNAPSHOT_OFF_PRIMARY.equals(event.getType())) { QueryBuilder sc = QueryBuilder.create(UsageSnapshotOnPrimaryVO.class); sc.and(sc.entity().getAccountId(), SearchCriteria.Op.EQ, event.getAccountId()); - sc.and(sc.entity().getId(), SearchCriteria.Op.EQ, vmId); + sc.and(sc.entity().getVmId(), SearchCriteria.Op.EQ, vmId); sc.and(sc.entity().getName(), SearchCriteria.Op.EQ, name); sc.and(sc.entity().getDeleted(), SearchCriteria.Op.NULL); List vmsnaps = sc.list(); @@ -2142,31 +2143,88 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna } private void handleNetworkEvent(UsageEventVO event) { - Account account = _accountDao.findByIdIncludingRemoved(event.getAccountId()); - long domainId = account.getDomainId(); - if (EventTypes.EVENT_NETWORK_DELETE.equals(event.getType())) { - usageNetworksDao.remove(event.getResourceId(), event.getCreateDate()); - } else if (EventTypes.EVENT_NETWORK_CREATE.equals(event.getType())) { - UsageNetworksVO usageNetworksVO = new UsageNetworksVO(event.getResourceId(), event.getOfferingId(), event.getZoneId(), event.getAccountId(), domainId, Network.State.Allocated.name(), event.getCreateDate(), null); - usageNetworksDao.persist(usageNetworksVO); - } else if (EventTypes.EVENT_NETWORK_UPDATE.equals(event.getType())) { - usageNetworksDao.update(event.getResourceId(), event.getOfferingId(), event.getResourceType()); + String eventType = event.getType(); + if (EventTypes.EVENT_NETWORK_DELETE.equals(eventType)) { + removeNetworkHelperEntry(event); + } else if (EventTypes.EVENT_NETWORK_CREATE.equals(eventType)) { + createNetworkHelperEntry(event); + } else if (EventTypes.EVENT_NETWORK_UPDATE.equals(eventType)) { + updateNetworkHelperEntry(event); } else { - s_logger.error(String.format("Unknown event type [%s] in Networks event parser. Skipping it.", event.getType())); + s_logger.error(String.format("Unknown event type [%s] in Networks event parser. Skipping it.", eventType)); } } - private void handleVpcEvent(UsageEventVO event) { + private void removeNetworkHelperEntry(UsageEventVO event) { + long networkId = event.getResourceId(); + s_logger.debug(String.format("Removing helper entries of network [%s].", networkId)); + usageNetworksDao.remove(networkId, event.getCreateDate()); + } + + private void createNetworkHelperEntry(UsageEventVO event) { + long networkId = event.getResourceId(); Account account = _accountDao.findByIdIncludingRemoved(event.getAccountId()); long domainId = account.getDomainId(); - if (EventTypes.EVENT_VPC_DELETE.equals(event.getType())) { - usageVpcDao.remove(event.getResourceId(), event.getCreateDate()); - } else if (EventTypes.EVENT_VPC_CREATE.equals(event.getType())) { - UsageVpcVO usageVPCVO = new UsageVpcVO(event.getResourceId(), event.getZoneId(), event.getAccountId(), domainId, Vpc.State.Enabled.name(), event.getCreateDate(), null); - usageVpcDao.persist(usageVPCVO); - } else { - s_logger.error(String.format("Unknown event type [%s] in VPC event parser. Skipping it.", event.getType())); + + List entries = usageNetworksDao.listAll(networkId); + if (!entries.isEmpty()) { + s_logger.warn(String.format("Received a NETWORK.CREATE event for a network [%s] that already has helper entries; " + + "therefore, we will not create a new one.", networkId)); + return; } + + s_logger.debug(String.format("Creating a helper entry for network [%s].", networkId)); + UsageNetworksVO usageNetworksVO = new UsageNetworksVO(networkId, event.getOfferingId(), event.getZoneId(), + event.getAccountId(), domainId, Network.State.Allocated.name(), event.getCreateDate(), null); + usageNetworksDao.persist(usageNetworksVO); + } + + private void updateNetworkHelperEntry(UsageEventVO event) { + long networkId = event.getResourceId(); + Account account = _accountDao.findByIdIncludingRemoved(event.getAccountId()); + long domainId = account.getDomainId(); + + s_logger.debug(String.format("Marking previous helper entries of network [%s] as removed.", networkId)); + usageNetworksDao.remove(networkId, event.getCreateDate()); + + s_logger.debug(String.format("Creating an updated helper entry for network [%s].", networkId)); + UsageNetworksVO usageNetworksVO = new UsageNetworksVO(networkId, event.getOfferingId(), event.getZoneId(), + event.getAccountId(), domainId, event.getResourceType(), event.getCreateDate(), null); + usageNetworksDao.persist(usageNetworksVO); + } + + private void handleVpcEvent(UsageEventVO event) { + String eventType = event.getType(); + if (EventTypes.EVENT_VPC_DELETE.equals(eventType)) { + removeVpcHelperEntry(event); + } else if (EventTypes.EVENT_VPC_CREATE.equals(eventType)) { + createVpcHelperEntry(event); + } else { + s_logger.error(String.format("Unknown event type [%s] in VPC event parser. Skipping it.", eventType)); + } + } + + private void removeVpcHelperEntry(UsageEventVO event) { + long vpcId = event.getResourceId(); + s_logger.debug(String.format("Removing helper entries of VPC [%s].", vpcId)); + usageVpcDao.remove(vpcId, event.getCreateDate()); + } + + private void createVpcHelperEntry(UsageEventVO event) { + long vpcId = event.getResourceId(); + Account account = _accountDao.findByIdIncludingRemoved(event.getAccountId()); + long domainId = account.getDomainId(); + + List entries = usageVpcDao.listAll(vpcId); + if (!entries.isEmpty()) { + s_logger.warn(String.format("Active helper entries already exist for VPC [%s]; therefore, we will not create a new one.", + vpcId)); + return; + } + + s_logger.debug(String.format("Creating a helper entry for VPC [%s].", vpcId)); + UsageVpcVO usageVPCVO = new UsageVpcVO(vpcId, event.getZoneId(), event.getAccountId(), domainId, Vpc.State.Enabled.name(), event.getCreateDate(), null); + usageVpcDao.persist(usageVPCVO); } private class Heartbeat extends ManagedContextRunnable { From 751a0ad55983986340f975562b80dbecb54f27b7 Mon Sep 17 00:00:00 2001 From: Fabricio Duarte Date: Fri, 9 May 2025 05:01:53 -0300 Subject: [PATCH 4/7] UI workaround for the inconsistent formatting of listVirtualMachinesUsageHistory (#10824) --- ui/src/components/view/StatsTab.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/components/view/StatsTab.vue b/ui/src/components/view/StatsTab.vue index 27595223899..b99d716804c 100644 --- a/ui/src/components/view/StatsTab.vue +++ b/ui/src/components/view/StatsTab.vue @@ -521,7 +521,7 @@ export default { this.chartLabels.push(currentLabel) if (this.resourceIsVirtualMachine) { - cpuLine.data.push({ timestamp: currentLabel, stat: element.cpuused.split('%')[0] }) + cpuLine.data.push({ timestamp: currentLabel, stat: element.cpuused.replace(',', '.').split('%')[0] }) element.memoryusedkbs = element.memorykbs - element.memoryintfreekbs memFreeLinePercent.data.push({ timestamp: currentLabel, stat: this.calculateMemoryPercentage(false, element.memorykbs, element.memoryintfreekbs) }) From ea32a1a71aa60bc8de3d06ea9af43007399da2b6 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Fri, 9 May 2025 10:19:30 +0200 Subject: [PATCH 5/7] server: fetch IP of VMs on L2 networks (#10431) --- .../agent/api/GetVmIpAddressCommand.java | 8 ++- .../LibvirtGetVmIpAddressCommandWrapper.java | 57 ++++++++++++++----- ...bvirtGetVmIpAddressCommandWrapperTest.java | 3 + .../xenbase/CitrixRequestWrapperTest.java | 2 +- .../java/com/cloud/vm/UserVmManagerImpl.java | 15 +++-- 5 files changed, 62 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/com/cloud/agent/api/GetVmIpAddressCommand.java b/core/src/main/java/com/cloud/agent/api/GetVmIpAddressCommand.java index a9c7413b9f0..4efc0781f91 100644 --- a/core/src/main/java/com/cloud/agent/api/GetVmIpAddressCommand.java +++ b/core/src/main/java/com/cloud/agent/api/GetVmIpAddressCommand.java @@ -24,11 +24,13 @@ public class GetVmIpAddressCommand extends Command { String vmName; String vmNetworkCidr; boolean windows = false; + String macAddress; - public GetVmIpAddressCommand(String vmName, String vmNetworkCidr, boolean windows) { + public GetVmIpAddressCommand(String vmName, String vmNetworkCidr, boolean windows, String macAddress) { this.vmName = vmName; this.windows = windows; this.vmNetworkCidr = vmNetworkCidr; + this.macAddress = macAddress; } @Override @@ -47,4 +49,8 @@ public class GetVmIpAddressCommand extends Command { public String getVmNetworkCidr() { return vmNetworkCidr; } + + public String getMacAddress() { + return macAddress; + } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java index 0dd52ddfb10..51a125a504f 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java @@ -69,10 +69,13 @@ public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper commands = new ArrayList<>(); commands.add(new String[]{virsh_path, "domifaddr", sanitizedVmName, "--source", "agent"}); Pair response = executePipedCommands(commands, 0); if (response != null) { String output = response.second(); - String[] lines = output.split("\n"); - for (String line : lines) { - if (line.contains("ipv4")) { - String[] parts = line.split(" "); - String[] ipParts = parts[parts.length-1].split("/"); - if (ipParts.length > 1) { - if (NetUtils.isIpWithInCidrRange(ipParts[0], networkCidr)) { - ip = ipParts[0]; - break; - } - } - } + Pair ipAddresses = getIpAddresses(output, macAddress); + String ipv4 = ipAddresses.first(); + if (networkCidr == null || NetUtils.isIpWithInCidrRange(ipv4, networkCidr)) { + ip = ipv4; } } else { s_logger.error("ipFromDomIf: Command execution failed for VM: " + sanitizedVmName); @@ -116,6 +111,38 @@ public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper getIpAddresses(String output, String macAddress) { + String ipv4 = null; + String ipv6 = null; + boolean found = false; + String[] lines = output.split("\n"); + for (String line : lines) { + String[] parts = line.replaceAll(" +", " ").trim().split(" "); + if (parts.length < 4) { + continue; + } + String device = parts[0]; + String mac = parts[1]; + if (found) { + if (!device.equals("-") || !mac.equals("-")) { + break; + } + } else if (!mac.equals(macAddress)) { + continue; + } + found = true; + String ipFamily = parts[2]; + String ipPart = parts[3].split("/")[0]; + if (ipFamily.equals("ipv4")) { + ipv4 = ipPart; + } else if (ipFamily.equals("ipv6")) { + ipv6 = ipPart; + } + } + s_logger.debug(String.format("Found ipv4: %s and ipv6: %s with mac address %s", ipv4, ipv6, macAddress)); + return new Pair<>(ipv4, ipv6); + } + private String ipFromDhcpLeaseFile(String sanitizedVmName, String networkCidr) { String ip = null; List commands = new ArrayList<>(); diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapperTest.java index bd09fe03d49..511306854cf 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapperTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapperTest.java @@ -66,6 +66,7 @@ public class LibvirtGetVmIpAddressCommandWrapperTest { when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName"); when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24"); + when(getVmIpAddressCommand.getMacAddress()).thenReturn("02:0c:02:f9:00:80"); when(getVmIpAddressCommand.isWindows()).thenReturn(false); when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, VIRSH_DOMIF_OUTPUT)); @@ -88,6 +89,7 @@ public class LibvirtGetVmIpAddressCommandWrapperTest { when(getVmIpAddressCommand.getVmName()).thenReturn("invalidVmName!"); when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24"); + when(getVmIpAddressCommand.getMacAddress()).thenReturn("02:0c:02:f9:00:80"); when(getVmIpAddressCommand.isWindows()).thenReturn(false); when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, VIRSH_DOMIF_OUTPUT)); @@ -114,6 +116,7 @@ public class LibvirtGetVmIpAddressCommandWrapperTest { when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName"); when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24"); + when(getVmIpAddressCommand.getMacAddress()).thenReturn("02:0c:02:f9:00:80"); when(getVmIpAddressCommand.isWindows()).thenReturn(true); when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, "192.168.0.10")); diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java index 9ecb14d841f..d464a266493 100755 --- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java +++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java @@ -1934,7 +1934,7 @@ public class CitrixRequestWrapperTest { vmIpsMap.put("Test", "127.0.0.1"); rec.networks = vmIpsMap; - final GetVmIpAddressCommand getVmIpAddrCmd = new GetVmIpAddressCommand("Test", "127.0.0.1/24", false); + final GetVmIpAddressCommand getVmIpAddrCmd = new GetVmIpAddressCommand("Test", "127.0.0.1/24", false, null); final CitrixRequestWrapper wrapper = CitrixRequestWrapper.getInstance(); assertNotNull(wrapper); diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 173cec83d71..64684d5e8d1 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -757,19 +757,21 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir boolean isWindows; Long hostId; String networkCidr; + String macAddress; - public VmIpAddrFetchThread(long vmId, long nicId, String instanceName, boolean windows, Long hostId, String networkCidr) { + public VmIpAddrFetchThread(long vmId, long nicId, String instanceName, boolean windows, Long hostId, String networkCidr, String macAddress) { this.vmId = vmId; this.nicId = nicId; this.vmName = instanceName; this.isWindows = windows; this.hostId = hostId; this.networkCidr = networkCidr; + this.macAddress = macAddress; } @Override protected void runInContext() { - GetVmIpAddressCommand cmd = new GetVmIpAddressCommand(vmName, networkCidr, isWindows); + GetVmIpAddressCommand cmd = new GetVmIpAddressCommand(vmName, networkCidr, isWindows, macAddress); boolean decrementCount = true; try { @@ -2397,9 +2399,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private void loadVmDetailsInMapForExternalDhcpIp() { List networks = _networkDao.listByGuestType(Network.GuestType.Shared); + networks.addAll(_networkDao.listByGuestType(Network.GuestType.L2)); for (NetworkVO network: networks) { - if(_networkModel.isSharedNetworkWithoutServices(network.getId())) { + if (GuestType.L2.equals(network.getGuestType()) || _networkModel.isSharedNetworkWithoutServices(network.getId())) { List nics = _nicDao.listByNetworkId(network.getId()); for (NicVO nic : nics) { @@ -2642,7 +2645,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir boolean isWindows = _guestOSCategoryDao.findById(_guestOSDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows"); _vmIpFetchThreadExecutor.execute(new VmIpAddrFetchThread(vmId, nicId, vmInstance.getInstanceName(), - isWindows, vm.getHostId(), network.getCidr())); + isWindows, vm.getHostId(), network.getCidr(), nicVo.getMacAddress())); } } catch (Exception e) { @@ -3279,7 +3282,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir final List nics = _nicDao.listByVmId(vmId); for (NicVO nic : nics) { Network network = _networkModel.getNetwork(nic.getNetworkId()); - if (_networkModel.isSharedNetworkWithoutServices(network.getId())) { + if (GuestType.L2.equals(network.getGuestType()) || _networkModel.isSharedNetworkWithoutServices(network.getId())) { s_logger.debug("Adding vm " +vmId +" nic id "+ nic.getId() +" into vmIdCountMap as part of vm " + "reboot for vm ip fetch "); vmIdCountMap.put(nic.getId(), new VmAndCountDetails(nic.getInstanceId(), VmIpFetchTrialMax.value())); @@ -5202,7 +5205,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir final List nics = _nicDao.listByVmId(vm.getId()); for (NicVO nic : nics) { Network network = _networkModel.getNetwork(nic.getNetworkId()); - if (_networkModel.isSharedNetworkWithoutServices(network.getId())) { + if (GuestType.L2.equals(network.getGuestType()) || _networkModel.isSharedNetworkWithoutServices(network.getId())) { vmIdCountMap.put(nic.getId(), new VmAndCountDetails(nic.getInstanceId(), VmIpFetchTrialMax.value())); } } From 0a090f4853316cdfdf484e514c3103ccad3e2b80 Mon Sep 17 00:00:00 2001 From: dahn Date: Fri, 9 May 2025 12:54:32 +0200 Subject: [PATCH 6/7] cleanup call on super (#10807) --- .../component/test_affinity_groups_projects.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/test/integration/component/test_affinity_groups_projects.py b/test/integration/component/test_affinity_groups_projects.py index 07811e79fde..ae94909a051 100644 --- a/test/integration/component/test_affinity_groups_projects.py +++ b/test/integration/component/test_affinity_groups_projects.py @@ -99,9 +99,12 @@ class TestCreateAffinityGroup(cloudstackTestCase): cls.api_client = cls.testClient.getApiClient() cls.services = Services().services + cls._cleanup = [] + #Get Zone, Domain and templates cls.rootdomain = get_domain(cls.api_client) cls.domain = Domain.create(cls.api_client, cls.services["domain"]) + cls._cleanup.append(cls.domain) cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) cls.template = get_template( @@ -177,21 +180,11 @@ class TestCreateAffinityGroup(cloudstackTestCase): self.cleanup = [] def tearDown(self): - try: -# #Clean up, terminate the created instance, volumes and snapshots - cleanup_resources(self.apiclient, self.cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return + super(TestCreateAffinityGroup, self).tearDownClass() @classmethod def tearDownClass(cls): - try: - #Clean up, terminate the created templates - cls.domain.delete(cls.api_client, cleanup=True) - cleanup_resources(cls.api_client, cls._cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) + super(TestCreateAffinityGroup, cls).tearDownClass() def create_aff_grp(self, api_client=None, aff_grp=None, aff_grp_name=None, projectid=None): From e68abcd85de18140dd4c4c377e5aa606e8c5dec9 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 12 May 2025 10:46:37 +0530 Subject: [PATCH 7/7] xenserver: destroy halted vm on expunge (#10833) * xenserver: destroy halted vm on expunge Signed-off-by: Abhishek Kumar --- .../com/cloud/hypervisor/XenServerGuru.java | 9 ++ .../CitrixCleanupVMCommandWrapper.java | 82 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCleanupVMCommandWrapper.java diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/XenServerGuru.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/XenServerGuru.java index 9de6ba8ab4f..98b778817b9 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/XenServerGuru.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/XenServerGuru.java @@ -35,6 +35,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; +import com.cloud.agent.api.CleanupVMCommand; import com.cloud.agent.api.Command; import com.cloud.agent.api.to.DataObjectType; import com.cloud.agent.api.to.DataStoreTO; @@ -236,4 +237,12 @@ public class XenServerGuru extends HypervisorGuruBase implements HypervisorGuru, public ConfigKey[] getConfigKeys() { return new ConfigKey[] {MaxNumberOfVCPUSPerVM}; } + + @Override + public List finalizeExpunge(VirtualMachine vm) { + List commands = new ArrayList<>(); + final CleanupVMCommand cleanupVMCommand = new CleanupVMCommand(vm.getInstanceName(), true); + commands.add(cleanupVMCommand); + return commands; + } } diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCleanupVMCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCleanupVMCommandWrapper.java new file mode 100644 index 00000000000..3b0408379ef --- /dev/null +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCleanupVMCommandWrapper.java @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.hypervisor.xenserver.resource.wrapper.xenbase; + +import java.util.Iterator; +import java.util.Set; + +import org.apache.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CleanupVMCommand; +import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.xensource.xenapi.Connection; +import com.xensource.xenapi.Types; +import com.xensource.xenapi.VM; + +@ResourceWrapper(handles = CleanupVMCommand.class) +public class CitrixCleanupVMCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = Logger.getLogger(CitrixCleanupVMCommandWrapper.class); + + @Override + public Answer execute(final CleanupVMCommand command, final CitrixResourceBase citrixResourceBase) { + if (citrixResourceBase.isDestroyHaltedVms()) { + s_logger.debug(String.format("Cleanup VM is not needed for host with version %s", + citrixResourceBase.getHost().getProductVersion())); + return new Answer(command); + } + final String vmName = command.getVmName(); + try { + final Connection conn = citrixResourceBase.getConnection(); + final Set vms = VM.getByNameLabel(conn, vmName); + if (vms.isEmpty()) { + return new Answer(command, true, "VM does not exist"); + } + // destroy vm which is in HALTED state on this host + final Iterator iter = vms.iterator(); + while (iter.hasNext()) { + final VM vm = iter.next(); + final VM.Record vmr = vm.getRecord(conn); + if (!Types.VmPowerState.HALTED.equals(vmr.powerState)) { + final String msg = String.format("VM %s is not in %s state", vmName, Types.VmPowerState.HALTED); + s_logger.error(msg); + return new Answer(command, false, msg); + } + if (citrixResourceBase.isRefNull(vmr.residentOn)) { + continue; + } + if (vmr.residentOn.getUuid(conn).equals(citrixResourceBase.getHost().getUuid())) { + continue; + } + iter.remove(); + } + for (final VM vm : vms) { + citrixResourceBase.destroyVm(vm, conn, true); + } + + } catch (final Exception e) { + final String msg = String.format("Clean up VM %s fail due to %s", vmName, e); + s_logger.error(msg, e); + return new Answer(command, false, e.getMessage()); + } + return new Answer(command); + } +}