/**
* Copyright 2016 benjobs
* <p>
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.opencron.agent;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.opencron.common.job.*;
import org.opencron.common.utils.*;
import org.apache.commons.exec.*;
import org.slf4j.Logger;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import static org.opencron.common.utils.CommonUtils.*;
/**
* Created by benjo on 2016/3/25.
*/
@Sharable
public class AgentHandler extends SimpleChannelInboundHandler<Request> implements Opencron {
private Logger logger = LoggerFactory.getLogger(AgentHandler.class);
private String password;
private Integer socketPort;
private final String EXITCODE_KEY = "exitCode";
private final String EXITCODE_SCRIPT = String.format(" \n echo %s:$?", EXITCODE_KEY);
private final String REPLACE_REX = "%s:\\sline\\s[0-9]+:";
private AgentMonitor agentMonitor;
private ChannelHandlerContext channelHandlerContext;
private Map<String, AgentHeartBeat> agentHeartBeatMap = new ConcurrentHashMap<String, AgentHeartBeat>(0);
private Response response;
public AgentHandler(String password) {
this.password = password;
this.register();
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Request request) throws Exception {
this.channelHandlerContext = channelHandlerContext;
Action action = request.getAction();
switch (action) {
case PING:
this.ping(request);
break;
case PATH:
this.path(request);
break;
case MONITOR:
this.monitor(request);
break;
case EXECUTE:
this.execute(request);
break;
case PASSWORD:
this.password(request);
break;
case KILL:
this.kill(request);
break;
case PROXY:
this.proxy(request);
break;
case GUID:
this.guid(request);
break;
case RESTART:
this.restart(request);
break;
default:
}
channelHandlerContext.writeAndFlush(this.response);
channelHandlerContext.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
@Override
public void ping(Request request) throws Exception {
if (!this.password.equalsIgnoreCase(request.getPassword())) {
this.response = errorPasswordResponse(request);
return;
}
//非直连
if ( CommonUtils.isEmpty(request.getParams().get("proxy")) ) {
String hostName = Globals.OPENCRON_SOCKET_ADDRESS.split(":")[0];
int serverPort = Integer.parseInt(request.getParams().get("serverPort"));
AgentHeartBeat agentHeartBeat = agentHeartBeatMap.get(hostName);
if (agentHeartBeat == null) {
try {
agentHeartBeat = new AgentHeartBeat(hostName, serverPort, request.getHostName());
agentHeartBeat.start();
agentHeartBeatMap.put(hostName, agentHeartBeat);
logger.info("[opencron]:ping ip:{},port:{}", hostName, serverPort);
} catch (IOException e) {
e.printStackTrace();
}
}
}
this.response = Response.response(request).setSuccess(true).setExitCode(Opencron.StatusCode.SUCCESS_EXIT.getValue()).end();
}
@Override
public void path(Request request) throws Exception {
//返回密码文件的路径...
this.response = Response.response(request).setSuccess(true)
.setExitCode(Opencron.StatusCode.SUCCESS_EXIT.getValue())
.setMessage(Globals.OPENCRON_HOME)
.end();
}
@Override
public void monitor(Request request) throws Exception {
Opencron.ConnType connType = Opencron.ConnType.getByName(request.getParams().get("connType"));
this.response = Response.response(request);
Map<String, String> map = new HashMap<String, String>(0);
if (agentMonitor == null) {
agentMonitor = new AgentMonitor();
}
switch (connType) {
case CONN:
if (CommonUtils.isEmpty(agentMonitor, socketPort) || agentMonitor.stoped()) {
//选举一个空闲可用的port
this.socketPort = HttpUtils.freePort();
try {
agentMonitor.start(socketPort);
} catch (Exception e) {
e.printStackTrace();
}
}
logger.debug("[opencron]:getMonitorPort @:{}", socketPort);
map.put("port", this.socketPort.toString());
response.setResult(map);
case PROXY:
Monitor monitor = agentMonitor.monitor();
map = serializableToMap(monitor);
response.setResult(map);
default:
}
}
@Override
public void execute(final Request request) throws Exception {
if (!this.password.equalsIgnoreCase(request.getPassword())) {
this.response = errorPasswordResponse(request);
return;
}
String command = request.getParams().get("command") + EXITCODE_SCRIPT;
String pid = request.getParams().get("pid");
//以分钟为单位
Long timeout = CommonUtils.toLong(request.getParams().get("timeout"), 0L);
boolean timeoutFlag = timeout > 0;
logger.info("[opencron]:execute:{},pid:{}", command, pid);
File shellFile = CommandUtils.createShellFile(command, pid);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
this.response = Response.response(request);
final ExecuteWatchdog watchdog = new ExecuteWatchdog(Integer.MAX_VALUE);
final Timer timer = new Timer();
DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
Integer exitValue;
try {
CommandLine commandLine = CommandLine.parse("/bin/bash +x " + shellFile.getAbsolutePath());
final DefaultExecutor executor = new DefaultExecutor();
ExecuteStreamHandler stream = new PumpStreamHandler(outputStream, outputStream);
executor.setStreamHandler(stream);
response.setStartTime(new Date().getTime());
//成功执行完毕时退出值为0,shell标准的退出
executor.setExitValue(0);
if (timeoutFlag) {
//设置监控狗...
executor.setWatchdog(watchdog);
//监控超时的计时器
timer.schedule(new TimerTask() {
@Override
public void run() {
//超时,kill...
if (watchdog.isWatching()) {
/**
* 调用watchdog的destroyProcess无法真正kill进程...
* watchdog.destroyProcess();
*/
timer.cancel();
watchdog.stop();
//call kill...
request.setAction(Action.KILL);
try {
kill(request);
response.setExitCode(Opencron.StatusCode.TIME_OUT.getValue());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}, timeout * 60 * 1000);
//正常执行完毕则清除计时器
resultHandler = new DefaultExecuteResultHandler() {
@Override
public void onProcessComplete(int exitValue) {
super.onProcessComplete(exitValue);
timer.cancel();
}
@Override
public void onProcessFailed(ExecuteException e) {
super.onProcessFailed(e);
timer.cancel();
}
};
}
executor.execute(commandLine, resultHandler);
resultHandler.waitFor();
} catch (Exception e) {
if (e instanceof ExecuteException) {
exitValue = ((ExecuteException) e).getExitValue();
} else {
exitValue = Opencron.StatusCode.ERROR_EXEC.getValue();
}
if (Opencron.StatusCode.KILL.getValue().equals(exitValue)) {
if (timeoutFlag) {
timer.cancel();
watchdog.stop();
}
logger.info("[opencron]:job has be killed!at pid :{}", request.getParams().get("pid"));
} else {
logger.info("[opencron]:job execute error:{}", e.getCause().getMessage());
}
} finally {
exitValue = resultHandler.getExitValue();
if (CommonUtils.notEmpty(outputStream.toByteArray())) {
try {
outputStream.flush();
String text = outputStream.toString();
if (notEmpty(text)) {
try {
text = text.replaceAll(String.format(REPLACE_REX, shellFile.getAbsolutePath()), "");
response.setMessage(text.substring(0, text.lastIndexOf(EXITCODE_KEY)));
exitValue = Integer.parseInt(text.substring(text.lastIndexOf(EXITCODE_KEY) + EXITCODE_KEY.length() + 1).trim());
} catch (IndexOutOfBoundsException e) {
response.setMessage(text);
}
}
outputStream.close();
} catch (Exception e) {
logger.error("[opencron]:error:{}", e);
}
}
if (Opencron.StatusCode.TIME_OUT.getValue() == response.getExitCode()) {
response.setSuccess(false).end();
} else {
response.setExitCode(exitValue).setSuccess(response.getExitCode() == Opencron.StatusCode.SUCCESS_EXIT.getValue()).end();
}
if (shellFile != null) {
shellFile.delete();//删除文件
}
}
logger.info("[opencron]:execute result:{}", response.toString());
watchdog.stop();
}
@Override
public void password(Request request) throws Exception {
if (!this.password.equalsIgnoreCase(request.getPassword())) {
this.response = errorPasswordResponse(request);
return;
}
String newPassword = request.getParams().get("newPassword");
this.response = Response.response(request);
if (isEmpty(newPassword)) {
response.setSuccess(false).setExitCode(Opencron.StatusCode.SUCCESS_EXIT.getValue()).setMessage("密码不能为空").end();
return;
}
this.password = newPassword.toLowerCase().trim();
IOUtils.writeText(Globals.OPENCRON_PASSWORD_FILE, this.password, "UTF-8");
response.setSuccess(true).setExitCode(Opencron.StatusCode.SUCCESS_EXIT.getValue()).end();
}
@Override
public void kill(Request request) throws Exception {
if (!this.password.equalsIgnoreCase(request.getPassword())) {
this.response = errorPasswordResponse(request);
return;
}
String pid = request.getParams().get("pid");
logger.info("[opencron]:kill pid:{}", pid);
this.response = Response.response(request);
String text = CommandUtils.executeShell(Globals.OPENCRON_KILL_SHELL, pid, EXITCODE_SCRIPT);
String message = "";
Integer exitVal = 0;
if (notEmpty(text)) {
try {
message = text.substring(0, text.lastIndexOf(EXITCODE_KEY));
exitVal = Integer.parseInt(text.substring(text.lastIndexOf(EXITCODE_KEY) + EXITCODE_KEY.length() + 1).trim());
} catch (StringIndexOutOfBoundsException e) {
message = text;
}
}
response.setExitCode(Opencron.StatusCode.ERROR_EXIT.getValue().equals(exitVal) ? Opencron.StatusCode.ERROR_EXIT.getValue() : Opencron.StatusCode.SUCCESS_EXIT.getValue())
.setMessage(message)
.end();
logger.info("[opencron]:kill result:{}" + response);
}
@Override
public void proxy(Request request) throws Exception {
}
/*
@Override
public void proxy(Request request) throws Exception {
String proxyHost = request.getParams().get("proxyHost");
String proxyPort = request.getParams().get("proxyPort");
String proxyAction = request.getParams().get("proxyAction");
String proxyPassword = request.getParams().get("proxyPassword");
//其他参数....
String proxyParams = request.getParams().get("proxyParams");
Map<String, String> params = new HashMap<String, String>(0);
if (CommonUtils.notEmpty(proxyParams)) {
params = (Map<String, String>) JSON.parse(proxyParams);
}
Request proxyReq = Request.request(proxyHost, toInt(proxyPort), Action.findByName(proxyAction), proxyPassword).setParams(params);
logger.info("[opencron]proxy params:{}", proxyReq.toString());
TTransport transport;
*//**
* ping的超时设置为5毫秒,其他默认
*//*
if (proxyReq.getAction().equals(Action.PING)) {
proxyReq.getParams().put("proxy","true");
transport = new TSocket(proxyReq.getHostName(), proxyReq.getPort(), 1000 * 5);
} else {
transport = new TSocket(proxyReq.getHostName(), proxyReq.getPort());
}
TProtocol protocol = new TBinaryProtocol(transport);
Opencron.Client client = new Opencron.Client(protocol);
transport.open();
Response response = null;
for (Method method : client.getClass().getMethods()) {
if (method.getName().equalsIgnoreCase(proxyReq.getAction().name())) {
try {
response = (Response) method.invoke(client, proxyReq);
} catch (Exception e) {
//proxy 执行失败,返回失败信息
response = Response.response(request);
response.setExitCode(Opencron.StatusCode.ERROR_EXIT.getValue())
.setMessage("[opencron]:proxy error:"+e.getLocalizedMessage())
.setSuccess(false)
.end();
}
break;
}
}
transport.flush();
transport.close();
return response;
}
*/
@Override
public void guid(Request request) throws Exception {
if (!this.password.equalsIgnoreCase(request.getPassword())) {
this.response = errorPasswordResponse(request);
return;
}
String macId = null;
try {
//多个网卡地址,按照字典顺序把他们连接在一块,用-分割.
List<String> macIds = MacUtils.getMacAddressList();
if (CommonUtils.notEmpty(macIds)) {
TreeSet<String> macSet = new TreeSet<String>(macIds);
macId = StringUtils.joinString(macSet,"-");
}
} catch (IOException e) {
logger.error("[opencron]:getMac error:{}",e);
}
this.response = Response.response(request).end();
if (notEmpty(macId)) {
response.setMessage(macId).setSuccess(true).setExitCode(Opencron.StatusCode.SUCCESS_EXIT.getValue());
return;
}
response.setSuccess(false).setExitCode(Opencron.StatusCode.ERROR_EXIT.getValue());
}
/**
*重启前先检查密码,密码不正确返回Response,密码正确则直接执行重启
* @param request
* @return
* @throws Exception
* @throws InterruptedException
*/
@Override
public void restart(Request request) throws Exception {
}
private Response errorPasswordResponse(Request request) {
return Response.response(request)
.setSuccess(false)
.setExitCode(Opencron.StatusCode.ERROR_PASSWORD.getValue())
.setMessage(Opencron.StatusCode.ERROR_PASSWORD.getDescription())
.end();
}
private Map<String, String> serializableToMap(Object obj) {
if (isEmpty(obj)) {
return Collections.EMPTY_MAP;
}
Map<String, String> resultMap = new HashMap<String, String>(0);
// 拿到属性器数组
try {
PropertyDescriptor[] pds = Introspector.getBeanInfo(obj.getClass()).getPropertyDescriptors();
for (int index = 0; pds.length > 1 && index < pds.length; index++) {
if (Class.class == pds[index].getPropertyType() || pds[index].getReadMethod() == null) {
continue;
}
Object value = pds[index].getReadMethod().invoke(obj);
if (notEmpty(value)) {
if (isPrototype(pds[index].getPropertyType())//java里的原始类型(去除自己定义类型)
|| pds[index].getPropertyType().isPrimitive()//基本类型
|| ReflectUitls.isPrimitivePackageType(pds[index].getPropertyType())
|| pds[index].getPropertyType() == String.class) {
resultMap.put(pds[index].getName(), value.toString());
} else {
resultMap.put(pds[index].getName(), JSON.toJSONString(value));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return resultMap;
}
public boolean register() {
if (CommonUtils.notEmpty(Globals.OPENCRON_SERVER)) {
String url = Globals.OPENCRON_SERVER+"/agent/autoreg";
String mac = MacUtils.getMacAddress();
String agentPassword = IOUtils.readText(Globals.OPENCRON_PASSWORD_FILE, "UTF-8").trim().toLowerCase();
Map<String,Object> params = new HashMap<String, Object>(0);
params.put("machineId",mac);
params.put("password",agentPassword);
params.put("port",Globals.OPENCRON_PORT);
params.put("key",Globals.OPENCRON_REGKEY);
logger.info("[opencron]agent auto register staring:{}",Globals.OPENCRON_SERVER);
try {
String result = HttpClientUtils.httpPostRequest(url,params);
if (result==null) {
return false;
}
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.get("status").toString().equals("200")) {
return true;
}
logger.error("[opencron:agent auto regsiter error:{}]",jsonObject.get("message"));
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
return false;
}
class AgentHeartBeat {
private String serverIp;
private String clientIp;
private Socket socket;
private boolean running = false;
private long lastSendTime;
public AgentHeartBeat(String serverIp, int port, String clientIp) throws IOException {
this.serverIp = serverIp;
this.clientIp = clientIp;
socket = new Socket(serverIp, port);
socket.setKeepAlive(true);
}
public void start() throws IOException {
running = true;
lastSendTime = System.currentTimeMillis();
new Thread(new KeepAliveWatchDog()).start();
}
public void stop() throws IOException {
if (running) {
running = false;
this.socket.close();
agentHeartBeatMap.remove(serverIp);
logger.info("[opencron]:heartBeat: stoped " + this.serverIp);
}
}
public void sendMessage(Object obj) throws IOException {
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(obj);
outputStream.flush();
}
class KeepAliveWatchDog implements Runnable {
long checkDelay = 10;
long keepAliveDelay = 1000*5;
public void run() {
while (running) {
if (System.currentTimeMillis() - lastSendTime > keepAliveDelay) {
lastSendTime = System.currentTimeMillis();
try {
AgentHeartBeat.this.sendMessage(AgentHeartBeat.this.clientIp);
} catch (IOException e) {
logger.debug("[opencron]:heartbeat error:{}", e.getMessage());
try {
int tryIndex = 0;
boolean autoReg = false;
//失联后自动发起注册...
while (!autoReg||tryIndex<3) {
autoReg = register();
++tryIndex;
}
AgentHeartBeat.this.stop();
} catch (Exception e1) {
logger.debug("[opencron]:heartbeat error:{}", e1.getMessage());
}
}
} else {
try {
Thread.sleep(checkDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}