org.apache.heron.scheduler.SchedulerMain.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.heron.scheduler.SchedulerMain.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.heron.scheduler;

import java.io.IOException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.heron.api.exception.InvalidTopologyException;
import org.apache.heron.api.generated.TopologyAPI;
import org.apache.heron.api.utils.TopologyUtils;
import org.apache.heron.common.basics.FileUtils;
import org.apache.heron.common.basics.SysUtils;
import org.apache.heron.common.config.SystemConfig;
import org.apache.heron.common.utils.logging.LoggingHelper;
import org.apache.heron.proto.system.PackingPlans;
import org.apache.heron.scheduler.server.SchedulerServer;
import org.apache.heron.scheduler.utils.LauncherUtils;
import org.apache.heron.scheduler.utils.Runtime;
import org.apache.heron.scheduler.utils.SchedulerConfigUtils;
import org.apache.heron.scheduler.utils.SchedulerUtils;
import org.apache.heron.scheduler.utils.Shutdown;
import org.apache.heron.spi.common.Config;
import org.apache.heron.spi.common.Context;
import org.apache.heron.spi.common.Key;
import org.apache.heron.spi.packing.PackingPlan;
import org.apache.heron.spi.packing.PackingPlanProtoDeserializer;
import org.apache.heron.spi.scheduler.IScheduler;
import org.apache.heron.spi.statemgr.IStateManager;
import org.apache.heron.spi.statemgr.SchedulerStateManagerAdaptor;
import org.apache.heron.spi.utils.ReflectionUtils;

/**
 * Main class of scheduler.
 */
public class SchedulerMain {
    private static final Logger LOG = Logger.getLogger(SchedulerMain.class.getName());

    private int schedulerServerPort = -1; // http port where the scheduler is listening

    private TopologyAPI.Topology topology = null; // topology definition
    private Config config; // holds all the config read
    // Properties passed from command line property arguments
    private final Properties properties;

    public SchedulerMain(Config config, TopologyAPI.Topology topology, int schedulerServerPort,
            Properties properties) {
        // initialize the options
        this.config = config;
        this.topology = topology;
        this.schedulerServerPort = schedulerServerPort;
        this.properties = properties;
    }

    // Print usage options
    private static void usage(Options options) {
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp("SchedulerMain", options);
    }

    // Construct all required command line options
    private static Options constructOptions() {
        Options options = new Options();

        Option cluster = Option.builder("c").desc("Cluster name in which the topology needs to run on")
                .longOpt("cluster").hasArgs().argName("cluster").required().build();

        Option role = Option.builder("r").desc("Role under which the topology needs to run").longOpt("role")
                .hasArgs().argName("role").required().build();

        Option environment = Option.builder("e").desc("Environment under which the topology needs to run")
                .longOpt("environment").hasArgs().argName("environment").required().build();

        Option topologyName = Option.builder("n").desc("Name of the topology").longOpt("topology_name").hasArgs()
                .argName("topology name").required().build();

        Option topologyJar = Option.builder("f").desc("Topology jar/pex file path").longOpt("topology_bin")
                .hasArgs().argName("topology binary file").required().build();

        Option schedulerHTTPPort = Option.builder("p")
                .desc("Http Port number on which the scheduler listens for requests").longOpt("http_port").hasArgs()
                .argName("http port").required().build();

        Option property = Option.builder(SchedulerUtils.SCHEDULER_COMMAND_LINE_PROPERTIES_OVERRIDE_OPTION)
                .desc("use value for given property").longOpt("property_override").hasArgs().valueSeparator()
                .argName("property=value").build();

        Option verbose = Option.builder("v").desc("Enable debug logs").longOpt("verbose").build();

        options.addOption(cluster);
        options.addOption(role);
        options.addOption(environment);
        options.addOption(topologyName);
        options.addOption(topologyJar);
        options.addOption(schedulerHTTPPort);
        options.addOption(property);
        options.addOption(verbose);

        return options;
    }

    // construct command line help options
    private static Options constructHelpOptions() {
        Options options = new Options();
        Option help = Option.builder("h").desc("List all options and their description").longOpt("help").build();

        options.addOption(help);
        return options;
    }

