com.github.nethad.clustermeister.provisioning.ec2.AmazonNodeManager.java Source code

Java tutorial

Introduction

Here is the source code for com.github.nethad.clustermeister.provisioning.ec2.AmazonNodeManager.java

Source

/*
 * Copyright 2012 The Clustermeister Team.
 *
 * 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 com.github.nethad.clustermeister.provisioning.ec2;

import com.github.nethad.clustermeister.api.LogLevel;
import com.github.nethad.clustermeister.api.Node;
import com.github.nethad.clustermeister.api.impl.PasswordCredentials;
import com.github.nethad.clustermeister.api.utils.NodeManagementConnector;
import com.github.nethad.clustermeister.provisioning.CommandLineEvaluation;
import com.github.nethad.clustermeister.provisioning.CommandLineHandle;
import com.github.nethad.clustermeister.provisioning.dependencymanager.DependencyConfigurationUtil;
import com.github.nethad.clustermeister.provisioning.jppf.JPPFManagementByJobsClient;
import com.github.nethad.clustermeister.provisioning.rmi.RmiInfrastructure;
import com.google.common.base.Optional;
import static com.google.common.base.Preconditions.*;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.Monitor;
import com.google.common.util.concurrent.MoreExecutors;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeoutException;
import org.apache.commons.configuration.Configuration;
import org.jclouds.compute.RunNodesException;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeState;
import org.jppf.management.JMXDriverConnectionWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author daniel
 */
public class AmazonNodeManager {

    public static final int DEFAULT_SSH_PORT = 22;

    private final static Logger logger = LoggerFactory.getLogger(AmazonNodeManager.class);

    //TODO: move these managers to out of here to a class holding all managers
    final AmazonInstanceManager amazonInstanceManager;
    final AwsEc2Facade ec2Facade;
    final CredentialsManager credentialsManager;

    final Configuration configuration;

    RmiInfrastructure rmiInfrastructure = null;

    JPPFManagementByJobsClient managementClient = null;

    //TODO: make sure this will not cause a memory leak
    //    Map<AmazonNode, JPPFManagementByJobsClient> managementClients =
    //            Collections.synchronizedMap(new HashMap<AmazonNode, JPPFManagementByJobsClient>());
    private Set<AmazonNode> drivers = new HashSet<AmazonNode>();
    private Set<AmazonNode> nodes = new HashSet<AmazonNode>();

    private final Monitor managedNodesMonitor = new Monitor(false);

    private final ContextManager contextManager;
    private final ListeningExecutorService executorService;
    private PasswordCredentials awsWebApiCredentials;
    private String nodeJvmOptions;
    private LogLevel nodeLogLevel;
    private Boolean nodeRemoteLogging;
    private Integer nodeRemoteLoggingPort;
    private Map<String, AWSInstanceProfile> profiles;
    private Collection<File> artifactsToPreload;

    public AmazonNodeManager(Configuration configuration) {
        this.configuration = configuration;
        loadConfiguration(configuration);

        this.executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
        this.contextManager = new ContextManager(awsWebApiCredentials, executorService);
        this.ec2Facade = new AwsEc2Facade(contextManager);
        this.credentialsManager = new CredentialsManager(configuration, contextManager);
        this.amazonInstanceManager = new AmazonInstanceManager(contextManager, ec2Facade, profiles,
                artifactsToPreload);
    }

    public static CommandLineEvaluation commandLineEvaluation(Configuration configuration, CommandLineHandle handle,
            RmiInfrastructure rmiInfrastructure) {
        AmazonNodeManager amazonNodeManager = new AmazonNodeManager(configuration);
        amazonNodeManager.registerRmiInfrastructure(rmiInfrastructure);
        CommandLineEvaluation commandLineEvaluation = amazonNodeManager.getCommandLineEvaluation(handle);

        return commandLineEvaluation;
    }

    public CommandLineEvaluation getCommandLineEvaluation(CommandLineHandle commandLineHandle) {
        return new AmazonCommandLineEvaluation(this, commandLineHandle);
    }

    public Collection<? extends Node> getNodes() {
        managedNodesMonitor.enter();
        try {
            List<AmazonNode> allNodes = new ArrayList<AmazonNode>(drivers.size() + nodes.size());
            allNodes.addAll(nodes);
            allNodes.addAll(drivers);
            return Collections.unmodifiableCollection(allNodes);
        } finally {
            managedNodesMonitor.leave();
        }
    }

    public AmazonInstanceManager getInstanceManager() {
        return amazonInstanceManager;
    }

    public AwsEc2Facade getEc2Facade() {
        return ec2Facade;
    }

    public CredentialsManager getCredentialsManager() {
        return credentialsManager;
    }

