测试开发这个工作最后都有可能会开发测试平台,测试工具等,这也难免会遇到这样的问题
:如何操作远程的服务器?如向服务器发送命令,收集或是判断命令执行结果,以及记录日志,拉取远程服务器上的测试报告等操作。
前阶段用公司的WF,SCF框架开发测试平台,遇到了这样的问题,研究了几天,发现java有这样的功能:JSch!!!
JSch
是SSH2的一个纯Java实现。它允许你连接到一个sshd 服务器,使用端口转发,X11转发,文件传输等等。你可以将它的功能集成到你自己的
程序中。同时该项目也提供一个J2ME版本用来在手机上直连SSHD服务器。同时该开发包也提供 J2ME 下的版本。(http://www.oschina.net/p/jsch)
下面介绍一下常用的功能:
一, 远程连接主机
首先要下载对应的jsch.jar包,然后添加相应的引用:
Import
com.jcraft.jsch.Channel;
Import
com.jcraft.jsch.ChannelExec;
Import
com.jcraft.jsch.ChannelShell;
Import
com.jcraft.jsch.JSch;
Import
com.jcraft.jsch.JSchException;
Import
com.jcraft.jsch.Session;
(1)Jsch远程连接主机的时候,最常用的方法是通过IP地址,端口号,用户名和密码新建session,如下所示:
Public
JschUtils(String
host,intport,Stringusername,Stringpassword) throwsJSchException{
jsch = newJSch();
session = jsch.getSession(username, host, port);
session.setPassword(password);
java.util.Propertiesconfig = new;
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect(3000);
log.info("建立连接");
}
通过调用这个JschUtils函数,传递IP地址,端口号,用户名和密码,即可建立与该服务器的连接,当然这个session变量需要定义成全局变量,方便其他函数使用。
(2)如果你是通过添加授权,kerberos,添加信件IP等方式的话,连接方式就不太一样了,具体的要根据具体的情况去网上查询,下面提供一个我们公司的示例,具体操作步骤如下:
Ø 在要操作的服务器上给平台机器添加work权限
vi /home/work/.k5login
....
host/XXX-YYY—ZZZ
....
Ø
在平台WF容器的conf文件夹中添加登录配置loginkeytab.conf
# pwd
/opt/web/CommercialTestPlatform/conf
# cat loginkeytab.conf
com.sun.security.jgss.krb5.initiate{
com.sun.security.auth.module.Krb5LoginModule
required
debug="false"
useKeyTab="true"
doNotPrompt="true"
keyTab="/etc/krb5.keytab"
storeKey="true"
principal="host/XXX-YYY—ZZZ ";
};
注意:principal必须是本机受权的用户名,也是就是(1)中添加的。
Ø
在平台服务器上执行下面的命令:
/usr/bin/kinit -k -t
/etc/krb5.keytab
Ø
通过jsch连接服务器
publicJschUtils(String host,intport) throwsJSchException{
String name = "work";
Session session;
try {
System.setProperty("java.security.auth.login.config", "$pwd/loginkeytab.conf");
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
System.setProperty("java.security.krb5.conf", "/etc/krb5.conf");
JSch jsch = new JSch();
jsch.setKnownHosts( "/root/.ssh/known_hosts"
);
loggerHelper.info("Workname:" + name+" hosts:"+host);
session = jsch.getSession(name, host, port);
session.setTimeout(3000);
session.setServerAliveInterval(1000);
java.util.Properties config = new ;
config.put("StrictHostKeyChecking", "no");
config.put("PreferredAuthentications","gssapi-with-mic,publickey"
);
config.put("kex", "diffie-hellman-group1-sha1");
session.setConfig(config);
session.connect();
session.sendKeepAliveMsg();
} catch
(Exception e) {
//公钥方式登录
JSch jsch = new JSch();
jsch.addIdentity("/root/.ssh/id_rsa");
jsch.setKnownHosts( "/root/.ssh/known_hosts"
);
session = jsch.getSession(name, host, port);
session.setTimeout(3000);
java.util.Properties Config = new ;
Config.put("StrictHostKeyChecking", "no");
session.setConfig(Config);
session.connect();
}
return session;}
在这个函数中我们通过了kerberos授权和添加信息的IP地址,当然还是需要IP地址,端口号,用户名和密码的。此时的IP地址指向连接到哪儿台服务器,用户名和密码就是授权的了,此时的用户名和密码被定义成了全局变量,所以不用传参。
二, 远程执行命令
连接远程服务器的目的是为操作服务器,所以我们要解决的第二个知识点就是如何向远程服务器发送命令?当然我们在执行命令的时候,不能只把命令发过去就不管了,要把命令执行的输出返回。如果命令常时间没有任何输出的时候,就需要断开与服务器的连接,故需要一个超时时间。
根据上面的分析,我们的示例代码如下:
public String execAndResult( String
cmd,longtimeout)throwsException{
ChannelExecchannelExec = null;
StringBufferresult = newStringBuffer();
InputStreamin= null;
try {
log.info("要执行的命令:" + cmd);
channelExec = (ChannelExec)session.openChannel( "exec" );
channelExec.setCommand(cmd );
channelExec.setInputStream(null );
channelExec.setErrStream(System.err );
in= channelExec.getInputStream();
channelExec.connect();
intres = -1;
byte[] tmp = newbyte[ 1024 ];
longstarttime =
System.currentTimeMillis();
while ( true ) {
while ( in.available() > 0 ) {
inti= in.read( tmp, 0, 1024 );
if ( i< 0 ) break;
result.append(new String( tmp, 0, i) + "\n");
starttime =
System.currentTimeMillis();
}
if (System.currentTimeMillis()
- starttime>= timeout * 1000) {
log.info("在" + timeout + "秒内没有日志输出,中断程序");
break;
}
if ( channelExec.isClosed() ) {
res = channelExec.getExitStatus();
break;
}
}
} catch
(Exception e) {
e.printStackTrace();
} finally
{
if (channelExec !=null) {
channelExec.disconnect();
}
if (in!=null) {
in.close();
}
session.disconnect();
log.info("关闭连接");
}
returnresult.toString();
}
代码解析:
(1) 函数execAndResult()为在远程服务器上执行命令cmd,如果命令在timeout期间没有任何输出的话则中断连接。
(2) 在调用函数前,需要先调用上面的连接服务器函数,连接到服务器,并初化session变量。命令执行的输出存放在StringBufferresult变量中,并作为函数的返回值返回给调用者。
(3) 通过ChannelExec执行命令和获取命令执行结果,channelExec.getInputStream()获取命令输出流,如果in.available()<0则没有输出任何信息。
(4)
在命令没有输出的时候,判断一下是否超时,如果不超时继续读取,如果超时则断开远程连接。
三, 记录远程命令执行日志
很多时候我们执行的命令会有大量的输出,而我们又不能一直盯着输出看,此时我们就需要把输出记录成日志,这样方便我们排查。当然程序有的时候会有日志记录,我们执行的命令就不一定有了,所以我们要人工来添加。
如我们在部署环境的时候,需要记录部署日志,但是同时也要根据输出来判断部署结果。此时就要检测什么时候算是部署成功,什么情况下是部署失败?那么我们就需要设置一个成功的标志,检查到有这个标志输出的时候就算成功,示例代码如下:
publicboolean
exec( String cmd,String outputFileName,String sucessStr,long timeout)
throws Exception{
BufferedWriterout =newBufferedWriter(newFileWriter(outputFileName));
ChannelExecchannelExec = null;
InputStreamin= null;
try {
log.info("要执行的命令:" + cmd);
channelExec = (ChannelExec)session.openChannel( "exec" );
channelExec.setCommand(cmd );
channelExec.setInputStream(null );
channelExec.setErrStream(System.err );
channelExec.setPty(true);
in= channelExec.getInputStream();
channelExec.connect();
intres = -1;
byte[] tmp = newbyte[ 1024 ];
longstarttime =
System.currentTimeMillis();
while ( true ) {
while ( in.available() > 0 ) {
inti= in.read( tmp, 0, 1024 );
if ( i< 0 ) break;
String tmp2
= newString(tmp, 0, i) + "\n";
if (sucessStr!=null) {
if (tmp2.indexOf(sucessStr) > -1) {
out.write(tmp2);
returntrue;
}
}
out.write(tmp2);
starttime =
System.currentTimeMillis();
}
if (System.currentTimeMillis()
- starttime>= timeout * 1000) {
log.info("在" + timeout + "秒内没有日志输出,中断程序");
break;
}
if ( channelExec.isClosed()) {
log.info("没有输出了,关闭了" + (System.currentTimeMillis()
- starttime));
res = channelExec.getExitStatus();
log.info("channel 关闭");
break;
}
}
} catch
(Exception e) {
e.printStackTrace();
} finally
{
out.close();
if (channelExec !=null) {
channelExec.disconnect();
}
if (in !=null) {
in.close();
}
session.disconnect();
log.info("关闭连接");
}
returnfalse;
}
代码解析:
(1)
函数exec()通过参数来执行命令,并把命令输出给保存到outputFileName指定的文件中。
(2)
在命令执行的时候,判断输出,如果输出包含sucessStr指定的成功标志,则返回True;其他的情况如超时,或是报错等都会返回False。
(3)
在异常的情况下,需要记录异常并且关闭与远程主机的连接,不然一次连接启动一个进程,会把服务器给卡死的。
(4)当然我们也可以去掉记录日志的部署,单纯地根据命令的输出来判断命令执行成功或是失败,这个就比较简单,我们在此就不展示代码了。
加载中,请稍候......