From 6bf5881703f8d5774ea8a5f23cc4ce46f344379a Mon Sep 17 00:00:00 2001 From: vishesh92 Date: Fri, 15 May 2026 13:26:33 +0530 Subject: [PATCH 1/4] Don't allow adding of existing host to another zone, pod or cluster --- .../cloud/resource/ResourceManagerImpl.java | 24 +++++++ .../resource/ResourceManagerImplTest.java | 69 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java index cc789bf56508..0255a4f9de5f 100755 --- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Random; import java.util.stream.Collectors; @@ -2281,6 +2282,27 @@ private HostVO getNewHost(StartupCommand[] startupCommands) { return null; } + protected void validateExistingHostLocationImmutable(final HostVO host, final boolean newHost, + final long dcId, final Long podId, final Long clusterId, final StartupCommand startup) { + if (newHost || host == null || host.getType() != Host.Type.Routing) { + return; + } + final Long existingDcId = host.getDataCenterId(); + final Long existingPodId = host.getPodId(); + final Long existingClusterId = host.getClusterId(); + if (existingDcId == null || existingPodId == null || existingClusterId == null) { + return; + } + if (existingDcId == dcId && Objects.equals(existingPodId, podId) && Objects.equals(existingClusterId, clusterId)) { + return; + } + final String identity = host.getUuid() != null ? host.getUuid() : host.getGuid(); + final String ip = startup != null ? startup.getPrivateIpAddress() : "unknown"; + throw new InvalidParameterValueException(String.format( + "Host %s (ip: %s) is already registered in [zone: %d, pod: %d, cluster: %d] and cannot be re-added or reconnected with [zone: %d, pod: %s, cluster: %s]. Zone, pod and cluster of an existing host are immutable.", + identity, ip, existingDcId, existingPodId, existingClusterId, dcId, podId, clusterId)); + } + protected HostVO createHostVO(final StartupCommand[] cmds, final ServerResource resource, final Map details, List hostTags, final ResourceStateAdapter.Event stateEvent) { boolean newHost = false; @@ -2356,6 +2378,8 @@ protected HostVO createHostVO(final StartupCommand[] cmds, final ServerResource } } + validateExistingHostLocationImmutable(host, newHost, dcId, podId, clusterId, startup); + host.setDataCenterId(dc.getId()); host.setPodId(podId); host.setClusterId(clusterId); diff --git a/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java b/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java index 91e4bf7a47b8..37bf4405b0f1 100644 --- a/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java +++ b/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java @@ -18,6 +18,7 @@ package com.cloud.resource; import com.cloud.agent.AgentManager; +import com.cloud.agent.api.StartupCommand; import com.cloud.agent.api.GetVncPortAnswer; import com.cloud.agent.api.GetVncPortCommand; import com.cloud.capacity.dao.CapacityDao; @@ -335,6 +336,74 @@ public void testGetHostCredentialsMissingParameter() { resourceManager.getHostCredentials(host); } + private HostVO mockExistingRoutingHost(long dcId, Long podId, Long clusterId) { + HostVO existing = Mockito.mock(HostVO.class); + when(existing.getType()).thenReturn(Host.Type.Routing); + when(existing.getDataCenterId()).thenReturn(dcId); + when(existing.getPodId()).thenReturn(podId); + when(existing.getClusterId()).thenReturn(clusterId); + when(existing.getUuid()).thenReturn("host-uuid"); + return existing; + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidateExistingHostLocationImmutableRejectsZoneChange() { + HostVO existing = mockExistingRoutingHost(1L, 10L, 100L); + StartupCommand startup = Mockito.mock(StartupCommand.class); + when(startup.getPrivateIpAddress()).thenReturn("10.10.10.10"); + resourceManager.validateExistingHostLocationImmutable(existing, false, 2L, 10L, 100L, startup); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidateExistingHostLocationImmutableRejectsPodChange() { + HostVO existing = mockExistingRoutingHost(1L, 10L, 100L); + StartupCommand startup = Mockito.mock(StartupCommand.class); + when(startup.getPrivateIpAddress()).thenReturn("10.10.10.10"); + resourceManager.validateExistingHostLocationImmutable(existing, false, 1L, 11L, 100L, startup); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidateExistingHostLocationImmutableRejectsClusterChange() { + HostVO existing = mockExistingRoutingHost(1L, 10L, 100L); + StartupCommand startup = Mockito.mock(StartupCommand.class); + when(startup.getPrivateIpAddress()).thenReturn("10.10.10.10"); + resourceManager.validateExistingHostLocationImmutable(existing, false, 1L, 10L, 101L, startup); + } + + @Test + public void testValidateExistingHostLocationImmutableAllowsSameTupleReconnect() { + HostVO existing = mockExistingRoutingHost(1L, 10L, 100L); + resourceManager.validateExistingHostLocationImmutable(existing, false, 1L, 10L, 100L, null); + } + + @Test + public void testValidateExistingHostLocationImmutableAllowsNewHost() { + HostVO existing = mockExistingRoutingHost(2L, 20L, 200L); + resourceManager.validateExistingHostLocationImmutable(existing, true, 1L, 10L, 100L, null); + } + + @Test + public void testValidateExistingHostLocationImmutableSkipsNonRoutingHost() { + HostVO existing = Mockito.mock(HostVO.class); + when(existing.getType()).thenReturn(Host.Type.SecondaryStorageVM); + resourceManager.validateExistingHostLocationImmutable(existing, false, 1L, 10L, 100L, null); + } + + @Test + public void testValidateExistingHostLocationImmutableSkipsPartialLocationRow() { + HostVO existing = Mockito.mock(HostVO.class); + when(existing.getType()).thenReturn(Host.Type.Routing); + when(existing.getDataCenterId()).thenReturn(1L); + when(existing.getPodId()).thenReturn(null); + when(existing.getClusterId()).thenReturn(null); + resourceManager.validateExistingHostLocationImmutable(existing, false, 2L, 10L, 100L, null); + } + + @Test + public void testValidateExistingHostLocationImmutableSkipsNullExistingHost() { + resourceManager.validateExistingHostLocationImmutable(null, false, 2L, 10L, 100L, null); + } + @Test public void testGetHostCredentials() { Ternary credentials = resourceManager.getHostCredentials(host); From 42369dc16616d56168184bcd50e61261de97d250 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 18 May 2026 13:41:29 +0530 Subject: [PATCH 2/4] server: add duplicate host check Fixes #13080 Signed-off-by: Abhishek Kumar --- .../cloud/resource/ResourceManagerImpl.java | 20 +++++++++++++++++++ .../resource/ResourceManagerImplTest.java | 13 ++++++++++++ 2 files changed, 33 insertions(+) diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java index 0255a4f9de5f..16724664ef45 100755 --- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java @@ -799,6 +799,8 @@ private List discoverHostsFull(final Long dcId, final Long podId, Long c throw new InvalidParameterValueException(url + " is not a valid uri"); } + checkForDuplicateHost(url); + final List hosts = new ArrayList<>(); logger.info("Trying to add a new host at {} in data center {}", url, zone); boolean isHypervisorTypeSupported = false; @@ -2609,6 +2611,24 @@ private Host createHostAndAgent(final ServerResource resource, final Map details, final boolean old, final List hostTags, final boolean forRebalance) { HostVO host = null; StartupCommand[] cmds = null; diff --git a/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java b/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java index 37bf4405b0f1..5085c3eea588 100644 --- a/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java +++ b/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java @@ -219,6 +219,19 @@ public void tearDown() throws Exception { closeable.close(); } + @Test(expected = InvalidParameterValueException.class) + public void testCheckForDuplicateHostThrowsWhenIpAlreadyExists() { + when(hostDao.findByIp("10.0.0.10")).thenReturn(host); + resourceManager.checkForDuplicateHost("http://10.0.0.10"); + } + + @Test + public void testCheckForDuplicateHostAllowsUniqueHost() { + when(hostDao.findByIp("10.0.0.30")).thenReturn(null); + resourceManager.checkForDuplicateHost("http://10.0.0.30"); + verify(hostDao, times(1)).findByIp("10.0.0.30"); + } + @Test public void testCheckAndMaintainEnterMaintenanceModeNoVms() throws NoTransitionException { // Test entering into maintenance with no VMs running on host. From 6aee0d976fe1e6756f3559da352ee5b8fb03e9f6 Mon Sep 17 00:00:00 2001 From: vishesh92 Date: Tue, 19 May 2026 00:47:45 +0530 Subject: [PATCH 3/4] Resolve IP from the hostname to avoid conflicts --- .../com/cloud/resource/ResourceManagerImpl.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java index 16724664ef45..ab0aa91e7bcb 100755 --- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java @@ -19,9 +19,11 @@ import static com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS; import static com.cloud.configuration.ConfigurationManagerImpl.SET_HOST_DOWN_TO_MAINTENANCE; +import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -2613,19 +2615,22 @@ private Host createHostAndAgent(final ServerResource resource, final Map Date: Wed, 20 May 2026 17:17:28 +0530 Subject: [PATCH 4/4] Address copilot comments --- .../cloud/resource/ResourceManagerImpl.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java index ab0aa91e7bcb..4ebfca54cc0f 100755 --- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java @@ -2291,10 +2291,10 @@ protected void validateExistingHostLocationImmutable(final HostVO host, final bo if (newHost || host == null || host.getType() != Host.Type.Routing) { return; } - final Long existingDcId = host.getDataCenterId(); + final long existingDcId = host.getDataCenterId(); final Long existingPodId = host.getPodId(); final Long existingClusterId = host.getClusterId(); - if (existingDcId == null || existingPodId == null || existingClusterId == null) { + if (existingPodId == null || existingClusterId == null) { return; } if (existingDcId == dcId && Objects.equals(existingPodId, podId) && Objects.equals(existingClusterId, clusterId)) { @@ -2302,9 +2302,11 @@ protected void validateExistingHostLocationImmutable(final HostVO host, final bo } final String identity = host.getUuid() != null ? host.getUuid() : host.getGuid(); final String ip = startup != null ? startup.getPrivateIpAddress() : "unknown"; - throw new InvalidParameterValueException(String.format( - "Host %s (ip: %s) is already registered in [zone: %d, pod: %d, cluster: %d] and cannot be re-added or reconnected with [zone: %d, pod: %s, cluster: %s]. Zone, pod and cluster of an existing host are immutable.", - identity, ip, existingDcId, existingPodId, existingClusterId, dcId, podId, clusterId)); + throw new InvalidParameterValueException( + String.format("Host %s (ip: %s) is already registered in [zone: %s, pod: %s, cluster: %s] and cannot " + + "be re-added or reconnected with [zone: %s, pod: %s, cluster: %s]. Zone, pod and " + + "cluster of an existing host are immutable.", + identity, ip, existingDcId, existingPodId, existingClusterId, dcId, podId, clusterId)); } protected HostVO createHostVO(final StartupCommand[] cmds, final ServerResource resource, final Map details, List hostTags, @@ -2618,19 +2620,23 @@ void checkForDuplicateHost(final String url) { String ipAddress = null; try { hostIpOrName = new URI(UriUtils.encodeURIComponent(url)).getHost(); + if (StringUtils.isBlank(hostIpOrName)) { + return; + } InetAddress ip = InetAddress.getByName(hostIpOrName); ipAddress = ip.getHostAddress(); } catch (final URISyntaxException | UnknownHostException ignore) { // unparseable URL or unknown host - discoverer will reject it shortly anyway - } - if (StringUtils.isBlank(hostIpOrName)) { return; } - final HostVO existingByIp = _hostDao.findByIp(ipAddress); - if (existingByIp != null) { - throw new InvalidParameterValueException(String.format( - "A host with IP address / hostname '%s' (%s) already exists (id: %s). Remove it before adding again.", - hostIpOrName, ipAddress, existingByIp.getUuid())); + + if (StringUtils.isNotBlank(ipAddress)) { + final HostVO existingByIp = _hostDao.findByIp(ipAddress); + if (existingByIp != null) { + throw new InvalidParameterValueException(String.format( + "A host with IP address '%s' (%s) already exists (id: %s). Remove it before adding again.", + ipAddress, hostIpOrName, existingByIp)); + } } }