All Downloads are FREE. Search and download functionalities are using the official Maven repository.

step.grid.proxy.GridProxy Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * 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.proxy;

import ch.exense.commons.app.ArgumentParser;
import ch.exense.commons.io.FileHelper;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.StreamingOutput;
import org.eclipse.jetty.server.Server;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJsonProvider;
import org.glassfish.jersey.server.ResourceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import step.grid.agent.RegistrationMessage;
import step.grid.app.configuration.ConfigurationParser;
import step.grid.app.server.BaseServer;
import step.grid.io.InputMessage;
import step.grid.io.OutputMessage;
import step.grid.proxy.conf.GridProxyConfiguration;
import step.grid.proxy.services.GridProxyServices;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

public class GridProxy extends BaseServer implements AutoCloseable {

    private static final Logger logger = LoggerFactory.getLogger(GridProxy.class);
    private final Server server;

    private final ConcurrentHashMap agentUrlToContextRoot = new ConcurrentHashMap<>();
    private final ConcurrentHashMap contextRootToAgentUrl = new ConcurrentHashMap<>();
    private final String gridProxyUrl;
    private Client client;

    private final String gridUrl;
    private final Integer gridConnectTimeout;
    private final Integer gridReadTimeout;
    private final Integer agentConnectTimeout;
    private final Integer agentReserveTimeout;
    private final Integer agentReleaseTimeout;

    public static void main(String[] args) throws Exception {
        GridProxy gridProxy = newInstanceFromArgs(args);
        Runtime.getRuntime().addShutdownHook(new Thread(()->{
            try {
                gridProxy.close();
            } catch (Exception e) {
                logger.error("Error while closing the grid proxy", e);
            }
        }));

    }

    public static GridProxy newInstanceFromArgs(String[] args) throws Exception {
        ArgumentParser arguments = new ArgumentParser(args);

        String gridProxyConfigPath = arguments.getOption("config");

        if(gridProxyConfigPath!=null) {
            ConfigurationParser gridProxyConfigurationConfigurationParser = new ConfigurationParser<>();
            GridProxyConfiguration config = gridProxyConfigurationConfigurationParser.parse(arguments, new File(gridProxyConfigPath), GridProxyConfiguration.class);
            GridProxy gridProxy = new GridProxy(config);
            Runtime.getRuntime().addShutdownHook(new Thread(()->{
                try {
                    gridProxy.close();
                } catch (Exception e) {
                    logger.error("Error while closing the grid proxy", e);
                }
            }));
            return gridProxy;
        } else {
            throw new RuntimeException("Argument '-config' is missing.");
        }
    }

    public GridProxy(GridProxyConfiguration gridProxyConfiguration) throws Exception {
        int serverPort = this.resolveServerPort(gridProxyConfiguration.getGridProxyUrl(), gridProxyConfiguration.getGridProxyPort());

        logger.info("Starting grid proxy...");
        ResourceConfig resourceConfig = new ResourceConfig();
        resourceConfig.packages(GridProxyServices.class.getPackage().getName());
        final GridProxy gridProxy = this;
        resourceConfig.register(new AbstractBinder() {
            @Override
            protected void configure() {
                bind(gridProxy).to(GridProxy.class);
            }
        });

        server = this.startServer(gridProxyConfiguration, serverPort, resourceConfig);

        int actualServerPort = this.getActualServerPort(server);
        logger.info("Successfully started grid proxy on port " + actualServerPort);

        gridUrl = gridProxyConfiguration.getGridUrl();
        //Determine the actual Url of the grid proxy based either on configuration or actual system hostname and port
        gridProxyUrl = this.getOrBuildActualUrl(gridProxyConfiguration.getGridProxyHost(),
                gridProxyConfiguration.getGridProxyUrl(),
                actualServerPort, gridProxyConfiguration.isSsl());

        //Create REST client
        client = ClientBuilder.newClient();
        client.register(JacksonJsonProvider.class);
        gridConnectTimeout = gridProxyConfiguration.getGridConnectTimeout();
        gridReadTimeout = gridProxyConfiguration.getGridReadTimeout();
        agentConnectTimeout = gridProxyConfiguration.getAgentConnectTimeout();
        agentReserveTimeout = gridProxyConfiguration.getAgentReserveTimeout();
        agentReleaseTimeout = gridProxyConfiguration.getAgentReleaseTimeout();
    }

    //Allow override for junits
    protected void overrideRestClient(Client client) {
        this.client = client;
    }