    public ListenableFuture<? extends Node> addNode(AmazonNodeConfiguration nodeConfiguration,
            Optional<String> instanceId) {
        if (!nodeConfiguration.getJvmOptions().isPresent()) {
            nodeConfiguration.setJvmOptions(nodeJvmOptions);
        }
        if (!nodeConfiguration.getLogLevel().isPresent()) {
            nodeConfiguration.setLogLevel(nodeLogLevel);
        }
        if (!nodeConfiguration.isRemoteLoggingActivataed().isPresent()) {
            nodeConfiguration.setRemoteLoggingActivated(nodeRemoteLogging);
        }
        if (!nodeConfiguration.getRemoteLoggingPort().isPresent()) {
            nodeConfiguration.setRemoteLoggingPort(nodeRemoteLoggingPort);
        }
        return executorService.submit(new AmazonNodeManager.AddNodeTask(nodeConfiguration, instanceId));
    }

    /**
     *
     * @param node
     * @param shutdownState 
     * @return   The future returns null upon successful completion.
     */
    public ListenableFuture<Boolean> removeNode(AmazonNode node, AmazonInstanceShutdownState shutdownState) {
        return executorService
                .submit(new AmazonNodeManager.RemoveNodeTask(node, shutdownState, amazonInstanceManager));

    }

    /**
     *
     * @param node
     * @return   The future returns null upon successful completion.
     */
    public ListenableFuture<Boolean> removeNode(AmazonNode node) {
        return executorService.submit(
                new AmazonNodeManager.RemoveNodeTask(node, node.getInstanceShutdownState(), amazonInstanceManager));
    }

    public void registerManagementClient(JPPFManagementByJobsClient client) {
        this.managementClient = client;
    }

    public void registerRmiInfrastructure(RmiInfrastructure rmiInfrastructure) {
        this.rmiInfrastructure = rmiInfrastructure;
    }

    //    public void removeAllNodes(AmazonInstanceShutdownState shutdownMethod) {
    //        try {
    //            managementClient.shutdownAllNodes();
    //            managedNodesMonitor.enter();
    //            try {
    //                for(AmazonNode node : nodes) {
    //                    switch(shutdownMethod) {
    //                        case SUSPENDED: {
    //                            amazonInstanceManager.suspendInstance(node.getInstanceId());
    //                            break;
    //                        }
    //                        case TERMINATED: {
    //                            amazonInstanceManager.terminateInstance(node.getInstanceId());
    //                            break;
    //                        }
    //                    }
    //                }
    //                nodes.clear();
    //            } finally {
    //                managedNodesMonitor.leave();
    //            }
    //        } catch (Exception ex) {
    //            logger.warn("Failed to shut down all nodes.", ex);
    //        }
    //    }

    public void close() {
        managedNodesMonitor.enter();
        try {
            drivers.clear();
            nodes.clear();
        } finally {
            managedNodesMonitor.leave();
        }
        amazonInstanceManager.close();
        contextManager.close();
    }

    private void addManagedNode(AmazonNode node) {
        managedNodesMonitor.enter();
        try {
            switch (node.getType()) {
            case NODE: {
                nodes.add(node);
                break;
            }
            case DRIVER: {
                drivers.add(node);
                //                    String publicIp = Iterables.getFirst(node.getPublicAddresses(), null);
                //                    managementClients.put(node, JPPFConfiguratedComponentFactory.getInstance().
                //                            createManagementByJobsClient(publicIp, AmazonNodeManager.DEFAULT_SERVER_PORT));
                break;
            }
            default: {
                throw new IllegalArgumentException("Invalid Node Type.");
            }
            }
        } finally {
            managedNodesMonitor.leave();
        }
    }

    private void removeManagedNode(AmazonNode node) {
        managedNodesMonitor.enter();
        try {
            switch (node.getType()) {
            case NODE: {
                nodes.remove(node);
                break;
            }
            case DRIVER: {
                drivers.remove(node);
                break;
            }
            default: {
                throw new IllegalArgumentException("Invalid Node Type.");
            }
            }
        } finally {
            managedNodesMonitor.leave();
        }
    }

    private AmazonNode getDriverForNode(final AmazonNode node) {
        managedNodesMonitor.enter();
        try {
            return Iterables.find(drivers, new Predicate<AmazonNode>() {
                @Override
                public boolean apply(AmazonNode driver) {
                    String driverAddress = Iterables.getFirst(driver.getPrivateAddresses(), null);
                    checkNotNull(driverAddress);
                    return node.getDriverAddress().equals(driverAddress);
                }
            }, null);
        } finally {
            managedNodesMonitor.leave();
        }
    }

    private void loadConfiguration(Configuration configuration) {
        AmazonConfigurationLoader configurationLoader = new AmazonConfigurationLoader(configuration);

        awsWebApiCredentials = new PasswordCredentials("AWS Web API Credentials",
                configurationLoader.getAccessKeyId(), configurationLoader.getSecretKey());
        nodeJvmOptions = configurationLoader.getNodeJvmOptions();
        nodeLogLevel = configurationLoader.getNodeLogLevel();
        nodeRemoteLogging = configurationLoader.getNodeRemoteLogging();
        nodeRemoteLoggingPort = configurationLoader.getNodeRemoteLoggingPort();

        profiles = Collections.synchronizedMap(configurationLoader.getConfiguredProfiles());

        artifactsToPreload = DependencyConfigurationUtil.getConfiguredDependencies(configuration);
    }

