/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package se.kth.karamel.backend; import com.google.gson.Gson; import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.log4j.Logger; import se.kth.karamel.backend.launcher.amazon.Ec2Context; import se.kth.karamel.backend.launcher.google.GceContext; import se.kth.karamel.backend.launcher.nova.NovaContext; import se.kth.karamel.backend.launcher.occi.OcciContext; import se.kth.karamel.backend.running.model.ClusterRuntime; import se.kth.karamel.core.clusterdef.ClusterDefinitionValidator; import se.kth.karamel.common.exception.KaramelException; import se.kth.karamel.common.clusterdef.json.JsonCluster; import se.kth.karamel.common.util.SshKeyPair; import se.kth.karamel.common.util.SshKeyService; /** * Keeps repository of running clusters with a unique name for each. Privacy sensitive data such as credentials is * stored inside a context. There is a common context with shared values between clusters and each cluster has its own * context inside which values can be overwritten. * * @author kamal */ public class ClusterService { private static final Logger logger = Logger.getLogger(ClusterService.class); private static final ClusterService instance = new ClusterService(); public static ExecutorService SHARED_GLOBAL_TP = Executors.newFixedThreadPool(50); private final ClusterContext commonContext = new ClusterContext(); private final Map<String, ClusterManager> repository = new HashMap<>(); private final Map<String, ClusterContext> clusterContexts = new HashMap<>(); public static ClusterService getInstance() { return instance; } public ClusterContext getCommonContext() { return commonContext; } public Map<String, ClusterManager> getRepository() { return repository; } public Map<String, ClusterContext> getClusterContexts() { return clusterContexts; } public synchronized void registerSudoAccountPassword(String password) { commonContext.setSudoAccountPassword(password); } public synchronized void registerEc2Context(Ec2Context ec2Context) throws KaramelException { commonContext.setEc2Context(ec2Context); } public synchronized void registerEc2Context(String clusterName, Ec2Context ec2Context) throws KaramelException { String name = clusterName.toLowerCase(); if (repository.containsKey(name)) { logger.error(String.format("'%s' is already running, you cannot change the ec2 credentials now :-|", clusterName)); throw new KaramelException(String.format("Cluster '%s' is already running", clusterName)); } ClusterContext clusterContext = clusterContexts.get(name); if (clusterContext == null) { clusterContext = new ClusterContext(); } clusterContext.setEc2Context(ec2Context); clusterContexts.put(name, clusterContext); } public synchronized void registerGceContext(GceContext gceContext) throws KaramelException { commonContext.setGceContext(gceContext); } public synchronized void registerSshKeyPair(SshKeyPair sshKeyPair) throws KaramelException { File pubKey = new File(sshKeyPair.getPublicKeyPath()); if (pubKey.exists() == false) { throw new KaramelException("Could not find public key: " + sshKeyPair.getPublicKeyPath()); } File privKey = new File(sshKeyPair.getPrivateKeyPath()); if (privKey.exists() == false) { throw new KaramelException("Could not find private key: " + sshKeyPair.getPrivateKeyPath()); } sshKeyPair.setNeedsPassword(SshKeyService.checkIfPasswordNeeded(sshKeyPair)); // boolean needsPassword = SshKeyService.checkIfPasswordNeeded(sshKeyPair); // if (needsPassword) { // if (sshKeyPair.getPassphrase() == null || sshKeyPair.getPassphrase().isEmpty()) { // throw new KaramelException("The passphrase needs to be entered for the OpenSshKey."); // } // sshKeyPair.setNeedsPassword(true); // } commonContext.setSshKeyPair(sshKeyPair); } public synchronized void registerSshKeyPair(String clusterName, SshKeyPair sshKeyPair) throws KaramelException { String name = clusterName.toLowerCase(); if (repository.containsKey(name)) { logger.error(String.format("'%s' is already running, you cannot change the ssh keypair now :-|", clusterName)); throw new KaramelException(String.format("Cluster '%s' is already running", clusterName)); } ClusterContext clusterContext = clusterContexts.get(name); if (clusterContext == null) { clusterContext = new ClusterContext(); } clusterContext.setSshKeyPair(sshKeyPair); clusterContexts.put(name, clusterContext); } public synchronized ClusterRuntime clusterStatus(String clusterName) throws KaramelException { String name = clusterName.toLowerCase(); if (!repository.containsKey(name)) { throw new KaramelException(String.format("Repository doesn't contain a cluster name '%s'", clusterName)); } ClusterManager cluster = repository.get(name); return cluster.getRuntime(); } public synchronized void startCluster(String json) throws KaramelException { Gson gson = new Gson(); JsonCluster jsonCluster = gson.fromJson(json, JsonCluster.class); ClusterDefinitionValidator.validate(jsonCluster); String yml = ClusterDefinitionService.jsonToYaml(jsonCluster); //We have to do it again otherwise the global scope attributes get lost //for more info refer to https://github.com/karamelchef/karamel/issues/28 jsonCluster = ClusterDefinitionService.yamlToJsonObject(yml); ClusterDefinitionService.saveYaml(yml); logger.debug(String.format("Let me see if I can start '%s' ...", jsonCluster.getName())); String clusterName = jsonCluster.getName(); String name = clusterName.toLowerCase(); if (repository.containsKey(name)) { logger.info(String.format("'%s' is already running :-|", jsonCluster.getName())); throw new KaramelException(String.format("Cluster '%s' is already running", clusterName)); } ClusterContext checkedContext = checkContext(jsonCluster); ClusterManager cluster = new ClusterManager(jsonCluster, checkedContext); repository.put(name, cluster); cluster.start(); cluster.enqueue(ClusterManager.Command.LAUNCH_CLUSTER); cluster.enqueue(ClusterManager.Command.SUBMIT_INSTALL_DAG); } public synchronized void submitInstallationDag(String clusterName) throws KaramelException { String name = clusterName.toLowerCase(); logger.info(String.format("User asked to install '%s'", clusterName)); if (!repository.containsKey(name)) { throw new KaramelException(String.format("Repository doesn't contain a cluster name '%s'", clusterName)); } ClusterManager cluster = repository.get(name); checkContext(cluster.getDefinition()); cluster.enqueue(ClusterManager.Command.INTERRUPT_DAG); cluster.enqueue(ClusterManager.Command.SUBMIT_INSTALL_DAG); } public synchronized void submitPurgeDag(String clusterName) throws KaramelException { String name = clusterName.toLowerCase(); logger.info(String.format("User asked for purging '%s'", clusterName)); if (!repository.containsKey(name)) { throw new KaramelException(String.format("Repository doesn't contain a cluster name '%s'", clusterName)); } ClusterManager cluster = repository.get(name); checkContext(cluster.getDefinition()); cluster.enqueue(ClusterManager.Command.INTERRUPT_DAG); cluster.enqueue(ClusterManager.Command.SUBMIT_PURGE_DAG); } public synchronized void pauseDag(String clusterName) throws KaramelException { String name = clusterName.toLowerCase(); logger.info(String.format("User asked for pausing the cluster '%s'", clusterName)); if (!repository.containsKey(name)) { throw new KaramelException(String.format("Repository doesn't contain a cluster name '%s'", clusterName)); } ClusterManager cluster = repository.get(name); checkContext(cluster.getDefinition()); cluster.enqueue(ClusterManager.Command.PAUSE_DAG); } public synchronized void resumeDag(String clusterName) throws KaramelException { String name = clusterName.toLowerCase(); logger.info(String.format("User asked for resuming the cluster '%s'", clusterName)); if (!repository.containsKey(name)) { throw new KaramelException(String.format("Repository doesn't contain a cluster name '%s'", clusterName)); } ClusterManager cluster = repository.get(name); checkContext(cluster.getDefinition()); cluster.enqueue(ClusterManager.Command.RESUME_DAG); } public synchronized void terminateCluster(String clusterName) throws KaramelException { String name = clusterName.toLowerCase(); logger.info(String.format("User asked for terminating the cluster '%s'", clusterName)); if (!repository.containsKey(name)) { throw new KaramelException(String.format("Repository doesn't contain a cluster name '%s'", clusterName)); } final ClusterManager cluster = repository.get(name); Thread t = new Thread() { @Override public void run() { try { ClusterRuntime runtime = cluster.getRuntime(); cluster.enqueue(ClusterManager.Command.INTERRUPT_CLUSTER); cluster.enqueue(ClusterManager.Command.TERMINATE_CLUSTER); while (runtime.getPhase() != ClusterRuntime.ClusterPhases.NOT_STARTED) { Thread.sleep(100); } String name = runtime.getName().toLowerCase(); logger.info(String.format("Cluster '%s' terminated, removing it from the list of running clusters", runtime.getName())); repository.remove(name); } catch (InterruptedException ex) { } catch (KaramelException ex) { logger.error("", ex); } } }; t.start(); } private ClusterContext checkContext(JsonCluster definition) throws KaramelException { String name = definition.getName().toLowerCase(); ClusterContext context = clusterContexts.get(name); ClusterContext validatedContext = ClusterContext.validateContext(definition, context, commonContext); clusterContexts.put(name, validatedContext); return validatedContext; } public synchronized void registerNovaContext(NovaContext context) { commonContext.setNovaContext(context); } public synchronized void registerOcciContext(OcciContext context) { commonContext.setOcciContext(context); } }