org.apache.hyracks.control.nc.service.NCService.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hyracks.control.nc.service.NCService.java

Source

/*
 * 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.apache.hyracks.control.nc.service;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.StringReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang3.SystemUtils;
import org.apache.hyracks.control.common.controllers.IniUtils;
import org.apache.hyracks.control.common.controllers.ServiceConstants;
import org.apache.hyracks.control.common.controllers.ServiceConstants.ServiceCommand;
import org.ini4j.Ini;
import org.kohsuke.args4j.CmdLineParser;

/**
 * Stand-alone process which listens for configuration information from the
 * CC and starts an NC. Intended to be a constantly-running service.
 */
public class NCService {

    private static final Logger LOGGER = Logger.getLogger(NCService.class.getName());

    /**
     * The .ini read from the CC (*not* the ncservice.ini file)
     */
    private static Ini ini = new Ini();

    /**
     * ID of *this* NC
     */
    private static String ncId = "";

    /**
     * The Ini section representing *this* NC
     */
    private static String nodeSection = null;

    /**
     * The NCServiceConfig
     */
    private static NCServiceConfig config;

    /**
     * The child Process, if one is active
     */
    private static Process proc = null;

    private static List<String> buildCommand() throws IOException {
        List<String> cList = new ArrayList<>();

        // Find the command to run. For now, we allow overriding the name, but
        // still assume it's located in the bin/ directory of the deployment.
        // Even this is likely more configurability than we need.
        String command = IniUtils.getString(ini, nodeSection, "command", "hyracksnc");
        // app.home is specified by the Maven appassembler plugin. If it isn't set,
        // fall back to user's home dir. Again this is likely more flexibility
        // than we need.
        String apphome = System.getProperty("app.home", System.getProperty("user.home"));
        String path = apphome + File.separator + "bin" + File.separator;
        if (SystemUtils.IS_OS_WINDOWS) {
            cList.add(path + command + ".bat");
        } else {
            cList.add(path + command);
        }

        cList.add("-config-file");
        // Store the Ini file from the CC locally so NCConfig can read it.
        File tempIni = File.createTempFile("ncconf", ".conf");
        tempIni.deleteOnExit();

        ini.store(tempIni);
        cList.add(tempIni.getCanonicalPath());

        // pass in the PID of the NCService
        cList.add("-ncservice-pid");
        cList.add(System.getProperty("app.pid", "0"));
        return cList;
    }

    private static void configEnvironment(Map<String, String> env) {
        String jvmargs = IniUtils.getString(ini, nodeSection, "jvm.args", null);
        if (jvmargs != null) {
            LOGGER.info("Using JAVA_OPTS from conf file (jvm.args)");
        } else {
            jvmargs = env.get("JAVA_OPTS");
            if (jvmargs != null) {
                LOGGER.info("Using JAVA_OPTS from environment");
            } else {
                LOGGER.info("Using default JAVA_OPTS");
                jvmargs = "-Xmx1536m";
            }
        }
        env.put("JAVA_OPTS", jvmargs);
        LOGGER.info("Setting JAVA_OPTS to " + jvmargs);
    }

