com.ottogroup.bi.asap.resman.node.ProcessingNodeManager.java Source code

Java tutorial

Introduction

Here is the source code for com.ottogroup.bi.asap.resman.node.ProcessingNodeManager.java

Source

/**
 * Copyright 2014 Otto (GmbH & Co KG)
 *
 * 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.ottogroup.bi.asap.resman.node;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import com.ottogroup.bi.asap.exception.ProcessingNodeAlreadyRegisteredException;
import com.ottogroup.bi.asap.exception.RemoteClientConnectionFailedException;
import com.ottogroup.bi.asap.exception.RequiredInputMissingException;
import com.ottogroup.bi.asap.exception.UnknownProcessingNodeException;
import com.ottogroup.bi.asap.resman.pipeline.PipelineDeploymentProfile;

/**
 * Collects and maintains all {@link ProcessingNode} instances 
 * @author mnxfst
 * @since Nov 26, 2014
 * TODO keep track of deployed pipelines
 * TODO keep track of processing node resources 
 * TODO use UUID as unique node identifier or keep host#servicePort
 * TODO test pipeline instantiation
 * TODO test pipeline update or instantiation
 * TODO test pipeline shutdown
 * TODO exceptions at instantation and update may be removed and handled inside the method
 */
public class ProcessingNodeManager {

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

    /** references towards handler instances of all running processing nodes */
    private final Map<String, ProcessingNode> processingNodes = new HashMap<>();

    /**
     * Registers a new {@link ProcessingNode} using the provided connection parameters
     * @param protocol
     * @param host
     * @param servicePort
     * @param adminPort
     * @throws RequiredInputMissingException
     */
    public String registerProcessingNode(final String protocol, final String host, final int servicePort,
            final int adminPort) throws RequiredInputMissingException, ProcessingNodeAlreadyRegisteredException {

        ///////////////////////////////////////////////////////
        // validate input
        if (StringUtils.isBlank(protocol))
            throw new RequiredInputMissingException("Missing required protocol");
        if (StringUtils.isBlank(host))
            throw new RequiredInputMissingException("Missing required host");
        if (servicePort < 1)
            throw new RequiredInputMissingException("Invalid service port");
        if (adminPort < 1)
            throw new RequiredInputMissingException("Invalid admin port");

        String hostIdentifier = createNodeIdentifier(host, servicePort);
        if (processingNodes.containsKey(hostIdentifier))
            throw new ProcessingNodeAlreadyRegisteredException("A processing node handler already exists for [host="
                    + host + ", servicePort=" + servicePort + ", adminPort=" + adminPort + "]");
        //
        ///////////////////////////////////////////////////////

        ProcessingNode processingNode = new ProcessingNode(hostIdentifier, protocol, host, servicePort, adminPort);
        this.processingNodes.put(hostIdentifier, processingNode);

        if (logger.isDebugEnabled())
            logger.debug("processing node registered [id=" + hostIdentifier + ", protocol=" + protocol + ", host="
                    + host + ", servicePort=" + servicePort + ", adminPort=" + adminPort + "]");

        return hostIdentifier;
    }

    /**
     * Shuts down the referenced processing node by sending a corresponding message and removing it from internal handler map 
     * @param processingNodeId
     * @return
     * @throws RequiredInputMissingException
     */
    public String shutdownProcessingNode(final String processingNodeId)
            throws RequiredInputMissingException, UnknownProcessingNodeException {

        ///////////////////////////////////////////////////////
        // validate input
        if (StringUtils.isBlank(processingNodeId))
            throw new RequiredInputMissingException("Missing processing node identifier");
        String pid = StringUtils.lowerCase(StringUtils.trim(processingNodeId));
        if (!this.processingNodes.containsKey(pid))
            throw new UnknownProcessingNodeException(
                    "Referenced processing node '" + processingNodeId + "' unknown to node manager");
        //
        ///////////////////////////////////////////////////////

        final ProcessingNode node = this.processingNodes.remove(pid);
        node.shutdown();

        if (logger.isDebugEnabled())
            logger.debug("processing node shutdown [id=" + node.getId() + ", protocol=" + node.getProtocol()
                    + ", host=" + node.getHost() + ", servicePort=" + node.getServicePort() + ", adminPort="
                    + node.getAdminPort() + "]");

        return processingNodeId;
    }

