diff --git a/Dockerfile b/Dockerfile index c25d696..c0936df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -73,7 +73,6 @@ COPY ./Docker/start.sh /adaguc/ COPY ./Docker/adaguc-server-logrotate /etc/logrotate.d/adaguc COPY ./Docker/adaguc-server-*.sh /adaguc/ COPY ./Docker/baselayers.xml /data/adaguc-datasets-internal/baselayers.xml -COPY ./Docker/tomcat-server.xml /etc/tomcat/server.xml RUN chmod +x /adaguc/adaguc-server-*.sh && chmod +x /adaguc/start.sh # Set adaguc-services configuration file diff --git a/src/main/java/nl/knmi/adaguc/AdagucServicesApplication.java b/src/main/java/nl/knmi/adaguc/AdagucServicesApplication.java index 2f7ec47..950affe 100644 --- a/src/main/java/nl/knmi/adaguc/AdagucServicesApplication.java +++ b/src/main/java/nl/knmi/adaguc/AdagucServicesApplication.java @@ -7,7 +7,11 @@ import org.springframework.boot.Banner; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.annotation.Bean; import nl.knmi.adaguc.config.MainServicesConfigurator; import nl.knmi.adaguc.security.SecurityConfigurator; @@ -135,6 +139,23 @@ static Properties getProperties() throws ElementNotFoundException, IOException { } + @Bean + public WebServerFactoryCustomizer + containerCustomizer(){ + return new EmbeddedTomcatCustomizer(); + } + + private static class EmbeddedTomcatCustomizer implements WebServerFactoryCustomizer { + + @Override + public void customize(TomcatServletWebServerFactory factory) { + factory.addConnectorCustomizers((TomcatConnectorCustomizer) connector -> { + connector.setAttribute("relaxedPathChars", "<>[\\]^`{|}"); + connector.setAttribute("relaxedQueryChars", "<>[\\]^`{|}"); + }); + } + } + } diff --git a/src/main/java/nl/knmi/adaguc/HttpServer.java b/src/main/java/nl/knmi/adaguc/HttpServer.java index 9780253..3642fa0 100644 --- a/src/main/java/nl/knmi/adaguc/HttpServer.java +++ b/src/main/java/nl/knmi/adaguc/HttpServer.java @@ -29,6 +29,8 @@ public ServletWebServerFactory servletContainer(@Value("${server.http.port:0}") if (portAlreadyUsed == false) { Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL); connector.setPort(httpPort); + connector.setAttribute("relaxedPathChars", "<>[\\]^`{|}"); + connector.setAttribute("relaxedQueryChars", "<>[\\]^`{|}"); tomcat.addAdditionalTomcatConnectors(connector); } } diff --git a/src/main/java/nl/knmi/adaguc/config/ConfigurationReader.java b/src/main/java/nl/knmi/adaguc/config/ConfigurationReader.java index f736c74..e2c5f66 100644 --- a/src/main/java/nl/knmi/adaguc/config/ConfigurationReader.java +++ b/src/main/java/nl/knmi/adaguc/config/ConfigurationReader.java @@ -124,9 +124,10 @@ static public synchronized void readConfig() throws ElementNotFoundException, I String envKey = key.substring(5,key.length()-1); String envValue = System.getenv(envKey); if(envValue == null){ - throw new ElementNotFoundException("Environment variable ["+envKey+"] not set"); + Debug.errprintln("[WARNING]: Environment variable ["+envKey+"] not set"); + } else { + configFile = configFile.replace(key,envValue); } - configFile = configFile.replace(key,envValue); } // Debug.println("configfile=" + configFile); diff --git a/src/main/java/nl/knmi/adaguc/services/adagucserver/ADAGUCConfigurator.java b/src/main/java/nl/knmi/adaguc/services/adagucserver/ADAGUCConfigurator.java index 4324fc0..997ac84 100644 --- a/src/main/java/nl/knmi/adaguc/services/adagucserver/ADAGUCConfigurator.java +++ b/src/main/java/nl/knmi/adaguc/services/adagucserver/ADAGUCConfigurator.java @@ -16,15 +16,14 @@ + 1000 + 4 + 4 /home/c3smagic/code/maartenplieger/adaguc-server/bin/adagucserver ADAGUC_PATH=/home/c3smagic/code/maartenplieger/adaguc-server/ - ADAGUC_TMP=/tmp ADAGUC_CONFIG=/home/c3smagic/code/maartenplieger/adaguc-server/data/config/adaguc.autoresource.xml ADAGUC_DATARESTRICTION="FALSE" - ADAGUC_LOGFILE=/tmp/adaguc.autoresource.log - ADAGUC_ERRORFILE=/tmp/adaguc.autoresource.errlog ADAGUC_FONT=/home/c3smagic/code/maartenplieger/adaguc-server/data/fonts/FreeSans.ttf - ADAGUC_ONLINERESOURCE=http://adaguc-services/adagucserver? */ @@ -38,10 +37,20 @@ public class ADAGUCConfigurator implements nl.knmi.adaguc.config.ConfiguratorInt private static String[] environmentVariables = { }; + + /* How long adaguc-server is allowed to run in milliseconds, -1 is unlimted */ + private static long timeOut = -1; + /* Number of maximum simultaneaous instances before instances are queued, -1 is unlimited */ + private static int maxInstances = -1; + /* Maximum queuesize, -1 is unlimited */ + private static int maxInstancesInQueue = -1; public static void doConfig(XMLElement configReader){ ADAGUCExecutable=configReader.getNodeValue("adaguc-services.adaguc-server.adagucexecutable"); environmentVariables = configReader.getNodeValues("adaguc-services.adaguc-server.export"); + try { timeOut = Long.parseLong(configReader.getNodeValue("adaguc-services.adaguc-server.timeout"));} catch (Exception e) {} + try { maxInstances = Integer.parseInt(configReader.getNodeValue("adaguc-services.adaguc-server.maxinstances"));} catch (Exception e) {} + try { maxInstancesInQueue = Integer.parseInt(configReader.getNodeValue("adaguc-services.adaguc-server.queuesize"));} catch (Exception e) {} } public static String getADAGUCExecutable() throws ElementNotFoundException, IOException { @@ -53,5 +62,20 @@ public static String[] getADAGUCEnvironment() throws ElementNotFoundException, I ConfigurationReader.readConfig(); return environmentVariables; + } + + public static long getTimeOut() throws ElementNotFoundException, IOException { + ConfigurationReader.readConfig(); + return timeOut; + } + + public static int getMaxInstances () throws ElementNotFoundException, IOException { + ConfigurationReader.readConfig(); + return maxInstances; + } + + public static int getMaxInstancesInQueue () throws ElementNotFoundException, IOException { + ConfigurationReader.readConfig(); + return maxInstancesInQueue; } } \ No newline at end of file diff --git a/src/main/java/nl/knmi/adaguc/services/adagucserver/ADAGUCRequestMapper.java b/src/main/java/nl/knmi/adaguc/services/adagucserver/ADAGUCRequestMapper.java index 5165992..a1851cc 100644 --- a/src/main/java/nl/knmi/adaguc/services/adagucserver/ADAGUCRequestMapper.java +++ b/src/main/java/nl/knmi/adaguc/services/adagucserver/ADAGUCRequestMapper.java @@ -3,6 +3,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.json.JSONObject; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @@ -11,9 +12,14 @@ import nl.knmi.adaguc.tools.Debug; import nl.knmi.adaguc.tools.JSONResponse; +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; + + + @RestController public class ADAGUCRequestMapper { - + @ResponseBody @CrossOrigin @RequestMapping("wms") @@ -22,8 +28,9 @@ public void ADAGUCSERVERWMS(HttpServletResponse response, HttpServletRequest req try { ADAGUCServer.runADAGUCWMS(request,response,null,null); } catch (Exception e) { + Debug.printStackTrace(e); JSONResponse jsonResponse = new JSONResponse(request); - jsonResponse.setException("ADAGUCServer WMS request failed",e); + jsonResponse.setException("[ADAGUC-Server] WMS request failed",e); try { jsonResponse.print(response); } catch (Exception e1) { @@ -34,6 +41,32 @@ public void ADAGUCSERVERWMS(HttpServletResponse response, HttpServletRequest req } @ResponseBody @CrossOrigin + @RequestMapping("adagucload") + public void adagucLoad(HttpServletResponse response, HttpServletRequest request){ + JSONResponse jsonResponse = new JSONResponse(request); + try { + OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean(); + + jsonResponse.setMessage(new JSONObject().put("adagucServer", + new JSONObject().put("instancesInQueue", ADAGUCServer.getNumInstancesInQueue()) + .put("instancesRunning", ADAGUCServer.getNumInstancesRunning()) + .put("maxQueueSize" ,ADAGUCConfigurator.getMaxInstancesInQueue()) + .put("maxInstances", ADAGUCConfigurator.getMaxInstances()) + .put("instanceTimeout", ADAGUCConfigurator.getTimeOut()) + .put("getSystemLoadAverage", operatingSystemMXBean.getSystemLoadAverage()) + .put("getAvailableProcessors", operatingSystemMXBean.getAvailableProcessors()) + )); + } catch (Exception e) { + jsonResponse.setException("ADAGUCServer ADAGUC Load request failed",e); + } + try { + jsonResponse.print(response); + } catch (Exception e1) { + + } + } + @ResponseBody + @CrossOrigin @RequestMapping("adagucserver") public void ADAGUCSERVER(HttpServletResponse response, HttpServletRequest request){ Debug.println("/adagucserver"); @@ -41,7 +74,7 @@ public void ADAGUCSERVER(HttpServletResponse response, HttpServletRequest reques ADAGUCServer.runADAGUCWMS(request,response,null,null); } catch (Exception e) { JSONResponse jsonResponse = new JSONResponse(request); - jsonResponse.setException("ADAGUCServer WMS request failed",e); + jsonResponse.setException("[ADAGUC-Server] WMS request failed",e); try { jsonResponse.print(response); } catch (Exception e1) { diff --git a/src/main/java/nl/knmi/adaguc/services/adagucserver/ADAGUCServer.java b/src/main/java/nl/knmi/adaguc/services/adagucserver/ADAGUCServer.java index 8f0a7e1..b882bcb 100644 --- a/src/main/java/nl/knmi/adaguc/services/adagucserver/ADAGUCServer.java +++ b/src/main/java/nl/knmi/adaguc/services/adagucserver/ADAGUCServer.java @@ -5,13 +5,19 @@ import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.UUID; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.json.JSONObject; + import nl.knmi.adaguc.config.MainServicesConfigurator; import nl.knmi.adaguc.security.AuthenticatorFactory; import nl.knmi.adaguc.security.AuthenticatorInterface; @@ -65,8 +71,58 @@ public static void runADAGUCWCS(HttpServletRequest request,HttpServletResponse r enum ADAGUCServiceType{ WMS, WCS, OPENDAP } + + private static int numInstancesRunning = 0; + private static int numInstancesInQue = 0; public static void runADAGUC(HttpServletRequest request,HttpServletResponse response,String queryString,OutputStream outputStream, ADAGUCServiceType serviceType) throws Exception{ - Debug.println("runADAGUCWMS"); + Debug.println("runADAGUC"); + int maxInstances = ADAGUCConfigurator.getMaxInstances(); + int maxInstancesInQueue = ADAGUCConfigurator.getMaxInstancesInQueue(); + Exception exception = null; + String instanceId = UUID.randomUUID().toString(); + + if (maxInstancesInQueue > 0 && numInstancesInQue > maxInstancesInQueue) { + + String msg = "[ADAGUC-Server] Queue limit [" + maxInstancesInQueue + "] exceeded"; + Debug.errprintln(msg); + response.setStatus(500); + OutputStream o = response.getOutputStream(); + o.write(msg.getBytes()); + return; + } + + try { + if (maxInstances > 0 && maxInstancesInQueue > 0) { + if (numInstancesRunning >= maxInstances) { + Debug.println("[ADAGUC-Server] Too many instances running, Queued: [" + numInstancesInQue + "], Running: [" + numInstancesRunning + "]"); + } + if (maxInstancesInQueue > 0)numInstancesInQue++; + try { + while (numInstancesRunning >= maxInstances) { + Thread.sleep(100); + } + }catch (Exception e){ + exception = e; + } + if (maxInstancesInQueue > 0)numInstancesInQue--; + } + }catch (Exception e) { + exception = e; + } + if (exception == null) { + numInstancesRunning++; + try { + _runADAGUC(request, response, queryString, outputStream, serviceType, instanceId); + }catch (Exception e) { + exception = e; + } + numInstancesRunning--; + } + + if (exception != null) throw exception; + } + + private static void _runADAGUC(HttpServletRequest request,HttpServletResponse response,String queryString,OutputStream outputStream, ADAGUCServiceType serviceType, String instanceId) throws Exception{ // Debug.println("Headers:"); // Enumeration headerNames = request.getHeaderNames(); @@ -76,7 +132,7 @@ public static void runADAGUC(HttpServletRequest request,HttpServletResponse resp // Debug.println(headerName + ":" + headerValue); // } // - List environmentVariables = new ArrayList(); + String userHomeDir="/tmp/"; AuthenticatorInterface authenticator = AuthenticatorFactory.getAuthenticator(request); @@ -88,10 +144,10 @@ public static void runADAGUC(HttpServletRequest request,HttpServletResponse resp } } - Debug.println("Using home " + userHomeDir); + //Debug.println("Using home " + userHomeDir); String homeURL=MainServicesConfigurator.getServerExternalURL(); String adagucExecutableLocation = ADAGUCConfigurator.getADAGUCExecutable(); - Debug.println("adagucExecutableLocation: "+adagucExecutableLocation); + //Debug.println("adagucExecutableLocation: "+adagucExecutableLocation); if(adagucExecutableLocation == null){ Debug.errprintln("Adagucserver executable not configured"); @@ -121,10 +177,16 @@ public static void runADAGUC(HttpServletRequest request,HttpServletResponse resp if(queryString == null){ queryString = request.getQueryString(); } - Debug.println("[ADAGUC-Server]" + queryString); + Debug.println("[ADAGUC-Server] queryString [" + queryString + "]"); - + List environmentVariables = new ArrayList(); + String tmpDir = userHomeDir+"/adaguctmp/"; + Tools.mksubdirs(tmpDir); + environmentVariables.add("ADAGUC_TMP="+tmpDir); + String tmpLogFile = tmpDir + "adaguc-server-log" + instanceId; + Debug.println("Logging to " + tmpLogFile); + environmentVariables.add("ADAGUC_LOGFILE=" + tmpLogFile); environmentVariables.add("HOME="+userHomeDir); environmentVariables.add("QUERY_STRING="+queryString); environmentVariables.add("CONTENT_TYPE="+request.getHeader("Content-Type")); @@ -137,30 +199,43 @@ public static void runADAGUC(HttpServletRequest request,HttpServletResponse resp if(serviceType == ADAGUCServiceType.OPENDAP){ - /* localenv['SCRIPT_NAME']="/myscriptname"; - SCRIPT_NAME [/cgi-bin/autoresource.cgi], - REQUEST_URI [/cgi-bin/autoresource.cgi/opendap/clipc/combinetest/wcs_nc2.nc.das] - localenv['REQUEST_URI']="/myscriptname/" + path*/ environmentVariables.add("ADAGUC_ONLINERESOURCE="+homeURL+"/adagucopendap?"); environmentVariables.add("REQUEST_URI="+request.getRequestURI()); environmentVariables.add("SCRIPT_NAME="); Debug.println(request.getRequestURI()); } - Tools.mksubdirs(userHomeDir+"/adaguctmp/"); - environmentVariables.add("ADAGUC_TMP="+userHomeDir+"/adaguctmp/"); + String[] configEnv = ADAGUCConfigurator.getADAGUCEnvironment(); if(configEnv == null){ Debug.println("ADAGUC environment is not configured"); }else{ - for(int j=0;j 0) { + String baseName = statusLocation.substring(statusLocation.lastIndexOf("/")).replace(".xml", ".execute.json"); + AuthenticatorInterface authenticator = AuthenticatorFactory.getAuthenticator(request); + String userDataDir = UserManager.getUser(authenticator).getDataDir(); + String wpsSettingsFile = userDataDir+"/WPS_Settings/"; + Tools.mksubdirs(wpsSettingsFile); + wpsSettingsFile+=baseName; + Tools.writeFile(wpsSettingsFile, data.toString()); + } else { + Debug.println("Synchronous execution"); + } } else { Debug.println("Synchronous execution"); } diff --git a/src/main/java/nl/knmi/adaguc/services/pywpsserver/PyWPSServer.java b/src/main/java/nl/knmi/adaguc/services/pywpsserver/PyWPSServer.java index 44b7109..c1beaee 100644 --- a/src/main/java/nl/knmi/adaguc/services/pywpsserver/PyWPSServer.java +++ b/src/main/java/nl/knmi/adaguc/services/pywpsserver/PyWPSServer.java @@ -152,7 +152,7 @@ public static void runPyWPS(HttpServletRequest request,HttpServletResponse respo String wpsRequest=HTTPTools.getHTTPParam(request, "request"); if (wpsRequest.equalsIgnoreCase("execute")) { ByteArrayOutputStream baos=new ByteArrayOutputStream(0); - CGIRunner.runCGIProgram(commands,environmentVariablesAsArray,userHomeDir,response,baos,null); + CGIRunner.runCGIProgram(commands,environmentVariablesAsArray,userHomeDir,response,baos,null, -1); Writer w = new OutputStreamWriter(outputStream, "UTF-8"); w.write(baos.toString()); @@ -160,7 +160,7 @@ public static void runPyWPS(HttpServletRequest request,HttpServletResponse respo getUserJobInfo(queryString, userDataDir, baos.toString()); } else { - CGIRunner.runCGIProgram(commands,environmentVariablesAsArray,userHomeDir,response,outputStream,null); + CGIRunner.runCGIProgram(commands,environmentVariablesAsArray,userHomeDir,response,outputStream,null, -1); } } catch (Exception e) { diff --git a/src/main/java/nl/knmi/adaguc/services/xml2json/ServiceHelperRequestMapper.java b/src/main/java/nl/knmi/adaguc/services/xml2json/ServiceHelperRequestMapper.java index 95cc5cf..89cf2f4 100644 --- a/src/main/java/nl/knmi/adaguc/services/xml2json/ServiceHelperRequestMapper.java +++ b/src/main/java/nl/knmi/adaguc/services/xml2json/ServiceHelperRequestMapper.java @@ -142,7 +142,7 @@ public void XML2JSON( /* Hookup WPS request calls */ if (requestStr.toUpperCase().contains("SERVICE=WPS")) { Debug.println("This is a WPS call"); - if (requestStr.toUpperCase().contains("REQUEST=EXECUTE")) { + if (requestStr.toUpperCase().contains("REQUEST=EXECUTE") && requestStr.toUpperCase().contains("STOREEXECUTERESPONSE=TRUE")) { Debug.println("This is a WPS Execute call, store in jobs!"); JobListRequestMapper.saveExecuteResponseToJob(requestStr, rootElement.toString(), servletRequest); } @@ -278,7 +278,7 @@ private static byte[] makeRequest(String requestStr, X509UserCertAndKey userCert Debug.println("Status: " + httpResponse.getStatusLine().getStatusCode() + " Size: " + a.length); - if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_FORBIDDEN || httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { Debug.println("Status code not ok, attempting cert"); throw new IOException("Request needs certificate"); } diff --git a/src/main/java/nl/knmi/adaguc/tools/CGIRunner.java b/src/main/java/nl/knmi/adaguc/tools/CGIRunner.java index e751c4e..1f329eb 100644 --- a/src/main/java/nl/knmi/adaguc/tools/CGIRunner.java +++ b/src/main/java/nl/knmi/adaguc/tools/CGIRunner.java @@ -16,11 +16,12 @@ public class CGIRunner { * @param response Can be null, when given the content-type for the response will be set. * @param outputStream A standard byte output stream in which the data of stdout is captured. Can for example be response.getOutputStream(). * @param postData The data to post. + * @return * @throws IOException * @throws InterruptedException * @throws Exception */ - public static void runCGIProgram(String[] commands,String[] environmentVariables,String directory,final HttpServletResponse response,OutputStream outputStream,String postData) throws InterruptedException, IOException { + public static int runCGIProgram(String[] commands,String[] environmentVariables,String directory,final HttpServletResponse response,OutputStream outputStream,String postData, long timeOutMs) throws InterruptedException, IOException { Debug.println("Working Directory: "+directory); class StderrPrinter implements ProcessRunner.StatusPrinterInterface{ @@ -144,8 +145,8 @@ public boolean hasData() { // for(int j=0;j0){ environmentVars = Tools.appendString(environmentVars, "CONTENT_LENGTH="+dataToPost.length()); @@ -121,12 +124,16 @@ public int runProcess(String[] commands,String dataToPost) throws InterruptedExc stdoutThread.start(); stderrThread.start(); - - - //Wait for the process to complete - child.waitFor(); - + if (timeOutMs <= 0) { + child.waitFor(); + } else { + if(!child.waitFor(timeOutMs, TimeUnit.MILLISECONDS)) { + Debug.errprintln("[ADAGUC-Server] TimeOut"); + child.destroy(); // consider using destroyForcibly instead + } + } + //Wait for the output monitoring threads to complete stdoutThread.join(); stderrThread.join();