    /**
     * Attempts to launch the "real" NCDriver, based on the configuration
     * information gathered so far.
     * @return true if the process was successfully launched and has now
     * exited with a 0 (normal) exit code. false if some configuration error
     * prevented the process from being launched or the process returned
     * a non-0 (abnormal) exit code.
     */
    private static boolean launchNCProcess() {
        try {
            ProcessBuilder pb = new ProcessBuilder(buildCommand());
            configEnvironment(pb.environment());
            // QQQ inheriting probably isn't right
            pb.inheritIO();

            if (LOGGER.isLoggable(Level.INFO)) {
                LOGGER.info("Launching NCDriver process");
            }

            // Logfile
            if (!"-".equals(config.logdir)) {
                pb.redirectErrorStream(true);
                File log = new File(config.logdir);
                if (!log.mkdirs()) {
                    if (!log.isDirectory()) {
                        throw new IOException(config.logdir + ": cannot create");
                    }
                    // If the directory IS there, all is well
                }
                File logfile = new File(config.logdir, "nc-" + ncId + ".log");
                // Don't care if this succeeds or fails:
                logfile.delete();
                pb.redirectOutput(ProcessBuilder.Redirect.appendTo(logfile));
                if (LOGGER.isLoggable(Level.INFO)) {
                    LOGGER.info("Logging to " + logfile.getCanonicalPath());
                }
            }
            proc = pb.start();

            boolean waiting = true;
            int retval = 0;
            while (waiting) {
                try {
                    retval = proc.waitFor();
                    waiting = false;
                } catch (InterruptedException ignored) {
                }
            }
            LOGGER.info("NCDriver exited with return value " + retval);
            if (retval == 99) {
                LOGGER.info("Terminating NCService based on return value from NCDriver");
                exit(0);
            }
            return retval == 0;
        } catch (Exception e) {
            if (LOGGER.isLoggable(Level.SEVERE)) {
                LOGGER.log(Level.SEVERE, "Configuration from CC broken", e);
            }
            return false;
        }
    }

    private static boolean acceptConnection(InputStream is) {
        // Simple on-wire protocol:
        // magic cookie (string)
        // either:
        //   START_NC, ini file
        // or:
        //   TERMINATE
        // If we see anything else or have any error, crap out and await a different connection.
        try {
            ObjectInputStream ois = new ObjectInputStream(is);
            String magic = ois.readUTF();
            if (!ServiceConstants.NC_SERVICE_MAGIC_COOKIE.equals(magic)) {
                LOGGER.severe("Connection used incorrect magic cookie");
                return false;
            }
            switch (ServiceCommand.valueOf(ois.readUTF())) {
            case START_NC:
                String iniString = ois.readUTF();
                ini = new Ini(new StringReader(iniString));
                ncId = IniUtils.getString(ini, "localnc", "id", "");
                nodeSection = "nc/" + ncId;
                return launchNCProcess();
            case TERMINATE:
                LOGGER.info("Terminating NCService based on command from CC");
                exit(0);
                break;
            }
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "Error decoding connection from server", e);
        }
        return false;
    }

    @SuppressWarnings("squid:S1147") // call to System.exit()
    private static void exit(int exitCode) {
        LOGGER.info("JVM Exiting.. Bye!");
        System.exit(exitCode);
    }

    public static void main(String[] args) throws Exception {
        // Register a shutdown hook which will kill the NC if the NC Service is killed.
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                if (proc != null) {
                    proc.destroy();
                }
            }
        });
        config = new NCServiceConfig();
        CmdLineParser cp = new CmdLineParser(config);
        try {
            cp.parseArgument(args);
        } catch (Exception e) {
            e.printStackTrace();
            cp.printUsage(System.err);
            System.exit(1);
        }
        config.loadConfigAndApplyDefaults();

        // For now we implement a trivial listener which just
        // accepts an IP/port combination from the CC. This could
        // be made more advanced in several ways depending on whether
        // we want to expand the functionality of this service.
        // For now this gets the job done, without radically changing
        // the NC itself so that Managix can continue to function.
        InetAddress addr = config.address == null ? null : InetAddress.getByName(config.address);
        int port = config.port;

        // Loop forever - the NCService will always return to "waiting for CC" state
        // when the child NC terminates for any reason.
        while (true) {
            try (ServerSocket listener = new ServerSocket(port, 5, addr)) {
                boolean launched = false;
                while (!launched) {
                    LOGGER.info("Waiting for connection from CC on " + addr + ":" + port);
                    try (Socket socket = listener.accept()) {
                        // QQQ Because acceptConnection() doesn't return if the
                        // service is started appropriately, the socket remains
                        // open but non-responsive.
                        launched = acceptConnection(socket.getInputStream());
                    }
                }
            }
        }
    }
}