    public void handleRegistrationMessage(RegistrationMessage message) throws MalformedURLException {
        String agentUrl = message.getAgentRef().getAgentUrl();
        // replace by proxyfied url (proxy base url + context root) and maintain the mapping
        message.getAgentRef().setAgentUrl(gridProxyUrl + "/" + getContextRoot(agentUrl));
        //Forward registration message to grid server
        try (Response r = client.target(gridUrl + "/grid/register").request().property(ClientProperties.READ_TIMEOUT, gridReadTimeout)
                    .property(ClientProperties.CONNECT_TIMEOUT, gridConnectTimeout).post(Entity.entity(message, MediaType.APPLICATION_JSON))) {

            r.readEntity(String.class);
        } catch (ProcessingException e) {
            if(e.getCause() instanceof java.net.ConnectException) {
                logger.error("Unable to reach " + gridUrl + " while proxyfying agent registration from " + agentUrl + " (java.net.ConnectException: "+e.getCause().getMessage()+")");
            } else {
                logger.error("Error while proxyfying registration request to " + gridUrl + " from " + agentUrl, e);
            }
            //Rethrow exception as response to the Agent
            throw e;
        }
    }

    /**
     * This method return the registered context root for the provided agent URL or generates a unique one for new registration
     * 

Note: parsing to URL might cause performance issue, could be replaced by URL encoding otherwise

*

In case of malformed URL the exceptions is rethrown

* * @param agentUrl the agent Url used to generate the context root * @return the unique context root for this agent */ private String getContextRoot(String agentUrl) throws MalformedURLException { //Not using computeIfAbsent for rethrowing MalformedURLException String contextRoot = agentUrlToContextRoot.get(agentUrl); if (contextRoot == null) { URL url = new URL(agentUrl); String host = url.getHost(); int port = url.getPort(); contextRoot = host + "-" + port; agentUrlToContextRoot.put(agentUrl, contextRoot); contextRootToAgentUrl.put(contextRoot, agentUrl); logger.info("Registering new agent from url '" + agentUrl + "' with contextRoot: " + contextRoot); return contextRoot; } return contextRoot; } public Response forwardGetFileRequest(String fileId, String version) throws IOException { Response fromGrid = null; try { fromGrid = client.target(gridUrl + "/grid/file/" + fileId + "/" + version).request().property(ClientProperties.READ_TIMEOUT, gridReadTimeout) .property(ClientProperties.CONNECT_TIMEOUT, gridConnectTimeout).get(); //Shallow copy to properly clean up the resources of the invocation to the grid server final InputStream inputStream = fromGrid.readEntity(InputStream.class); StreamingOutput oupputStream = output -> { try { FileHelper.copy(inputStream, output, 2048); output.flush(); } finally { inputStream.close(); } }; Response.ResponseBuilder responseBuilder = Response.fromResponse(fromGrid); return responseBuilder.entity(oupputStream).build(); } catch (Exception e) { //In case of exception, explicitly close the resource. For successful calls the inputStream/outputStream are closed when consumed if (fromGrid != null) { fromGrid.close(); } throw e; } } public void reserveToken(String agentContext, String tokenId) { forwardToAgent(agentContext, "reserve", tokenId, agentReserveTimeout); } public void releaseToken(String agentContext, String tokenId) { forwardToAgent(agentContext, "release", tokenId, agentReleaseTimeout); } public OutputMessage forwardMessageToAgent(String agentContext, String operation, String tokenId, InputMessage message) { String agentUrl = contextRootToAgentUrl.get(agentContext); if (agentUrl != null) { try (Response response = client.target(agentUrl + "/token/" + tokenId + "/" + operation).request() .property(ClientProperties.READ_TIMEOUT, agentConnectTimeout + message.getCallTimeout()) .property(ClientProperties.CONNECT_TIMEOUT, agentConnectTimeout).post(Entity.entity(message, MediaType.APPLICATION_JSON))) { return response.readEntity(OutputMessage.class); } } else { logger.error("Received request for unknown agent Id " + agentContext); throw new RuntimeException("Agent not registered to the Grid Proxy"); } } public void forwardToAgent(String agentContext, String operation, String tokenId, Integer callTimeout) { String agentUrl = contextRootToAgentUrl.get(agentContext); if (agentUrl != null) { //Event if the variable is not used, use try-with-resource construct to make use resources are cleaned-up try (Response response = client.target(agentUrl + "/token/" + tokenId + "/" + operation).request() .property(ClientProperties.READ_TIMEOUT, callTimeout) .property(ClientProperties.CONNECT_TIMEOUT, agentConnectTimeout).get()) { } } else { logger.error("Received request for unknown agent Id " + agentContext); throw new RuntimeException("Agent not registered to the Grid Proxy"); } } public boolean isRunning() { return server != null && server.isRunning(); } @Override public void close() throws Exception { if (server != null && !server.isStopped()) { server.stop(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy