Java tutorial
/* * Copyright 2016 Midokura SARL * * Licensed 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.midonet.midolman; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; import scala.concurrent.Promise; import scala.concurrent.Promise$; import com.codahale.metrics.MetricRegistry; import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.Service; import com.google.inject.Guice; import com.google.inject.Injector; import com.sun.jna.LastErrorException; import com.typesafe.config.Config; import com.typesafe.config.ConfigRenderOptions; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.Options; import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.midonet.cluster.backend.zookeeper.ZookeeperConnectionWatcher; import org.midonet.cluster.services.MidonetBackend; import org.midonet.cluster.storage.MidonetBackendModule; import org.midonet.conf.HostIdGenerator; import org.midonet.conf.LoggerLevelWatcher; import org.midonet.conf.MidoNodeConfigurator; import org.midonet.midolman.cluster.LegacyClusterModule; import org.midonet.midolman.cluster.serialization.SerializationModule; import org.midonet.midolman.cluster.zookeeper.ZookeeperConnectionModule; import org.midonet.midolman.config.MidolmanConfig; import org.midonet.midolman.logging.FlowTracingAppender; import org.midonet.midolman.services.MidolmanService; import org.midonet.midolman.simulation.PacketContext$; import org.midonet.util.cLibrary; import org.midonet.util.process.MonitoredDaemonProcess; public class Midolman { static final Logger log = LoggerFactory.getLogger(Midolman.class); public static final int MIDOLMAN_ERROR_CODE_MISSING_CONFIG_FILE = 1; public static final int MIDOLMAN_ERROR_CODE_LOST_HOST_OWNERSHIP = 2; public static final int MIDOLMAN_ERROR_CODE_UNHANDLED_EXCEPTION = 6001; public static final int MIDOLMAN_ERROR_CODE_PACKET_WORKER_DIED = 6002; public static final int MIDOLMAN_ERROR_CODE_MINION_PROCESS_DIED = 6003; public static final int MIDOLMAN_ERROR_CODE_VPP_PROCESS_DIED = 6004; private static final long MIDOLMAN_EXIT_TIMEOUT_MILLIS = 30000; private static final int MINION_PROCESS_MAXIMUM_STARTS = 3; private static final int MINION_PROCESS_FAILING_PERIOD = 180000; private static final int MINION_PROCESS_WAIT_TIMEOUT = 10; private Injector injector; private WatchedProcess watchedProcess = new WatchedProcess(); private volatile MonitoredDaemonProcess minionProcess = null; private Midolman() { } private static void lockMemory() { try { cLibrary.lib.mlockall(cLibrary.MCL_FUTURE | cLibrary.MCL_CURRENT); log.info("Successfully locked the process address space to RAM."); } catch (LastErrorException e) { log.warn("Failed to lock process into RAM: {}", cLibrary.lib.strerror(e.getErrorCode())); log.warn("This implies that parts of the agents may be swapped out " + "causing long GC pauses that have various adverse effects. " + "It's strongly recommended that this process runs either as " + "root or with the CAP_IPC_LOCK capability and a high enough " + "memlock limit (RLIMIT_MEMLOCK)."); log.warn("You may disable these warnings by setting the " + "'agent.midolman.lock_memory' configuration key to 'false' " + "in mn-conf(1)."); } } /** * Exits by calling System.exits() with status code, * MIDOLMAN_ERROR_CODE_MISSING_CONFIG_FILE. * * @param configFilePath A path for the Midolman config file. */ private static void exitsMissingConfigFile(String configFilePath) { log.error("MidoNet Agent config file \'" + configFilePath + "\' " + "missing: exiting"); System.exit(MIDOLMAN_ERROR_CODE_MISSING_CONFIG_FILE); } public static void dumpStacks() { Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces(); for (Thread thread : traces.keySet()) { System.err.print("\"" + thread.getName() + "\" "); if (thread.isDaemon()) System.err.print("daemon "); System.err.print(String.format("prio=%x tid=%x %s [%x]\n", thread.getPriority(), thread.getId(), thread.getState(), System.identityHashCode(thread))); StackTraceElement[] trace = traces.get(thread); for (StackTraceElement e : trace) { System.err.println(" at " + e.toString()); } } } private void initialize(String[] args) throws IOException { // log git commit info Properties properties = new Properties(); properties.load(getClass().getClassLoader().getResourceAsStream("git.properties")); log.info("MidoNet Agent main start... ---------"); log.info("branch: {}", properties.get("git.branch")); log.info("commit.time: {}", properties.get("git.commit.time")); log.info("commit.id: {}", properties.get("git.commit.id")); log.info("commit.user: {}", properties.get("git.commit.user.name")); log.info("build.time: {}", properties.get("git.build.time")); log.info("build.user: {}", properties.get("git.build.user.name")); log.info("-------------------------------------"); // log cmdline and JVM info log.info("Command-line arguments: {}", Arrays.toString(args)); RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); List<String> arguments = runtimeMxBean.getInputArguments(); log.info("JVM options: "); for (String a : arguments) { log.info(" {}", a); } log.info("-------------------------------------"); log.info("Adding shutdown hook"); Runtime.getRuntime().addShutdownHook(new Thread("shutdown") { @Override public void run() { doServicesCleanup(); } }); } private void setUncaughtExceptionHandler() { Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { log.error("Unhandled exception: ", e); dumpStacks(); System.exit(MIDOLMAN_ERROR_CODE_UNHANDLED_EXCEPTION); } }); } private void run(String[] args) throws Exception { Promise<Boolean> initializationPromise = Promise$.MODULE$.apply(); setUncaughtExceptionHandler(); int initTimeout = Integer.valueOf(System.getProperty("midolman.init_timeout", "120")); try { watchedProcess.start(initializationPromise, initTimeout); initialize(args); } catch (Throwable t) { log.error("Exception while initializing the MidoNet Agent", t); watchedProcess.close(); throw t; } minionProcess = new MonitoredDaemonProcess("/usr/share/midolman/minions-start", log, "org.midonet.services", MINION_PROCESS_MAXIMUM_STARTS, MINION_PROCESS_FAILING_PERIOD, (Exception e) -> { log.debug(e.getMessage()); Midolman.exitAsync(MIDOLMAN_ERROR_CODE_MINION_PROCESS_DIED); }); minionProcess.startAsync(); Options options = new Options(); options.addOption("c", "configFile", true, "config file path"); CommandLineParser parser = new GnuParser(); CommandLine cl = parser.parse(options, args); String configFilePath = cl.getOptionValue('c', "./conf/midolman.conf"); if (!java.nio.file.Files.isReadable(Paths.get(configFilePath))) { // The config file is missing. Exits Midolman. Midolman.exitsMissingConfigFile(configFilePath); } MidoNodeConfigurator configurator = MidoNodeConfigurator.apply(configFilePath); if (configurator.deployBundledConfig()) log.info("Deployed new configuration schema into NSDB"); configurator.observableRuntimeConfig(HostIdGenerator.getHostId()) .subscribe(new LoggerLevelWatcher(scala.Option.apply("agent"))); MidolmanConfig config = createConfig(configurator); MetricRegistry metricRegistry = new MetricRegistry(); Reflections reflections = new Reflections("org.midonet"); injector = Guice.createInjector( new MidonetBackendModule(config.zookeeper(), scala.Option.apply(reflections), metricRegistry), new ZookeeperConnectionModule(ZookeeperConnectionWatcher.class), new SerializationModule(), new LegacyClusterModule()); injector = injector.createChildInjector(new MidolmanModule(injector, config, metricRegistry, reflections)); ConfigRenderOptions renderOpts = ConfigRenderOptions.defaults().setJson(true).setComments(false) .setOriginComments(false).setFormatted(true); Config conf = injector.getInstance(MidolmanConfig.class).conf(); Boolean showConfigPasswords = System.getProperties().containsKey("midonet.show_config_passwords"); log.info("Loaded configuration: {}", configurator.dropSchema(conf, showConfigPasswords).root().render(renderOpts)); // start the services injector.getInstance(MidonetBackend.class).startAsync().awaitRunning(); injector.getInstance(MidolmanService.class).startAsync().awaitRunning(); enableFlowTracingAppender(injector.getInstance(FlowTracingAppender.class)); log.info("Running manual GC to tenure pre-allocated objects"); System.gc(); if (config.lockMemory()) lockMemory(); if (!initializationPromise.trySuccess(true)) { log.error( "MidoNet Agent failed to initialize in {} seconds and " + "is shutting down: try increasing the initialization " + "timeout using the \"midonet.init_timeout\" system " + "property or check the upstart logs to determine the " + "cause of the failure.", initTimeout); System.exit(-1); } log.info("MidoNet Agent started"); injector.getInstance(MidolmanService.class).awaitTerminated(); } private void doServicesCleanup() { log.info("MidoNet Agent shutting down..."); try { minionProcess.stopAsync().awaitTerminated(MINION_PROCESS_WAIT_TIMEOUT, TimeUnit.SECONDS); } catch (Exception e) { log.error("Minion process failed while stopping.", e); } if (injector == null) return; MidolmanService instance = injector.getInstance(MidolmanService.class); if (instance.state() == Service.State.TERMINATED) return; try { instance.stopAsync().awaitTerminated(); } catch (Exception e) { log.error("Agent service failed while stopping", e); } finally { watchedProcess.close(); log.info("MidoNet Agent exiting. Bye!"); } } @VisibleForTesting public static void enableFlowTracingAppender(FlowTracingAppender appender) { Object logger = PacketContext$.MODULE$.traceLog().underlying(); Object loggerFactory = LoggerFactory.getILoggerFactory(); if (logger instanceof ch.qos.logback.classic.Logger && loggerFactory instanceof ch.qos.logback.classic.LoggerContext) { ch.qos.logback.classic.Logger logbackLogger = (ch.qos.logback.classic.Logger) logger; ch.qos.logback.classic.LoggerContext loggerCtx = (ch.qos.logback.classic.LoggerContext) loggerFactory; appender.setContext(loggerCtx); appender.start(); logbackLogger.addAppender(appender); logbackLogger.setAdditive(true); } else { log.warn("Unable to get logback logger, FlowTracingAppender" + " not enabled. Logger is of type {}," + " LoggerFactory is of type {}", logger.getClass(), loggerFactory.getClass()); } } private static MidolmanConfig createConfig(MidoNodeConfigurator configurator) { try { return new MidolmanConfig(configurator.runtimeConfig(HostIdGenerator.getHostId()), configurator.mergedSchemas()); } catch (HostIdGenerator.PropertiesFileNotWritableException e) { throw new RuntimeException(e); } } /** * Expose Midolman instance and Guice injector * Using the following methods makes it easier for host management * tools to access Midolman's internal data structure directly and * diagnose issues at runtime. */ private static class MidolmanHolder { private static final Midolman instance = new Midolman(); } public static Midolman getInstance() { return MidolmanHolder.instance; } public static Injector getInjector() { return getInstance().injector; } public static void main(String[] args) { try { Midolman midolman = getInstance(); midolman.run(args); } catch (Throwable e) { log.error("Unhandled exception in main method", e); dumpStacks(); System.exit(-1); } } public static void exitAsync(final int status) { new Thread("exit") { @Override public void run() { Runtime.getRuntime().exit(status); } }.start(); new Timer("exit-timer", true).schedule(new TimerTask() { @Override public void run() { Runtime.getRuntime().halt(status); } }, MIDOLMAN_EXIT_TIMEOUT_MILLIS); } }