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


##########
agent/src/main/java/com/cloud/agent/AgentShell.java:
##########
@@ -424,11 +434,15 @@ public void init(String[] args) throws 
ConfigurationException {
             _properties.put(cmdLineProp.getKey(), cmdLineProp.getValue());
         }
 
-        LOGGER.info("Defaulting to the constant time backoff algorithm");
-        _backoff = new ConstantTimeBackoff();
-        Map<String, Object> map = new HashMap<>();
-        map.put("seconds", _properties.getProperty("backoff.seconds"));
-        _backoff.configure("ConstantTimeBackoff", map);
+        try {
+            LOGGER.info("Creating backoff delay algorithm implementation");
+            setBackoffAlgorithm(BackoffFactory.create(_properties));
+            LOGGER.info("Created {} delay algorithm implementation", 
getBackoffAlgorithm().getClass().getName());
+        } catch (RuntimeException e) {
+            String msg = String.format("Failed to create backoff with provided 
properties %s, failing back to default", _properties);

Review Comment:
   The fallback message formats the entire properties object, which can include 
the persisted keystore passphrase. This leaks the same credential on the WARN 
path when backoff configuration is invalid; do not include `_properties` in the 
message.



##########
engine/orchestration/src/test/java/com/cloud/agent/manager/AgentManagerImplTest.java:
##########
@@ -52,11 +73,12 @@ public class AgentManagerImplTest {
 
     @Before
     public void setUp() throws Exception {
-        host = Mockito.spy(new HostVO("some-Uuid"));
-        Mockito.when(host.getId()).thenReturn(1L);
+        host = new HostVO("some-Uuid");
+        Mockito.when(host.getId()).thenReturn(1L)
+        FieldUtils.writeField(host, "id", HOST_ID, true);

Review Comment:
   `host` is now a real `HostVO`, not a Mockito mock or spy, so this 
`when(...)` call throws `MissingMethodInvocationException`; as written it also 
lacks a semicolon and prevents compilation. The following reflection write 
already sets the ID, so remove the stubbing line.



##########
utils/src/main/java/com/cloud/utils/nio/HandlerFactory.java:
##########
@@ -32,6 +32,6 @@ default int getMaxConcurrentNewConnectionsCount() {
     default int getNewConnectionsCount() {
         return 0;
     }
-    default void registerNewConnection(SocketAddress address) {}
-    default void unregisterNewConnection(SocketAddress address) {}
+    default void registerNewConnection(InetSocketAddress address) {}
+    default void unregisterNewConnection(InetSocketAddress address) {}

Review Comment:
   `HandlerFactory` is a public interface and previously accepted the general 
`SocketAddress`; narrowing these default-method parameters to 
`InetSocketAddress` breaks existing implementations that override the old 
signatures (their methods will no longer override and the new defaults will 
run). Keep the public parameter type as `SocketAddress` and narrow only at the 
implementation boundary.



##########
framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostDaoImpl.java:
##########
@@ -64,6 +65,11 @@ public void invalidateRunSession(long id, long runid) {
         }
     }
 
+    @Override
+    public List<ManagementServerHostVO> findAllIncludingRemoved() {
+        return listIncludingRemovedBy(ActiveSearch.create());

Review Comment:
   `ActiveSearch` filters `removed IS NULL` and requires a recent 
`lastUpdateTime`, so this method does not return “all management servers 
(including down and removed)” as its new contract says. It excludes removed and 
stale/down rows; use the inherited `listAllIncludingRemoved()` instead.



##########
plugins/maintenance/src/main/java/org/apache/cloudstack/maintenance/ManagementServerMaintenanceManagerImpl.java:
##########
@@ -466,9 +466,20 @@ public ManagementServerMaintenanceResponse 
prepareForMaintenance(PrepareForMaint
         boolean ignoreMaintenanceHosts = 
ManagementServerMaintenanceIgnoreMaintenanceHosts.value();
         if (indirectAgentLB.haveAgentBasedHosts(msHost.getMsid(), 
ignoreMaintenanceHosts)) {
             List<String> indirectAgentMsList = 
indirectAgentLB.getManagementServerList();
-            indirectAgentMsList.remove(msHost.getServiceIP());
-            List<String> nonUpMsList = msHostDao.listNonUpStateMsIPs();
-            indirectAgentMsList.removeAll(nonUpMsList);
+            // Remove current server by both hostname and IP since the list 
could contain either
+            if (msHost.getName() != null) {
+                indirectAgentMsList.remove(msHost.getName());
+            }
+            if (msHost.getServiceIP() != null) {
+                indirectAgentMsList.remove(msHost.getServiceIP());
+            }

Review Comment:
   `indirectAgentMsList` contains configured addresses, but this removes the 
persisted canonical hostname/IP. For hostname aliases neither removal is 
guaranteed to match, so maintenance can send agents a list that still contains 
the server being put into maintenance. Resolve the current node to its 
configured address before removing it.



##########
framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletHttpHandler.java:
##########
@@ -113,6 +117,14 @@ private void writeResponse(HttpResponse response, int 
statusCode, String content
         response.setEntity(body);
     }
 
+    private void logRequest(HttpRequest request, String requestBody) {
+        Optional<HttpRequest> requestOpt = Optional.ofNullable(request);
+        Optional<RequestLine> requestLineOpt = 
requestOpt.map(HttpRequest::getRequestLine);
+        String method = 
requestLineOpt.map(RequestLine::getMethod).orElse(null);
+        String uri = requestLineOpt.map(RequestLine::getUri).orElse(null);
+        logger.debug("{} {} {}", method, uri, requestBody);

Review Comment:
   This logs the complete cluster POST body, including the `gsonPackage` 
command/answer JSON. Commands on this channel can contain credential fields, so 
the new DEBUG statement creates a plaintext secret sink in management-server 
logs; log request metadata or redact the payload.



##########
agent/src/main/java/com/cloud/agent/ServerAttache.java:
##########
@@ -0,0 +1,493 @@
+// 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.agent;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.Command;
+import com.cloud.agent.transport.Request;
+import com.cloud.agent.transport.Response;
+import com.cloud.exception.AgentUnavailableException;
+import com.cloud.exception.CloudException;
+import com.cloud.exception.OperationTimedoutException;
+import com.cloud.utils.concurrency.NamedThreadFactory;
+import com.cloud.utils.nio.Link;
+import org.apache.cloudstack.managed.context.ManagedContextRunnable;
+import org.apache.cloudstack.threadcontext.ThreadContextCommandUtil;
+import 
org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+
+import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
+import java.security.SecureRandom;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * ServerAttache provides basic server communication commands to be 
implemented.
+ *
+ * @author mprokopchuk
+ */
+public class ServerAttache {
+    private static final Logger logger = 
LogManager.getLogger(ServerAttache.class);
+
+    private static final ScheduledExecutorService s_listenerExecutor = 
Executors.newScheduledThreadPool(10,
+            new NamedThreadFactory("ListenerTimer"));
+
+    protected static Comparator<Request> s_reqComparator = (o1, o2) -> {
+        long seq1 = o1.getSequence();
+        long seq2 = o2.getSequence();
+        if (seq1 < seq2) {
+            return -1;
+        } else if (seq1 > seq2) {
+            return 1;
+        } else {
+            return 0;
+        }
+    };
+
+    protected static Comparator<Object> s_seqComparator = (o1, o2) -> {
+        long seq1 = ((Request) o1).getSequence();
+        long seq2 = (Long) o2;
+        if (seq1 < seq2) {
+            return -1;
+        } else if (seq1 > seq2) {
+            return 1;
+        } else {
+            return 0;
+        }
+    };
+
+    private static final Random s_rand = new SecureRandom();
+    protected String _name;
+    private Link _link;
+    protected ConcurrentHashMap<Long, ServerListener> _waitForList;
+    protected ConcurrentHashMap<Long, java.util.concurrent.ScheduledFuture<?>> 
_alarmFutures;
+    protected LinkedList<Request> _requests;
+    protected Long _currentSequence;
+    protected long _nextSequence;
+
+    protected ServerAttache(Link link) {
+        _name = link.getIpAddress();
+        _link = link;
+        _waitForList = new ConcurrentHashMap<>();
+        _alarmFutures = new ConcurrentHashMap<>();
+        _requests = new LinkedList<>();
+        _nextSequence = Long.valueOf(s_rand.nextInt(Short.MAX_VALUE)) << 48;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("ServerAttache %s", 
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this,
+                "_name"));
+    }
+
+    public synchronized long getNextSequence() {
+        return ++_nextSequence;
+    }
+
+    protected synchronized void addRequest(Request req) {
+        int index = findRequest(req);
+        assert (index < 0) : "How can we get index again? " + index + ":" + 
req.toString();
+        _requests.add(-index - 1, req);
+    }
+
+    protected void cancel(Request req) {
+        cancel(req.getSequence());
+    }
+
+    protected synchronized void cancel(long seq) {
+        logger.debug(log(seq, "Cancelling."));
+
+        ServerListener listener = _waitForList.remove(seq);
+        if (listener != null) {
+            listener.processDisconnect();
+        }

Review Comment:
   On an asynchronous send failure, `cancel(seq)` removes the listener but 
leaves its scheduled alarm in `_alarmFutures`; unlike `unregisterListener`, it 
does not cancel the future. Repeated connection failures can retain one delayed 
task per request until the timeout, so cancel the corresponding future here as 
well.



##########
core/src/main/java/org/apache/cloudstack/threadcontext/ThreadContextCommandUtil.java:
##########
@@ -0,0 +1,63 @@
+// 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 org.apache.cloudstack.threadcontext;
+
+import com.cloud.agent.api.Command;
+import com.cloud.utils.StringUtils;
+import org.apache.logging.log4j.ThreadContext;
+
+/**
+ * Utility class for Command-specific MDC operations.
+ * This class handles propagation of MDC values to and from Command objects.
+ *
+ * @author mprokopchuk
+ */
+public class ThreadContextCommandUtil {
+
+    /**
+     * Propagate UUID and log context ID from Command trace context to MDC.
+     *
+     * @param cmd the command containing trace context parameters
+     */
+    public static void propagateContextFromCommand(Command cmd) {
+        if (cmd != null) {
+            
ThreadContextUtil.setLogContextId(cmd.getTraceContextParam(ThreadContextUtil.CONTEXT_LOG_ID_KEY));
+            
ThreadContextUtil.setUuid(cmd.getTraceContextParam(ThreadContextUtil.CONTEXT_UUID_KEY));

Review Comment:
   These helpers only write non-empty values, and agent worker threads do not 
clear MDC between requests. A command without trace context can therefore 
inherit the UUID or log context from the previous command, misattributing logs 
and any subsequently propagated command; clear each MDC key when its command 
parameter is absent.



##########
server/src/main/java/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImpl.java:
##########
@@ -331,7 +332,9 @@ public void propagateMSListToAgents(boolean triggerHostLB) {
                 zoneHostIds.addAll(hostIds);
             }
             zoneHostIds.sort(Comparator.comparingLong(x -> x));
-            final List<String> avoidMsList = mshostDao.listNonUpStateMsIPs();
+
+            final List<String> avoidMsList = agentManager.getAvoidMsList();

Review Comment:
   Hostname mode sends the configured address list to agents, but this avoid 
list is built from `mshost.name`, which is the canonical hostname persisted by 
ClusterManager. With a configured CNAME/alias that differs from that canonical 
name, the down server's name is not present in the agent's list, so it is not 
avoided during rebalancing. Build the avoid list in the same configured-address 
namespace.



##########
core/src/main/java/com/cloud/agent/api/AgentConnectStatusAnswer.java:
##########
@@ -0,0 +1,65 @@
+//
+// 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.agent.api;
+
+import com.cloud.host.HostVO;
+import com.cloud.host.Status;
+import com.cloud.utils.db.GlobalLock;
+
+/**
+ * Answer for {@link AgentConnectStatusCommand}.
+ *
+ * @author mprokopchuk
+ */
+public class AgentConnectStatusAnswer extends Answer {
+
+    /**
+     * {@link Boolean#TRUE} means host has {@link GlobalLock#lock(int)} 
acquired, otherwise {@link Boolean#FALSE},
+     * and null if there is an error during executing {@link 
AgentConnectStatusCommand}.

Review Comment:
   This documentation reverses the value returned by the implementation: 
`AgentManagerImpl` sets it from `GlobalLock.isLockAvailable()`, which is true 
when `IS_FREE_LOCK` reports that no server holds the lock. `HostStatusTask` 
also treats true as permission to send startup. Document true as “lock 
available,” not “lock acquired,” to avoid unsafe changes based on this contract.



-- 
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