    /**
     * De-Registers the referenced processing node from the internal handler map. The node is expected to be shut down already
     * and thus no corresponding message is sent towards it
     * @param processingNodeId
     * @throws RequiredInputMissingException
     */
    public String deregisterProcessingNode(final String processingNodeId)
            throws RequiredInputMissingException, UnknownProcessingNodeException {

        ///////////////////////////////////////////////////////
        // validate input
        if (StringUtils.isBlank(processingNodeId))
            throw new RequiredInputMissingException("Missing processing node identifier");
        String pid = StringUtils.lowerCase(StringUtils.trim(processingNodeId));
        if (!this.processingNodes.containsKey(pid))
            throw new UnknownProcessingNodeException(
                    "Referenced processing node '" + processingNodeId + "' unknown to node manager");
        //
        ///////////////////////////////////////////////////////

        final ProcessingNode node = this.processingNodes.remove(pid);

        if (logger.isDebugEnabled())
            logger.debug("processing node deregistered [id=" + node.getId() + ", protocol=" + node.getProtocol()
                    + ", host=" + node.getHost() + ", servicePort=" + node.getServicePort() + ", adminPort="
                    + node.getAdminPort() + "]");

        return processingNodeId;
    }

    /**
     * Instantiates a pipeline on remote processing nodes
     * @param deploymentProfile provides information on what and how to distribute the pipeline within the cluster
     * @return
     * @throws RequiredInputMissingException
     */
    public PipelineDeploymentProfile instantiatePipeline(final PipelineDeploymentProfile deploymentProfile)
            throws RequiredInputMissingException {

        ///////////////////////////////////////////////////////
        // validate input
        if (deploymentProfile == null)
            throw new RequiredInputMissingException("Missing required deployment profile");
        //
        ///////////////////////////////////////////////////////

        for (final ProcessingNode pn : this.processingNodes.values()) {
            try {
                pn.instantiatePipeline(deploymentProfile.getConfiguration());
                deploymentProfile.addProcessingNode(StringUtils.lowerCase(StringUtils.trim(pn.getId())));
            } catch (RemoteClientConnectionFailedException | IOException e) {
                logger.error("Failed to deploy pipeline '" + deploymentProfile.getId() + "' on processing node '"
                        + pn.getId() + "'. Error: " + e.getMessage());
            }
        }

        return deploymentProfile;
    }

    /**
     * Updates or instantiates a pipeline on remote processing nodes
     * @param deploymentProfile
     * @return
     * @throws RequiredInputMissingException
     */
    public PipelineDeploymentProfile updateOrInstantiatePipeline(final PipelineDeploymentProfile deploymentProfile)
            throws RequiredInputMissingException {

        ///////////////////////////////////////////////////////
        // validate input
        if (deploymentProfile == null)
            throw new RequiredInputMissingException("Missing required pipeline configuration");
        // TODO validate even more
        //
        ///////////////////////////////////////////////////////

        for (final ProcessingNode pn : this.processingNodes.values()) {
            try {
                pn.updateOrInstantiatePipeline(deploymentProfile.getConfiguration());
                deploymentProfile.addProcessingNode(StringUtils.lowerCase(StringUtils.trim(pn.getId())));
            } catch (RemoteClientConnectionFailedException | IOException e) {
                logger.error("Failed to update or deploy pipeline '" + deploymentProfile.getId()
                        + "' on processing node '" + pn.getId() + "'. Error: " + e.getMessage());
            }
        }

        return deploymentProfile;
    }

    /**
     * Shuts down the referenced pipeline on remote processing nodes
     * @param pipelineDeploymentProfile
     * @return
     * @throws RequiredInputMissingException
     * @throws RemoteClientConnectionFailedException 
     * @throws IOException
     */
    public PipelineDeploymentProfile shutdownPipeline(final PipelineDeploymentProfile pipelineDeploymentProfile)
            throws RequiredInputMissingException {
        ///////////////////////////////////////////////////////
        // validate input
        if (pipelineDeploymentProfile == null)
            throw new RequiredInputMissingException("Missing required pipeline deployment profile");
        if (StringUtils.isBlank(pipelineDeploymentProfile.getId()))
            throw new RequiredInputMissingException("Missing required pipeline identifier");
        // TODO validate even more
        //
        ///////////////////////////////////////////////////////

        for (final Iterator<String> procNodeIdIter = pipelineDeploymentProfile.getProcessingNodes()
                .iterator(); procNodeIdIter.hasNext();) {
            String processingNodeId = procNodeIdIter.next();
            final ProcessingNode pn = this.processingNodes.get(processingNodeId);
            try {
                pn.shutdownPipeline(pipelineDeploymentProfile.getId());
                procNodeIdIter.remove();
            } catch (RemoteClientConnectionFailedException | RequiredInputMissingException | IOException e) {
                logger.error("Failed to shutdown pipeline '" + pipelineDeploymentProfile.getId()
                        + "' on processing node '" + processingNodeId + "'. Error: " + e.getMessage());
            }
        }

        return pipelineDeploymentProfile;
    }

    /**
     * Generates an unique node identifier
     * @param host
     * @param servicePort
     * @return
     * @throws RequiredInputMissingException thrown in case either the host or service port is missing
     */
    protected String createNodeIdentifier(final String host, final int servicePort)
            throws RequiredInputMissingException {
        return new StringBuffer(StringUtils.lowerCase(StringUtils.trim(host))).append("#").append(servicePort)
                .toString();
    }

}