/* * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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; /** * Created by benjobs on 16/3/3. */ import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import org.opencron.common.job.Request; import org.opencron.common.serialization.OpencronDecoder; import org.opencron.common.serialization.RequestEncoder; import org.opencron.common.utils.IOUtils; import org.opencron.common.utils.LoggerFactory; import org.apache.commons.codec.digest.DigestUtils; import org.slf4j.Logger; import java.io.*; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.lang.reflect.InvocationTargetException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; import java.security.AccessControlException; import java.util.Random; import static org.opencron.common.utils.CommonUtils.isEmpty; import static org.opencron.common.utils.CommonUtils.notEmpty; public class Bootstrap implements Serializable { private static final long serialVersionUID = 20150614L; private static Logger logger = LoggerFactory.getLogger(Bootstrap.class); private ChannelFuture channelFuture; private EventLoopGroup bossGroup; private EventLoopGroup workerGroup; /** * agent port */ private int port; /** * agent password */ private String password; /** * charset... */ private final String CHARSET = "UTF-8"; /** * bootstrap instance.... */ private static Bootstrap daemon; private volatile boolean stopAwait = false; /** * Server socket that is used to wait for the shutdown command. */ private volatile ServerSocket awaitSocket = null; /** * The shutdown command string we are looking for. */ private String shutdown = "stop"; /** * A random number generator that is <strong>only</strong> used if * the shutdown command string is longer than 1024 characters. */ private Random random = null; public static void main(String[] args) { if (daemon == null) { daemon = new Bootstrap(); } try { if (isEmpty(args)) { logger.warn("Bootstrap: error,usage start|stop"); } else { String command = args[0]; if ("start".equals(command)) { daemon.init(); daemon.start(); /** * await for shundown */ daemon.await(); daemon.stopServer(); } else if ("stop".equals(command)) { daemon.shutdown(); } else { logger.warn("Bootstrap: command \"" + command + "\" does not exist."); } } } catch (Throwable t) { if (t instanceof InvocationTargetException && t.getCause() != null) { t = t.getCause(); } handleThrowable(t); t.printStackTrace(); System.exit(1); } } /** * init start........ * * @throws Exception */ private void init() throws Exception { port = Integer.valueOf(Integer.parseInt(Globals.OPENCRON_PORT)); String inputPassword = Globals.OPENCRON_PASSWORD; if (notEmpty(inputPassword)) { Globals.OPENCRON_PASSWORD_FILE.deleteOnExit(); this.password = DigestUtils.md5Hex(inputPassword).toLowerCase(); IOUtils.writeText(Globals.OPENCRON_PASSWORD_FILE, this.password, CHARSET); } else { boolean writeDefault = false; //.password file already exists if (Globals.OPENCRON_PASSWORD_FILE.exists()) { //read password from .password file String filePassowrd = IOUtils.readText(Globals.OPENCRON_PASSWORD_FILE, CHARSET).trim().toLowerCase(); if (notEmpty(filePassowrd)) { this.password = filePassowrd; } else { writeDefault = true; } } else { writeDefault = true; } if (writeDefault) { this.password = DigestUtils.md5Hex(Globals.OPENCRON_DEFPASSWORD).toLowerCase(); Globals.OPENCRON_PASSWORD_FILE.deleteOnExit(); IOUtils.writeText(Globals.OPENCRON_PASSWORD_FILE, this.password, CHARSET); } } } public void start() throws Exception { this.bossGroup = new NioEventLoopGroup(); this.workerGroup = new NioEventLoopGroup(); try { final ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws IOException { ch.pipeline().addLast( new OpencronDecoder<Request>(Request.class,1<<20, 4, 4)); ch.pipeline().addLast(new RequestEncoder()); ch.pipeline().addLast(new AgentHandler(password)); } }); new Thread(new Runnable() { @Override public void run() { try { channelFuture = bootstrap.bind(port).sync(); channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }).start(); logger.info("opencron-agent started at:port:{},pid:{}" ,this.port,getPid()); /** * write pid to pidfile... */ IOUtils.writeText(Globals.OPENCRON_PID_FILE, getPid(), CHARSET); logger.info("[opencron]agent started @ port:{},pid:{}", port, getPid()); } catch (Exception e) { logger.info("opencron-agent start error:{}",e.getMessage()); stopServer(); } } private void await() throws Exception { // Negative values - don't wait on port - opencron is embedded or we just don't like ports if (port == -2) { return; } if (port == -1) { try { while (!stopAwait) { try { Thread.sleep(10000); } catch (InterruptedException ex) { // continue and check the flag } } } finally { } return; } Integer shutdownPort = Integer.valueOf(System.getProperty("opencron.shutdown")); // Set up a server socket to wait on try { awaitSocket = new ServerSocket(shutdownPort); } catch (IOException e) { logger.error("[opencron] agent .await: create[{}] ", shutdownPort, e); return; } try { // Loop waiting for a connection and a valid command while (!stopAwait) { ServerSocket serverSocket = awaitSocket; if (serverSocket == null) { break; } // Wait for the next connection Socket socket = null; StringBuilder command = new StringBuilder(); try { InputStream stream; long acceptStartTime = System.currentTimeMillis(); try { socket = serverSocket.accept(); socket.setSoTimeout(10 * 1000); // Ten seconds stream = socket.getInputStream(); } catch (SocketTimeoutException ste) { // This should never happen but bug 56684 suggests that // it does. logger.warn("[opencron] agentServer accept.timeout", Long.valueOf(System.currentTimeMillis() - acceptStartTime), ste); continue; } catch (AccessControlException ace) { logger.warn("[opencron] agentServer .accept security exception: {}", ace.getMessage(), ace); continue; } catch (IOException e) { if (stopAwait) { break; } logger.error("[opencron] agent .await: accept: ", e); break; } // Read a set of characters from the socket int expected = 1024; // Cut off to avoid DoS attack while (expected < shutdown.length()) { if (random == null) { random = new Random(); } expected += (random.nextInt() % 1024); } while (expected > 0) { int ch = -1; try { ch = stream.read(); } catch (IOException e) { logger.warn("[opencron] agent .await: read: ", e); ch = -1; } if (ch < 32) // Control character or EOF terminates loop break; command.append((char) ch); expected--; } } finally { try { if (socket != null) { socket.close(); } } catch (IOException e) { } } boolean match = command.toString().equals(shutdown); if (match) { break; } else { logger.warn("[opencron] agent .await: Invalid command '" + command.toString() + "' received"); } } } finally { ServerSocket serverSocket = awaitSocket; awaitSocket = null; // Close the server socket and return if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { // Ignore } } } } /** * @throws Exception */ private void shutdown() throws Exception { /** * connect to startup socket and send stop command。。。。。。 */ Socket socket = new Socket("localhost", Integer.valueOf(System.getProperty("opencron.shutdown"))); OutputStream os = socket.getOutputStream(); PrintWriter pw = new PrintWriter(os); InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); pw.write(shutdown); pw.flush(); socket.shutdownOutput(); String reply = null; while (!((reply = br.readLine()) == null)) { logger.info("[opencron]shutdown:{}" + reply); } br.close(); is.close(); pw.close(); os.close(); socket.close(); } private void stopServer() { if (bossGroup != null && !bossGroup.isShutdown()) { bossGroup.shutdownGracefully(); } if (workerGroup != null && !workerGroup.isShutdown()) { workerGroup.shutdownGracefully(); } if (channelFuture != null) { channelFuture.channel().close(); /** * delete pid file... */ Globals.OPENCRON_PID_FILE.deleteOnExit(); System.exit(0); } } private static void handleThrowable(Throwable t) { if (t instanceof ThreadDeath) { throw (ThreadDeath) t; } if (t instanceof VirtualMachineError) { throw (VirtualMachineError) t; } } private static Integer getPid() { RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); String name = runtime.getName(); try { return Integer.parseInt(name.substring(0, name.indexOf('@'))); } catch (Exception e) { } return -1; } }