/** * Copyright 2015 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.spqr.node.server; import io.dropwizard.Application; import io.dropwizard.client.JerseyClientBuilder; import io.dropwizard.setup.Environment; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; import com.codahale.metrics.jvm.ClassLoadingGaugeSet; import com.codahale.metrics.jvm.FileDescriptorRatioGauge; import com.codahale.metrics.jvm.GarbageCollectorMetricSet; import com.codahale.metrics.jvm.MemoryUsageGaugeSet; import com.codahale.metrics.jvm.ThreadStatesGaugeSet; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.ottogroup.bi.spqr.exception.RemoteClientConnectionFailedException; import com.ottogroup.bi.spqr.exception.RequiredInputMissingException; import com.ottogroup.bi.spqr.metrics.MetricsHandler; import com.ottogroup.bi.spqr.metrics.MetricsReporterFactory; import com.ottogroup.bi.spqr.node.message.NodeRegistration.NodeRegistrationResponse; import com.ottogroup.bi.spqr.node.message.NodeRegistration.NodeRegistrationState; import com.ottogroup.bi.spqr.node.resman.SPQRResourceManagerClient; import com.ottogroup.bi.spqr.node.resource.pipeline.MicroPipelineResource; import com.ottogroup.bi.spqr.node.server.cfg.SPQRNodeMetricsConfiguration; import com.ottogroup.bi.spqr.node.server.cfg.SPQRNodeServerConfiguration; import com.ottogroup.bi.spqr.node.server.cfg.SPQRResourceManagerConfiguration; import com.ottogroup.bi.spqr.node.server.cfg.SPQRResourceManagerConfiguration.ResourceManagerMode; import com.ottogroup.bi.spqr.pipeline.MicroPipeline; import com.ottogroup.bi.spqr.pipeline.MicroPipelineManager; import com.ottogroup.bi.spqr.pipeline.metrics.MicroPipelineMetricsReporterConfiguration; import com.ottogroup.bi.spqr.repository.ComponentDescriptor; import com.ottogroup.bi.spqr.repository.ComponentRepository; /** * Ramps up SPQR processing nodes which receive requests for {@link MicroPipeline} execution * @author mnxfst * @since Mar 13, 2015 */ public class SPQRNodeServer extends Application<SPQRNodeServerConfiguration> { private static final Logger logger = Logger.getLogger(SPQRNodeServer.class); private MicroPipelineManager microPipelineManager; private String nodeId; private SPQRResourceManagerClient resourceManagerClient; /** * @see io.dropwizard.Application#run(io.dropwizard.Configuration, io.dropwizard.setup.Environment) */ public void run(SPQRNodeServerConfiguration configuration, Environment environment) throws Exception { // initialize log4j environment using the settings provided via node configuration initializeLog4j(configuration.getSpqrNode().getLog4jConfiguration()); // check which type of resource management is requested via configuration if(configuration.getResourceManagerConfiguration().getMode() == ResourceManagerMode.REMOTE) { // prepare for use remote resource management: fetch configuration SPQRResourceManagerConfiguration resManCfg = configuration.getResourceManagerConfiguration(); logger.info("[resource manager [mode="+ResourceManagerMode.REMOTE+", protocol="+resManCfg.getProtocol()+", host="+resManCfg.getHost()+", port="+resManCfg.getPort()+"]"); // initialize client to use for remote communication this.resourceManagerClient = new SPQRResourceManagerClient(resManCfg.getProtocol(), resManCfg.getHost(), resManCfg.getPort(), new JerseyClientBuilder(environment).using(configuration.getHttpClient()).build("resourceManagerClient")); // finally: register node with remote resource manager this.nodeId = registerProcessingNode(configuration.getSpqrNode().getProtocol(), configuration.getSpqrNode().getHost(), configuration.getSpqrNode().getServicePort(), configuration.getSpqrNode().getAdminPort(), this.resourceManagerClient); logger.info("node successfully registered [id="+nodeId+", proto="+configuration.getSpqrNode().getProtocol()+", host="+configuration.getSpqrNode().getHost()+", servicePort="+configuration.getSpqrNode().getServicePort()+", adminPort="+configuration.getSpqrNode().getAdminPort()+"]"); } else { this.nodeId = "standalone"; this.resourceManagerClient = null; logger.info("resource manager [mode="+ResourceManagerMode.LOCAL+"]"); } // initialize the micro pipeline manager this.microPipelineManager = new MicroPipelineManager(this.nodeId, loadAndDeployApplicationRepository(configuration.getSpqrNode().getComponentRepositoryFolder()), configuration.getSpqrNode().getNumOfThreads()); logger.info("pipeline manager initialized [threads="+configuration.getSpqrNode().getNumOfThreads()+", repo="+configuration.getSpqrNode().getComponentRepositoryFolder()+"]"); // register exposed resources environment.jersey().register(new MicroPipelineResource(this.microPipelineManager)); // register shutdown handler Runtime.getRuntime().addShutdownHook(new SPQRNodeShutdownHandler(this.microPipelineManager, this.resourceManagerClient, nodeId)); // register metrics handler MetricsHandler handler = new MetricsHandler(); if(configuration.getSpqrNode().getSpqrMetrics() != null) { final SPQRNodeMetricsConfiguration metricsCfg = configuration.getSpqrNode().getSpqrMetrics(); if(metricsCfg.getMetricsReporter() != null && !metricsCfg.getMetricsReporter().isEmpty()) { if(metricsCfg.isAttachClassLoadingMetricCollector()) { handler.register(new ClassLoadingGaugeSet()); } if(metricsCfg.isAttachFileDescriptorMetricCollector()) { handler.register("fs", new FileDescriptorRatioGauge()); } if(metricsCfg.isAttachGCMetricCollector()) { handler.register(new GarbageCollectorMetricSet()); } if(metricsCfg.isAttachMemoryUsageMetricCollector()) { handler.register(new MemoryUsageGaugeSet()); } if(metricsCfg.isAttachThreadStateMetricCollector()) { handler.register(new ThreadStatesGaugeSet()); } MetricsReporterFactory.attachReporters(handler, metricsCfg.getMetricsReporter()); logger.info("metrics[classloader="+metricsCfg.isAttachClassLoadingMetricCollector()+ ",fileSystem="+metricsCfg.isAttachFileDescriptorMetricCollector()+ ",gc="+metricsCfg.isAttachGCMetricCollector()+ ",memory="+metricsCfg.isAttachMemoryUsageMetricCollector()+ ",threadState="+metricsCfg.isAttachThreadStateMetricCollector()+"]"); StringBuffer buf = new StringBuffer(); for(final MicroPipelineMetricsReporterConfiguration mr : metricsCfg.getMetricsReporter()) { buf.append("(id=").append(mr.getId()).append(", type=").append(mr.getType()).append(")"); } logger.info("metricReporters["+buf.toString()+"]"); } } else { logger.info("no metrics and metric reporters configured for processing node '"+nodeId+"'"); } } /** * Initializes the log4j framework by pointing it to the configuration file referenced by parameter * @param log4jConfigurationFile */ protected void initializeLog4j(final String log4jConfigurationFile) { String log4jPropertiesFile = StringUtils.lowerCase(StringUtils.trim(log4jConfigurationFile)); if(StringUtils.isNoneBlank(log4jConfigurationFile)) { File log4jFile = new File(log4jPropertiesFile); if(log4jFile.isFile()) { try { PropertyConfigurator.configure(new FileInputStream(log4jFile)); } catch (FileNotFoundException e) { System.out.println("No log4j configuration found at '"+log4jConfigurationFile+"'"); } } else { System.out.println("No log4j configuration found at '"+log4jConfigurationFile+"'"); } } else { System.out.println("No log4j configuration file provided"); } } /** * Registers this processing node with the remote resource manager * @param protocol * @param host * @param servicePort * @param adminPort * @return * @throws RequiredInputMissingException * @throws RemoteClientConnectionFailedException * @throws IOException */ protected String registerProcessingNode(final String protocol, final String host, final int servicePort, final int adminPort, final SPQRResourceManagerClient client) throws RequiredInputMissingException, RemoteClientConnectionFailedException, IOException { /////////////////////////////////////////////////////////////////////////// // validate provided input if(StringUtils.isBlank(protocol)) throw new RequiredInputMissingException("Missing require communication protocol used by resource manager for accessing this node. See 'protocol' property in config file."); if(StringUtils.isBlank(host)) throw new RequiredInputMissingException("Missing required host name used by resource manager for accessing this node. See 'host' property in config file."); if(servicePort < 1) throw new RequiredInputMissingException("Missing required service port used by resource manager for accessing this node. See 'servicePort' property in config file."); if(adminPort < 1) throw new RequiredInputMissingException("Missing required admin port used by resource manager for accessing this node. See 'adminPort' property in config file."); // /////////////////////////////////////////////////////////////////////////// final NodeRegistrationResponse registrationResponse = client.registerNode(protocol, host, servicePort, adminPort); if(registrationResponse == null) throw new RemoteClientConnectionFailedException("Failed to connect with resource manager. Error: no response received"); if(registrationResponse.getState() != NodeRegistrationState.OK) throw new RemoteClientConnectionFailedException("Failed to register processing node with resource manage. Reason: " + registrationResponse.getState() + ". Message: " + registrationResponse.getMessage()); return registrationResponse.getId(); } /** * Reads contents from {@link ApplicationRepositoryConfiguration referenced repository} and deploys them to given * {@link ActorRef deployment receiver}. * @param cfg * @param jarDeploymentReceiverRef * @throws RequiredInputMissingException * @throws JsonParseException * @throws JsonMappingException * @throws IOException */ protected ComponentRepository loadAndDeployApplicationRepository(final String repositoryPath) throws RequiredInputMissingException, JsonParseException, JsonMappingException, IOException { //////////////////////////////////////////////////////////////////////////// // validate provided input and ensure that folder exists if(StringUtils.isBlank(repositoryPath)) throw new RequiredInputMissingException("Missing required input for parameter 'repositoryPath'"); File repositoryFolder = new File(StringUtils.trim(repositoryPath)); if(repositoryFolder == null || !repositoryFolder.isDirectory()) throw new RequiredInputMissingException("No repository found at provided folder '"+repositoryPath+"'"); // //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// final ComponentRepository library = new ComponentRepository(); //////////////////////////////////////////////////////////////////////////// // find component repositories below repository folder File[] repoFolders = repositoryFolder.listFiles(); if(repoFolders == null || repoFolders.length < 1) return library; // //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// // for each entry check if it is a folder and pass it on the library int folderCount = 0; int componentsCount = 0; for(File folder : repoFolders) { if(folder.isDirectory()) { try { logger.info("Processing folder '"+folder.getAbsolutePath()+"'"); Map<String, ComponentDescriptor> descriptors = library.addComponentFolder(folder.getAbsolutePath()); componentsCount = componentsCount + descriptors.size(); folderCount++; } catch(Exception e) { logger.error("Failed to add folder '"+folder.getAbsolutePath()+"' to component repository. Error: " + e.getMessage()); } } } // //////////////////////////////////////////////////////////////////////////// logger.info("Components deployment finished [repo="+repositoryPath+", componentFolders="+folderCount+", componets="+componentsCount+"]"); return library; } /** * Ramps up the node server instance * @param args */ public static void main(String[] args) throws Exception { new SPQRNodeServer().run(args); } }