Java tutorial
/** * 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(); } }