/* * Copyright 2014 University of Southern California * * 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 edu.usc.pgroup.floe.coordinator.transitions.coordinatortransitions; /** * @author kumbhare */ import edu.usc.pgroup.floe.coordinator.transitions.ClusterTransition; import edu.usc.pgroup.floe.resourcemanager.ResourceMapping; import edu.usc.pgroup.floe.signals.ContainerSignal; import edu.usc.pgroup.floe.signals.SignalHandler; import edu.usc.pgroup.floe.thriftgen.AppStatus; import edu.usc.pgroup.floe.thriftgen.InsufficientResourcesException; import edu.usc.pgroup.floe.thriftgen.TFloeApp; import edu.usc.pgroup.floe.utils.Utils; import edu.usc.pgroup.floe.zookeeper.ZKClient; import edu.usc.pgroup.floe.zookeeper.ZKUtils; import org.apache.curator.framework.recipes.barriers.DistributedDoubleBarrier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; /** * Transition to start a new application. */ public abstract class BaseAppTransition extends ClusterTransition { /** * Logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(BaseAppTransition.class); /** * Start the execution for the given transition. Returns immediately * after starting? * * @param args transaction specific arguments * @throws Exception if there is an unrecoverable error while * processing the transition. */ @Override protected final void execute(final Map<String, Object> args) throws Exception { LOGGER.info("Executing base app transition."); String appName = (String) args.get("appName"); TFloeApp app = (TFloeApp) args.get("app"); ResourceMapping currentMapping = ZKUtils.getResourceMapping(appName); if (!preTransition(appName, currentMapping)) { LOGGER.error("Pre-Transition failed."); throw new Exception("Transition cannot be executed. " + "Pre-transition failed."); } //STEP 1: Schedule app, Get resource mapping and update it in ZK ResourceMapping updatedMapping = schedule(appName, app, currentMapping, args); String appUpdateBarrierPath = ZKUtils .getApplicationBarrierPath(appName); int numContainersToUpdate = updatedMapping.getContainersToUpdate(); DistributedDoubleBarrier barrier = new DistributedDoubleBarrier( ZKClient.getInstance().getCuratorClient(), appUpdateBarrierPath, numContainersToUpdate + 1 ); //Step 2. Send the command to *ALL* containers to check the // resource mapping and CREATE the required flakes. createFlakes(updatedMapping, barrier); LOGGER.info("All containers finished launching flakes."); initializeFlakes(updatedMapping, barrier); LOGGER.info("All containers finished initializing flakes."); // if (1 == 1) { // return; // } //Step 3. Send connect signals to flakes so that they establish all // the appropriate channels. connectFlakes(updatedMapping, barrier); LOGGER.info("All containers finished CONNECTING flakes."); //Step 4. wait for all containers to finish launching or removing // pellets. increaseOrDecreasePellets(updatedMapping, barrier); LOGGER.info("All pellets successfully created."); //Step 5. wait for container to terminate flakes, if required. terminateFlakes(updatedMapping, barrier); LOGGER.info("All required flakes terminated."); //Step 6. Send signal Start pellets. startPellets(updatedMapping, barrier); LOGGER.info("All pellets Started. The application is now " + "running"); if (!postTransition(currentMapping)) { LOGGER.error("Post-Transition failed."); throw new Exception("Transition may have been executed partially. " + "Post-transition failed."); } } /** * Send TerminateFlakes requests to all containers and waits for their * response. * @param updatedMapping the resource mapping to use. * @param barrier The barrier used for synchronization. * @throws Exception if an error occurs. */ private void terminateFlakes(final ResourceMapping updatedMapping, final DistributedDoubleBarrier barrier) throws Exception { String appName = updatedMapping.getAppName(); SignalHandler.getInstance().signal(appName, "ALL-CONTAINERS", ContainerSignal.ContainerSignalType.TERMINATE_FLAKES, Utils.serialize("dummy")); ZKUtils.setAppStatus(appName, AppStatus.UPDATING_FLAKES); barrier.enter(); //wait for all containers to receive the new // resource mapping and start processing. LOGGER.info("Waiting for containers to finish flake deployment."); barrier.leave(); //wait for all containers to deploy (or update) // flakes and complete their execution. ZKUtils.setAppStatus(appName, AppStatus.UPDATING_FLAKES_COMPLETED); } /** * Send start pellets requests to all containers and waits for * their response. * @param updatedMapping the resource mapping to use. * @param barrier The barrier used for synchronization. * @throws Exception if an error occurs. */ private void startPellets(final ResourceMapping updatedMapping, final DistributedDoubleBarrier barrier) throws Exception { String appName = updatedMapping.getAppName(); SignalHandler.getInstance().signal(appName, "ALL-CONTAINERS", ContainerSignal.ContainerSignalType.START_PELLETS, Utils.serialize("dummy")); ZKUtils.setAppStatus(appName, AppStatus.STARTING_PELLETS); barrier.enter(); LOGGER.info("Waiting for containers to Start all pellets."); barrier.leave(); ZKUtils.setAppStatus(appName, AppStatus.RUNNING); } /** * Send Connect or disconnect requests to all containers and waits for * their response. * @param updatedMapping the resource mapping to use. * @param barrier The barrier used for synchronization. * @throws Exception if an error occurs. */ private void connectFlakes(final ResourceMapping updatedMapping, final DistributedDoubleBarrier barrier) throws Exception { String appName = updatedMapping.getAppName(); SignalHandler.getInstance().signal(appName, "ALL-CONTAINERS", ContainerSignal.ContainerSignalType.CONNECT_OR_DISCONNECT_FLAKES, Utils.serialize("dummy")); barrier.enter(); LOGGER.info("Waiting for containers to finish flake channel creation."); barrier.leave(); ZKUtils.setAppStatus(appName, AppStatus.UPDATING_FLAKES_COMPLETED); } /** * Send Increase or decrease pellets requests to all containers and waits * for their response. * @param updatedMapping the resource mapping to use. * @param barrier The barrier used for synchronization. * @throws Exception if an error occurs. */ private void increaseOrDecreasePellets( final ResourceMapping updatedMapping, final DistributedDoubleBarrier barrier) throws Exception { String appName = updatedMapping.getAppName(); SignalHandler.getInstance().signal(appName, "ALL-CONTAINERS", ContainerSignal.ContainerSignalType.INCREASE_OR_DECREASE_PELLETS, Utils.serialize("dummy")); ZKUtils.setAppStatus(appName, AppStatus.UPDATING_PELLETS); barrier.enter(); LOGGER.info("Waiting for containers to launch pellets in the flakes"); barrier.leave(); ZKUtils.setAppStatus(appName, AppStatus.UPDATING_PELLETS_COMPLETED); } /** * Send INITIALIZE requests to all containers and waits for their response. * @param updatedMapping the resource mapping to use. * @param barrier The barrier used for synchronization. * @throws Exception if an error occurs. */ private void initializeFlakes(final ResourceMapping updatedMapping, final DistributedDoubleBarrier barrier) throws Exception { String appName = updatedMapping.getAppName(); SignalHandler.getInstance().signal(appName, "ALL-CONTAINERS", ContainerSignal.ContainerSignalType.INITIALIZE_FLAKES, Utils.serialize("dummy")); ZKUtils.setAppStatus(appName, AppStatus.UPDATING_FLAKES); barrier.enter(); //wait for all containers to receive the new // resource mapping and start processing. LOGGER.info("Waiting for containers to finish flake deployment."); barrier.leave(); //wait for all containers to deploy (or update) // flakes and complete their execution. ZKUtils.setAppStatus(appName, AppStatus.UPDATING_FLAKES_COMPLETED); } /** * Send CreateFlake requests to all containers and waits for their response. * @param updatedMapping the resource mapping to use. * @param barrier The barrier used for synchronization. * @throws Exception if an error occurs. */ private void createFlakes(final ResourceMapping updatedMapping, final DistributedDoubleBarrier barrier) throws Exception { String appName = updatedMapping.getAppName(); SignalHandler.getInstance().signal(appName, "ALL-CONTAINERS", ContainerSignal.ContainerSignalType.CREATE_FLAKES, Utils.serialize("dummy")); ZKUtils.setAppStatus(appName, AppStatus.UPDATING_FLAKES); barrier.enter(); //wait for all containers to receive the new // resource mapping and start processing. LOGGER.info("Waiting for containers to finish flake deployment."); barrier.leave(); //wait for all containers to deploy (or update) // flakes and complete their execution. ZKUtils.setAppStatus(appName, AppStatus.UPDATING_FLAKES_COMPLETED); } /** * Updates the resource mapping for the application. * @param appName the applicaiton name. * @param app The TFloeApp applicaiton object. * @param currentMapping current resource mapping. * @param args transaction specific arguments * @return updated resource mapping. * @throws Exception if an error occurs. */ private ResourceMapping schedule(final String appName, final TFloeApp app, final ResourceMapping currentMapping, final Map<String, Object> args) throws Exception { ZKUtils.setAppStatus(appName, AppStatus.SCHEDULING); ResourceMapping updatedMapping = updateResourceMapping( appName, app, currentMapping, args); LOGGER.info("Planned initial resource mapping:" + updatedMapping); if (updatedMapping == null) { LOGGER.warn("Insufficient resources to deploy the application."); throw new InsufficientResourcesException("Unable to acquire " + "required resources."); } //STEP 3: Put the resource Mapping in ZK. ZKUtils.updateResourceMapping(appName, updatedMapping); ZKUtils.setAppStatus(appName, AppStatus.SCHEDULING_COMPLETED); return updatedMapping; } /** * Pre-transition activities (like verification of topology, * app exists etc.). * @param appName the applicaiton name. * @param mapping Current Resource Mapping. (null for a new deployment) * @return True if preTransition was successful and the transition itself * should be executed. False implies that there are an error and the * transition should be skipped. */ public abstract boolean preTransition( final String appName, final ResourceMapping mapping); /** * Post-transition activities (like move app to archive etc.). * @param mapping Updated Resource Mapping. * @return True if postTransition was successful, false otherwise. */ public abstract boolean postTransition(final ResourceMapping mapping); /** * Transition specific update resource mapping function. * @param appName the applicaiton name. * @param app The TFloeApp applicaiton object. * @param currentMapping Current Resource mapping. * @param args transaction specific arguments * @return updated resource mapping based on the transition. */ public abstract ResourceMapping updateResourceMapping( final String appName, final TFloeApp app, final ResourceMapping currentMapping, final Map<String, Object> args); }