/* * Copyright 2013 Netflix, Inc. * * 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 com.netflix.suro; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.common.io.Closeables; import com.google.inject.Injector; import com.google.inject.Module; import com.netflix.governator.configuration.PropertiesConfigurationProvider; import com.netflix.governator.guice.BootstrapBinder; import com.netflix.governator.guice.BootstrapModule; import com.netflix.governator.guice.LifecycleInjector; import com.netflix.governator.lifecycle.LifecycleManager; import com.netflix.suro.input.DynamicPropertyInputConfigurator; import com.netflix.suro.input.SuroInputPlugin; import com.netflix.suro.routing.DynamicPropertyRoutingMapConfigurator; import com.netflix.suro.routing.RoutingPlugin; import com.netflix.suro.server.StatusServer; import com.netflix.suro.sink.DynamicPropertySinkConfigurator; import com.netflix.suro.sink.ServerSinkPlugin; import org.apache.commons.cli.*; import org.apache.commons.io.FileUtils; import javax.annotation.Nullable; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.List; import java.util.Properties; import java.util.concurrent.atomic.AtomicReference; /** * Command line driver for Suro * * @author jbae * @author elandau */ public class SuroServer { private static final String PROP_PREFIX = "SuroServer."; private static final int DEFAULT_CONTROL_PORT = 9090; public static final String OPT_CONTROL_PORT = "controlPort"; public static void main(String[] args) throws IOException { final AtomicReference<Injector> injector = new AtomicReference<Injector>(); try { // Parse the command line Options options = createOptions(); final CommandLine line = new BasicParser().parse(options, args); // Load the properties file final Properties properties = new Properties(); if (line.hasOption('p')) { properties.load(new FileInputStream(line.getOptionValue('p'))); } // Bind all command line options to the properties with prefix "SuroServer." for (Option opt : line.getOptions()) { String name = opt.getOpt(); String value = line.getOptionValue(name); String propName = PROP_PREFIX + opt.getArgName(); if (propName.equals(DynamicPropertyRoutingMapConfigurator.ROUTING_MAP_PROPERTY)) { properties.setProperty(DynamicPropertyRoutingMapConfigurator.ROUTING_MAP_PROPERTY, FileUtils.readFileToString(new File(value))); } else if (propName.equals(DynamicPropertySinkConfigurator.SINK_PROPERTY)) { properties.setProperty(DynamicPropertySinkConfigurator.SINK_PROPERTY, FileUtils.readFileToString(new File(value))); } else if (propName.equals(DynamicPropertyInputConfigurator.INPUT_CONFIG_PROPERTY)) { properties.setProperty(DynamicPropertyInputConfigurator.INPUT_CONFIG_PROPERTY, FileUtils.readFileToString(new File(value))); } else { properties.setProperty(propName, value); } } List<Module> extensionModules = null; if (line.hasOption('x')) { String moduleFile = line.getOptionValue('x'); List<String> extensionModuleClasses = new ObjectMapper().readValue( FileUtils.readFileToString(new File(moduleFile)), new TypeReference<List<String>>(){}); if(extensionModuleClasses != null){ extensionModules = Lists.transform(extensionModuleClasses, new Function<String, Module>() { @Nullable @Override public Module apply(String input) { try { return (Module)Class.forName(input).newInstance(); } catch (Throwable e) { throw new RuntimeException(String.format("Unable to load module class %s", input), e); } } }); } } if(extensionModules == null) { //catch-all for either no configuration or empty configuration file extensionModules = Lists.newArrayList(); } create(injector, properties, extensionModules.toArray(new Module[extensionModules.size()])); injector.get().getInstance(LifecycleManager.class).start(); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { Closeables.close(injector.get().getInstance(LifecycleManager.class), true); } catch (IOException e) { // do nothing because Closeables.close will swallow IOException } } }); waitForShutdown(getControlPort(options)); } catch (Throwable e) { System.err.println("SuroServer startup failed: " + e.getMessage()); System.exit(-1); } finally { Closeables.close(injector.get().getInstance(LifecycleManager.class), true); } } public static void create(AtomicReference<Injector> injector, final Properties properties, Module... modules) throws Exception { // Create the injector injector.set(LifecycleInjector.builder() .withBootstrapModule( new BootstrapModule() { @Override public void configure(BootstrapBinder binder) { binder.bindConfigurationProvider().toInstance( new PropertiesConfigurationProvider(properties)); } } ) .withModules( new RoutingPlugin(), new ServerSinkPlugin(), new SuroInputPlugin(), new SuroDynamicPropertyModule(), new SuroModule(), StatusServer.createJerseyServletModule() ) .withAdditionalModules(modules) .build().createInjector()); } private static void waitForShutdown(int port) throws IOException { new SuroControl().start(port); } private static int getControlPort(Options options) { Option opt = options.getOption("c"); String value = opt.getValue(); if(value == null) { return DEFAULT_CONTROL_PORT; } return Integer.parseInt(value); } @SuppressWarnings("static-access") private static Options createOptions() { Option propertyFile = OptionBuilder.withArgName("serverProperty") .hasArg() .withDescription("server property file path") .create('p'); Option mapFile = OptionBuilder.withArgName("routingMap") .hasArg() .isRequired(true) .withDescription("message routing map file path") .create('m'); Option sinkFile = OptionBuilder.withArgName("sinkConfig" ) .hasArg() .isRequired(true) .withDescription("sink") .create('s'); Option inputFile = OptionBuilder.withArgName("inputConfig" ) .hasArg() .isRequired(true) .withDescription("input") .create('i'); Option accessKey = OptionBuilder.withArgName("AWSAccessKey" ) .hasArg() .isRequired(false) .withDescription("AWSAccessKey") .create('a'); Option secretKey = OptionBuilder.withArgName("AWSSecretKey" ) .hasArg() .isRequired(false) .withDescription("AWSSecretKey") .create('k'); Option controlPort = OptionBuilder.withArgName(OPT_CONTROL_PORT) .hasArg() .isRequired(false) .withDescription("The port used to send command to this server") .create('c'); Option extensions = OptionBuilder.withArgName("extensions") .hasArg() .isRequired(false) .withDescription("extension module list configuration file") .create('x'); Options options = new Options(); options.addOption(propertyFile); options.addOption(mapFile); options.addOption(sinkFile); options.addOption(inputFile); options.addOption(accessKey); options.addOption(secretKey); options.addOption(controlPort); options.addOption(extensions); return options; } }