package org.radargun.service; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Base64; import java.util.List; import org.radargun.Service; import org.radargun.ServiceHelper; import org.radargun.config.Init; import org.radargun.config.Property; import org.radargun.logging.Log; import org.radargun.logging.LogFactory; import org.radargun.traits.ProvidesTrait; /** * A service for starting Tomcat 8 server. * * The username and password properties must be set so the Tomcat * service can verify that the server is properly running. * The server is considered properly running once the query for * the following address returns "OK" in the response body: * http://bindAddress:bindHttpPort/manager/text/list * * The conf/tomcat-users.xml file within Tomcat must include a user * with "manager-script" role and corresponding credentials. * * @author Martin Gencur */ @Service(doc = TomcatServerService.SERVICE_DESCRIPTION) public class TomcatServerService extends JavaProcessService { protected final Log log = LogFactory.getLog(getClass()); protected static final String SERVICE_DESCRIPTION = "Tomcat Server"; private static final String MANAGER_CHARSET = "utf-8"; @Property(doc = "Tomcat server home directory. Default value is commonly used environment variable CATALINA_HOME.") private String catalinaHome = System.getenv("CATALINA_HOME"); @Property(doc = "Tomcat server base directory. Optional when only one instance of Tomcat is running on a single physical node. See http://tomcat.apache.org/tomcat-8.0-doc/RUNNING.txt for more details. Default value is commonly used environment variable CATALINA_BASE.") private String catalinaBase = System.getenv("CATALINA_BASE"); @Property(doc = "Bind address. Default is localhost.") private String bindAddress = "localhost"; @Property(doc = "HTTP port where the target Tomcat server is listening. Default is 8080.") private int bindHttpPort = 8080; @Property(doc = "Username to be used for managing the server. Empty by default.") private String user; @Property(doc = "Password to be used for managing the server. Empty by default.") private String pass; @Property(doc = "Remote JMX port. Default is 8089.") private int remotejmxPort = 8089; @Property(doc = "Logging properties file. Default is ${CATALINA_HOME}/conf/logging.properties") private String loggingProperties = "logging.properties"; @ProvidesTrait public TomcatConfigurationProvider createTomcatConfigurationProvider() { return new TomcatConfigurationProvider(this); } @Init public void init() { if (catalinaBase == null || "".equals(catalinaBase)) catalinaBase = catalinaHome; log.info("Java home: " + java); lifecycle = new TomcatServerLifecycle(this); try { URL resource = getClass().getResource("/" + file); Path filesystemFile = FileSystems.getDefault().getPath(file); Path target = FileSystems.getDefault().getPath(catalinaBase, "conf", "radargun-tomcat-" + ServiceHelper.getSlaveIndex() + ".xml"); if (resource != null) { try (InputStream is = resource.openStream()) { log.info("Found " + file + " as a resource"); Files.copy(is, target, StandardCopyOption.REPLACE_EXISTING); } } else if (filesystemFile.toFile().exists()) { log.info("Found " + file + " in plugin directory"); Files.copy(filesystemFile, target, StandardCopyOption.REPLACE_EXISTING); } else if (FileSystems.getDefault().getPath(catalinaBase, "conf", file).toFile().exists()) { log.info("Found " + file + " in server conf/ directory"); filesystemFile = FileSystems.getDefault().getPath(catalinaBase, "conf", file); // Set the file variable to the full path to the file to satisfy AbstractConfigurationProvider file = filesystemFile.toString(); Files.copy(filesystemFile, target, StandardCopyOption.REPLACE_EXISTING); } else { throw new FileNotFoundException("File " + file + " not found neither as resource nor in filesystem."); } } catch (IOException e) { log.error("Failed to copy file", e); throw new RuntimeException(e); } } @Override protected List<String> getCommand() { final List<String> cmd = new ArrayList<String>(); cmd.add(java + "/bin/java"); cmd.add("-Djava.util.logging.config.file=" + catalinaBase + "/conf/" + loggingProperties); cmd.add("-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager"); cmd.add("-Dcom.sun.management.jmxremote.port=" + remotejmxPort); cmd.add("-Dcom.sun.management.jmxremote.ssl=false"); cmd.add("-Dcom.sun.management.jmxremote.authenticate=false"); cmd.addAll(args); String classpath = catalinaHome + "/bin/bootstrap.jar" + File.pathSeparator + catalinaHome + "/bin/tomcat-juli.jar"; cmd.add("-classpath"); cmd.add(classpath); cmd.add("-Djava.endorsed.dirs=" + catalinaHome + "/endorsed"); cmd.add("-Dcatalina.base=" + catalinaBase); cmd.add("-Dcatalina.home=" + catalinaHome); cmd.add("-Djava.io.tmpdir=" + catalinaBase + "/temp"); cmd.add("org.apache.catalina.startup.Bootstrap"); cmd.add("-config"); cmd.add(catalinaBase + "/conf/radargun-tomcat-" + ServiceHelper.getSlaveIndex() + ".xml"); cmd.add("start"); return cmd; } boolean isTomcatReady() { try { queryTomcat("/text/list"); return true; } catch (final IOException e) { return false; } } /** * Copied from Arquillian and modified. * * Execute the specified command, based on the configured properties. The input stream will be closed upon completion of * this task, whether it was executed successfully or not. * * @param command Command to be executed * @throws IOException */ private void queryTomcat(final String command) throws IOException { URLConnection conn = new URL(getManagerUrl() + command).openConnection(); final HttpURLConnection hconn = (HttpURLConnection) conn; // Set up standard connection characteristics hconn.setAllowUserInteraction(false); hconn.setDoInput(true); hconn.setUseCaches(false); hconn.setDoOutput(false); hconn.setRequestMethod("GET"); if (user != null && user.length() != 0) { hconn.setRequestProperty("Authorization", constructHttpBasicAuthHeader()); } hconn.setRequestProperty("Accept", "text/plain"); // Establish the connection with the server hconn.connect(); // Send the request data (if any) processResponse(command, hconn); } /** * Copied from Arquillian and modified. */ private void processResponse(final String command, final HttpURLConnection hconn) throws IOException { final int httpResponseCode = hconn.getResponseCode(); if (httpResponseCode >= 400 && httpResponseCode < 500) { throw new IllegalStateException( "Unable to connect to Tomcat manager. " + "The server command (" + command + ") failed with responseCode (" + httpResponseCode + ") and responseMessage (" + hconn.getResponseMessage()+ ").\n\n" + "Please make sure that you provided correct credentials for a user which is allowed to access Tomcat manager application.\n" + "The user must have 'manager-script' role specified in tomcat-users.xml file.\n"); } else if (httpResponseCode >= 300) { throw new IllegalStateException("The server command (" + command + ") failed with responseCode (" + httpResponseCode + ") and responseMessage (" + hconn.getResponseMessage() + ")."); } try (BufferedReader reader = new BufferedReader(new InputStreamReader(hconn.getInputStream(), MANAGER_CHARSET))) { String line = reader.readLine(); String contentError = null; if (line != null && !line.startsWith("OK -")) { contentError = line; } while (line != null) { line = reader.readLine(); } if (contentError != null) { throw new IllegalStateException("The server command (" + command + ") failed with content (" + contentError + ")."); } } } private String constructHttpBasicAuthHeader() { // Set up an authorization header with our credentials final String credentials = user + ":" + pass; // Encodes the user:password pair as a sequence of ISO-8859-1 bytes. try { return "Basic " + new String(Base64.getEncoder().encode(credentials.getBytes("ISO-8859-1"))); } catch (final UnsupportedEncodingException e) { throw new RuntimeException(e); } } private URL getManagerUrl() { try { final String template = "http://%s:%d/manager"; final String urlString = String.format(template, bindAddress, bindHttpPort); return new URL(urlString); } catch (MalformedURLException e) { throw new IllegalStateException("Manager URL is not valid", e); } } }