测试开发这个工作最后都有可能会开发测试平台,测试工具等,这也难免会遇到这样的问题
:如何操作远程的服务器?如向服务器发送命令,收集或是判断命令执行结果,以及记录日志,拉取远程服务器上的测试报告等操作。
前阶段用公司的
WF
,
SCF
框架开发测试平台,遇到了这样的问题,研究了几天,发现
java
有这样的功能:
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
,
int
port
,String
username
,String
password
)
throws
JSchException{
jsch
=
new
JSch();
session
=
jsch
.getSession(
username
,
host
,
port
);
session
.setPassword(
password
);
java.util.Properties
config
=
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
连接服务器
public
JschUtils(String
host
,
int
port
)
throws
JSchException{
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
,
long
timeout
)
throws
Exception{
ChannelExec
channelExec
=
null
;
StringBuffer
result
=
new
StringBuffer();
InputStream
in
=
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();
int
res
= -1;
byte
[]
tmp
=
newbyte
[ 1024 ];
long
starttime
=
System.
currentTimeMillis
();
while
(
true
) {
while
(
in
.available() > 0 ) {
int
i
=
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(
"
关闭连接
"
);
return
result
.toString();
代码解析:
(1)
函数
execAndResult
()为在远程服务器上执行命令
cmd,
如果命令在
timeout
期间没有任何输出的话则中断连接。
(2)
在调用函数前,需要先调用上面的连接服务器函数,连接到服务器,并初化
session
变量。命令执行的输出存放在
StringBufferresult
变量中,并作为函数的返回值返回给调用者。
(3)
通过
ChannelExec
执行命令和获取命令执行结果,
channelExec
.getInputStream()
获取命令输出流,如果
in
.available()<0
则没有输出任何信息。
在命令没有输出的时候,判断一下是否超时,如果不超时继续读取,如果超时则断开远程连接。
三,
记录远程命令执行日志
很多时候我们执行的命令会有大量的输出,而我们又不能一直盯着输出看,此时我们就需要把输出记录成日志,这样方便我们排查。当然程序有的时候会有日志记录,我们执行的命令就不一定有了,所以我们要人工来添加。
如我们在部署环境的时候,需要记录部署日志,但是同时也要根据输出来判断部署结果。此时就要检测什么时候算是部署成功,什么情况下是部署失败?那么我们就需要设置一个成功的标志,检查到有这个标志输出的时候就算成功,示例代码如下:
publicboolean
exec( String
cmd
,String
outputFileName
,String
sucessStr
,
long
timeout
)
throws
Exception{
BufferedWriter
out
=
new
BufferedWriter(
new
FileWriter(
outputFileName
));
ChannelExec
channelExec
=
null
;
InputStream
in
=
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();
int
res
= -1;
byte
[]
tmp
=
newbyte
[ 1024 ];
long
starttime
=
System.
currentTimeMillis
();
while
(
true
) {
while
(
in
.available() > 0 ) {
int
i
=
in
.read(
tmp
, 0, 1024 );
if
(
i
< 0 )
break
;
String
tmp2
=
new
String(
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
;
代码解析:
函数
exec
()通过参数来执行命令,并把命令输出给保存到
outputFileName
指定的文件中。
在命令执行的时候,判断输出,如果输出包含
sucessStr
指定的成功标志,则返回
True
;其他的情况如超时,或是报错等都会返回
False
。
在异常的情况下,需要记录异常并且关闭与远程主机的连接,不然一次连接启动一个进程,会把服务器给卡死的。
(4)
当然我们也可以去掉记录日志的部署,单纯地根据命令的输出来判断命令执行成功或是失败,这个就比较简单,我们在此就不展示代码了。
新浪简介
|
About Sina
|
广告服务
|
联系我们
|
招聘信息
|
网站律师
|
SINA English
|
产品答疑