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}