package storm.applications; import backtype.storm.Config; import backtype.storm.LocalCluster; import backtype.storm.StormSubmitter; import backtype.storm.generated.AlreadyAliveException; import backtype.storm.generated.InvalidTopologyException; import backtype.storm.generated.StormTopology; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterException; import com.google.common.collect.Lists; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Properties; import java.util.Random; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import storm.applications.topology.AdsAnalyticsTopology; import storm.applications.topology.BargainIndexTopology; import storm.applications.topology.ClickAnalyticsTopology; import storm.applications.topology.FraudDetectionTopology; import storm.applications.topology.LinearRoadTopology; import storm.applications.topology.LogProcessingTopology; import storm.applications.topology.MachineOutlierTopology; import storm.applications.topology.ReinforcementLearnerTopology; import storm.applications.topology.SentimentAnalysisTopology; import storm.applications.topology.SpamFilterTopology; import storm.applications.topology.SpikeDetectionTopology; import storm.applications.topology.TrafficMonitoringTopology; import storm.applications.topology.TrendingTopicsTopology; import storm.applications.topology.VoIPSTREAMTopology; import storm.applications.topology.WordCountTopology; import storm.applications.util.config.Configuration; /** * Utility class to run a Storm topology * @author Maycon Viana Bordin <mayconbordin@gmail.com> */ public class StormRunner { private static final Logger LOG = LoggerFactory.getLogger(StormRunner.class); private static final String RUN_LOCAL = "local"; private static final String RUN_REMOTE = "remote"; private static final String CFG_PATH = "/config/%s.properties"; @Parameter public List<String> parameters = Lists.newArrayList(); @Parameter(names = {"-m", "--mode"}, description = "Mode for running the topology") public String mode = "local"; @Parameter(names = {"-a", "--app"}, description = "The application to be executed", required = true) public String application; @Parameter(names = {"-t", "--topology-name"}, required = false, description = "The name of the topology") public String topologyName; @Parameter(names = {"--config-str"}, required = false, description = "Path to the configuration file for the application") public String configStr; @Parameter(names = {"-r", "--runtime"}, description = "Runtime in seconds for the topology (local mode only)") public int runtimeInSeconds = 300; private final AppDriver driver; private Config config; public StormRunner() { driver = new AppDriver(); driver.addApp("ads-analytics" , AdsAnalyticsTopology.class); driver.addApp("bargain-index" , BargainIndexTopology.class); driver.addApp("click-analytics" , ClickAnalyticsTopology.class); driver.addApp("fraud-detection" , FraudDetectionTopology.class); driver.addApp("linear-road" , LinearRoadTopology.class); driver.addApp("log-processing" , LogProcessingTopology.class); driver.addApp("machine-outlier" , MachineOutlierTopology.class); driver.addApp("reinforcement-learner", ReinforcementLearnerTopology.class); driver.addApp("sentiment-analysis" , SentimentAnalysisTopology.class); driver.addApp("spam-filter" , SpamFilterTopology.class); driver.addApp("spike-detection" , SpikeDetectionTopology.class); driver.addApp("trending-topics" , TrendingTopicsTopology.class); driver.addApp("voipstream" , VoIPSTREAMTopology.class); driver.addApp("word-count" , WordCountTopology.class); driver.addApp("traffic-monitoring" , TrafficMonitoringTopology.class); } public void run() throws InterruptedException, AlreadyAliveException, InvalidTopologyException { // Loads the configuration file set by the user or the default configuration try { // load default configuration if (configStr == null) { String cfg = String.format(CFG_PATH, application); Properties p = loadProperties(cfg, (configStr == null)); config = Configuration.fromProperties(p); LOG.info("Loaded default configuration file {}", cfg); } else { config = Configuration.fromStr(configStr); LOG.info("Loaded configuration from command line argument"); } } catch (IOException ex) { LOG.error("Unable to load configuration file", ex); throw new RuntimeException("Unable to load configuration file", ex); } // Get the descriptor for the given application AppDriver.AppDescriptor app = driver.getApp(application); if (app == null) { throw new RuntimeException("The given application name "+application+" is invalid"); } // In case no topology names is given, create one if (topologyName == null) { topologyName = String.format("%s-%d", application, new Random().nextInt()); } // Get the topology and execute on Storm StormTopology stormTopology = app.getTopology(topologyName, config); switch (mode) { case RUN_LOCAL: runTopologyLocally(stormTopology, topologyName, config, runtimeInSeconds); break; case RUN_REMOTE: runTopologyRemotely(stormTopology, topologyName, config); break; default: throw new RuntimeException("Valid running modes are 'local' and 'remote'"); } } public static void main(String[] args) throws Exception { StormRunner runner = new StormRunner(); JCommander cmd = new JCommander(runner); try { cmd.parse(args); } catch (ParameterException ex) { System.err.println("Argument error: " + ex.getMessage()); cmd.usage(); System.exit(1); } try { runner.run(); } catch (AlreadyAliveException | InvalidTopologyException ex) { LOG.error("Error in running topology remotely", ex); } catch (InterruptedException ex) { LOG.error("Error in running topology locally", ex); } } /** * Run the topology locally * @param topology The topology to be executed * @param topologyName The name of the topology * @param conf The configurations for the execution * @param runtimeInSeconds For how much time the topology will run * @throws InterruptedException */ public static void runTopologyLocally(StormTopology topology, String topologyName, Config conf, int runtimeInSeconds) throws InterruptedException { LOG.info("Starting Storm on local mode to run for {} seconds", runtimeInSeconds); LocalCluster cluster = new LocalCluster(); LOG.info("Topology {} submitted", topologyName); cluster.submitTopology(topologyName, conf, topology); Thread.sleep((long) runtimeInSeconds * 1000); cluster.killTopology(topologyName); LOG.info("Topology {} finished", topologyName); cluster.shutdown(); LOG.info("Local Storm cluster was shutdown", topologyName); } /** * Run the topology remotely * @param topology The topology to be executed * @param topologyName The name of the topology * @param conf The configurations for the execution * @throws AlreadyAliveException * @throws InvalidTopologyException */ public static void runTopologyRemotely(StormTopology topology, String topologyName, Config conf) throws AlreadyAliveException, InvalidTopologyException { StormSubmitter.submitTopology(topologyName, conf, topology); } public static Properties loadProperties(String filename, boolean classpath) throws IOException { Properties properties = new Properties(); InputStream is; if (classpath) { is = StormRunner.class.getResourceAsStream(filename); } else { is = new FileInputStream(filename); } properties.load(is); is.close(); return properties; } }