diff --git a/cachecloud-web/pom.xml b/cachecloud-web/pom.xml index 0a21654d..c539f05e 100644 --- a/cachecloud-web/pom.xml +++ b/cachecloud-web/pom.xml @@ -156,14 +156,17 @@ provided + - ch.ethz.ganymed - ganymed-ssh2 + org.apache.sshd + sshd-core + ${ssh-version} - com.hierynomus - sshj + org.apache.sshd + sshd-scp + ${ssh-version} diff --git a/cachecloud-web/src/main/java/com/sohu/cache/configuration/SSHPoolConfig.java b/cachecloud-web/src/main/java/com/sohu/cache/configuration/SSHPoolConfig.java new file mode 100644 index 00000000..390f9b90 --- /dev/null +++ b/cachecloud-web/src/main/java/com/sohu/cache/configuration/SSHPoolConfig.java @@ -0,0 +1,43 @@ +package com.sohu.cache.configuration; + +import com.sohu.cache.ssh.SSHSessionPooledObjectFactory; +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; +import org.apache.sshd.client.session.ClientSession; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.io.IOException; +import java.security.GeneralSecurityException; + +/** + * @Author: zengyizhao + * @CreateTime: 2024/2/22 15:20 + * @Description: ssh session 连接池配置 + * @Version: 1.0 + */ +@Configuration +public class SSHPoolConfig { + + /** + * ssh连接池配置 + * @return + */ + @Bean + public GenericKeyedObjectPool clientSessionPool() throws GeneralSecurityException, IOException { + GenericKeyedObjectPoolConfig genericKeyedObjectPoolConfig = new GenericKeyedObjectPoolConfig(); + genericKeyedObjectPoolConfig.setTestWhileIdle(true); + genericKeyedObjectPoolConfig.setTestOnReturn(true); + genericKeyedObjectPoolConfig.setMaxTotalPerKey(5); + genericKeyedObjectPoolConfig.setMaxIdlePerKey(1); + genericKeyedObjectPoolConfig.setMinIdlePerKey(1); + genericKeyedObjectPoolConfig.setMaxWaitMillis(30000); + genericKeyedObjectPoolConfig.setTimeBetweenEvictionRunsMillis(20000); + genericKeyedObjectPoolConfig.setJmxEnabled(false); + SSHSessionPooledObjectFactory factory = new SSHSessionPooledObjectFactory(); + GenericKeyedObjectPool genericKeyedObjectPool = new GenericKeyedObjectPool<>( + factory, + genericKeyedObjectPoolConfig); + return genericKeyedObjectPool; + } +} diff --git a/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHClient.java b/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHClient.java new file mode 100644 index 00000000..075a1c1a --- /dev/null +++ b/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHClient.java @@ -0,0 +1,84 @@ +package com.sohu.cache.ssh; + +import lombok.Data; +import org.apache.commons.lang3.StringUtils; +import org.apache.sshd.client.SshClient; +import org.apache.sshd.client.auth.password.PasswordIdentityProvider; +import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader; +import org.apache.sshd.common.keyprovider.KeyIdentityProvider; +import org.apache.sshd.common.session.Session; +import org.apache.sshd.common.util.security.SecurityUtils; + +import java.io.IOException; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.util.Collection; +import java.util.concurrent.TimeUnit; + +/** + * ssh client + * + * @Auther: yongfeigao + * @Date: 2023/10/23 + */ +@Data +public class SSHClient { + + // 服务器 ssh 用户 + private String serverUser; + + // 服务器 ssh 密码 + private String serverPassword; + + // 服务器 ssh 端口 + private Integer serverPort; + + // 服务器 ssh 链接建立超时时间 + private Integer serverConnectTimeout; + + // 服务器 ssh 操作超时时间 + private Integer serverOPTimeout; + + // 服务器 ssh 私钥 + private String privateKeyPath; + + private SshClient client; + + public void init() throws GeneralSecurityException, IOException { + client = buildSshClient(); + if (StringUtils.isNotEmpty(privateKeyPath)) { + setAuthByKey(client); + } + client.setPasswordIdentityProvider(PasswordIdentityProvider.wrapPasswords(getServerPassword())); + client.start(); + } + + private SshClient buildSshClient() { + SshClient client = SshClient.setUpDefaultClient(); + client.setSessionHeartbeat(Session.HeartbeatType.IGNORE, TimeUnit.SECONDS, 10); + return client; + } + + private void setAuthByKey(SshClient client) throws GeneralSecurityException, IOException { + KeyPairResourceLoader loader = SecurityUtils.getKeyPairResourceParser(); + + Collection keys = loader.loadKeyPairs(null, Paths.get(privateKeyPath), null); + client.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keys)); + } + + /** + * 连接服务器 + * + * @param ip + * @return + * @throws IOException + */ + public ClientSession connect(String ip) throws IOException { + ClientSession session = getClient().connect(getServerUser(), ip, getServerPort()).verify(getServerConnectTimeout(), TimeUnit.MILLISECONDS).getSession(); + session.auth().verify(getServerConnectTimeout(), TimeUnit.MILLISECONDS); + return session; + } + +} diff --git a/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHService.java b/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHService.java index 42c38213..36948e77 100644 --- a/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHService.java +++ b/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHService.java @@ -43,7 +43,7 @@ public interface SSHService { * @return * @throws SSHException */ - SSHTemplate.Result executeWithResult(String ip, int port, String username, String password, final String command,int timeoutMills) throws SSHException; + SSHTemplate.Result executeWithResult(String ip, int port, String username, String password, final String command, int timeoutMills) throws SSHException; /** * 拷贝文件到远程目录 @@ -57,7 +57,7 @@ public interface SSHService { * @throws SSHException */ SSHTemplate.Result scpFileToRemote(String ip, int port, String username, - String password, final String localPath, final String remoteDir) throws SSHException; + String password, final String localPath, final String remoteDir) throws SSHException; /** * 拷贝文件到远程目录 @@ -94,7 +94,7 @@ SSHTemplate.Result scpFileToRemote(String ip, int port, String username, * @return * @throws SSHException */ - SSHTemplate.Result executeWithResult(String ip, String cmd,int millsSecond) throws SSHException; + SSHTemplate.Result executeWithResult(String ip, String cmd, int millsSecond) throws SSHException; /** * 查看机器ip上的端口port是否已被占用; diff --git a/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHSessionPooledObjectFactory.java b/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHSessionPooledObjectFactory.java new file mode 100644 index 00000000..8616961e --- /dev/null +++ b/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHSessionPooledObjectFactory.java @@ -0,0 +1,82 @@ +package com.sohu.cache.ssh; + +import com.sohu.cache.util.ConstUtils; +import com.sohu.cache.web.enums.SshAuthTypeEnum; +import org.apache.commons.pool2.KeyedPooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.apache.sshd.client.session.ClientSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.concurrent.TimeUnit; + +/** + * ssh session链接池工厂 + * + * @Auther: yongfeigao + * @Date: 2023/10/20 + */ +public class SSHSessionPooledObjectFactory implements KeyedPooledObjectFactory { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private SSHClient sshClient; + + public SSHSessionPooledObjectFactory() throws GeneralSecurityException, IOException { + sshClient = new SSHClient(); + sshClient.setServerUser(ConstUtils.USERNAME); + sshClient.setServerPassword(ConstUtils.PASSWORD); + sshClient.setServerPort(ConstUtils.SSH_PORT_DEFAULT); + sshClient.setServerConnectTimeout(ConstUtils.SSH_CONNECTION_TIMEOUT); + if (ConstUtils.SSH_AUTH_TYPE == SshAuthTypeEnum.PUBLIC_KEY.getValue()) { + sshClient.setPrivateKeyPath(ConstUtils.PUBLIC_KEY_PEM); + } + sshClient.init(); + } + + @Override + public PooledObject makeObject(String ip) throws Exception { + int port = ConstUtils.SSH_PORT_DEFAULT; + ClientSession session = sshClient.getClient().connect(ConstUtils.USERNAME, ip, + port).verify(ConstUtils.SSH_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS).getSession(); + session.auth().verify(ConstUtils.SSH_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS); + logger.info("create object, key:{}", ip); + return new DefaultPooledObject<>(session); + } + + @Override + public void destroyObject(String ip, PooledObject pooledObject) throws Exception { + ClientSession clientSession = pooledObject.getObject(); + if (clientSession != null) { + try { + clientSession.close(); + } catch (Exception e) { + logger.warn("close err, key:{}", ip, e); + } + } + logger.info("destroy object {}", ip); + } + + @Override + public boolean validateObject(String ip, PooledObject pooledObject) { + boolean closed = pooledObject.getObject().isClosed(); + if (closed) { + logger.warn("{} session closed", ip); + return false; + } + return true; + } + + @Override + public void activateObject(String ip, PooledObject pooledObject) throws Exception { + + } + + @Override + public void passivateObject(String ip, PooledObject pooledObject) throws Exception { + + } +} diff --git a/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHTemplate.java b/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHTemplate.java index 5163779d..8af4ab98 100644 --- a/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHTemplate.java +++ b/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHTemplate.java @@ -1,21 +1,38 @@ package com.sohu.cache.ssh; -import ch.ethz.ssh2.Connection; -import ch.ethz.ssh2.SCPClient; -import ch.ethz.ssh2.Session; -import ch.ethz.ssh2.StreamGobbler; -import com.sohu.cache.async.AsyncThreadPoolFactory; import com.sohu.cache.exception.SSHException; import com.sohu.cache.util.ConstUtils; -import com.sohu.cache.util.IdempotentConfirmer; import com.sohu.cache.web.enums.SshAuthTypeEnum; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.sshd.client.channel.ClientChannel; +import org.apache.sshd.client.channel.ClientChannelEvent; +import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader; +import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.scp.client.ScpClient; +import org.apache.sshd.scp.client.ScpClientCreator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.io.*; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.FileSystems; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.security.KeyPair; import java.util.Arrays; -import java.util.concurrent.*; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.stream.Collectors; + /** * SSH操作模板类 */ @@ -23,14 +40,18 @@ public class SSHTemplate { private static final Logger logger = LoggerFactory.getLogger(SSHTemplate.class); - private static final int CONNCET_TIMEOUT = 5000; + public static final List PERMS = Arrays.asList(PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.GROUP_READ, PosixFilePermission.OTHERS_READ); - private static final int OP_TIMEOUT = 10000; + @Autowired + private GenericKeyedObjectPool clientSessionPool; + + private static final int CONNCET_TIMEOUT = 5000; - private static ThreadPoolExecutor taskPool = AsyncThreadPoolFactory.MACHINE_THREAD_POOL; + private static final int OP_TIMEOUT = 10000; public Result execute(String ip, SSHCallback callback) throws SSHException{ - return execute(ip,ConstUtils.DEFAULT_SSH_PORT_DEFAULT, ConstUtils.USERNAME, + return execute(ip,ConstUtils.SSH_PORT_DEFAULT, ConstUtils.USERNAME, ConstUtils.PASSWORD, callback); } @@ -45,125 +66,34 @@ public Result execute(String ip, SSHCallback callback) throws SSHException{ */ public Result execute(String ip, int port, String username, String password, SSHCallback callback) throws SSHException{ - Connection conn = null; - try { - conn = getConnection(ip, port, username, password); - return callback.call(new SSHSession(conn, ip+":"+port)); - } catch (Exception e) { - throw new SSHException("SSH exception: " + e.getMessage(), e); - } finally { - close(conn); - } - } - - public Result executeByPerm(String ip, int port, SSHCallback callback) throws SSHException{ - Connection conn = null; + ClientSession session = null; try { - conn = getConnectionByPerm(ip, port); - return callback.call(new SSHSession(conn, ip+":"+port)); + session = clientSessionPool.borrowObject(ip); + session.setUsername(username); + if (ConstUtils.SSH_AUTH_TYPE == SshAuthTypeEnum.PASSWORD.getValue()) { + session.addPasswordIdentity(password); + } else if (ConstUtils.SSH_AUTH_TYPE == SshAuthTypeEnum.PUBLIC_KEY.getValue()) { + KeyPairResourceLoader loader = SecurityUtils.getKeyPairResourceParser(); + Collection keys = loader.loadKeyPairs(null, Paths.get(ConstUtils.PUBLIC_KEY_PEM), null); + session.addPublicKeyIdentity(keys.iterator().next()); + } + return callback.call(new SSHSession(session, ip)); } catch (Exception e) { throw new SSHException("SSH exception: " + e.getMessage(), e); } finally { - close(conn); + close(ip, session); } } - /** - * 获取连接并校验 - * @param ip - * @param port - * @param username - * @param password - * @return Connection - * @throws Exception - */ - private Connection getConnection(final String ip, final int port, final String username, final String password) throws Exception { - - int connectRetryTimes = 3; - - final Connection conn = new Connection(ip, port); - - final StringBuffer pemFilePath = new StringBuffer(); - if (ConstUtils.MEMCACHE_USER.equals(username)) { - pemFilePath.append(ConstUtils.MEMCACHE_KEY_PEM); - } else { - pemFilePath.append(ConstUtils.PUBLIC_KEY_PEM); - } - - new IdempotentConfirmer(connectRetryTimes) { - private int timeOutFactor = 1; - @Override - public boolean execute() { - try { - if (timeOutFactor > 1) { - logger.warn("connect {}:{} timeOutFactor is {}", ip, port, timeOutFactor); - } - int timeout = (timeOutFactor++) * CONNCET_TIMEOUT; - conn.connect(null, timeout, timeout); - boolean isAuthenticated = false; - if (ConstUtils.SSH_AUTH_TYPE == SshAuthTypeEnum.PASSWORD.getValue()) { - isAuthenticated = conn.authenticateWithPassword(username, password); - } else if (ConstUtils.SSH_AUTH_TYPE == SshAuthTypeEnum.PUBLIC_KEY.getValue()) { - isAuthenticated = conn.authenticateWithPublicKey( ConstUtils.PUBLIC_USERNAME, new File(pemFilePath.toString()), password); - } - if (isAuthenticated == false) { - if (ConstUtils.SSH_AUTH_TYPE == SshAuthTypeEnum.PASSWORD.getValue()) { - logger.error("SSH authentication {} failed with [userName: {} password: {}]", ip, username, password); - } else if (ConstUtils.SSH_AUTH_TYPE == SshAuthTypeEnum.PUBLIC_KEY.getValue()) { - logger.error("SSH authentication {} failed with [userName: {} pemfile: {}]", ip, username, ConstUtils.PUBLIC_KEY_PEM); - } - } - return isAuthenticated; - } catch (Exception e) { - logger.error("getConnection {}:{} error message is {} ", ip, port, e.getMessage(), e); - return false; - } - } - }.run(); - - return conn; - } - - /** - * 获取连接并校验 - * @param ip - * @param port - * @return Connection - * @throws Exception - */ - private Connection getConnectionByPerm(final String ip, final int port) throws Exception { - - int connectRetryTimes = 3; - - final Connection conn = new Connection(ip, port); - - final StringBuffer pemFilePath = new StringBuffer(); - pemFilePath.append(ConstUtils.PUBLIC_KEY_PEM); - - new IdempotentConfirmer(connectRetryTimes) { - private int timeOutFactor = 1; - @Override - public boolean execute() { - try { - if (timeOutFactor > 1) { - logger.warn("connect {}:{} timeOutFactor is {}", ip, port, timeOutFactor); - } - int timeout = (timeOutFactor++) * CONNCET_TIMEOUT; - conn.connect(null, timeout, timeout); - boolean isAuthenticated = conn.authenticateWithPublicKey( ConstUtils.PUBLIC_USERNAME, new File(pemFilePath.toString()), ""); - - if (isAuthenticated == false) { - logger.error("SSH authentication {} failed with [ pemfile: {}]", ip, ConstUtils.PUBLIC_KEY_PEM); - } - return isAuthenticated; - } catch (Exception e) { - logger.error("getConnection {}:{} error message is {} ", ip, port, e.getMessage(), e); - return false; + private DefaultLineProcessor generateDefaultLineProcessor(StringBuilder buffer) { + return new DefaultLineProcessor() { + public void process(String line, int lineNum) throws Exception { + if (lineNum > 1) { + buffer.append(System.lineSeparator()); } + buffer.append(line); } - }.run(); - - return conn; + }; } /** @@ -192,18 +122,21 @@ public void process(String line, int lineNum) throws Exception { private void processStream(InputStream is, LineProcessor lineProcessor) { BufferedReader reader = null; try { - reader = new BufferedReader(new InputStreamReader(new StreamGobbler(is), "UTF-8")); + reader = new BufferedReader(new InputStreamReader(is)); String line = null; int lineNum = 1; while ((line = reader.readLine()) != null) { try { lineProcessor.process(line, lineNum); } catch (Exception e) { - logger.error("err line:"+line, e); + logger.error("err line:" + line, e); } + if (lineProcessor instanceof DefaultLineProcessor) { + ((DefaultLineProcessor) lineProcessor).setLineNum(lineNum); + } lineNum++; } - lineProcessor.finish(); + lineProcessor.finish(); } catch (IOException e) { logger.error(e.getMessage(), e); } finally { @@ -221,20 +154,10 @@ private void close(BufferedReader read) { } } - private void close(Connection conn) { - if (conn != null) { - try { - conn.close(); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - } - } - - private static void close(Session session) { + private void close(String ip, ClientSession session) { if (session != null) { try { - session.close(); + clientSessionPool.returnObject(ip, session); } catch (Exception e) { logger.error(e.getMessage(), e); } @@ -244,13 +167,16 @@ private static void close(Session session) { /** * 可以调用多次executeCommand, 并返回结果 */ - public class SSHSession{ + public class SSHSession { + private String address; - private Connection conn; - private SSHSession(Connection conn, String address) { - this.conn = conn; + private ClientSession clientSession; + + private SSHSession(ClientSession clientSession, String address) { + this.clientSession = clientSession; this.address = address; } + /** * 执行命令并返回结果,可以执行多次 * @param cmd @@ -277,56 +203,47 @@ public Result executeCommand(String cmd, LineProcessor lineProcessor) { * @return 如果lineProcessor不为null,那么永远返回Result.true */ public Result executeCommand(String cmd, LineProcessor lineProcessor, int timoutMillis) { - Session session = null; - try { - session = conn.openSession(); - return executeCommand(session, cmd, timoutMillis, lineProcessor); - } catch (Exception e) { - logger.error("ip:{} cmd:{} {}",conn.getHostname(),cmd, e); - return new Result(e); - } finally { - close(session); - } - } - - public Result executeCommand(final Session session, final String cmd, - final int timoutMillis, final LineProcessor lineProcessor) throws Exception{ - Future future = taskPool.submit(new Callable() { - public Result call() throws Exception { - session.execCommand(cmd); - //如果客户端需要进行行处理,则直接进行回调 - if(lineProcessor != null) { - processStream(session.getStdout(), lineProcessor); - } else { - //获取标准输出 - String rst = getResult(session.getStdout()); - if(rst != null) { - return new Result(true, rst); - } - //返回为null代表可能有异常,需要检测标准错误输出,以便记录日志 - Result errResult = tryLogError(session.getStderr(), cmd); - if(errResult != null) { - return errResult; - } + try (ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + ByteArrayOutputStream stderr = new ByteArrayOutputStream(); + ClientChannel channel = clientSession.createExecChannel(cmd)) { + channel.setOut(stdout); + channel.setErr(stderr); + channel.open().verify(timoutMillis); + // Wait (forever) for the channel to close - signalling command finished + channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L); + LineProcessor tmpLP = lineProcessor; + // 如果客户端需要进行行处理,则直接进行回调 + if (tmpLP != null) { + processStream(new ByteArrayInputStream(stdout.toByteArray()), tmpLP); + } else { + StringBuilder buffer = new StringBuilder(); + tmpLP = generateDefaultLineProcessor(buffer); + processStream(new ByteArrayInputStream(stdout.toByteArray()), tmpLP); + if (buffer.length() > 0) { + return new Result(true, buffer.toString()); } - return new Result(true, null); } - }); - Result rst = null; - try { - rst = future.get(timoutMillis, TimeUnit.MILLISECONDS); - future.cancel(true); - } catch (TimeoutException e) { - logger.error("ip :{} exec {} timeout:{}",conn.getHostname(), cmd, timoutMillis); - throw new SSHException(e); + if(tmpLP.lineNum() == 0) { + // 返回为null代表可能有异常,需要检测标准错误输出,以便记录日志 + Result errResult = tryLogError(new ByteArrayInputStream(stderr.toByteArray()), cmd); + if (errResult != null) { + return errResult; + } + } + return new Result(true, null); + } catch (Exception e) { + logger.error("execute ip:{} cmd:{}", address, cmd, e); + return new Result(e); } - return rst; } private Result tryLogError(InputStream is, String cmd) { - String errInfo = getResult(is); - if(errInfo != null) { - logger.error("address "+address+" execute cmd:({}), err:{}", cmd, errInfo); + StringBuilder buffer = new StringBuilder(); + LineProcessor lp = generateDefaultLineProcessor(buffer); + processStream(is, lp); + String errInfo = buffer.length() > 0 ? buffer.toString() : null; + if (errInfo != null) { + logger.error("address " + address + " execute cmd:({}), err:{}", cmd, errInfo); return new Result(false, errInfo); } return null; @@ -337,7 +254,7 @@ private Result tryLogError(InputStream is, String cmd) { * creating the file on the remote side. * @param localFiles * Path and name of local file. - * @param remoteFiles + * @param remoteFile * name of remote file. * @param remoteTargetDirectory * Remote target directory. Use an empty string to specify the default directory. @@ -345,15 +262,44 @@ private Result tryLogError(InputStream is, String cmd) { * a four digit string (e.g., 0644, see "man chmod", "man open") * @throws IOException */ - public Result scp(String[] localFiles, String[] remoteFiles, String remoteTargetDirectory, String mode) { + public Result scp(String[] localFiles, String remoteFile, String remoteTargetDirectory, String mode) { try { - SCPClient client = conn.createSCPClient(); - client.put(localFiles, remoteFiles, remoteTargetDirectory, mode); - + ScpClient client = ScpClientCreator.instance().createScpClient(clientSession); + String separator = FileSystems.getDefault().getSeparator(); + if(localFiles.length == 1){ + if(StringUtils.isBlank(remoteFile)){ + client.upload(localFiles, remoteTargetDirectory, ScpClient.Option.TargetIsDirectory); + int index = localFiles[0].lastIndexOf(separator); + if(index <= 0){ + index = 0; + }else{ + index = index + 1; + } + String fileName = localFiles[0].substring(index); + clientSession.executeRemoteCommand("chmod " + mode + " \"" + remoteTargetDirectory + "/" + fileName + "\""); + } else { + client.upload(localFiles, remoteTargetDirectory + "/" + remoteFile); + clientSession.executeRemoteCommand("chmod " + mode + " \"" + remoteTargetDirectory + "/" + remoteFile + "\""); + } + } else { + client.upload(localFiles, remoteTargetDirectory, ScpClient.Option.TargetIsDirectory); + StringBuffer sb = new StringBuffer(); + List files = Arrays.asList(localFiles); + String remoteFiles = files.stream().map(file -> { + int index = file.lastIndexOf(separator); + if(index <= 0){ + index = 0; + }else{ + index = index + 1; + } + return " \"" + remoteTargetDirectory + "/" + file.substring(index) + "\""; + }).collect(Collectors.joining(" ")); + clientSession.executeRemoteCommand("chmod " + mode + " " + remoteFiles); + } return new Result(true); } catch (Exception e) { - logger.error("scp local="+Arrays.toString(localFiles)+" to "+ - remoteTargetDirectory+" remote="+Arrays.toString(remoteFiles)+" err", e); + logger.error("scp local="+Arrays.toString(localFiles) + " to " + + remoteTargetDirectory + " remote=" + remoteFile + " err", e); return new Result(e); } } @@ -373,7 +319,7 @@ public Result scpToFile(String localFile, String remoteFile, String remoteTarget return scpToFile(localFile, remoteFile, remoteTargetDirectory, "0744"); } public Result scpToFile(String localFile, String remoteFile, String remoteTargetDirectory, String mode) { - return scp(new String[] { localFile }, new String[] { remoteFile }, remoteTargetDirectory, "0744"); + return scp(new String[] { localFile }, remoteFile, remoteTargetDirectory, "0744"); } } @@ -384,13 +330,16 @@ public static class Result{ private boolean success; private String result; private Exception excetion; + public Result(boolean success) { this.success = success; } + public Result(boolean success, String result) { this.success = success; this.result = result; } + public Result(Exception excetion) { this.success = false; this.excetion = excetion; @@ -399,21 +348,27 @@ public Result(Exception excetion) { public Exception getExcetion() { return excetion; } + public void setExcetion(Exception excetion) { this.excetion = excetion; } + public boolean isSuccess() { return success; } + public void setSuccess(boolean success) { this.success = success; } + public String getResult() { return result; } + public void setResult(String result) { this.result = result; } + @Override public String toString() { return "Result [success=" + success + ", result=" + result @@ -435,7 +390,7 @@ public interface SSHCallback{ /** * 从流中直接解析数据 */ - public static interface LineProcessor{ + public static interface LineProcessor { /** * 处理行 * @param line 内容 @@ -444,13 +399,30 @@ public static interface LineProcessor{ */ void process(String line, int lineNum) throws Exception; + /** + * 返回内容的行数,如果为0需要检测错误流 + * @return + */ + int lineNum(); + /** * 所有的行处理完毕回调该方法 */ void finish(); } - public static abstract class DefaultLineProcessor implements LineProcessor{ + public static abstract class DefaultLineProcessor implements LineProcessor { + protected int lineNum; + + @Override + public int lineNum() { + return lineNum; + } + + public void setLineNum(int lineNum) { + this.lineNum = lineNum; + } + public void finish() {} } } diff --git a/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHUtil.java b/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHUtil.java index 847a6908..05e232c3 100644 --- a/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHUtil.java +++ b/cachecloud-web/src/main/java/com/sohu/cache/ssh/SSHUtil.java @@ -1,359 +1,370 @@ -package com.sohu.cache.ssh; - -import com.google.common.collect.Maps; -import com.sohu.cache.entity.MachineStats; -import com.sohu.cache.exception.IllegalParamException; -import com.sohu.cache.exception.SSHException; -import com.sohu.cache.ssh.SSHTemplate; -import com.sohu.cache.ssh.SSHTemplate.DefaultLineProcessor; -import com.sohu.cache.ssh.SSHTemplate.Result; -import com.sohu.cache.ssh.SSHTemplate.SSHCallback; -import com.sohu.cache.ssh.SSHTemplate.SSHSession; -import com.sohu.cache.util.ConstUtils; -import com.sohu.cache.util.IntegerUtil; -import com.sohu.cache.util.StringUtil; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.math.NumberUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.text.DecimalFormat; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static com.sohu.cache.constant.BaseConstant.WORD_SEPARATOR; -import static com.sohu.cache.constant.EmptyObjectConstant.EMPTY_STRING; -import static com.sohu.cache.constant.SymbolConstant.COMMA; - -/** - * Created by yijunzhang on 14-6-20. - */ -public class SSHUtil { - private static final Logger logger = LoggerFactory.getLogger(SSHUtil.class); - - private final static String COMMAND_TOP = "top -b -n 1 | head -5"; - private final static String COMMAND_DF_LH = "df -lh"; - private final static String LOAD_AVERAGE_STRING = "load average: "; - private final static String COMMAND_MEM = "cat /proc/meminfo | grep -E -w 'MemTotal|MemFree|Buffers|Cached'"; - - private final static String COMMAND_CPU_MEM_DF = "(top -b -n 1 | head -5) && (cat /proc/meminfo | grep -E -w 'MemTotal|MemFree|Buffers|Cached') && (df -h)"; - - private final static String MEM_TOTAL = "MemTotal"; - private final static String MEM_FREE = "MemFree"; - private final static String MEM_BUFFERS = "Buffers"; - private final static String MEM_CACHED = "Cached"; - - //使用 @SSHTemplate 重构SSHUtil - private final static SSHTemplate sshTemplate = new SSHTemplate(); - - /** - * Get HostPerformanceEntity[cpuUsage, memUsage, load] by ssh.
- * 方法返回前已经释放了所有资源,调用方不需要关心 - * - * @param ip - * @param userName - * @param password - * @throws Exception - * @since 1.0.0 - */ - public static MachineStats getMachineInfo(String ip, int port, String userName, - String password) throws SSHException { - if (StringUtil.isBlank(ip)) { - try { - throw new IllegalParamException("Param ip is empty!"); - } catch (IllegalParamException e) { - throw new SSHException(e.getMessage(), e); - } - } - port = IntegerUtil.defaultIfSmallerThan0(port, ConstUtils.SSH_PORT_DEFAULT); - final MachineStats machineStats = new MachineStats(); - machineStats.setIp(ip); - - sshTemplate.execute(ip, port, userName, password, session -> { - //解析top命令 - session.executeCommand(COMMAND_CPU_MEM_DF, new DefaultLineProcessor() { - private String totalMem; - private String freeMem; - private String buffersMem; - private String cachedMem; - private Map diskUsageMap = Maps.newHashMap(); - - public void process(String line, int lineNum) throws Exception { - if (1 == lineNum) { - // 第一行,通常是这样: - // top - 19:58:52 up 416 days, 30 min, 1 user, load average: - // 0.00, 0.00, 0.00 - int loadAverageIndex = line.indexOf(LOAD_AVERAGE_STRING); - String loadAverages = line.substring(loadAverageIndex) - .replace(LOAD_AVERAGE_STRING, EMPTY_STRING); - String[] loadAverageArray = loadAverages.split(","); - if (3 == loadAverageArray.length) { - machineStats.setLoad(StringUtil.trimToEmpty(loadAverageArray[0])); - } - } else if (3 == lineNum) { - // 第三行通常是这样: - // , 0.0% sy, 0.0% ni, 100.0% id, 0.0% wa, - // 0.0% hi, 0.0% si - // redhat:%Cpu(s): 0.0 us - // centos7:Cpu(s): 0.0% us - double cpuUs = getUsCpu(line); - machineStats.setCpuUsage(String.valueOf(cpuUs)); - } else if (lineNum > 5 && lineNum < 10) { - if (line.contains(MEM_TOTAL)) { - totalMem = matchMemLineNumber(line).trim(); - } else if (line.contains(MEM_FREE)) { - freeMem = matchMemLineNumber(line).trim(); - } else if (line.contains(MEM_BUFFERS)) { - buffersMem = matchMemLineNumber(line).trim(); - } else if (line.contains(MEM_CACHED)) { - cachedMem = matchMemLineNumber(line).trim(); - } - } else if (lineNum >= 10) { - /** - * 内容通常是这样: Filesystem 容量 已用 可用 已用% 挂载点 /dev/xvda2 5.8G 3.2G 2.4G - * 57% / /dev/xvda1 99M 8.0M 86M 9% /boot none 769M 0 769M 0% - * /dev/shm /dev/xvda7 68G 7.1G 57G 12% /home /dev/xvda6 2.0G 36M - * 1.8G 2% /tmp /dev/xvda5 2.0G 199M 1.7G 11% /var - **/ - line = line.replaceAll(" {1,}", WORD_SEPARATOR); - String[] lineArray = line.split(WORD_SEPARATOR); - if (6 == lineArray.length) { - String diskUsage = lineArray[4]; - String mountedOn = lineArray[5]; - diskUsageMap.put(mountedOn, diskUsage); - } - } - } - - public void finish() { - if (!StringUtil.isBlank(totalMem, freeMem, buffersMem)) { - Long totalMemLong = NumberUtils.toLong(totalMem); - Long freeMemLong = NumberUtils.toLong(freeMem); - Long buffersMemLong = NumberUtils.toLong(buffersMem); - Long cachedMemLong = NumberUtils.toLong(cachedMem); - Long usedMemFree = freeMemLong + buffersMemLong + cachedMemLong; - Double memoryUsage = 1 - (NumberUtils.toDouble(usedMemFree.toString()) / NumberUtils - .toDouble(totalMemLong.toString()) / 1.0); - machineStats.setMemoryTotal(String.valueOf(totalMemLong)); - machineStats.setMemoryFree(String.valueOf(usedMemFree)); - DecimalFormat df = new DecimalFormat("0.00"); - machineStats.setMemoryUsageRatio(df.format(memoryUsage * 100)); - } - machineStats.setDiskUsageMap(diskUsageMap); - } - }); - return null; - }); - - // 统计当前网络流量 @TODO - Double traffic = 0.0; - machineStats.setTraffic(traffic.toString()); - - return machineStats; - } - - /** - * SSH 方式登录远程主机,执行命令,方法内部会关闭所有资源,调用方无须关心。 - * - * @param ip 主机ip - * @param username 用户名 - * @param password 密码 - * @param command 要执行的命令 - */ - public static String execute(String ip, int port, String username, String password, - final String command) throws SSHException { - - if (StringUtil.isBlank(command)) { - return EMPTY_STRING; - } - port = IntegerUtil.defaultIfSmallerThan0(port, ConstUtils.SSH_PORT_DEFAULT); - - Result rst = sshTemplate.execute(ip, port, username, password, new SSHCallback() { - public Result call(SSHSession session) { - return session.executeCommand(command); - } - }); - if (rst.isSuccess()) { - return rst.getResult(); - } - return ""; - } - - - public static String execute(String ip, int port, String username, String password, - final String command, int timeout) throws SSHException { - - if (StringUtil.isBlank(command)) { - return EMPTY_STRING; - } - port = IntegerUtil.defaultIfSmallerThan0(port, ConstUtils.SSH_PORT_DEFAULT); - - Result rst = sshTemplate.execute(ip, port, username, password, new SSHCallback() { - public Result call(SSHSession session) { - return session.executeCommand(command, timeout); - } - }); - if (rst != null) { - return rst.getResult(); - } - return ""; - } - - /** - * @param ip - * @param port - * @param username - * @param password - * @param localPath - * @param remoteDir - * @return - * @throws SSHException - */ - public static boolean scpFileToRemote(String ip, int port, String username, - String password, final String localPath, final String remoteDir) throws SSHException { - Result rst = sshTemplate.execute(ip, port, username, password, new SSHCallback() { - public Result call(SSHSession session) { - return session.scpToDir(localPath, remoteDir); - } - }); - if (rst.isSuccess()) { - return true; - } - if (rst.getExcetion() != null) { - throw new SSHException(rst.getExcetion()); - } - return false; - } - - /** - * 重载,使用默认端口、用户名和密码 - * - * @param ip - * @param localPath - * @param remoteDir - * @return - * @throws SSHException - */ - public static boolean scpFileToRemote(String ip, String localPath, String remoteDir) throws SSHException { - int sshPort = SSHUtil.getSshPort(ip); - return scpFileToRemote(ip, sshPort, ConstUtils.USERNAME, ConstUtils.PASSWORD, localPath, remoteDir); - } - - /** - * 重载,使用默认端口、用户名和密码 - * - * @param ip - * @param cmd - * @return - * @throws SSHException - */ - public static String execute(String ip, String cmd) throws SSHException { - int sshPort = SSHUtil.getSshPort(ip); - return execute(ip, sshPort, ConstUtils.USERNAME, ConstUtils.PASSWORD, cmd); - } - - public static String execute(String ip, String cmd, Integer timeout) throws SSHException { - int sshPort = SSHUtil.getSshPort(ip); - if (timeout == null) { - return execute(ip, sshPort, ConstUtils.USERNAME, ConstUtils.PASSWORD, cmd, 30000); - } else { - return execute(ip, sshPort, ConstUtils.USERNAME, ConstUtils.PASSWORD, cmd, timeout); - } - - } - - /** - * 查看机器ip上的端口port是否已被占用; - * - * @param ip 机器ip - * @param port 要检查的端口 - * @return 如果被占用返回true,否则返回false; - * @throws SSHException - */ - public static boolean isPortUsed(String ip, int port) throws SSHException { - /** - * 执行ps命令,查看端口,以确认刚才执行的shell命令是否成功,返回一般是这样的: - * root 12510 12368 0 14:34 pts/0 00:00:00 redis-server *:6379 - */ - String psCmd = "/bin/ps -ef | grep %s | grep -v grep"; - psCmd = String.format(psCmd, port); - String psResponse = execute(ip, psCmd); - boolean isUsed = false; - - if (StringUtils.isNotBlank(psResponse)) { - String[] resultArr = psResponse.split(System.lineSeparator()); - for (String resultLine : resultArr) { - if (resultLine.contains(String.valueOf(port))) { - isUsed = true; - break; - } - } - } - return isUsed; - } - - /** - * 通过ip来判断ssh端口 - * - * @param ip - * @return - */ - public static int getSshPort(String ip) { - /** - * 如果ssh默认端口不是22,请自行实现该逻辑 - */ - return ConstUtils.SSH_PORT_DEFAULT; - } - - /** - * 匹配字符串中的数字 - * - * @param content - * @return - */ - private static String matchMemLineNumber(String content) { - String result = EMPTY_STRING; - if (content == null || EMPTY_STRING.equals(content.trim())) { - return result; - } - Pattern pattern = Pattern.compile("(\\d+)"); - Matcher matcher = pattern.matcher(content); - if (matcher.find()) { - result = matcher.group(1); - } - return result; - } - - /** - * 从top的cpuLine解析出us - * - * @param cpuLine - * @return - */ - public static double getUsCpu(String cpuLine) { - if (cpuLine == null || EMPTY_STRING.equals(cpuLine.trim())) { - return 0; - } - String[] items = cpuLine.split(COMMA); - if (items.length < 1) { - return 0; - } - String usCpuStr = items[0]; - return NumberUtils.toDouble(matchCpuLine(usCpuStr)); - } - - private static String matchCpuLine(String content) { - String result = EMPTY_STRING; - if (content == null || EMPTY_STRING.equals(content.trim())) { - return result; - } - Pattern pattern = Pattern.compile("(\\d+).(\\d+)"); - Matcher matcher = pattern.matcher(content); - if (matcher.find()) { - result = matcher.group(); - } - return result; - } - -} +package com.sohu.cache.ssh; + +import com.google.common.collect.Maps; +import com.sohu.cache.entity.MachineStats; +import com.sohu.cache.exception.IllegalParamException; +import com.sohu.cache.exception.SSHException; +import com.sohu.cache.ssh.SSHTemplate.DefaultLineProcessor; +import com.sohu.cache.ssh.SSHTemplate.Result; +import com.sohu.cache.ssh.SSHTemplate.SSHCallback; +import com.sohu.cache.ssh.SSHTemplate.SSHSession; +import com.sohu.cache.util.ConstUtils; +import com.sohu.cache.util.IntegerUtil; +import com.sohu.cache.util.StringUtil; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.math.NumberUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.text.DecimalFormat; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.sohu.cache.constant.BaseConstant.WORD_SEPARATOR; +import static com.sohu.cache.constant.EmptyObjectConstant.EMPTY_STRING; +import static com.sohu.cache.constant.SymbolConstant.COMMA; + +/** + * Created by yijunzhang on 14-6-20. + */ +@Component +public class SSHUtil { + private static final Logger logger = LoggerFactory.getLogger(SSHUtil.class); + + private final static String COMMAND_TOP = "top -b -n 1 | head -5"; + private final static String COMMAND_DF_LH = "df -lh"; + private final static String LOAD_AVERAGE_STRING = "load average: "; + private final static String COMMAND_MEM = "cat /proc/meminfo | grep -E -w 'MemTotal|MemFree|Buffers|Cached'"; + + private final static String COMMAND_CPU_MEM_DF = "(top -b -n 1 | head -5) && (cat /proc/meminfo | grep -E -w 'MemTotal|MemFree|Buffers|Cached') && (df -h)"; + + private final static String MEM_TOTAL = "MemTotal"; + private final static String MEM_FREE = "MemFree"; + private final static String MEM_BUFFERS = "Buffers"; + private final static String MEM_CACHED = "Cached"; + + @Autowired + private SSHTemplate sshTemplate; + + //使用 @SSHTemplate 重构SSHUtil + private static SSHTemplate sshTemplateNew; + + @PostConstruct + private void initSSHTemplate(){ + sshTemplateNew = sshTemplate; + } + + /** + * Get HostPerformanceEntity[cpuUsage, memUsage, load] by ssh.
+ * 方法返回前已经释放了所有资源,调用方不需要关心 + * + * @param ip + * @param userName + * @param password + * @throws Exception + * @since 1.0.0 + */ + public static MachineStats getMachineInfo(String ip, int port, String userName, + String password) throws SSHException { + if (StringUtil.isBlank(ip)) { + try { + throw new IllegalParamException("Param ip is empty!"); + } catch (IllegalParamException e) { + throw new SSHException(e.getMessage(), e); + } + } + port = IntegerUtil.defaultIfSmallerThan0(port, ConstUtils.SSH_PORT_DEFAULT); + final MachineStats machineStats = new MachineStats(); + machineStats.setIp(ip); + + sshTemplateNew.execute(ip, port, userName, password, session -> { + //解析top命令 + session.executeCommand(COMMAND_CPU_MEM_DF, new DefaultLineProcessor() { + private String totalMem; + private String freeMem; + private String buffersMem; + private String cachedMem; + private Map diskUsageMap = Maps.newHashMap(); + + public void process(String line, int lineNum) throws Exception { + if (1 == lineNum) { + // 第一行,通常是这样: + // top - 19:58:52 up 416 days, 30 min, 1 user, load average: + // 0.00, 0.00, 0.00 + int loadAverageIndex = line.indexOf(LOAD_AVERAGE_STRING); + String loadAverages = line.substring(loadAverageIndex) + .replace(LOAD_AVERAGE_STRING, EMPTY_STRING); + String[] loadAverageArray = loadAverages.split(","); + if (3 == loadAverageArray.length) { + machineStats.setLoad(StringUtil.trimToEmpty(loadAverageArray[0])); + } + } else if (3 == lineNum) { + // 第三行通常是这样: + // , 0.0% sy, 0.0% ni, 100.0% id, 0.0% wa, + // 0.0% hi, 0.0% si + // redhat:%Cpu(s): 0.0 us + // centos7:Cpu(s): 0.0% us + double cpuUs = getUsCpu(line); + machineStats.setCpuUsage(String.valueOf(cpuUs)); + } else if (lineNum > 5 && lineNum < 10) { + if (line.contains(MEM_TOTAL)) { + totalMem = matchMemLineNumber(line).trim(); + } else if (line.contains(MEM_FREE)) { + freeMem = matchMemLineNumber(line).trim(); + } else if (line.contains(MEM_BUFFERS)) { + buffersMem = matchMemLineNumber(line).trim(); + } else if (line.contains(MEM_CACHED)) { + cachedMem = matchMemLineNumber(line).trim(); + } + } else if (lineNum >= 10) { + /** + * 内容通常是这样: Filesystem 容量 已用 可用 已用% 挂载点 /dev/xvda2 5.8G 3.2G 2.4G + * 57% / /dev/xvda1 99M 8.0M 86M 9% /boot none 769M 0 769M 0% + * /dev/shm /dev/xvda7 68G 7.1G 57G 12% /home /dev/xvda6 2.0G 36M + * 1.8G 2% /tmp /dev/xvda5 2.0G 199M 1.7G 11% /var + **/ + line = line.replaceAll(" {1,}", WORD_SEPARATOR); + String[] lineArray = line.split(WORD_SEPARATOR); + if (6 == lineArray.length) { + String diskUsage = lineArray[4]; + String mountedOn = lineArray[5]; + diskUsageMap.put(mountedOn, diskUsage); + } + } + } + + public void finish() { + if (!StringUtil.isBlank(totalMem, freeMem, buffersMem)) { + Long totalMemLong = NumberUtils.toLong(totalMem); + Long freeMemLong = NumberUtils.toLong(freeMem); + Long buffersMemLong = NumberUtils.toLong(buffersMem); + Long cachedMemLong = NumberUtils.toLong(cachedMem); + Long usedMemFree = freeMemLong + buffersMemLong + cachedMemLong; + Double memoryUsage = 1 - (NumberUtils.toDouble(usedMemFree.toString()) / NumberUtils + .toDouble(totalMemLong.toString()) / 1.0); + machineStats.setMemoryTotal(String.valueOf(totalMemLong)); + machineStats.setMemoryFree(String.valueOf(usedMemFree)); + DecimalFormat df = new DecimalFormat("0.00"); + machineStats.setMemoryUsageRatio(df.format(memoryUsage * 100)); + } + machineStats.setDiskUsageMap(diskUsageMap); + } + }); + return null; + }); + + // 统计当前网络流量 @TODO + Double traffic = 0.0; + machineStats.setTraffic(traffic.toString()); + + return machineStats; + } + + /** + * SSH 方式登录远程主机,执行命令,方法内部会关闭所有资源,调用方无须关心。 + * + * @param ip 主机ip + * @param username 用户名 + * @param password 密码 + * @param command 要执行的命令 + */ + public static String execute(String ip, int port, String username, String password, + final String command) throws SSHException { + + if (StringUtil.isBlank(command)) { + return EMPTY_STRING; + } + port = IntegerUtil.defaultIfSmallerThan0(port, ConstUtils.SSH_PORT_DEFAULT); + + Result rst = sshTemplateNew.execute(ip, port, username, password, new SSHCallback() { + public Result call(SSHSession session) { + return session.executeCommand(command); + } + }); + if (rst.isSuccess()) { + return rst.getResult(); + } + return ""; + } + + + public static String execute(String ip, int port, String username, String password, + final String command, int timeout) throws SSHException { + + if (StringUtil.isBlank(command)) { + return EMPTY_STRING; + } + port = IntegerUtil.defaultIfSmallerThan0(port, ConstUtils.SSH_PORT_DEFAULT); + + Result rst = sshTemplateNew.execute(ip, port, username, password, new SSHCallback() { + public Result call(SSHSession session) { + return session.executeCommand(command, timeout); + } + }); + if (rst != null) { + return rst.getResult(); + } + return ""; + } + + /** + * @param ip + * @param port + * @param username + * @param password + * @param localPath + * @param remoteDir + * @return + * @throws SSHException + */ + public static boolean scpFileToRemote(String ip, int port, String username, + String password, final String localPath, final String remoteDir) throws SSHException { + Result rst = sshTemplateNew.execute(ip, port, username, password, new SSHCallback() { + public Result call(SSHSession session) { + return session.scpToDir(localPath, remoteDir); + } + }); + if (rst.isSuccess()) { + return true; + } + if (rst.getExcetion() != null) { + throw new SSHException(rst.getExcetion()); + } + return false; + } + + /** + * 重载,使用默认端口、用户名和密码 + * + * @param ip + * @param localPath + * @param remoteDir + * @return + * @throws SSHException + */ + public static boolean scpFileToRemote(String ip, String localPath, String remoteDir) throws SSHException { + int sshPort = SSHUtil.getSshPort(ip); + return scpFileToRemote(ip, sshPort, ConstUtils.USERNAME, ConstUtils.PASSWORD, localPath, remoteDir); + } + + /** + * 重载,使用默认端口、用户名和密码 + * + * @param ip + * @param cmd + * @return + * @throws SSHException + */ + public static String execute(String ip, String cmd) throws SSHException { + int sshPort = SSHUtil.getSshPort(ip); + return execute(ip, sshPort, ConstUtils.USERNAME, ConstUtils.PASSWORD, cmd); + } + + public static String execute(String ip, String cmd, Integer timeout) throws SSHException { + int sshPort = SSHUtil.getSshPort(ip); + if (timeout == null) { + return execute(ip, sshPort, ConstUtils.USERNAME, ConstUtils.PASSWORD, cmd, 30000); + } else { + return execute(ip, sshPort, ConstUtils.USERNAME, ConstUtils.PASSWORD, cmd, timeout); + } + + } + + /** + * 查看机器ip上的端口port是否已被占用; + * + * @param ip 机器ip + * @param port 要检查的端口 + * @return 如果被占用返回true,否则返回false; + * @throws SSHException + */ + public static boolean isPortUsed(String ip, int port) throws SSHException { + /** + * 执行ps命令,查看端口,以确认刚才执行的shell命令是否成功,返回一般是这样的: + * root 12510 12368 0 14:34 pts/0 00:00:00 redis-server *:6379 + */ + String psCmd = "/bin/ps -ef | grep %s | grep -v grep"; + psCmd = String.format(psCmd, port); + String psResponse = execute(ip, psCmd); + boolean isUsed = false; + + if (StringUtils.isNotBlank(psResponse)) { + String[] resultArr = psResponse.split(System.lineSeparator()); + for (String resultLine : resultArr) { + if (resultLine.contains(String.valueOf(port))) { + isUsed = true; + break; + } + } + } + return isUsed; + } + + /** + * 通过ip来判断ssh端口 + * + * @param ip + * @return + */ + public static int getSshPort(String ip) { + /** + * 如果ssh默认端口不是22,请自行实现该逻辑 + */ + return ConstUtils.SSH_PORT_DEFAULT; + } + + /** + * 匹配字符串中的数字 + * + * @param content + * @return + */ + private static String matchMemLineNumber(String content) { + String result = EMPTY_STRING; + if (content == null || EMPTY_STRING.equals(content.trim())) { + return result; + } + Pattern pattern = Pattern.compile("(\\d+)"); + Matcher matcher = pattern.matcher(content); + if (matcher.find()) { + result = matcher.group(1); + } + return result; + } + + /** + * 从top的cpuLine解析出us + * + * @param cpuLine + * @return + */ + public static double getUsCpu(String cpuLine) { + if (cpuLine == null || EMPTY_STRING.equals(cpuLine.trim())) { + return 0; + } + String[] items = cpuLine.split(COMMA); + if (items.length < 1) { + return 0; + } + String usCpuStr = items[0]; + return NumberUtils.toDouble(matchCpuLine(usCpuStr)); + } + + private static String matchCpuLine(String content) { + String result = EMPTY_STRING; + if (content == null || EMPTY_STRING.equals(content.trim())) { + return result; + } + Pattern pattern = Pattern.compile("(\\d+).(\\d+)"); + Matcher matcher = pattern.matcher(content); + if (matcher.find()) { + result = matcher.group(); + } + return result; + } + +} diff --git a/cachecloud-web/src/main/java/com/sohu/cache/util/ConstUtils.java b/cachecloud-web/src/main/java/com/sohu/cache/util/ConstUtils.java index a38eb451..5c09185f 100644 --- a/cachecloud-web/src/main/java/com/sohu/cache/util/ConstUtils.java +++ b/cachecloud-web/src/main/java/com/sohu/cache/util/ConstUtils.java @@ -254,6 +254,7 @@ public class ConstUtils { public static int LIST_MAX_LENGTH = DEFAULT_LIST_MAX_LENGTH; public static int SET_MAX_LENGTH = DEFAULT_SET_MAX_LENGTH; public static int ZSET_MAX_LENGTH = DEFAULT_ZSET_MAX_LENGTH; + public static int SSH_CONNECTION_TIMEOUT = 5000; public static String getRedisMigrateToolCmd(String name) { return ConstUtils.getRedisToolDir(name) + "src/redis-migrate-tool"; diff --git a/cachecloud-web/src/main/java/com/sohu/cache/web/service/impl/AppRedisCommandCheckServiceImpl.java b/cachecloud-web/src/main/java/com/sohu/cache/web/service/impl/AppRedisCommandCheckServiceImpl.java index 74c75219..e2f67938 100644 --- a/cachecloud-web/src/main/java/com/sohu/cache/web/service/impl/AppRedisCommandCheckServiceImpl.java +++ b/cachecloud-web/src/main/java/com/sohu/cache/web/service/impl/AppRedisCommandCheckServiceImpl.java @@ -14,11 +14,9 @@ import com.sohu.cache.redis.RedisCenter; import com.sohu.cache.redis.enums.DirEnum; import com.sohu.cache.ssh.SSHUtil; -import com.sohu.cache.util.ConstUtils; import com.sohu.cache.util.StringUtil; import com.sohu.cache.util.TypeUtil; import com.sohu.cache.web.enums.BooleanEnum; -import com.sohu.cache.web.enums.SshAuthTypeEnum; import com.sohu.cache.web.service.AppRedisCommandCheckService; import com.sohu.cache.web.vo.*; import lombok.extern.slf4j.Slf4j; diff --git a/cachecloud-web/src/main/resources/logback-spring.xml b/cachecloud-web/src/main/resources/logback-spring.xml index 3df3c315..e0e98f8d 100644 --- a/cachecloud-web/src/main/resources/logback-spring.xml +++ b/cachecloud-web/src/main/resources/logback-spring.xml @@ -131,8 +131,8 @@ - - + diff --git a/pom.xml b/pom.xml index f4f53ac2..9c265031 100644 --- a/pom.xml +++ b/pom.xml @@ -49,6 +49,7 @@ 2.9.2 2.9.2 1.10.11 + 2.9.2 @@ -115,16 +116,15 @@ - ch.ethz.ganymed - ganymed-ssh2 - ${ganymed.ssh.version} + org.apache.sshd + sshd-core + ${ssh-version} - - com.hierynomus - sshj - ${sshj.version} + org.apache.sshd + sshd-scp + ${ssh-version}