step.grid.agent.Agent Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (C) 2020, exense GmbH
*
* This file is part of STEP
*
* STEP is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* STEP is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with STEP. If not, see .
******************************************************************************/
package step.grid.agent;
import ch.exense.commons.app.ArgumentParser;
import io.prometheus.client.hotspot.DefaultExports;
import io.prometheus.client.servlet.jakarta.exporter.MetricsServlet;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJsonProvider;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import step.grid.Token;
import step.grid.agent.conf.AgentConf;
import step.grid.agent.conf.AgentConfParser;
import step.grid.agent.conf.TokenConf;
import step.grid.agent.conf.TokenGroupConf;
import step.grid.agent.tokenpool.AgentTokenPool;
import step.grid.agent.tokenpool.AgentTokenWrapper;
import step.grid.contextbuilder.ApplicationContextBuilder;
import step.grid.filemanager.FileManagerClient;
import step.grid.filemanager.FileManagerClientImpl;
import step.grid.tokenpool.Interest;
import java.io.File;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Supplier;
import java.util.regex.Pattern;
public class Agent implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(Agent.class);
private static final String TOKEN_ID = "$tokenid";
private static final String AGENT_ID = "$agentid";
private final String id = UUID.randomUUID().toString();
private final AgentTokenPool tokenPool = new AgentTokenPool();
private final Server server;
private final Timer timer;
private final RegistrationTask registrationTask;
private final AgentTokenServices agentTokenServices;
private final String agentUrl;
private final long gracefulShutdownTimeout;
private final RegistrationClient registrationClient;
private volatile boolean stopped = false;
private volatile boolean registered = false;
public static void main(String[] args) throws Exception {
newInstanceFromArgs(args);
}
public static Agent newInstanceFromArgs(String[] args) throws Exception {
ArgumentParser arguments = new ArgumentParser(args);
String agentConfStr = arguments.getOption("config");
if(agentConfStr!=null) {
AgentConfParser parser = new AgentConfParser();
AgentConf agentConf = parser.parse(arguments, new File(agentConfStr));
if(arguments.hasOption("gridHost")) {
agentConf.setGridHost(arguments.getOption("gridHost"));
}
if(arguments.hasOption("fileServerHost")) {
agentConf.setFileServerHost(arguments.getOption("fileServerHost"));
}
if(arguments.hasOption("agentPort")) {
agentConf.setAgentPort(Integer.decode(arguments.getOption("agentPort")));
}
if(arguments.hasOption("agentHost")) {
agentConf.setAgentHost(arguments.getOption("agentHost"));
}
if(arguments.hasOption("agentUrl")) {
agentConf.setAgentUrl(arguments.getOption("agentUrl"));
}
return new Agent(agentConf);
} else {
throw new RuntimeException("Argument '-config' is missing.");
}
}
public Agent(AgentConf agentConf) throws Exception {
super();
validateConfiguration(agentConf);
String agentHost = agentConf.getAgentHost();
String agentUrl = agentConf.getAgentUrl();
Integer agentPort = agentConf.getAgentPort();
boolean ssl = agentConf.isSsl();
Long agentConfGracefulShutdownTimeout = agentConf.getGracefulShutdownTimeout();
gracefulShutdownTimeout = agentConfGracefulShutdownTimeout != null ? agentConfGracefulShutdownTimeout : 30000;
String gridUrl = agentConf.getGridHost();
String fileServerHost = Optional.ofNullable(agentConf.getFileServerHost()).orElse(gridUrl);
registrationClient = new RegistrationClient(gridUrl, fileServerHost,
agentConf.getGridConnectTimeout(), agentConf.getGridReadTimeout());
FileManagerClient fileManagerClient = initFileManager(registrationClient, agentConf.getWorkingDir());
agentTokenServices = new AgentTokenServices(fileManagerClient);
agentTokenServices.setAgentProperties(agentConf.getProperties());
agentTokenServices.setApplicationContextBuilder(new ApplicationContextBuilder());
buildTokenList(agentConf);
int serverPort = getServerPort(agentUrl, agentPort);
logger.info("Starting server...");
server = startServer(agentConf, serverPort, ssl);
int actualServerPort = getActualServerPort();
logger.info("Successfully started server on port " + actualServerPort);
this.agentUrl = getOrBuildActualAgentUrl(agentHost, agentUrl, actualServerPort, ssl);
logger.info("Starting grid registration task using grid URL " + gridUrl + "...");
registrationTask = createGridRegistrationTask(registrationClient);
timer = createGridRegistrationTimerAndRegisterTask(agentConf);
logger.info("Agent successfully started on port " + actualServerPort
+ ". The agent will publish following URL for incoming connections: " + this.agentUrl);
}
private RegistrationTask createGridRegistrationTask(RegistrationClient registrationClient) {
return new RegistrationTask(this, registrationClient);
}
private Timer createGridRegistrationTimerAndRegisterTask(AgentConf agentConf) {
Timer timer = new Timer();
timer.schedule(registrationTask, agentConf.getRegistrationOffset(), agentConf.getRegistrationPeriod());
return timer;
}
private void buildTokenList(AgentConf agentConf) {
List tokenGroups = agentConf.getTokenGroups();
if(tokenGroups!=null) {
for(TokenGroupConf group:tokenGroups) {
TokenConf tokenConf = group.getTokenConf();
if(tokenConf != null) {
addTokens(group.getCapacity(), tokenConf.getAttributes(), tokenConf.getSelectionPatterns(),
tokenConf.getProperties());
} else {
throw new IllegalArgumentException("Missing section 'tokenConf' in agent configuration");
}
}
}
}
private int getServerPort(String agentUrl, Integer agentPort) throws MalformedURLException {
int port;
if (agentPort != null) {
port = agentPort;
} else {
if (agentUrl != null) {
URL url = new URL(agentUrl);
int urlPort = url.getPort();
port = urlPort != -1 ? urlPort : url.getDefaultPort();
} else {
port = 0;
}
}
return port;
}
private String getOrBuildActualAgentUrl(String agentHost, String agentUrl, int localPort, boolean ssl)
throws UnknownHostException {
String actualAgentUrl;
if (agentUrl == null) {
// agentUrl not set. generate it
String scheme;
if (ssl) {
scheme = "https://";
} else {
scheme = "http://";
}
String host;
if (agentHost == null) {
// agentHost not specified. Calculate it
host = Inet4Address.getLocalHost().getCanonicalHostName();
} else {
host = agentHost;
}
actualAgentUrl = scheme + host + ":" + localPort;
} else {
actualAgentUrl = agentUrl;
}
return actualAgentUrl;
}
private int getActualServerPort() {
return ((ServerConnector) server.getConnectors()[0]).getLocalPort();
}
public boolean isRunning() {
return server.isRunning();
}
private Server startServer(AgentConf agentConf, int port, boolean ssl) throws Exception {
ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.packages(AgentServices.class.getPackage().getName());
resourceConfig.register(JacksonJsonProvider.class);
resourceConfig.register(JacksonFeature.class);
resourceConfig.register(ObjectMapperResolver.class);
final Agent agent = this;
resourceConfig.register(new AbstractBinder() {
@Override
protected void configure() {
bind(agent).to(Agent.class);
}
});
ServletContainer servletContainer = new ServletContainer(resourceConfig);
ServletHolder sh = new ServletHolder(servletContainer);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
context.addServlet(sh, "/*");
Server server = new Server();
ServerConnector connector;
if (ssl) {
String keyStorePath = agentConf.getKeyStorePath();
String keyStorePassword = agentConf.getKeyStorePassword();
String keyManagerPassword = agentConf.getKeyManagerPassword();
HttpConfiguration https = new HttpConfiguration();
SecureRequestCustomizer secureRequestCustomizer = new SecureRequestCustomizer();
//require to accept local host connection
secureRequestCustomizer.setSniHostCheck(false);
https.addCustomizer(secureRequestCustomizer);
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keyStorePath);
sslContextFactory.setKeyStorePassword(keyStorePassword);
sslContextFactory.setKeyManagerPassword(keyManagerPassword);
connector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"),
new HttpConnectionFactory(https));
} else {
HttpConfiguration http = new HttpConfiguration();
http.addCustomizer(new SecureRequestCustomizer());
connector = new ServerConnector(server);
connector.addConnectionFactory(new HttpConnectionFactory(http));
}
connector.setPort(port);
server.addConnector(connector);
ContextHandlerCollection contexts = new ContextHandlerCollection();
contexts.addHandler(context);
addMetricServletIfRequired(agentConf, contexts);
server.setHandler(contexts);
server.start();
return server;
}
private void addMetricServletIfRequired(AgentConf agentConf, ContextHandlerCollection handlers) {
if (agentConf.isExposeMetrics()) {
ServletContextHandler servletContext = new ServletContextHandler();
servletContext.setContextPath("/metrics");
servletContext.addServlet(new ServletHolder(new MetricsServlet()), "");
//Start default JVM metrics
DefaultExports.initialize();
handlers.addHandler(servletContext);
logger.info("Exposing prometheus JVM metrics under path '/metrics'");
}
}
private void validateConfiguration(AgentConf agentConf) {
assertMandatoryOption(agentConf.getGridHost(), "gridHost");
if (agentConf.isSsl()) {
assertMandatorySslOption(agentConf.getKeyStorePath(), "keyStorePath");
assertMandatorySslOption(agentConf.getKeyStorePassword(), "keyStorePassword");
assertMandatorySslOption(agentConf.getKeyManagerPassword(), "keyManagerPassword");
}
}
private void assertMandatoryOption(String actualOptionValue, String optionName) {
assertOption(actualOptionValue, "Missing option '" + optionName + "'. This option is mandatory.");
}
private void assertMandatorySslOption(String actualOptionValue, String optionName) {
assertOption(actualOptionValue,
"Missing option '" + optionName + "'. This option is mandatory when SSL is enabled.");
}
private void assertOption(String actualOptionValue, String errorMessage) {
if (actualOptionValue == null || actualOptionValue.trim().length() == 0) {
throw new IllegalArgumentException(errorMessage);
}
}
public String getId() {
return id;
}
public void addTokens(int count, Map attributes, Map selectionPatterns, Map properties) {
for(int i=0;i allAttributes = new HashMap<>();
if(attributes != null) {
allAttributes.putAll(attributes);
}
allAttributes.put(AgentTypes.AGENT_TYPE_KEY, AgentTypes.AGENT_TYPE);
allAttributes.put(AGENT_ID, id);
allAttributes.put(TOKEN_ID, token.getUid());
token.setAttributes(allAttributes);
token.setSelectionPatterns(createInterestMap(selectionPatterns));
token.setProperties(properties);
token.setServices(agentTokenServices);
tokenPool.offerToken(token);
}
}
private Map createInterestMap(Map selectionPatterns) {
HashMap result = new HashMap();
if(selectionPatterns!=null) {
for(Entry entry:selectionPatterns.entrySet()) {
Interest i = new Interest(Pattern.compile(entry.getValue()), true);
result.put(entry.getKey(), i);
}
}
return result;
}
private FileManagerClient initFileManager(RegistrationClient registrationClient, String workingDir) throws IOException {
String fileManagerDirPath;
if(workingDir!=null) {
fileManagerDirPath = workingDir;
} else {
fileManagerDirPath = ".";
}
fileManagerDirPath+="/filemanager";
File fileManagerDir = new File(fileManagerDirPath);
if(!fileManagerDir.exists()) {
Files.createDirectories(fileManagerDir.toPath());
}
FileManagerClient fileManagerClient = new FileManagerClientImpl(fileManagerDir, registrationClient);
return fileManagerClient;
}
protected String getAgentUrl() {
return agentUrl;
}
protected AgentTokenPool getTokenPool() {
return tokenPool;
}
public AgentTokenServices getAgentTokenServices() {
return agentTokenServices;
}
protected List getTokens() {
List tokens = new ArrayList<>();
for(AgentTokenWrapper wrapper:tokenPool.getTokens()) {
tokens.add(wrapper.getToken());
}
return tokens;
}
public synchronized void preStop() throws Exception {
if(!stopped) {
logger.info("Shutting down...");
// Stopping registration task
if (timer != null) {
timer.cancel();
}
if (registrationTask != null) {
registrationTask.cancel();
registrationTask.unregister();
registrationTask.destroy();
}
logger.info("Waiting for tokens to be released...");
// Wait until all tokens are released
boolean gracefullyStopped = pollUntil(tokenPool::areAllTokensFree, gracefulShutdownTimeout);
if (gracefullyStopped) {
logger.info("Agent gracefully stopped");
} else {
logger.warn("Timeout while waiting for all tokens to be released. Agent forcibly stopped");
}
//client is shared between registration tasks and file manager, closing it only once all token are released
if (registrationClient != null) {
registrationClient.close();
}
}
}
@Override
public synchronized void close() throws Exception {
if(!stopped) {
preStop();
// Stopping HTTP server
server.stop();
logger.info("Web server stopped");
stopped = true;
}
}
public boolean isRegistered() {
return registered;
}
public void setRegistered(boolean registered) {
this.registered = registered;
}
private static boolean pollUntil(Supplier predicate, long timeout) throws InterruptedException {
long t1 = System.currentTimeMillis();
while (System.currentTimeMillis() < t1 + (timeout)) {
if(predicate.get()) {
return true;
}
Thread.sleep(100);
}
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy