package com.zillabyte.motherbrain.top; import java.io.IOException; import java.sql.SQLException; import java.util.EnumSet; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.cli.ParseException; import org.apache.log4j.Logger; import org.eclipse.jdt.annotation.NonNull; import com.zillabyte.motherbrain.coordination.CoordinationException; import com.zillabyte.motherbrain.flow.App; import com.zillabyte.motherbrain.flow.Component; import com.zillabyte.motherbrain.flow.FlowException; import com.zillabyte.motherbrain.flow.FlowInstance; import com.zillabyte.motherbrain.flow.FlowRecoveryException; import com.zillabyte.motherbrain.flow.FlowState; import com.zillabyte.motherbrain.flow.StateMachineException; import com.zillabyte.motherbrain.flow.config.FlowConfig; import com.zillabyte.motherbrain.flow.operations.OperationLogger; import com.zillabyte.motherbrain.flow.rpc.RPCHelper; import com.zillabyte.motherbrain.universe.Config; import com.zillabyte.motherbrain.universe.Universe; import com.zillabyte.motherbrain.utils.JarCompilationException; import com.zillabyte.motherbrain.utils.Utils; public class LocalServiceMain { static Universe _universe = null; static final Logger log = Utils.getLogger(LocalServiceMain.class); static FlowInstance _flowInstance = null; static final ConcurrentHashMap<String, ReentrantLock> _locks = new ConcurrentHashMap<>(); public static final long INIT_TIMEOUT = 5000; public static final long LOCK_TIMEOUT_SECONDS = 15; public static final String LOCK_ERROR_RETURN_STRING = "{\"status\": \"error\", \"error_code\": \"lock_timeout\", \"error_message\": \"The app is currently locked by another process.\"}"; public static final String INTERRUPT_ERROR_RETURN_STRING = "{\"status\": \"error\", \"error_code\": \"interrupt\"}"; public static final @NonNull FlowState[] nonStartingFlowStates; static { final EnumSet<FlowState> nonStartingFlowStateSet = EnumSet.complementOf(EnumSet.of(FlowState.INITIAL, FlowState.STARTING)); final FlowState[] nonStartingFlowStateArray = nonStartingFlowStateSet.toArray(new FlowState[] {}); /* * Contract of Collection#toArray */ assert (nonStartingFlowStateArray != null); nonStartingFlowStates = nonStartingFlowStateArray; } public static final long OPERATION_STARTING_TIMEOUT_MS = TimeUnit.MILLISECONDS.convert(30, TimeUnit.MINUTES); public static final long OPERATION_STARTED_TIMEOUT = TimeUnit.MILLISECONDS.convert(30, TimeUnit.MINUTES); public static final long REGISTER_TIMEOUT = OPERATION_STARTED_TIMEOUT; /*** * * @param uni * @param fetcher */ public LocalServiceMain() { _universe = Universe.instance(); } /** * @throws InterruptedException * @throws CoordinationException * @throws SQLException * @throws JarCompilationException * */ public static void init() throws Exception { Universe.instance().topService().init(); } /******************************************************************************/ /** Helpers ******************************************************************/ /******************************************************************************/ /**** * This method is called whenver API gets an RPC request. If the RPC is already running, then do nothing. Otherwise, spawn it up. * * @param flowId * @param authToken * @return * @throws InterruptedException * @throws StateMachineException */ private static String handleStartingRPC(final String flowId, final FlowConfig flowConfig) throws InterruptedException { // Init, Sanity if (flowId == null) { throw new NullPointerException("flowId cannot be null!"); } final OperationLogger flowLogger = Universe.instance().loggerFactory().logger(flowId, "component"); // Prepare a timeout because we don't want the lock to never expire! ExecutorService executor = Executors.newSingleThreadExecutor(); Future<String> future = executor.submit(new Callable<String>() { @Override public String call() throws Exception { // Acquire a lock final ReentrantLock lock = getLock(flowId); try { if (!lock.tryLock(LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { log.info("Component currently locked by another process"); return LOCK_ERROR_RETURN_STRING; } } catch (InterruptedException e) { flowLogger.logError(e); return INTERRUPT_ERROR_RETURN_STRING; } try { // Init log.info("start_rpc: " + flowId); flowLogger.writeLog("Beginning RPC", OperationLogger.LogPriority.STARTUP); // Get the flow App flow = (App) RPCHelper.wrapComponentInRpcApp( (Component) Universe.instance().flowBuilderFactory() .createFlowBuilder(flowConfig, flowLogger) .buildFlow(flowId) ); // Start running the flow... FlowInstance flowInstance = Universe.instance().topService().registerApp(flow); log.info("Flow instance name: " + flowInstance.id() + "__" + flowInstance.name()); _flowInstance = flowInstance; flowLogger.writeLog("RPC deploying to cluster.", OperationLogger.LogPriority.STARTUP); /* * We unlock here because from here on we don't really write, only read. */ lock.unlock(); /* * Wait for the operations to come online... */ if (!flowInstance.waitUntilAllOperationsAlive(LocalServiceMain.OPERATION_STARTING_TIMEOUT_MS, FlowState.INITIAL, FlowState.STARTING, FlowState.STARTED, FlowState.RUNNING)) { if (flowInstance.inState(FlowState.INITIAL, FlowState.STARTING, FlowState.STARTED, FlowState.RUNNING) == false) { flowLogger.writeLog("Component in unexpected state " + flowInstance.getFlowState() + ". Aborting...", OperationLogger.LogPriority.STARTUP); return "{\"status\": \"error\", \"error_message\": \"unexpected state " + flowInstance.getFlowState() + " \"}"; } flowLogger.writeLog("Operation boot timeout", OperationLogger.LogPriority.STARTUP); flowInstance.transitionToState(FlowState.ERROR); return "{\"status\": \"error\", \"error_message\": \"operation boot timeout\"}"; } log.info("Beginning individual operation initialization."); /* * Wait for all the operations to report they are ready to start working... */ if (!flowInstance.waitForState(OPERATION_STARTED_TIMEOUT, LocalServiceMain.nonStartingFlowStates)) { flowLogger.writeLog("Operation prepare timeout", OperationLogger.LogPriority.STARTUP); flowInstance.transitionToState(FlowState.ERROR); return "{\"status\": \"error\", \"error_message\": \"operation prepare timeout\"}"; } if (flowInstance.inState(FlowState.STARTED, FlowState.RUNNING) == false) { flowLogger.writeLog("Component in unexpected state " + flowInstance.getFlowState() + ". Aborting...", OperationLogger.LogPriority.STARTUP); return "{\"status\": \"error\", \"error_message\": \"unexpected state " + flowInstance.getFlowState() + " \"}"; } // Done! flowLogger.writeLog("RPC deployed.", OperationLogger.LogPriority.STARTUP); } catch (InterruptedException e) { flowLogger.logError(e); return INTERRUPT_ERROR_RETURN_STRING; } finally { /* * Unlock if someone else didn't grab the lock. */ while (lock.getHoldCount() > 0) { lock.unlock(); } } return "{\"status\": \"success\"}"; } }); // get the result before timeout try { // Success! return future.get(REGISTER_TIMEOUT, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { log.info("flow register timeout"); flowLogger.logError(e); return "{\"status\": \"error\", \"error_message\": \"flow register timeout\"}}"; } catch (ExecutionException e) { log.info("execution exception"); flowLogger.logError(e); e.printStackTrace(); return "{\"status\": \"error\", \"error_message\": \"execution exception\"}}"; } finally { executor.shutdownNow(); } } private static String handleRegisteringApp(final FlowConfig flowConfig) throws InterruptedException { // Init // Create the logger... final OperationLogger flowLogger = Universe.instance().loggerFactory().logger(flowConfig.getFlowId(), "app"); // Prepare a timeout because we don't want the lock to never expire! ExecutorService executor = Executors.newSingleThreadExecutor(); Future<String> future = executor.submit(new Callable<String>() { @Override public String call() throws Exception { if (flowConfig.getFlowId() == null) { throw new NullPointerException(flowConfig.getFlowId()); } // Acquire a lock final ReentrantLock lock = getLock(flowConfig.getFlowId()); try { if (!lock.tryLock(LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { log.info("App currently locked by another process"); return LOCK_ERROR_RETURN_STRING; } } catch (InterruptedException e) { flowLogger.logError(e); return INTERRUPT_ERROR_RETURN_STRING; } try { // Init log.info("register_flow: " + flowConfig.getFlowId() + " v" + flowConfig.getFlowVersion()); flowLogger.writeLog("Beginning app deployment", OperationLogger.LogPriority.STARTUP); // Get the flow App flow = (App)Universe.instance().flowBuilderFactory() .createFlowBuilder(flowConfig, flowLogger) .buildFlow(flowConfig.getFlowId()); // Start running the flow... FlowInstance flowInstance = Universe.instance().topService().registerApp(flow); log.info("Flow instance id: " + flowInstance.id() + " version: " + flowInstance.version() + " name:" + flowInstance.name()); _flowInstance = flowInstance; flowLogger.writeLog("App deploying to cluster.", OperationLogger.LogPriority.STARTUP); /* * We unlock here because from here on we don't really write, only read. */ lock.unlock(); /* * Wait for the operations to come online... */ if (!flowInstance.waitUntilAllOperationsAlive(LocalServiceMain.OPERATION_STARTING_TIMEOUT_MS, FlowState.INITIAL, FlowState.STARTING, FlowState.STARTED, FlowState.RUNNING)) { if (flowInstance.inState(FlowState.INITIAL, FlowState.STARTING, FlowState.STARTED, FlowState.RUNNING) == false) { flowLogger.writeLog("App in unexpected state " + flowInstance.getFlowState() + ". Aborting...", OperationLogger.LogPriority.STARTUP); return "{\"status\": \"error\", \"error_message\": \"unexpected state " + flowInstance.getFlowState() + " \"}"; } flowLogger.writeLog("Operation boot timeout", OperationLogger.LogPriority.STARTUP); flowInstance.transitionToState(FlowState.ERROR); return "{\"status\": \"error\", \"error_message\": \"operation boot timeout\"}"; } log.info("Beginning individual operation initialization."); /* * Wait for all the operations to report they are ready to start working... */ if (!flowInstance.waitForState(OPERATION_STARTED_TIMEOUT, LocalServiceMain.nonStartingFlowStates)) { flowLogger.writeLog("Operation prepare timeout", OperationLogger.LogPriority.STARTUP); flowInstance.transitionToState(FlowState.ERROR); return "{\"status\": \"error\", \"error_message\": \"operation prepare timeout\"}"; } if (flowInstance.inState(FlowState.STARTED, FlowState.RUNNING) == false) { flowLogger.writeLog("App in unexpected state " + flowInstance.getFlowState() + ". Aborting...", OperationLogger.LogPriority.STARTUP); return "{\"status\": \"error\", \"error_message\": \"unexpected state " + flowInstance.getFlowState() + " \"}"; } // Done! flowLogger.writeLog("App deployed.", OperationLogger.LogPriority.STARTUP); } catch (InterruptedException e) { flowLogger.logError(e); return INTERRUPT_ERROR_RETURN_STRING; } finally { /* * Unlock if someone else didn't grab the lock. */ while (lock.getHoldCount() > 0) { lock.unlock(); } } return "{\"status\": \"success\"}"; } }); // get the result before timeout try { // Success! return future.get(REGISTER_TIMEOUT, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { log.info("flow register timeout"); flowLogger.logError(e); return "{\"status\": \"error\", \"error_message\": \"flow register timeout\"}}"; } catch (ExecutionException e) { log.info("execution exception"); flowLogger.logError(e); e.printStackTrace(); return "{\"status\": \"error\", \"error_message\": \"execution exception\"}}"; } finally { executor.shutdownNow(); } } /******************************************************************************/ /** Aux ***********************************************************************/ /******************************************************************************/ /*** * * @param flowId */ static ReentrantLock getLock(String flowId) { _locks.putIfAbsent(flowId, new ReentrantLock(true)); final ReentrantLock lock = _locks.get(flowId); return lock; } /*** * * @param id */ public static FlowInstance getFlowInstance(String id) { return _flowInstance; } /******************************************************************************/ /** Main **********************************************************************/ /** * @throws ParseException * @throws IOException * @throws JarCompilationException * @throws FlowException * @throws FlowRecoveryException * @throws SQLException * @throws InterruptedException * @throws StateMachineException * @throws ExecutionException * @throws CoordinationException * @throws IllegalArgumentException */ public static void main(String[] args) throws ParseException, JarCompilationException, SQLException, FlowRecoveryException, FlowException, IOException, InterruptedException, StateMachineException, CoordinationException, ExecutionException { // Parse arguments LocalCommandLineHelper.printObligatoryCoolBanner(); LocalCommandLineHelper.createUniverse(args); // Init the server... try { Utils.executeWithin(INIT_TIMEOUT, new Callable<Void>() { @Override public Void call() throws Exception { init(); return null; } }); } catch (Exception e) { e.printStackTrace(); System.exit(0); } String flowName = Universe.instance().config().getOrException("flow.name"); String authToken = Universe.instance().config().getOrException("auth.token"); if(Universe.instance().config().getOrException("flow.class").equals("app")) { handleRegisteringApp(FlowConfig.createLocal(flowName, authToken) ); } else { Config.setDefault("rpc.source.idle.interval", 1000L * 3); handleStartingRPC(flowName, FlowConfig.createLocal(flowName, authToken)); } _flowInstance.startNewCycle(); _flowInstance.waitForState(FlowState.WAITING_FOR_NEXT_CYCLE, FlowState.IDLE); _flowInstance.kill(); clear(); System.exit(0); } public static void clear() { _flowInstance = null; _locks.clear(); } }