/** * 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(); } } } } } } }