This is an automated email from the ASF dual-hosted git repository. Pearl1594 pushed a commit to branch store-lastsuccessful-MS in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit a4d05c29ceef6844a85ac1be2455cda9d3fd676c Author: mprokopchuk <[email protected]> AuthorDate: Wed Apr 1 19:01:42 2026 +0200 Store last successful MS host for agent reconnection fallback * Store last successful MS host for agent reconnection fallback * Add newline at end of CapacityManagerImplTest.java --- agent/src/main/java/com/cloud/agent/Agent.java | 18 ++++++++++++- .../src/main/java/com/cloud/agent/AgentShell.java | 30 +++++++++++++++++++++- .../src/main/java/com/cloud/agent/IAgentShell.java | 9 +++++++ .../cloud/agent/properties/AgentProperties.java | 8 ++++++ agent/src/test/java/com/cloud/agent/AgentTest.java | 8 +++--- 5 files changed, 67 insertions(+), 6 deletions(-) diff --git a/agent/src/main/java/com/cloud/agent/Agent.java b/agent/src/main/java/com/cloud/agent/Agent.java index c01f025c6a8..75dea0cc711 100644 --- a/agent/src/main/java/com/cloud/agent/Agent.java +++ b/agent/src/main/java/com/cloud/agent/Agent.java @@ -131,7 +131,7 @@ public class Agent implements HandlerFactory, IAgentControl, AgentStatusUpdater CopyOnWriteArrayList<IAgentControlListener> controlListeners = new CopyOnWriteArrayList<>(); IAgentShell shell; - NioConnection connection; + NioClient connection; ServerResource serverResource; Link link; Long id; @@ -919,6 +919,20 @@ public class Agent implements HandlerFactory, IAgentControl, AgentStatusUpdater } } + /** + * Saves the currently connected management server host after successful setup completion. + * This host is persisted and later added to the reconnection list as a fallback option. + * Called after receiving a Ready command from the management server, indicating that + * the agent has successfully completed its initialization and is ready to work. + * + * @param connectedHost the hostname or IP address of the successfully connected management server + */ + private void updateLastSetupCompletedHost(String connectedHost) { + if (StringUtils.isNotBlank(connectedHost)) { + shell.setLastSetupCompletedHost(connectedHost); + } + } + private Answer setupManagementServerList(final SetupMSListCommand cmd) { processManagementServerList(cmd.getMsList(), cmd.getLbAlgorithm(), cmd.getLbCheckInterval()); return new SetupMSListAnswer(true); @@ -959,6 +973,8 @@ public class Agent implements HandlerFactory, IAgentControl, AgentStatusUpdater verifyAgentArch(ready.getArch()); processManagementServerList(ready.getMsHostList(), ready.getLbAlgorithm(), ready.getLbCheckInterval()); + String connectedHost = shell.getConnectedHost(); + updateLastSetupCompletedHost(connectedHost); logger.info("Ready command is processed for agent [id: {}, uuid: {}, name: {}]", getId(), getUuid(), getName()); } diff --git a/agent/src/main/java/com/cloud/agent/AgentShell.java b/agent/src/main/java/com/cloud/agent/AgentShell.java index c5257b95b7c..2898a16bd2a 100644 --- a/agent/src/main/java/com/cloud/agent/AgentShell.java +++ b/agent/src/main/java/com/cloud/agent/AgentShell.java @@ -154,7 +154,20 @@ public class AgentShell implements IAgentShell, Daemon { @Override public String[] getHosts() { - return _host.split(","); + String lastSetupCompletedHost = getLastSetupCompletedHost(); + String host; + // Add the last successful setup host as a fallback option at the end of the host list. + // This host is tried only after all configured hosts have failed, providing a + // last-resort connection option since this host previously completed setup successfully. + if (StringUtils.isNotBlank(lastSetupCompletedHost) + && StringUtils.isNotBlank(_host) + && !_host.contains(lastSetupCompletedHost)) { + host = _host + "," + lastSetupCompletedHost; + } else { + host = _host; + } + + return host.split(","); } @Override @@ -464,6 +477,21 @@ public class AgentShell implements IAgentShell, Daemon { return AgentPropertiesFileHandler.getPropertyValue(AgentProperties.SSL_HANDSHAKE_TIMEOUT); } + @Override + public void setLastSetupCompletedHost(String host) { + setPersistentProperty(null, AgentProperties.LAST_SETUP_COMPLETED_HOST.getName(), host); + } + + /** + * Gets the last host where the agent successfully completed its setup process + * and received a Ready command. + * + * @return the hostname or IP address of the last successfully setup host, or null if none exists + */ + private String getLastSetupCompletedHost() { + return AgentPropertiesFileHandler.getPropertyValue(AgentProperties.LAST_SETUP_COMPLETED_HOST); + } + public synchronized int getNextAgentId() { return _nextAgentId++; } diff --git a/agent/src/main/java/com/cloud/agent/IAgentShell.java b/agent/src/main/java/com/cloud/agent/IAgentShell.java index 7f04048795d..c5c94c7e51d 100644 --- a/agent/src/main/java/com/cloud/agent/IAgentShell.java +++ b/agent/src/main/java/com/cloud/agent/IAgentShell.java @@ -72,4 +72,13 @@ public interface IAgentShell { void launchNewAgent(ServerResource resource) throws ConfigurationException; Integer getSslHandshakeTimeout(); + + /** + * Sets the last host where the agent successfully completed its setup process + * and received a Ready command. This value is persisted across agent restarts + * and used to prioritize reconnection attempts to previously working hosts. + * + * @param host the hostname or IP address where the agent setup completed successfully + */ + void setLastSetupCompletedHost(String host); } diff --git a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java index c781c07c227..bf88564901a 100644 --- a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java +++ b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java @@ -57,6 +57,14 @@ public class AgentProperties{ */ public static final Property<String> HOST = new Property<>("host", "localhost"); + /** + * The name of the last host where the agent successfully completed its setup process + * and received a Ready command + * Data type: String.<br> + * Default value: <code>null</code> + */ + public static final Property<String> LAST_SETUP_COMPLETED_HOST = new Property<>("last.setup.completed.host", null, String.class); + /** * The time interval (in seconds) after which the agent will check if the connected host is the preferred host.<br> * After that interval, if the agent is connected to one of the secondary/backup hosts, it will attempt to reconnect to the preferred host.<br> diff --git a/agent/src/test/java/com/cloud/agent/AgentTest.java b/agent/src/test/java/com/cloud/agent/AgentTest.java index 65dc030ebd7..0dda32481e7 100644 --- a/agent/src/test/java/com/cloud/agent/AgentTest.java +++ b/agent/src/test/java/com/cloud/agent/AgentTest.java @@ -36,6 +36,7 @@ import java.net.InetSocketAddress; import javax.naming.ConfigurationException; import org.apache.logging.log4j.Logger; +import com.cloud.utils.nio.NioClient; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,7 +46,6 @@ import org.springframework.test.util.ReflectionTestUtils; import com.cloud.resource.ServerResource; import com.cloud.utils.backoff.impl.ConstantTimeBackoff; import com.cloud.utils.nio.Link; -import com.cloud.utils.nio.NioConnection; @RunWith(MockitoJUnitRunner.class) public class AgentTest { @@ -224,7 +224,7 @@ public class AgentTest { @Test public void testStopAndCleanupConnectionValidConnectionNoWaitStopsAndCleansUp() throws IOException { - NioConnection mockConnection = mock(NioConnection.class); + NioClient mockConnection = mock(NioClient.class); agent.connection = mockConnection; agent.stopAndCleanupConnection(false); verify(mockConnection).stop(); @@ -233,7 +233,7 @@ public class AgentTest { @Test public void testStopAndCleanupConnectionCleanupThrowsIOExceptionLogsWarning() throws IOException { - NioConnection mockConnection = mock(NioConnection.class); + NioClient mockConnection = mock(NioClient.class); agent.connection = mockConnection; doThrow(new IOException("Cleanup failed")).when(mockConnection).cleanUp(); agent.stopAndCleanupConnection(false); @@ -243,7 +243,7 @@ public class AgentTest { @Test public void testStopAndCleanupConnectionValidConnectionWaitForStopWaitsForStartupToStop() throws IOException { - NioConnection mockConnection = mock(NioConnection.class); + NioClient mockConnection = mock(NioClient.class); ConstantTimeBackoff mockBackoff = mock(ConstantTimeBackoff.class); mockBackoff.setTimeToWait(0); agent.connection = mockConnection;
