Copilot commented on code in PR #13182:
URL: https://github.com/apache/cloudstack/pull/13182#discussion_r3261552836


##########
server/src/main/java/com/cloud/resource/ResourceManagerImpl.java:
##########
@@ -2585,6 +2613,27 @@ private Host createHostAndAgent(final ServerResource 
resource, final Map<String,
         return host;
     }
 
+    void checkForDuplicateHost(final String url) {
+        String hostIpOrName = null;
+        String ipAddress = null;
+        try {
+            hostIpOrName = new URI(UriUtils.encodeURIComponent(url)).getHost();
+            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()));
+        }

Review Comment:
   The linked issue states: "validate if the host already exists in the 
database with same hostname or IP address". This implementation only looks up 
the host by resolved IP (`findByIp`). If a previously added host's stored IP 
differs from what the new URL's hostname resolves to (e.g., DNS changes, 
multi-NIC hosts, or hosts stored with a different management IP than what DNS 
now returns for the same name), the duplicate-by-name case will not be caught. 
Consider also checking by `name`/hostname in addition to IP to fully cover the 
issue's requirement.
   



##########
server/src/main/java/com/cloud/resource/ResourceManagerImpl.java:
##########
@@ -2585,6 +2613,27 @@ private Host createHostAndAgent(final ServerResource 
resource, final Map<String,
         return host;
     }
 
+    void checkForDuplicateHost(final String url) {
+        String hostIpOrName = null;
+        String ipAddress = null;
+        try {
+            hostIpOrName = new URI(UriUtils.encodeURIComponent(url)).getHost();
+            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);

Review Comment:
   When `InetAddress.getByName(hostIpOrName)` throws `UnknownHostException`, 
`hostIpOrName` has already been set (non-blank) but `ipAddress` remains `null`. 
The early-return guard only checks `hostIpOrName` for blankness, so execution 
falls through to `_hostDao.findByIp(null)` with a null argument. Consider 
returning early on `UnknownHostException` (or also guarding on `ipAddress == 
null`) so an unresolvable hostname doesn't trigger an unnecessary DAO query 
with a null IP.



##########
server/src/main/java/com/cloud/resource/ResourceManagerImpl.java:
##########
@@ -2281,6 +2286,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) {

Review Comment:
   `validateExistingHostLocationImmutable` is skipped for any host whose `Type 
!= Routing` (e.g., SecondaryStorageVM, ConsoleProxy, storage hosts). The PR 
title and linked issue both speak about disallowing zone/pod/cluster changes 
for "an existing host" in general, not just routing/KVM-style hypervisor hosts. 
If the intent is to protect all persistent hosts, restricting the check to 
`Routing` will silently allow other host types to have their 
`data_center_id`/`pod_id`/`cluster_id` mutated on reconnect. Please confirm 
this restriction is intentional and document the rationale; otherwise, broaden 
the check.
   



##########
server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java:
##########
@@ -218,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

Review Comment:
   There is no unit test covering the `checkForDuplicateHost` path that is 
invoked from `discoverHostsFull` (the integration of the duplicate check into 
the add-host flow). The two new tests only invoke `checkForDuplicateHost` 
directly. Consider adding a test that ensures 
`discoverHostsFull`/`discoverHosts` propagates the 
`InvalidParameterValueException` for a duplicate IP before any discoverer is 
invoked, mirroring the convention of other end-to-end tests in this class.
   



##########
server/src/main/java/com/cloud/resource/ResourceManagerImpl.java:
##########
@@ -2281,6 +2286,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.",

Review Comment:
   The error message embeds `existingPodId`/`existingClusterId` with `%d` 
format specifiers but `podId`/`clusterId` with `%s`. Since both pairs are 
`Long` and may be null in the input case (podId/clusterId can legitimately be 
null), using `%d` on the existing values is technically safe due to the earlier 
null guard, but mixing `%d` and `%s` for the same logical fields is 
inconsistent and slightly confusing. Consider using `%s` consistently for all 
six values so a null in any of them is rendered cleanly rather than risking a 
`IllegalFormatConversionException` if future refactors loosen the null guard.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to