    public static void main(String[] args) throws Exception {

        // construct the options and help options first.
        Options options = constructOptions();
        Options helpOptions = constructHelpOptions();

        // parse the options
        CommandLineParser parser = new DefaultParser();
        CommandLine cmd = parser.parse(helpOptions, args, true);

        // print help, if we receive wrong set of arguments
        if (cmd.hasOption("h")) {
            usage(options);
            return;
        }

        // Now parse the required options
        try {
            cmd = parser.parse(options, args);
        } catch (ParseException e) {
            usage(options);
            throw new RuntimeException("Error parsing command line options: ", e);
        }

        // It returns a new empty Properties instead of null,
        // if no properties passed from command line. So no need for null check.
        Properties schedulerProperties = cmd
                .getOptionProperties(SchedulerUtils.SCHEDULER_COMMAND_LINE_PROPERTIES_OVERRIDE_OPTION);

        // initialize the scheduler with the options
        String topologyName = cmd.getOptionValue("topology_name");
        SchedulerMain schedulerMain = createInstance(cmd.getOptionValue("cluster"), cmd.getOptionValue("role"),
                cmd.getOptionValue("environment"), cmd.getOptionValue("topology_bin"), topologyName,
                Integer.parseInt(cmd.getOptionValue("http_port")), cmd.hasOption("verbose"), schedulerProperties);

        LOG.info("Scheduler command line properties override: " + schedulerProperties.toString());

        // run the scheduler
        boolean ret = schedulerMain.runScheduler();

        // Log the result and exit
        if (!ret) {
            throw new RuntimeException("Failed to schedule topology: " + topologyName);
        } else {
            // stop the server and close the state manager
            LOG.log(Level.INFO, "Shutting down topology: {0}", topologyName);
        }
    }

    public static SchedulerMain createInstance(String cluster, String role, String env, String topologyJar,
            String topologyName, int httpPort, Boolean verbose) throws IOException, InvalidTopologyException {
        return createInstance(cluster, role, env, topologyJar, topologyName, httpPort, verbose, new Properties());
    }

    public static SchedulerMain createInstance(String cluster, String role, String env, String topologyJar,
            String topologyName, int httpPort, Boolean verbose, Properties schedulerProperties)
            throws IOException, InvalidTopologyException {
        // Look up the topology def file location
        String topologyDefnFile = TopologyUtils.lookUpTopologyDefnFile(".", topologyName);

        // load the topology definition into topology proto
        TopologyAPI.Topology topology = TopologyUtils.getTopology(topologyDefnFile);

        // build the config by expanding all the variables
        Config schedulerConfig = SchedulerConfigUtils.loadConfig(cluster, role, env, topologyJar, topologyDefnFile,
                verbose, topology);

        // set up logging with complete Config
        setupLogging(schedulerConfig);

        // Create a new instance
        SchedulerMain schedulerMain = new SchedulerMain(schedulerConfig, topology, httpPort, schedulerProperties);

        LOG.log(Level.INFO, "Loaded scheduler config: {0}", schedulerMain.config);
        return schedulerMain;
    }

    // Set up logging based on the Config
    private static void setupLogging(Config config) throws IOException {
        String systemConfigFilename = Context.systemConfigFile(config);

        SystemConfig systemConfig = SystemConfig.newBuilder(true).putAll(systemConfigFilename, true).build();

        // Init the logging setting and redirect the stdout and stderr to logging
        // For now we just set the logging level as INFO; later we may accept an argument to set it.
        Level loggingLevel = Level.INFO;
        if (Context.verbose(config).booleanValue()) {
            loggingLevel = Level.FINE;
        }
        // TODO(mfu): The folder creation may be duplicated with heron-executor in future
        // TODO(mfu): Remove the creation in future if feasible
        String loggingDir = systemConfig.getHeronLoggingDirectory();
        if (!FileUtils.isDirectoryExists(loggingDir)) {
            FileUtils.createDirectory(loggingDir);
        }

        // Log to file
        LoggingHelper.loggerInit(loggingLevel, true);

        // TODO(mfu): Pass the scheduler id from cmd
        String processId = String.format("%s-%s-%s", "heron", Context.topologyName(config), "scheduler");
        LoggingHelper.addLoggingHandler(LoggingHelper.getFileHandler(processId, loggingDir, true,
                systemConfig.getHeronLoggingMaximumSize(), systemConfig.getHeronLoggingMaximumFiles()));

        LOG.info("Logging setup done.");
    }