    private class AddNodeTask implements Callable<AmazonNode> {

        private final AmazonNodeConfiguration nodeConfiguration;
        private final Optional<String> instanceId;

        public AddNodeTask(AmazonNodeConfiguration nodeConfiguration, Optional<String> instanceId) {
            this.nodeConfiguration = nodeConfiguration;
            this.instanceId = instanceId;
        }

        @Override
        public AmazonNode call() throws Exception {
            NodeMetadata instanceMetadata;
            if (!instanceId.isPresent()) {
                try {
                    Optional<Map<String, String>> noMap = Optional.absent();
                    instanceMetadata = amazonInstanceManager.createInstance(nodeConfiguration, noMap);
                } catch (RunNodesException ex) {
                    logger.warn("Failed to create instance.", ex);
                    return null;
                }
            } else {
                instanceMetadata = ec2Facade.getInstanceMetadata(instanceId.get());
                if (instanceMetadata.getState() == NodeState.SUSPENDED) {
                    ec2Facade.resumeInstance(instanceMetadata.getId());
                    instanceMetadata = ec2Facade.getInstanceMetadata(instanceId.get());
                }
            }
            AmazonNode node;
            try {
                logger.info("Deploying JPPF-{} on {}", nodeConfiguration.getType().toString(),
                        instanceMetadata.getId());
                node = amazonInstanceManager.deploy(instanceMetadata, nodeConfiguration);
                node.setDriver(getDriverForNode(node));
                logger.info("JPPF-{} deployed on {}.", nodeConfiguration.getType().toString(),
                        instanceMetadata.getId());
            } catch (Throwable ex) {
                logger.warn("Failed to deploy node.", ex);
                if (instanceId.isPresent()) {
                    amazonInstanceManager.suspendInstance(instanceMetadata.getId());
                } else {
                    amazonInstanceManager.terminateInstance(instanceMetadata.getId());
                }
                return null;
            }

            addManagedNode(node);

            return node;
        }
    }

    private class RemoveNodeTask implements Callable<Boolean> {

        private final AmazonNode node;
        private final AmazonInstanceShutdownState shutdownState;
        private final AmazonInstanceManager instanceManager;

        public RemoveNodeTask(AmazonNode node, AmazonInstanceShutdownState shutdownState,
                AmazonInstanceManager instanceManager) {
            this.node = node;
            if (node.nodeConfiguration.getProfile().getSpotPrice().isPresent()
                    && shutdownState == AmazonInstanceShutdownState.SUSPENDED) {
                this.shutdownState = AmazonInstanceShutdownState.TERMINATED;
            } else {
                this.shutdownState = shutdownState;
            }
            this.instanceManager = instanceManager;
        }

        @Override
        public Boolean call() throws Exception {
            String publicIp = Iterables.getFirst(node.getPublicAddresses(), null);
            checkNotNull(publicIp, "Can not get public IP of node " + node + ".");
            switch (node.getType()) {
            case DRIVER: {
                //                    JPPFManagementByJobsClient client = managementClients.remove(node);
                //                    if(client != null) {
                //                        client.close();
                //                    }
                driverShutdown(publicIp);
                break;
            }
            case NODE: {
                nodeShutdown(node);
                break;
            }
            default: {
                throw new IllegalArgumentException("Invalid node type");
            }
            }

            switch (shutdownState) {
            case SUSPENDED: {
                instanceManager.suspendInstance(node.getInstanceId());
                break;
            }
            case TERMINATED: {
                instanceManager.terminateInstance(node.getInstanceId());
                break;
            }
            case RUNNING: {
                //do nothing
                break;
            }
            default: {
                logger.warn("Invalid shutdown method specified. Suspending instance...");
                instanceManager.suspendInstance(node.getInstanceId());
                break;
            }
            }

            removeManagedNode(node);

            return Boolean.TRUE;
        }

        private void driverShutdown(String publicIp) throws TimeoutException, Exception {
            JMXDriverConnectionWrapper wrapper = new JMXDriverConnectionWrapper(publicIp, node.getManagementPort());
            NodeManagementConnector.connectToNodeManagement(wrapper);
            logger.info("Shutting driver node {}:{}.", publicIp, node.getManagementPort());
            wrapper.restartShutdown(0l, -1l);
            try {
                wrapper.close();
            } catch (Exception ex) {
                logger.warn("Could not close connection to node management.", ex);
            }
        }

        private void nodeShutdown(AmazonNode node) {
            if (managementClient != null) {
                logger.info("Shutting down node {}.", node);
                try {
                    managementClient.shutdownNode(node.getID());
                } catch (Exception ex) {
                    logger.warn("Failed to shut down {}.\n{}", node, ex.getMessage());
                }
            } else {
                logger.warn("Can not shut down {}. No management client registered.", node);
            }
        }
    }
}