Indirect agent connection improvements - #13345
sureshanaparti wants to merge 12 commits into
Conversation
|
@blueorangutan package |
|
@sureshanaparti a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress. |
Codecov Report✅ All modified and coverable lines are covered by tests.
Additional details and impacted files@@ Coverage Diff @@
## main #13345 +/- ##
=============================================
- Coverage 19.91% 3.71% -16.21%
=============================================
Files 6373 487 -5886
Lines 577230 41992 -535238
Branches 70696 7942 -62754
=============================================
- Hits 114974 1558 -113416
+ Misses 449690 40208 -409482
+ Partials 12566 226 -12340
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR is a broad refactor/enhancement of indirect agent connection handling across both Management Server and Agent sides. It introduces a host-side connect/status-check process, adds configurable backoff and host-status heuristics, and expands logging/lock-check mechanisms to reduce reconnect storms and improve observability.
Changes:
- Adds agent-side connection orchestration (status polling + startup submission) and a server-side
AgentConnectStatus*command/answer pair to coordinate connect progress. - Introduces configurable backoff (factory + exponential-with-jitter implementation) and propagates configuration from Management Server to Agent during startup.
- Refactors NIO connection lifecycle and adds supporting utilities/logging, plus DB lock availability checks and DB indexes for performance.
Reviewed changes
Copilot reviewed 47 out of 47 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| utils/src/test/java/com/cloud/utils/testcase/NioTest.java | Test logging cleanup (exception logging). |
| utils/src/test/java/com/cloud/utils/backoff/impl/ConstantTimeBackoffTest.java | Updates config key name for constant backoff. |
| utils/src/main/java/com/cloud/utils/nio/NioServer.java | NIO server init tweaks and docstring. |
| utils/src/main/java/com/cloud/utils/nio/NioConnection.java | Connection lifecycle refactor, selector loop/logging, reject logic. |
| utils/src/main/java/com/cloud/utils/nio/NioClient.java | Client connect/handshake logging and expanded cleanup. |
| utils/src/main/java/com/cloud/utils/nio/Link.java | Adds local-port tracking, richer toString, termination helpers/logging changes. |
| utils/src/main/java/com/cloud/utils/nio/HandlerFactory.java | Changes new-connection registration API to InetSocketAddress. |
| utils/src/main/java/com/cloud/utils/net/NetUtils.java | Null guard in hostname-to-IP resolution helper. |
| utils/src/main/java/com/cloud/utils/LogUtils.java | Adds host logging helper with optional reverse lookup. |
| utils/src/main/java/com/cloud/utils/exception/CSExceptionErrorCode.java | Adds error-code mapping for ConnectionException. |
| utils/src/main/java/com/cloud/utils/DateUtil.java | Adds formatMillis duration formatter. |
| utils/src/main/java/com/cloud/utils/backoff/impl/ExponentialWithJitterBackoffMBean.java | New MBean interface for exponential-jitter backoff. |
| utils/src/main/java/com/cloud/utils/backoff/impl/ExponentialWithJitterBackoff.java | New exponential-with-jitter backoff implementation. |
| utils/src/main/java/com/cloud/utils/backoff/impl/ConstantTimeBackoff.java | Migrates to namespaced config keys + exposes configuration. |
| utils/src/main/java/com/cloud/utils/backoff/BackoffFactory.java | New backoff factory for selecting/configuring algorithms. |
| utils/src/main/java/com/cloud/utils/backoff/BackoffAlgorithm.java | Adds getConfiguration() API for propagating settings. |
| server/src/main/java/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImpl.java | Adds notes/timeout tweaks around agent migration command dispatch. |
| framework/db/src/main/java/com/cloud/utils/db/GlobalLock.java | Refactors lock bookkeeping/logging and adds lock-availability query. |
| framework/db/src/main/java/com/cloud/utils/db/DbUtil.java | Adds IS_FREE_LOCK helper and improves logging around lock DB connections. |
| framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKeyUtil.java | New utility to parse key=value;... configuration strings. |
| framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostDaoImpl.java | Adds DAO method to list all MS hosts including removed. |
| framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostDao.java | Adds DAO API for “including removed” listing. |
| framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletImpl.java | Adds request logging and more detailed RemoteException messages. |
| framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletHttpHandler.java | Adds debug logging of inbound request line/body. |
| engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql | Adds indexes via idempotent add-index procedure calls. |
| engine/schema/src/main/resources/META-INF/db/procedures/cloud.idempotent_add_index.sql | Adds/defines IDEMPOTENT_ADD_INDEX procedure. |
| engine/orchestration/src/test/java/com/cloud/agent/manager/ClusteredAgentManagerImplTest.java | Expands tests for disconnect broadcast behavior and lock usage. |
| engine/orchestration/src/test/java/com/cloud/agent/manager/AgentManagerImplTest.java | Adds tests for config keys and deregister/disconnect behaviors. |
| engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java | Refactors attache creation/removal and event handling behavior. |
| engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java | Major connect/disconnect refactor, new config keys, status checks, backoff propagation. |
| engine/orchestration/src/main/java/com/cloud/agent/manager/AgentAttache.java | Cancels listener alarm futures on unregister/cancel paths. |
| core/src/main/java/org/apache/cloudstack/threadcontext/ThreadContextUtil.java | New helper for propagating Log4j ThreadContext across threads. |
| core/src/main/java/com/cloud/resource/ServerResource.java | Changes default isExitOnFailures() behavior. |
| core/src/main/java/com/cloud/agent/transport/Request.java | Improves JSON deserialization error logging. |
| core/src/main/java/com/cloud/agent/api/StartupAnswer.java | Adds params + agent-side status-check delay transport fields. |
| core/src/main/java/com/cloud/agent/api/AgentConnectStatusCommand.java | New command for host connect-status checks. |
| core/src/main/java/com/cloud/agent/api/AgentConnectStatusAnswer.java | New answer carrying lock availability and host status. |
| agent/src/test/java/com/cloud/agent/HostConnectProcessTest.java | New test around scheduling the agent connect process. |
| agent/src/test/java/com/cloud/agent/AgentTest.java | Updates tests for new link/logging and reconnect helpers. |
| agent/src/main/java/com/cloud/agent/SynchronousListener.java | New blocking listener for synchronous waits on answers. |
| agent/src/main/java/com/cloud/agent/ServerListener.java | New listener interface for agent-side ServerAttache callbacks. |
| agent/src/main/java/com/cloud/agent/ServerAttache.java | New agent-side counterpart to MS Attache for command/answer flow. |
| agent/src/main/java/com/cloud/agent/properties/AgentProperties.java | Adds new agent properties for async timeouts and status-check delay. |
| agent/src/main/java/com/cloud/agent/IAgentShell.java | Adds setter for dynamically updated backoff algorithm. |
| agent/src/main/java/com/cloud/agent/HostConnectProcess.java | New connect/status-check orchestration loop on the agent side. |
| agent/src/main/java/com/cloud/agent/AgentShell.java | Uses backoff factory + persists configuration; relaxes version handling. |
| agent/src/main/java/com/cloud/agent/Agent.java | Major reconnect/startup refactor; integrates HostConnectProcess and ServerAttache. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 18146 |
|
@blueorangutan package |
|
@sureshanaparti a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress. |
|
Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 18149 |
|
@blueorangutan package |
|
@sureshanaparti a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress. |
|
Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 18154 |
| /** | ||
| * Command to check status of {@link StartupCommand} from the Agent. | ||
| * | ||
| * @author mprokopchuk | ||
| */ |
There was a problem hiding this comment.
These @author fields are not necessary, right?
| /** | |
| * Command to check status of {@link StartupCommand} from the Agent. | |
| * | |
| * @author mprokopchuk | |
| */ | |
| /** | |
| * Command to check status of {@link StartupCommand} from the Agent. | |
| * | |
| */ |
There was a problem hiding this comment.
@bernardodemarco I think, it's ok to have it. we've author mentioned in few other classes as well. (check with '@author' in the code base).
|
This pull request has merge conflicts. Dear author, please fix the conflicts and sync your branch with the base branch. |
d5a6d20 to
22e832c
Compare
There was a problem hiding this comment.
🟡 Changes recommended
It introduces a real configuration bug in default backoff creation and adds new logging that can expose sensitive configuration/request data.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
agent/src/main/java/com/cloud/agent/HostConnectProcess.java:228
sendStartupCommandreceivesconnectionTransferbut callsserverResource.initialize()instead ofinitialize(connectionTransfer). This prevents resources from tailoring their StartupCommand content/behavior for transferred connections, even though theServerResourceinterface now supports it.
Call the boolean overload so implementations can react to connection transfers.
ServerResource serverResource = _agent.getResource();
StartupCommand[] startup = serverResource.initialize();
if (ArrayUtils.isEmpty(startup)) {
- Files reviewed: 63/63 changed files
- Comments generated: 3
- Review effort level: Lite
| if (LOGGER.isDebugEnabled()) { | ||
| List<String> properties = Collections.list((Enumeration<String>)_properties.propertyNames()); | ||
| for (String property : properties) { | ||
| LOGGER.debug("Found property: {}", property); | ||
| LOGGER.debug("Found property: {}, value: {}", property, _properties.getProperty(property)); | ||
| } |
| 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); | ||
| } |
| static BackoffAlgorithm createDefault(Properties properties) { | ||
| Properties newProperties = new Properties(properties); | ||
| newProperties.put(BACKOFF_IMPLEMENTATION_KEY, DEFAULT_BACKOFF_IMPLEMENTATION); | ||
| return create(newProperties); | ||
| } |
|
This pull request has merge conflicts. Dear author, please fix the conflicts and sync your branch with the base branch. |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings remain, including credential exposure, connection-flow failures, compatibility issues, and NIO lifecycle risk.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (19)
Previously missed (3) — in code that hasn't changed since the last review.
core/src/main/java/org/apache/cloudstack/threadcontext/ThreadContextCommandUtil.java:39
- These setters ignore absent values, but agent request tasks reuse executor threads and call this method for every command. A command without trace context therefore retains the previous command's UUID/log ID and is logged under the wrong resource. Clear each MDC key when its command value is absent, or run command processing in a context scope that resets the map.
framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostDaoImpl.java:70 ActiveSearchincludesremoved IS NULLand a recentlastUpdateTimecondition, so this method does not return the “all ... including down and removed” rows promised by its contract.MsCacherelies on these rows to recognize old Management Server IPs; omitting stale/removed entries can make them look like new agent connections. Use the unfilteredlistAllIncludingRemoved()here.
utils/src/main/java/com/cloud/utils/backoff/impl/ExponentialWithJitterBackoff.java:166- The MBean exposes
setTimeToWait, but this implementation silently ignores every JMX update. Operators cannot tune or disable the exponential backoff through the advertised management interface, unlike ConstantTimeBackoff; either implement the setter or remove it from the MBean contract.
agent/src/main/java/com/cloud/agent/AgentShell.java:423
- This debug log now dumps every agent property value. The property set includes
keystore.passphrase(AgentProperties.java:903) and may contain operator-supplied secrets, so enabling debug logging exposes credentials in the agent log. Keep the previous name-only log or redact sensitive keys.
LOGGER.debug("Found property: {}, value: {}", property, _properties.getProperty(property));
agent/src/main/java/com/cloud/agent/HostConnectProcess.java:109
ScheduledExecutorService.scheduleWithFixedDelayrejects a non-positive delay. The management-server setting is an unrestricted integer, so configuringagent.host.status.check.delay.secas 0 or negative makes every new connection fail while scheduling this task instead of falling back safely.
var future = hostStatusExecutor.scheduleWithFixedDelay(ThreadContextUtil.wrapThreadContext(task),
HOST_STATUS_CHECK_INITIAL_DELAY_SEC,
hostStatusCheckDelaySec, TimeUnit.SECONDS);
agent/src/main/java/com/cloud/agent/HostConnectProcess.java:180
- These reconnect paths pass a null preferred host. The agent's host selection then falls back to
InetSocketAddress.getAddress().getHostAddress(), so a connection made using a hostname is retried against its currently resolved IP. After a management-server IP change, reconnection still targets the stale address, defeating the hostname support described by this PR; preserve the original host string.
_agent.getRequestHandler().submit(() -> _agent.reconnect(_link, null, _forceConnect));
agent/src/main/java/com/cloud/agent/HostConnectProcess.java:274
- When the startup request timed out,
Boolean.FALSEmeans the Management Server still holds the host lock. This branch only logs and returns;runInternal()then unconditionally cancels the periodic task, so the agent stops checking while the previous connect is still in progress and can remain connected but never initialize. Keep the status task alive/retry (or explicitly reconnect) until the lock state is resolved.
} else if (Boolean.FALSE.equals(lockAvailable)) {
logger.info("Host is locked and has state {} on {}", status, _link);
agent/src/main/java/com/cloud/agent/HostConnectProcess.java:247
StartupAnswerresponses are handled byAgent.processResponseviaprocessStartupAnswerand are not forwarded toServerAttache.processAnswers. Consequently this synchronous send cannot receive the normal startup response and can wait up to the 300-second timeout; successful startup handling also stops/interrupts this task while it is waiting. Send the startup command through the existing startup-response path, or explicitly deliver the answer to this listener without double-processing it.
var answer = send(attache, commands, StartupAnswer.class, DEFAULT_ASYNC_STARTUP_COMMAND_TIMEOUT_SEC);
agent/src/main/java/com/cloud/agent/ServerAttache.java:302
- Interrupting the scheduled connect task does not stop an in-flight status request here:
waitForinterruption is caught and the loop waits again. When a new connection process replaces this one, the old task can therefore resume and submit another StartupCommand, recreating the connection storm this process is intended to prevent. Preserve interruption and abort the request.
answers = sl.waitFor(wait);
} catch (InterruptedException e) {
logger.debug(log(seq, "Interrupted"));
}
agent/src/main/java/com/cloud/agent/ServerAttache.java:364
- If sending the next queued request fails, the unconditional assignment still marks that failed sequence as
_currentSequence. Subsequent execute-in-sequence requests will remain queued behind a request that can never receive a response until the link is disconnected. Set the current sequence only after a successful send.
try {
send(req);
} catch (CloudException e) {
logger.debug(log(req.getSequence(), "Unable to send the next sequence"));
cancel(req.getSequence());
}
_currentSequence = req.getSequence();
core/src/main/java/com/cloud/agent/api/AgentConnectStatusAnswer.java:35
- The description is inverted:
lockAvailableis set fromGlobalLock.isLockAvailable()and callers treatTRUEas “no lock”, but this Javadoc saysTRUEmeans the lock is acquired. DocumentTRUEas the lock being available/free so implementations do not follow the wrong contract.
* {@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}.
engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java:602
- This helper unconditionally prefers
ManagementServerHost.name. That column is populated with each node's canonical hostname independently of whethermanagement.server.addressuses IPs, soconnectToPeernow breaks IP-only deployments when canonical names are not resolvable or reachable. Select the name versusserviceIPaccording to the configured address format, preserving the other only as fallback.
final String hostName = host.getName();
if (StringUtils.isNotBlank(hostName)) {
return hostName;
engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java:296
- When
old != null,_agents.put(host.getId(), attache)has already installed the new attachment.removeAgent(old.getId(), attache)then removes that same new attachment; the helper only restores the removed entry when it differs from its second argument. The host is therefore left absent from_agentsafter a replacement. Passoldto this helper (or remove the old entry before installing the new one) so the new attachment remains registered.
removeAgent(old.getId(), attache);
framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java:1265
- This hostname self-check makes the new hostname-conflict detection ineffective:
checkConflicts()callspingManagementNodeon a peer with the same hostname, and this branch returnsfalsebefore probing it. A live second management server that reused the hostname is therefore treated as non-pingable and initialization continues. Identify self by the local management-server id/bind identity, not by the peer hostname alone.
if (currentHostName != null && currentHostName.equals(targetHostName)) {
logger.info("ping management node cluster service can not be performed on self (hostname match: {})", currentHostName);
return false;
framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletAdapter.java:98
- This now prefers
mshost.namefor every peer, butClusterManagerImplstores the canonical hostname there even whenmanagement.server.addressis configured with IPs. Existing IP-based deployments will silently switch cluster RPC to DNS and can fail when that hostname is not reachable; choose the identifier using the configured address format, with the other field only as fallback.
final String hostName = msHost.getName();
final String identifier = hostName != null ? hostName : msHost.getServiceIP();
framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletHttpHandler.java:125
- The new HTTP-handler log writes the complete request body, including the serialized PDU payload, to the debug log. That payload is not guaranteed to be free of credentials or other sensitive command data; log only the request line or a sanitized payload instead.
framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java:684 cmdInfois not uniformly JSON: VM work jobs are written byVmWorkSerializer.serializeusing object serialization. Parsing those payloads asMap<String, String>fails, so UUID propagation is skipped for VM/volume-related async jobs. Derive the UUID from the job/entity or persist it in a format shared by these jobs.
plugins/maintenance/src/main/java/org/apache/cloudstack/maintenance/ManagementServerMaintenanceManagerImpl.java:474- Removing only
msHost.getName()does not reliably remove the current configured address:ClusterManagerImplstores the canonical hostname, whilemanagement.server.addressmay contain a CNAME/alias. In that deployment the server entering maintenance remains inindirectAgentMsList, so agents can be told to reconnect to it. Resolve the row to the exact configured address before removing it.
utils/src/main/java/com/cloud/utils/backoff/impl/ExponentialWithJitterBackoff.java:68 - This backoff object is shared through the single
AgentShellinstance, while reconnect and command-handling tasks can callwaitBeforeRetry()andreset()concurrently.attemptNumberandincreasingare unsynchronized, so retries can lose each other's updates or reset another retry's state, producing an unpredictable backoff. Protect the state transition or keep attempt state per retry process.
- Files reviewed: 64/64 changed files
- Comments generated: 4
- Review effort level: Lite
| 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); |
|
|
||
| int getHostSshPort(HostVO host); | ||
|
|
||
| List<String> getAvoidMsList(); |
| profiler.stop(); | ||
| if (logger.isDebugEnabled()) { | ||
| logger.debug("POST " + serviceUrl + " response :" + result + ", responding time: " + profiler.getDurationInMillis() + " ms"); | ||
| logger.debug("POST {} request: {}, response :{}, responding time: {} ms", serviceUrl, request, result, profiler.getDurationInMillis()); |
| public boolean isTerminated() { | ||
| return _key == null; | ||
| } |
|
Hi @sureshanaparti can you please fix the merge conflicts? |
…ements. - Enhances the Host connecting logic to avoid connecting storm (where Agent opens multiple sockets against Management Server). - Implements HostConnectProcess task where Host upon connection checks whether lock is available, traces Host connecting progress, status and timeout. - Introduces AgentConnectStatusCommand, where Host checks whether lock for the Host is available (i.e. "previous" connect process is finished). - Implementes logic to check whether Management Server has lock against Host (exposed MySQL DB lock presence via API) - Removes synchronization on Host disconnect process, double-disconnect logic in clustered Management Server environment, added early removal from ping map (in case of combination ping timeout delay + synchronized disconnect process the Agent Manager submits more disconnect requests) - Introduces parameterized connection and status check timeouts - Implements backoff algorithm abstraction - can be used either constant backoff timeout or exponential with jitter to wait between connection Host attempts to Management Server - Implements ServerAttache to be used on the Agent side of communication (similar to Attache on Management Server side) - Enhances/Adds logs significantly to Host Agent and Agent Manager logic to trace Host connecting and disconnecting process, including ids, names, context UUIDs and timings (how much time took overall initialization/deinitialization) - Adds logs to communication between Management Servers (PDU requests) - Adds DB indexes to improve search performance, uses IDEMPOTENT_ADD_INDEX for safer DB schema updates
- Bug 1 fix (AgentManagerImpl.java) — GlobalLock.isLockAvailable() now only runs when the host status is not alive. This eliminates one IS_FREE_LOCK DB query per ping per healthy host, which is the direct cause of listHosts/listNetworks degradation. - Bug 2 fix (HostConnectProcess.java) — shutdown() → shutdownNow(). Old thread pools from prior connect cycles are now interrupted immediately instead of draining their queued tasks, preventing thread accumulation during reconnect storms. - Bug 3 fix (AgentManagerImpl.java) — Lock timeout in handleDisconnectWithoutInvestigation now logs a warn instead of silently discarding the disconnect event. - Bug 4 fix (ServerAttache.java) — Alarm ScheduledFuture handles are now tracked in _alarmFutures and cancelled when the corresponding listener is unregistered or all commands are cancelled on disconnect. - Bug 5 fix (AgentAttache.java) - Fix Alarm ScheduledFuture handles in AgentAttache as well
Dedupes agent connection requests. Observed scenarios where on agent with old agent code send a storm of connection requests. In such a case, to ensure other agents are not impacted, deduping the connection requests.
Allows the management.server.address config to be a list of hostnames instead of static IPs. Rebalancing, cluster propagation, alerts, and stats collection all detect the format at runtime via ManagementServerAddressUtil and use the matching lookup (listNonUpStateMsHostnames vs listNonUpStateMsIPs) when computing the "avoid" list sent to agents. Hostnames survive MS restarts and IP reassignments where static IPs do not, which is required in deployment environments where management server instances can be rescheduled or replaced.
14fe358 to
e5dc90b
Compare
|
@blueorangutan package |
|
@sureshanaparti a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress. |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved credential-exposure and compilation issues, along with connection, hostname, context, and retry defects, block approval.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (19)
agent/src/main/java/com/cloud/agent/AgentShell.java:423
- This now logs every
agent.propertiesvalue at DEBUG. The file can contain the keystore passphrase persisted underKeyStoreUtils.KS_PASSPHRASE_PROPERTY, so enabling debug logging exposes a credential; log only property names or redact sensitive values.
LOGGER.debug("Found property: {}, value: {}", property, _properties.getProperty(property));
agent/src/main/java/com/cloud/agent/HostConnectProcess.java:133
- After a startup timeout, this set is used to decide whether the server already accepted the connection. It omits
Status.CreatingandStatus.Alert, although the management-side connected/alive status contract includes them; a host in either state is treated as failed and reconnected, which can recreate the connection storm this process is meant to prevent. Share the connected-status set or include those states here.
private final Set<Status> operationalStatuses = Set.of(Status.Connecting, Status.Up, Status.Rebalancing);
agent/src/main/java/com/cloud/agent/SynchronousListener.java:90
- An empty answer array can reach this listener (the caller explicitly checks for empty answers after
sendreturns), but indexing_answers[0]throwsArrayIndexOutOfBoundsException. That converts an empty response into a timeout/transport failure instead of the intended empty-response error; guard the array length before indexing.
profiler.getDurationInMillis(), _answers != null ? _answers[0].toString() : "null");
engine/orchestration/src/main/java/com/cloud/agent/manager/AgentAttache.java:249
- The same future is added in this change, but
AgentAttache.cancel(seq)still removes only_waitForList; it does not cancel/remove_alarmFutures. Every send failure can therefore leave a scheduled alarm alive until its timeout, adding timer work during the connection storm this PR is intended to prevent.
java.util.concurrent.ScheduledFuture<?> alarmFuture =
s_listenerExecutor.schedule(new Alarm(seq), listener.getTimeout(), TimeUnit.SECONDS);
_alarmFutures.put(seq, alarmFuture);
engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java:296
attacheis the new object just inserted into_agentsabove.removeAgentremoves the map entry and only restores it when the removed object differs from its second argument; passing this same new attache therefore leaves_agentswithout the active forward attache. Passoldas the second argument (or remove only the stale object) so subsequentfindAttachecalls still find the replacement.
removeAgent(old.getId(), attache);
framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java:1263
- Hostnames are case-insensitive, but self-detection uses
equalshere while the conflict check below correctly usesequalsIgnoreCase. If the canonical and persisted names differ only by case, this node will be probed as a peer and can be reported as a duplicate of itself; use a case-insensitive comparison.
if (currentHostName != null && currentHostName.equals(targetHostName)) {
framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletImpl.java:162
- The outbound debug log includes both the serialized PDU request and its response. The request's
gsonPackagecan contain password-bearing agent commands, so this duplicates sensitive command data into management-server logs; retain only URL/timing/status metadata or redact both bodies.
framework/cluster/src/test/java/com/cloud/cluster/ManagementServerAddressUtilTest.java:35 - The test mutates the singleton
ApiServiceConfiguration.ManagementServerAddressesobject's private_defaultValueand never restores it. After this class runs, the value remains whatever the last test used (currently::1), contaminating unrelated tests that read the global configuration. Save the original value and restore it in an@Aftermethod.
framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java:684 cmdInfois not uniformly JSON: VM work jobs serialize throughVmWorkSerializer/JobSerializerHelper, so parsing those values asMap<String,String>fails and leaves the MDC UUID unset. This means the new propagation does not cover VM/volume async jobs; extract the UUID from the existing serialized work object or persist/read it separately.
framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java:679- The comment says the parent fallback applies when the current job has no
cmdInfo, but this condition only checksrelated. A child that has its owncmdInfowill therefore be replaced by its parent's data and receive the wrong UUID; guard this lookup withStringUtils.isBlank(job.getCmdInfo()).
plugins/maintenance/src/main/java/org/apache/cloudstack/maintenance/ManagementServerMaintenanceManagerImpl.java:481 - The same namespace mismatch applies here:
listNonUpStateMsHostnames()yields canonicalmshost.namevalues, butindirectAgentMsListis built from configured aliases. Removing these canonical values can leave down management servers in the maintenance candidate list.
server/src/main/java/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImpl.java:547 - This source address is added from
ManagementServerHostVO.getName(), which is the canonical hostname, not necessarily the hostname/alias in the configuredhostlist. With an alias-based configuration, adding this value does not actually exclude the source management server during migration.
utils/src/main/java/com/cloud/utils/LogUtils.java:127 getHostName()can perform a blocking reverse-DNS lookup for a literal IP. This method is called before eachNioClientconnection and from the reconnect loop, so a slow/unavailable DNS service can stall connection attempts and defeat the intended bounded backoff behavior. Avoid reverse resolution on this hot path or use a non-blocking/cached value.
utils/src/main/java/com/cloud/utils/backoff/impl/ExponentialWithJitterBackoff.java:107- When the decreasing phase reaches attempt 0, this flips
increasingbut leavesattemptNumberat 1. The next cycle therefore starts at the second delay instead of the configured minimum, producing a max→...→second-delay→second-delay cycle rather than returning to the base delay. AssignattemptNumber = nextAttemptNumberin this branch.
utils/src/main/java/com/cloud/utils/backoff/impl/ExponentialWithJitterBackoff.java:161 getNextDelay()is capped atmaxDelayMs, but adding jitter here returns values up to 1.5× that configured maximum. This violates the documented maximum and can make retry delays longer than the operator's bound; cap the final value after applying jitter.
utils/src/main/java/com/cloud/utils/backoff/impl/ExponentialWithJitterBackoff.java:166- The MBean exposes
setTimeToWait, but this implementation silently ignores every update. An operator can therefore change the advertised backoff delay and observe no effect; implement a defined mapping to the exponential configuration or remove the setter from this MBean.
utils/src/main/java/com/cloud/utils/backoff/impl/ExponentialWithJitterBackoff.java:68 - The backoff object is shared by the agent shell, and
waitBeforeRetry()is invoked from multiple reconnect tasks, butattemptNumberandincreasingare plain fields andcalculateNextAttempt()is unsynchronized. Concurrent retries can read and overwrite the same state, causing several connections to use the same delay and defeating the storm-prevention backoff; protect the state transition or use an atomic state representation.
utils/src/main/java/com/cloud/utils/nio/Link.java:396 terminated()writes_keyunder synchronization, but this new reader is neither synchronized nor backed by a volatile field. The connection-storm guard and manager read it from other threads, so they can observe a stalefalseafter termination and skip a needed reconnect; make this accessor synchronized or make_keyvolatile.
utils/src/main/java/org/apache/cloudstack/threadcontext/ThreadContextUtil.java:107- This treats every async-job
cmdInfoas a JSON object map, but VM work jobs are serialized throughJobSerializerHelperbyVmWorkSerializer, not as JSON maps. Gson parsing therefore fails for those jobs and leaves the UUID unset, so the new propagation does not cover VM async operations; extract the UUID from the typed job/work data or pass it separately.
- Files reviewed: 63/63 changed files
- Comments generated: 10
- Review effort level: Lite
| 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); |
| Mockito.when(host.getId()).thenReturn(1L) | ||
| FieldUtils.writeField(host, "id", HOST_ID, true); |
| 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); |
| default void registerNewConnection(InetSocketAddress address) {} | ||
| default void unregisterNewConnection(InetSocketAddress address) {} |
| ServerListener listener = _waitForList.remove(seq); | ||
| if (listener != null) { | ||
| listener.processDisconnect(); | ||
| } |
| ThreadContextUtil.setLogContextId(cmd.getTraceContextParam(ThreadContextUtil.CONTEXT_LOG_ID_KEY)); | ||
| ThreadContextUtil.setUuid(cmd.getTraceContextParam(ThreadContextUtil.CONTEXT_UUID_KEY)); |
|
|
||
| @Override | ||
| public List<ManagementServerHostVO> findAllIncludingRemoved() { | ||
| return listIncludingRemovedBy(ActiveSearch.create()); |
| // 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()); | ||
| } |
| zoneHostIds.sort(Comparator.comparingLong(x -> x)); | ||
| final List<String> avoidMsList = mshostDao.listNonUpStateMsIPs(); | ||
|
|
||
| final List<String> avoidMsList = agentManager.getAvoidMsList(); |
| /** | ||
| * {@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}. |
|
Packaging result [SF]: ✖️ el8 ✖️ el9 ✖️ debian ✖️ suse15. SL-JID 19282 |
Description
Continued from #13028
This PR improves the Indirect agent connection handling, has the following improvements.
Types of changes
Feature/Enhancement Scale or Bug Severity
Feature/Enhancement Scale
Bug Severity
Screenshots (if appropriate):
How Has This Been Tested?
Checked the indirect agents connections (and re-connections) with KVM hosts, SSVM & CPVM.
How did you try to break this feature and the system with this change?