Support for custom SSH port for KVM hosts from the host url on add host and the configuration (#12571)

This commit is contained in:
Suresh Kumar Anaparti 2026-02-19 00:35:51 +05:30 committed by GitHub
parent 8c12a13216
commit 9dd93cef76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 94 additions and 10 deletions

View File

@ -59,6 +59,9 @@ public interface Host extends StateObject<Status>, Identity, Partition, HAResour
String HOST_INSTANCE_CONVERSION = "host.instance.conversion";
String HOST_OVFTOOL_VERSION = "host.ovftool.version";
String HOST_VIRTV2V_VERSION = "host.virtv2v.version";
String HOST_SSH_PORT = "host.ssh.port";
int DEFAULT_SSH_PORT = 22;
/**
* @return name of the machine.

View File

@ -60,7 +60,8 @@ public class AddHostCmd extends BaseCmd {
@Parameter(name = ApiConstants.POD_ID, type = CommandType.UUID, entityType = PodResponse.class, required = true, description = "The Pod ID for the host")
private Long podId;
@Parameter(name = ApiConstants.URL, type = CommandType.STRING, required = true, description = "The host URL")
@Parameter(name = ApiConstants.URL, type = CommandType.STRING, required = true, description = "The host URL, optionally add ssh port (format: 'host:port') for KVM hosts," +
" otherwise falls back to the port defined at the config 'kvm.host.discovery.ssh.port'")
private String url;
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, required = true, description = "The Zone ID for the host")

View File

@ -54,6 +54,10 @@ public interface AgentManager {
"This timeout overrides the wait global config. This holds a comma separated key value pairs containing timeout (in seconds) for specific commands. " +
"For example: DhcpEntryCommand=600, SavePasswordCommand=300, VmDataCommand=300", false);
ConfigKey<Integer> KVMHostDiscoverySshPort = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class,
"kvm.host.discovery.ssh.port", String.valueOf(Host.DEFAULT_SSH_PORT), "SSH port used for KVM host discovery and any other operations on host (using SSH)." +
" Please note that this is applicable when port is not defined through host url while adding the KVM host.", true, ConfigKey.Scope.Cluster);
enum TapAgentsAction {
Add, Del, Contains,
}
@ -170,4 +174,6 @@ public interface AgentManager {
void notifyMonitorsOfRemovedHost(long hostId, long clusterId);
void propagateChangeToAgents(Map<String, String> params);
int getHostSshPort(HostVO host);
}

View File

@ -40,6 +40,7 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.utils.StringUtils;
import org.apache.cloudstack.agent.lb.IndirectAgentLB;
import org.apache.cloudstack.ca.CAManager;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
@ -55,7 +56,6 @@ import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToSt
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.ThreadContext;
import com.cloud.agent.AgentManager;
@ -1977,7 +1977,7 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
return new ConfigKey<?>[] { CheckTxnBeforeSending, Workers, Port, Wait, AlertWait, DirectAgentLoadSize,
DirectAgentPoolSize, DirectAgentThreadCap, EnableKVMAutoEnableDisable, ReadyCommandWait,
GranularWaitTimeForCommands, RemoteAgentSslHandshakeTimeout, RemoteAgentMaxConcurrentNewConnections,
RemoteAgentNewConnectionsMonitorInterval };
RemoteAgentNewConnectionsMonitorInterval, KVMHostDiscoverySshPort };
}
protected class SetHostParamsListener implements Listener {
@ -2093,6 +2093,25 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
}
}
@Override
public int getHostSshPort(HostVO host) {
if (host == null) {
return KVMHostDiscoverySshPort.value();
}
if (host.getHypervisorType() != HypervisorType.KVM) {
return Host.DEFAULT_SSH_PORT;
}
_hostDao.loadDetails(host);
String hostPort = host.getDetail(Host.HOST_SSH_PORT);
if (StringUtils.isBlank(hostPort)) {
return KVMHostDiscoverySshPort.valueIn(host.getClusterId());
}
return Integer.parseInt(hostPort);
}
private GlobalLock getHostJoinLock(Long hostId) {
return GlobalLock.getInternLock(String.format("%s-%s", "Host-Join", hostId));
}

View File

@ -22,9 +22,11 @@ import com.cloud.agent.api.ReadyCommand;
import com.cloud.agent.api.StartupCommand;
import com.cloud.agent.api.StartupRoutingCommand;
import com.cloud.exception.ConnectionException;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.Status;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.utils.Pair;
import org.junit.Assert;
import org.junit.Before;
@ -103,4 +105,36 @@ public class AgentManagerImplTest {
Assert.assertEquals(50, result);
}
@Test
public void testGetHostSshPortWithHostNull() {
int hostSshPort = mgr.getHostSshPort(null);
Assert.assertEquals(22, hostSshPort);
}
@Test
public void testGetHostSshPortWithNonKVMHost() {
HostVO host = Mockito.mock(HostVO.class);
Mockito.when(host.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.XenServer);
int hostSshPort = mgr.getHostSshPort(host);
Assert.assertEquals(22, hostSshPort);
}
@Test
public void testGetHostSshPortWithKVMHostDefaultPort() {
HostVO host = Mockito.mock(HostVO.class);
Mockito.when(host.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
Mockito.when(host.getClusterId()).thenReturn(1L);
int hostSshPort = mgr.getHostSshPort(host);
Assert.assertEquals(22, hostSshPort);
}
@Test
public void testGetHostSshPortWithKVMHostCustomPort() {
HostVO host = Mockito.mock(HostVO.class);
Mockito.when(host.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
Mockito.when(host.getDetail(Host.HOST_SSH_PORT)).thenReturn(String.valueOf(3922));
int hostSshPort = mgr.getHostSshPort(host);
Assert.assertEquals(3922, hostSshPort);
}
}

View File

@ -16,6 +16,7 @@
// under the License.
package org.apache.cloudstack.backup;
import com.cloud.agent.AgentManager;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.host.HostVO;
import com.cloud.host.Status;
@ -117,6 +118,9 @@ public class NetworkerBackupProvider extends AdapterBase implements BackupProvid
@Inject
private VMInstanceDao vmInstanceDao;
@Inject
private AgentManager agentMgr;
private static String getUrlDomain(String url) throws URISyntaxException {
URI uri;
try {
@ -229,8 +233,13 @@ public class NetworkerBackupProvider extends AdapterBase implements BackupProvid
String nstRegex = "\\bcompleted savetime=([0-9]{10})";
Pattern saveTimePattern = Pattern.compile(nstRegex);
if (host == null) {
LOG.warn("Unable to take backup, host is null");
return null;
}
try {
Pair<Boolean, String> response = SshHelper.sshExecute(host.getPrivateIpAddress(), 22,
Pair<Boolean, String> response = SshHelper.sshExecute(host.getPrivateIpAddress(), agentMgr.getHostSshPort(host),
username, null, password, command, 120000, 120000, 3600000);
if (!response.first()) {
LOG.error(String.format("Backup Script failed on HYPERVISOR %s due to: %s", host, response.second()));
@ -249,9 +258,13 @@ public class NetworkerBackupProvider extends AdapterBase implements BackupProvid
return null;
}
private boolean executeRestoreCommand(HostVO host, String username, String password, String command) {
if (host == null) {
LOG.warn("Unable to restore backup, host is null");
return false;
}
try {
Pair<Boolean, String> response = SshHelper.sshExecute(host.getPrivateIpAddress(), 22,
Pair<Boolean, String> response = SshHelper.sshExecute(host.getPrivateIpAddress(), agentMgr.getHostSshPort(host),
username, null, password, command, 120000, 120000, 3600000);
if (!response.first()) {

View File

@ -272,7 +272,12 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
}
}
sshConnection = new Connection(agentIp, 22);
int port = uri.getPort();
if (port <= 0) {
port = AgentManager.KVMHostDiscoverySshPort.valueIn(clusterId);
}
sshConnection = new Connection(agentIp, port);
sshConnection.connect(null, 60000, 60000);
@ -380,6 +385,9 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
Map<String, String> hostDetails = connectedHost.getDetails();
hostDetails.put("password", password);
hostDetails.put("username", username);
if (uri.getPort() > 0) {
hostDetails.put(Host.HOST_SSH_PORT, String.valueOf(uri.getPort()));
}
_hostDao.saveDetails(connectedHost);
return resources;
} catch (DiscoveredWithErrorException e) {

View File

@ -776,7 +776,6 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
_clusterDetailsDao.persist(cluster_cpu_detail);
_clusterDetailsDao.persist(cluster_memory_detail);
}
}
try {
@ -871,7 +870,6 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
hosts.add(host);
}
discoverer.postDiscovery(hosts, _nodeId);
}
logger.info("server resources successfully discovered by " + discoverer.getName());
return hosts;
@ -2960,7 +2958,7 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
*/
protected void connectAndRestartAgentOnHost(HostVO host, String username, String password, String privateKey) {
final com.trilead.ssh2.Connection connection = SSHCmdHelper.acquireAuthorizedConnection(
host.getPrivateIpAddress(), 22, username, password, privateKey);
host.getPrivateIpAddress(), _agentMgr.getHostSshPort(host), username, password, privateKey);
if (connection == null) {
throw new CloudRuntimeException(String.format("SSH to agent is enabled, but failed to connect to %s via IP address [%s].", host, host.getPrivateIpAddress()));
}

View File

@ -360,12 +360,14 @@ public class ResourceManagerImplTest {
@Test
public void testConnectAndRestartAgentOnHost() {
when(agentManager.getHostSshPort(any())).thenReturn(22);
resourceManager.connectAndRestartAgentOnHost(host, hostUsername, hostPassword, hostPrivateKey);
}
@Test
public void testHandleAgentSSHEnabledNotConnectedAgent() {
when(host.getStatus()).thenReturn(Status.Disconnected);
when(agentManager.getHostSshPort(any())).thenReturn(22);
resourceManager.handleAgentIfNotConnected(host, false);
verify(resourceManager).getHostCredentials(eq(host));
verify(resourceManager).connectAndRestartAgentOnHost(eq(host), eq(hostUsername), eq(hostPassword), eq(hostPrivateKey));

View File

@ -77,7 +77,7 @@ public class SSHCmdHelper {
}
public static com.trilead.ssh2.Connection acquireAuthorizedConnection(String ip, int port, String username, String password) {
return acquireAuthorizedConnection(ip, 22, username, password, null);
return acquireAuthorizedConnection(ip, port, username, password, null);
}
public static boolean acquireAuthorizedConnectionWithPublicKey(final com.trilead.ssh2.Connection sshConnection, final String username, final String privateKey) {