/* * 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.usergrid.chop.webapp.coordinator; import java.io.File; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.usergrid.chop.api.Commit; import org.apache.usergrid.chop.api.Module; import org.apache.usergrid.chop.stack.CoordinatedStack; import org.apache.usergrid.chop.stack.SetupStackSignal; import org.apache.usergrid.chop.stack.SetupStackState; import org.apache.usergrid.chop.stack.Stack; import org.apache.usergrid.chop.stack.User; import org.apache.usergrid.chop.webapp.ChopUiFig; import org.apache.usergrid.chop.webapp.dao.CommitDao; import org.apache.usergrid.chop.webapp.dao.ModuleDao; import org.apache.usergrid.chop.webapp.dao.UserDao; import org.apache.usergrid.chop.webapp.dao.model.BasicModule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Inject; import com.google.inject.Singleton; /** * Coordinates all chop runs in the server. */ @Singleton public class StackCoordinator { private static final Logger LOG = LoggerFactory.getLogger( StackCoordinator.class ); private final ExecutorService service = Executors.newCachedThreadPool(); @Inject private ChopUiFig chopUiFig; @Inject private UserDao userDao; @Inject private CommitDao commitDao; @Inject private ModuleDao moduleDao; private Map<CoordinatedStack, SetupStackThread> setupStackThreads = new ConcurrentHashMap<CoordinatedStack, SetupStackThread>(); private Map<Integer, CoordinatedStack> registeredStacks = new ConcurrentHashMap<Integer, CoordinatedStack>(); /** * Sets up all clusters and runner instances defined by given parameters * <p> * Don't call this method without checking parameters first, * this method assumes that a runner with given parameters is already deployed * and its stack is ready to be set up * * @param commitId * @param artifactId * @param groupId * @param version * @param user * @param runnerCount * @return */ public CoordinatedStack setupStack( String commitId, String artifactId, String groupId, String version, String user, int runnerCount ) { User chopUser = userDao.get( user ); File runnerJar = CoordinatorUtils.getRunnerJar( chopUiFig.getContextPath(), user, groupId, artifactId, version, commitId ); Stack stack = CoordinatorUtils.getStackFromRunnerJar( runnerJar ); Module module = moduleDao.get( BasicModule.createId( groupId, artifactId, version ) ); Commit commit = null; for( Commit c: commitDao.getByModule( module.getId() ) ) { if( commitId.equals( c.getId() ) ) { commit = c; break; } } return setupStack( stack, chopUser, commit, module, runnerCount ); } /** * Sets up all clusters and runner instances defined by given parameters * * @param stack Stack object to be set up * @param user User who is doing the operation * @param commit Commit to be chop tested * @param module Module to be chop tested * @param runnerCount Number of runner instances that will run the tests * @return the CoordinatedStack object if setup succeeds * @throws Exception */ public CoordinatedStack setupStack( Stack stack, User user, Commit commit, Module module, int runnerCount ) { CoordinatedStack coordinatedStack = getCoordinatedStack( stack, user, commit, module ); if ( coordinatedStack != null && coordinatedStack.getRunnerCount() == runnerCount ) { LOG.info( "Stack {} is already registered", stack.getName() ); if( coordinatedStack.getSetupState() == SetupStackState.SetUp ) { return coordinatedStack; } } else if ( coordinatedStack != null && coordinatedStack.getRunnerCount() != runnerCount ) { LOG.info( "Stack {} is registered with different runner count, first removing the old stack", stack.getName() ); registeredStacks.remove( coordinatedStack.hashCode() ); } LOG.info( "Registering new stack {}...", stack.getName() ); coordinatedStack = new CoordinatedStack( stack, user, commit, module, runnerCount ); LOG.info( "Starting setup stack thread of {}...", stack.getName() ); synchronized ( coordinatedStack ) { coordinatedStack.setSetupState( SetupStackSignal.SETUP ); registeredStacks.put( coordinatedStack.hashCode(), coordinatedStack ); SetupStackThread setupThread = new SetupStackThread( coordinatedStack ); setupStackThreads.put( coordinatedStack, setupThread ); // Not registering the results for now, since they are not being used service.submit( setupThread ); } return coordinatedStack; } public void destroyStack( String commitId, String artifactId, String groupId, String version, String user ) { User chopUser = userDao.get( user ); File runnerJar = CoordinatorUtils.getRunnerJar( chopUiFig.getContextPath(), user, groupId, artifactId, version, commitId ); Stack stack = CoordinatorUtils.getStackFromRunnerJar( runnerJar ); Module module = moduleDao.get( BasicModule.createId( groupId, artifactId, version ) ); Commit commit = null; for( Commit c: commitDao.getByModule( module.getId() ) ) { if( commitId.equals( c.getId() ) ) { commit = c; break; } } destroyStack( stack, chopUser, commit, module ); } public void destroyStack( Stack stack, User user, Commit commit, Module module ) { CoordinatedStack coordinatedStack = getCoordinatedStack( stack, user, commit, module ); if ( coordinatedStack == null || coordinatedStack.getSetupState() == SetupStackState.JarNotFound ) { LOG.info( "No such stack was found." ); return; } synchronized ( coordinatedStack ) { if ( ! coordinatedStack.getSetupState().accepts( SetupStackSignal.DESTROY ) ) { LOG.info( "Stack is in {} state, will not destroy.", coordinatedStack.getSetupState().toString() ); return; } // TODO should we also check run state of stack? LOG.info( "Starting to destroy stack instances of {}...", stack.getName() ); coordinatedStack.setSetupState( SetupStackSignal.DESTROY ); StackDestroyer destroyer = new StackDestroyer( coordinatedStack ); destroyer.destroy(); registeredStacks.remove( coordinatedStack.hashCode() ); setupStackThreads.remove( coordinatedStack ); coordinatedStack.setSetupState( SetupStackSignal.COMPLETE ); coordinatedStack.notifyAll(); } } public SetupStackThread getSetupStackThread( CoordinatedStack stack ) { return setupStackThreads.get( stack ); } public CoordinatedStack getMatching( User user, Commit commit, Module module ) { for( CoordinatedStack stack: registeredStacks.values() ) { if( stack.getUser().equals( user ) && stack.getCommit().equals( commit ) && stack.getModule().equals( module ) ) { return stack; } } return null; } /** * Looks for a registered <code>CoordinatedStack</code> matching given parameters * * @param stack * @param user * @param commit * @param module * @return */ public CoordinatedStack getCoordinatedStack( Stack stack, User user, Commit commit, Module module ) { return registeredStacks.get( CoordinatedStack.calcHashCode( stack, user, commit, module ) ); } /** * Tries to find a registered <code>CoordinatedStack</code> object matching given parameters * <p> * Returns null if; * <ul> * <li>no users, modules or commits exist by supplied parameters</li> * <li>or no runner jars have been deployed matching these parameters</li> * <li>or no matching stack has been registered to be set up yet</li> * </ul> * Returns the matching <code>CoordinatedStack<code/> object otherwise * * @param commitId * @param artifactId * @param groupId * @param version * @param user * @return matching coordinated stack, or null */ public CoordinatedStack findCoordinatedStack( String commitId, String artifactId, String groupId, String version, String user ) { User chopUser = userDao.get( user ); if( chopUser == null ) { LOG.warn( "No such user: {}", user ); return null; } File runnerJar = CoordinatorUtils.getRunnerJar( chopUiFig.getContextPath(), user, groupId, artifactId, version, commitId ); if( ! runnerJar.exists() ) { LOG.warn( "No runner jars have been found by these parameters, deploy first" ); return null; } Stack stack = CoordinatorUtils.getStackFromRunnerJar( runnerJar ); if( stack == null ) { LOG.warn( "Could not read stack from runner.jar's resources" ); return null; } Module module = moduleDao.get( BasicModule.createId( groupId, artifactId, version ) ); if( module == null ) { LOG.warn( "No registered modules found by {}" + groupId + ":" + artifactId + ":" + version ); return null; } Commit commit = null; for( Commit c: commitDao.getByModule( module.getId() ) ) { if( commitId.equals( c.getId() ) ) { commit = c; break; } } if( commit == null ) { LOG.warn( "Commit with id {} is not found", commitId ); return null; } return getCoordinatedStack( stack, chopUser, commit, module ); } /** * @param commitId * @param artifactId * @param groupId * @param version * @param user * @return Setup state of given parameters' stack */ public SetupStackState stackStatus( String commitId, String artifactId, String groupId, String version, String user ) { CoordinatedStack stack = findCoordinatedStack( commitId, artifactId, groupId, version, user ); /** Stack is not registered in StackCoordinator */ if( stack == null ) { File runnerJar = CoordinatorUtils.getRunnerJar( chopUiFig.getContextPath(), user, groupId, artifactId, version, commitId ); if( ! runnerJar.exists() ) { return SetupStackState.JarNotFound; } return SetupStackState.NotSetUp; } return stack.getSetupState(); } /** * Removes the given failed coordinated stack object from <code>setupStackThreads</code> and * <code>registeredStacks</code> if indeed such a stack really exists and its setup failed * * @param stack CoordinatedStack object whose set up operation has failed */ public void removeFailedStack( CoordinatedStack stack ) { if( stack == null ) { return; } synchronized ( stack ) { if ( stack.getSetupState() != SetupStackState.SetupFailed ) { LOG.debug( "Setup didn't fail for given stack, so not removed" ); return; } registeredStacks.remove( stack.hashCode() ); setupStackThreads.remove( stack ); stack.notifyAll(); } } public CoordinatedStack registerStack( final String commitId, final String artifactId, final String groupId, final String version, final String user, final int runnerCount ) { User chopUser = userDao.get( user ); File runnerJar = CoordinatorUtils.getRunnerJar( chopUiFig.getContextPath(), user, groupId, artifactId, version, commitId ); Stack stack = CoordinatorUtils.getStackFromRunnerJar( runnerJar ); Module module = moduleDao.get( BasicModule.createId( groupId, artifactId, version ) ); Commit commit = null; for( Commit c: commitDao.getByModule( module.getId() ) ) { if( commitId.equals( c.getId() ) ) { commit = c; break; } } return registerStack( stack, chopUser, commit, module, runnerCount ); } public CoordinatedStack registerStack( final Stack stack, final User user, final Commit commit, final Module module, final int runnerCount ) { CoordinatedStack coordinatedStack = getCoordinatedStack( stack, user, commit, module ); if ( coordinatedStack != null ) { LOG.info( "Stack {} is already registered", stack.getName() ); return coordinatedStack; } else { coordinatedStack = new CoordinatedStack( stack, user, commit, module, runnerCount ); } LOG.info( "Registering stack...", stack.getName() ); synchronized ( coordinatedStack ) { coordinatedStack.setSetupState( SetupStackSignal.DEPLOY ); registeredStacks.put( coordinatedStack.hashCode(), coordinatedStack ); } return coordinatedStack; } }