    /**
     * Get the http server for receiving scheduler requests
     *
     * @param runtime, the runtime configuration
     * @param scheduler, an instance of the scheduler
     * @param port, the port for scheduler to listen on
     * @return an instance of the http server
     */
    protected SchedulerServer getServer(Config runtime, IScheduler scheduler, int port) throws IOException {

        // create an instance of the server using scheduler class and port
        return new SchedulerServer(runtime, scheduler, port);
    }

    /**
     * Run the scheduler.
     * It is a blocking call, and it will return in 2 cases:
     * 1. The topology is requested to kill
     * 2. Unexpected exceptions happen
     *
     * @return true if scheduled successfully
     */
    public boolean runScheduler() {
        IScheduler scheduler = null;

        String statemgrClass = Context.stateManagerClass(config);
        IStateManager statemgr;

        try {
            // create an instance of state manager
            statemgr = ReflectionUtils.newInstance(statemgrClass);
        } catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {
            LOG.log(Level.SEVERE, "Failed to instantiate instances using config: " + config, e);
            return false;
        }

        SchedulerServer server = null;
        boolean isSuccessful = false;

        // Put it in a try block so that we can always clean resources
        try {
            // initialize the state manager
            statemgr.initialize(config);

            // TODO(mfu): timeout should read from config
            SchedulerStateManagerAdaptor adaptor = new SchedulerStateManagerAdaptor(statemgr, 5000);

            // get a packed plan and schedule it
            PackingPlans.PackingPlan serializedPackingPlan = adaptor.getPackingPlan(topology.getName());
            if (serializedPackingPlan == null) {
                LOG.log(Level.SEVERE, "Failed to fetch PackingPlan for topology:{0} from the state manager",
                        topology.getName());
                return false;
            }
            LOG.log(Level.INFO, "Packing plan fetched from state: {0}", serializedPackingPlan);
            PackingPlan packedPlan = new PackingPlanProtoDeserializer().fromProto(serializedPackingPlan);

            // build the runtime config
            LauncherUtils launcherUtils = LauncherUtils.getInstance();
            Config runtime = Config.newBuilder().putAll(launcherUtils.createPrimaryRuntime(topology))
                    .putAll(launcherUtils.createAdaptorRuntime(adaptor)).put(Key.SCHEDULER_SHUTDOWN, getShutdown())
                    .put(Key.SCHEDULER_PROPERTIES, properties).build();

            Config ytruntime = launcherUtils.createConfigWithPackingDetails(runtime, packedPlan);

            // invoke scheduler
            scheduler = launcherUtils.getSchedulerInstance(config, ytruntime);
            if (scheduler == null) {
                return false;
            }

            isSuccessful = scheduler.onSchedule(packedPlan);
            if (!isSuccessful) {
                LOG.severe("Failed to schedule topology");
                return false;
            }

            // Failures in server initialization throw exceptions
            // get the scheduler server endpoint for receiving requests
            server = getServer(ytruntime, scheduler, schedulerServerPort);
            // start the server to manage runtime requests
            server.start();

            // write the scheduler location to state manager
            // Make sure it happens after IScheduler.onScheduler
            isSuccessful = SchedulerUtils.setSchedulerLocation(runtime,
                    String.format("%s:%d", server.getHost(), server.getPort()), scheduler);

            if (isSuccessful) {
                // wait until kill request or some interrupt occurs if the scheduler starts successfully
                LOG.info("Waiting for termination... ");
                Runtime.schedulerShutdown(ytruntime).await();
            }
        } catch (IOException e) {
            LOG.log(Level.SEVERE, "Failed to start server", e);
            return false;
        } finally {
            // Clean the resources
            if (server != null) {
                server.stop();
            }

            // 4. Close the resources
            SysUtils.closeIgnoringExceptions(scheduler);
            SysUtils.closeIgnoringExceptions(statemgr);
        }

        return isSuccessful;
    }

    // Utils method
    protected Shutdown getShutdown() {
        return new Shutdown();
    }
}