package cz.cuni.mff.d3s.been.node; import static cz.cuni.mff.d3s.been.cluster.Names.*; import static cz.cuni.mff.d3s.been.core.StatusCode.*; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.net.UnknownHostException; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.hazelcast.core.HazelcastInstance; import cz.cuni.mff.d3s.been.BeenServiceConfiguration; import cz.cuni.mff.d3s.been.cluster.*; import cz.cuni.mff.d3s.been.cluster.context.ClusterContext; import cz.cuni.mff.d3s.been.hostruntime.HostRuntime; import cz.cuni.mff.d3s.been.hostruntime.HostRuntimes; import cz.cuni.mff.d3s.been.logging.ServiceLogPersister; import cz.cuni.mff.d3s.been.manager.Managers; import cz.cuni.mff.d3s.been.objectrepository.ObjectRepository; import cz.cuni.mff.d3s.been.storage.Storage; import cz.cuni.mff.d3s.been.storage.StorageBuilderFactory; import cz.cuni.mff.d3s.been.swrepository.SoftwareRepositories; import cz.cuni.mff.d3s.been.swrepository.SoftwareRepository; /** * Entry point for BEEN nodes. * * @author Martin Sixta */ public class Runner implements Reapable { // ------------------------------------------------------------------------ // LOGGING // ------------------------------------------------------------------------ private static final Logger log = LoggerFactory.getLogger(Runner.class); // ------------------------------------------------------------------------ // COMMAND LINE ARGUMENTS // ------------------------------------------------------------------------ @Option(name = "-t", aliases = { "--node-type" }, usage = "Type of the node. DEFAULT is DATA") private NodeType nodeType = NodeType.DATA; @Option(name = "-cf", aliases = { "--config-file" }, usage = "Path or URL to BEEN config file.") private String configFile; @Option(name = "-dc", aliases = { "--dump-config" }, usage = "Whether to print runtime configuration and exit") private boolean dumpConfig; @Option(name = "-r", aliases = { "--host-runtime" }, usage = "Whether to run Host runtime on this node") private boolean runHostRuntime = false; @Option(name = "-sw", aliases = { "--software-repository" }, usage = "Whether to run Software Repository on this node.") private boolean runSWRepository = false; @Option(name = "-rr", aliases = { "--repository" }, usage = "Whether to run Repository on this node. Requires a running matching persistence layer.") private boolean runRepository = false; @Option(name = "-h", aliases = { "--help" }, usage = "Prints help") private boolean printHelp = false; /** * An ID of this BEEN running JVM */ private UUID runtimeId = null; /** * ID of the Host Runtime service running on this node. If no HR is running, * will remain <code>null</code> */ private String hostRuntimeId = null; /** * Synthetic ID of this BEEN node. Will always be non-<code>null</code> once * this BEEN node is initialized */ private String beenId = null; private ClusterContext clusterContext; /** * Main method * * @param args Command-line arguments */ public static void main(String[] args) { new Runner().doMain(args); } // ------------------------------------------------------------------------ // MAIN BEEN FUNCTION // ------------------------------------------------------------------------ private void doMain(final String[] args) { parseCmdLineArguments(args); Properties properties = loadProperties(); if (dumpConfig) { printBeenConfiguration(properties); EX_OK.sysExit(); } if (printHelp) { printUsage(); EX_USAGE.sysExit(); } initIds(); HazelcastInstance instance = null; try { // Join the cluster log.info("The node is connecting to the cluster"); instance = getInstance(nodeType, properties); log.info("The node is now connected to the cluster"); } catch (ServiceException e) { log.error("Failed to initialize cluster instance", e); EX_COMPONENT_FAILED.sysExit(); } Reaper clusterReaper = new ClusterReaper(instance); this.clusterContext = Instance.createContext(); if (nodeType == NodeType.DATA) { // must happen as soon as possible, see documentation for BEEN MapStore implementation initializeMaps( TASKS_MAP_NAME, TASK_CONTEXTS_MAP_NAME, BENCHMARKS_MAP_NAME, NAMED_TASK_CONTEXT_DESCRIPTORS_MAP_NAME, NAMED_TASK_DESCRIPTORS_MAP_NAME); } registerServiceCleaner(); try { // standalone services if (runSWRepository) { clusterReaper.pushTarget(startSWRepository()); } // Run Task Manager on DATA nodes if (nodeType == NodeType.DATA) { clusterReaper.pushTarget(startTaskManager()); } if (runHostRuntime) { clusterReaper.pushTarget(startHostRuntime(clusterContext, properties)); } clusterReaper.pushTarget(startLogPersister()); // Services that require a persistence layer if (runRepository) { final Storage storage = StorageBuilderFactory.createBuilder(properties).build(); clusterReaper.pushTarget(startRepository(storage)); } } catch (ServiceException se) { log.error("Service bootstrap failed.", se); clusterReaper.start(); try { clusterReaper.join(); } catch (InterruptedException e) { log.error("Failed to perform cleanup due to user interruption. Exiting dirty."); } EX_COMPONENT_FAILED.sysExit(); } clusterReaper.pushTarget(this); Runtime.getRuntime().addShutdownHook(clusterReaper); } /** * Will make Hazelcast to "touch" the maps. * * It causes Hazelcast to load data through its MapStore, if used, from a data * store. * * @param mapNames * names of Maps to force to initialize */ private void initializeMaps(String... mapNames) { for (String mapName : mapNames) { clusterContext.getMap(mapName); } } private void registerServiceCleaner() { // when member is removed from cluster, remove its services immediately clusterContext.getCluster().addMembershipListener(new ServiceCleaner(clusterContext)); } private void printUsage() { CmdLineParser parser = new CmdLineParser(this); parser.printUsage(System.out); } // ------------------------------------------------------------------------ // AUXILIARY FUNCTIONS // ------------------------------------------------------------------------ /** * Parses supplied command line arguments for this object. * <p/> * In case of error, an error message and usage is print to System.err, then * program quits. * * @param args * Command line arguments */ private void parseCmdLineArguments(final String[] args) { // Handle command-line arguments CmdLineParser parser = new CmdLineParser(this); try { // parse the arguments. parser.parseArgument(args); } catch (CmdLineException e) { System.err.println(e.getMessage()); System.err.println("\nUsage:"); parser.printUsage(System.err); System.exit(EX_USAGE.getCode()); } } private void initIds() { this.runtimeId = UUID.randomUUID(); try { this.beenId = InetAddress.getLocalHost().getHostName() + "--" + runtimeId.toString(); } catch (UnknownHostException e) { log.error("Cannot determine local hostname, will terminate.", e); EX_NETWORK_ERROR.sysExit(); } } private IClusterService startTaskManager() throws ServiceException { IClusterService taskManager = Managers.getManager(clusterContext); taskManager.start(); return taskManager; } private IClusterService startHostRuntime(ClusterContext context, Properties properties) throws ServiceException { HostRuntime hostRuntime = HostRuntimes.createRuntime(context, properties); hostRuntime.start(); this.hostRuntimeId = hostRuntime.getId(); return hostRuntime; } private IClusterService startSWRepository() throws ServiceException { SoftwareRepository softwareRepository = SoftwareRepositories.createSWRepository(clusterContext, beenId); softwareRepository.init(); softwareRepository.start(); return softwareRepository; } private IClusterService startLogPersister() throws ServiceException { log.info("Starting log persister"); ServiceLogPersister logPersister = ServiceLogPersister.getHandlerInstance(clusterContext, beenId, hostRuntimeId); logPersister.start(); log.info("Log persister started"); return logPersister; } private IClusterService startRepository(Storage storage) throws ServiceException { ObjectRepository objectRepository = ObjectRepository.create(clusterContext, storage, beenId); objectRepository.start(); return objectRepository; } private HazelcastInstance getInstance(final NodeType nodeType, Properties properties) throws ServiceException { Instance.init(nodeType, properties); return Instance.getInstance(); } private Properties loadProperties() { if (configFile == null || configFile.isEmpty()) { log.info("No config file or url specified. Will start with default configuration."); return new Properties(); } PropertyLoader loader = null; // try as a file try { Path path = Paths.get(configFile); if (Files.exists(path)) { loader = PropertyLoader.fromPath(path); } } catch (InvalidPathException e) { // quell } // try as an URL if (loader == null) { try { URL url = new URL(configFile); loader = PropertyLoader.fromUrl(url); } catch (MalformedURLException e) { // quell } } if (loader == null) { log.error("{} is not a file nor an URL. Aborting.", configFile); EX_USAGE.sysExit(); throw new AssertionError(); // make the compiler happy } try { Properties properties = loader.load(); log.info("Configuration loaded from {}", configFile); return properties; } catch (IOException e) { String msg = String.format("Cannot load properties from %s. Aborting.", configFile); log.error(msg, e); EX_USAGE.sysExit(); } throw new AssertionError(); // will not get here, make the compiler happy } @Override public Reaper createReaper() { return new Reaper() { @Override protected void reap() throws InterruptedException { clusterContext.stop(); } }; } /** * Prints runtime configuration of BEEN. * * @param properties * user specified properties */ private void printBeenConfiguration(final Properties properties) { final String DEFAULT_VALUE_PREFIX = "DEFAULT_"; // by convention final ServiceLoader<BeenServiceConfiguration> configs = ServiceLoader.load(BeenServiceConfiguration.class); for (BeenServiceConfiguration config : configs) { Class<?> klazz = config.getClass(); System.out.printf("#%n# %s%n#%n%n", klazz.getSimpleName()); Map<String, Object> defaultValues = new HashMap<>(); Map<String, String> propertyNames = new HashMap<>(); for (Field field : klazz.getDeclaredFields()) { final String name = field.getName(); try { if (name.startsWith(DEFAULT_VALUE_PREFIX)) { String propertyName = name.substring(DEFAULT_VALUE_PREFIX.length()); defaultValues.put(propertyName, field.get(config)); } else { propertyNames.put(name, field.get(config).toString()); } } catch (IllegalAccessException e) { String msg = String.format("Cannot get value for '%s'", name); log.error(msg, e); } } for (Map.Entry<String, String> entry : propertyNames.entrySet()) { String name = entry.getValue(); Object value = defaultValues.get(entry.getKey()); if (properties.containsKey(name)) { System.out.printf("%s=%s%n", entry.getValue(), value); } else { System.out.printf("# %s=%s%n", entry.getValue(), value); } } System.out.printf("%n%n%n"); } } }