/* * Copyright 2013 Harald Wellmann * * 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 org.ops4j.pax.exam.wildfly80; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.ConnectException; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.Socket; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.UnknownHostException; import java.util.Stack; import java.util.UUID; import java.util.concurrent.ExecutionException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.jboss.as.controller.client.helpers.standalone.DeploymentPlan; import org.jboss.as.controller.client.helpers.standalone.InitialDeploymentPlanBuilder; import org.jboss.as.controller.client.helpers.standalone.ServerDeploymentActionResult; import org.jboss.as.controller.client.helpers.standalone.ServerDeploymentManager; import org.jboss.as.controller.client.helpers.standalone.ServerDeploymentPlanResult; import org.jboss.as.controller.client.helpers.standalone.ServerUpdateActionResult.Result; import org.jboss.as.embedded.EmbeddedServerFactory; import org.jboss.as.embedded.ServerStartException; import org.jboss.as.embedded.StandaloneServer; import org.ops4j.io.FileUtils; import org.ops4j.pax.exam.ConfigurationManager; import org.ops4j.pax.exam.ExamSystem; import org.ops4j.pax.exam.ProbeInvoker; import org.ops4j.pax.exam.ProbeInvokerFactory; import org.ops4j.pax.exam.TestAddress; import org.ops4j.pax.exam.TestContainer; import org.ops4j.pax.exam.TestContainerException; import org.ops4j.pax.exam.TestDirectory; import org.ops4j.pax.exam.TestInstantiationInstruction; import org.ops4j.pax.exam.options.UrlDeploymentOption; import org.ops4j.pax.exam.options.WarProbeOption; import org.ops4j.pax.exam.spi.security.CredentialsCallbackHandler; import org.ops4j.pax.exam.zip.ZipInstaller; import org.ops4j.spi.ServiceProviderFinder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.xml.sax.SAXException; /** * @author Harald Wellmann * @since 3.2.0 */ public class WildFly80TestContainer implements TestContainer { /** * Configuration property specifying the download URL for a JBoss AS distribution. The default * value is {@link #WILDFLY80_DIST_URL_DEFAULT}. */ public static final String WILDFLY80_DIST_URL_KEY = "pax.exam.wildfly80.dist.url"; /** * Default download URL for JBoss AS distribution. */ public static final String WILDFLY80_DIST_URL_DEFAULT = "mvn:org.wildfly/wildfly-dist/8.2.0.Final/zip"; /** * Configuration property key for WildFly installation configuration file directory. The files * contained in this directory will be copied to the config directory of the WildFly instance. */ public static final String WILDFLY80_CONFIG_DIR_KEY = "pax.exam.wildfly80.config.dir"; /** * Configuration property key for overwriting standalone.xml and other configuration files in an * existing WildFly installation. If the value is {@code true}, existing files in * {@code standalone/configuration/} will be overwritten with files from * {@code wildfly80-config/}, if present. The default value is {@code false}. */ public static final String WILDFLY80_CONFIG_OVERWRITE_KEY = "pax.exam.wildfly80.config.overwrite"; /** * Configuration property key for additional JBoss AS modules to be installed. The value is * a comma-separated list of URLs. Each URL refers to a zipped module structure which will be unpacked * under {@code modules/system/add-ons/pax-exam}. */ public static final String WILDFLY80_MODULES_KEY = "pax.exam.wildfly80.modules"; /** * Configuration property for system properties to be loaded before starting WildFly. * See {@link ConfigurationManager#loadSystemProperties(String)} for syntax details. */ public static final String WILDFLY80_SYSTEM_PROPERTIES_KEY = "pax.exam.wildfly80.system.properties"; /** * Configuration property for JBoss Module loader system packages. Classes from these packages * will be loaded from the system class loader. The value is a comma-separated list of * package names. Each comma may be followed by whitespace. The default value is * {@code org.jboss.logging, org.slf4j}. */ public static final String WILDFLY80_SYSTEM_PACKAGES_KEY = "pax.exam.wildfly80.system.packages"; /** * Configuration property for connecting to a running WildFly server on a given host. * The value of this property is the hostname. If this property is not set, Pax Exam will * launch an embedded WildFly server and stop it at the end of the test suite. Otherwise, * Pax Exam will connect to a server on the given host. */ public static final String WILDFLY80_REMOTE_HOST_KEY = "pax.exam.wildfly80.remote.host"; /** * Configuration property for the HTTP port of a remote WildFly server. */ public static final String WILDFLY80_REMOTE_HTTP_PORT_KEY = "pax.exam.wildfly80.remote.http.port"; /** * Configuration property for the management port of a remote WildFly server. */ public static final String WILDFLY80_REMOTE_MGMT_PORT_KEY = "pax.exam.wildfly80.remote.mgmt.port"; /** * Configuration property for the management user of a remote WildFly server. */ public static final String WILDFLY80_REMOTE_USERNAME_KEY = "pax.exam.wildfly80.remote.username"; /** * Configuration property for the password of the management user of a remote WildFly server. */ public static final String WILDFLY80_REMOTE_PASSWORD_KEY = "pax.exam.wildfly80.remote.password"; private static final Logger LOG = LoggerFactory.getLogger(WildFly80TestContainer.class); private static final String HTTP_PORT_XPATH = "/server/socket-binding-group/socket-binding[@name='http']/@port"; private static final String MGMT_PORT_XPATH = "/server/socket-binding-group/socket-binding[@name='management-http']/@port"; private final Stack<String> deployed = new Stack<String>(); private String warProbe; private final ExamSystem system; private final TestDirectory testDirectory; private String wildFlyHome; private StandaloneServer server; private ServerDeploymentManager deploymentManager; private int httpPort; private int mgmtPort; private File configSourceDir; private File configTargetDir; private ConfigurationManager cm; public WildFly80TestContainer(ExamSystem system) { this.system = system; this.testDirectory = TestDirectory.getInstance(); this.cm = new ConfigurationManager(); } @Override public synchronized void call(TestAddress address) { TestInstantiationInstruction instruction = testDirectory.lookup(address); ProbeInvokerFactory probeInvokerFactory = ServiceProviderFinder .loadUniqueServiceProvider(ProbeInvokerFactory.class); ProbeInvoker invoker = probeInvokerFactory.createProbeInvoker(null, instruction.toString()); invoker.call(address.arguments()); } @Override public synchronized long install(String location, InputStream stream) { // just make sure we don't get an "option not recognized" warning system.getOptions(WarProbeOption.class); deployModule("Pax-Exam-Probe", stream); return -1; } @Override public synchronized long install(InputStream stream) { return install("local", stream); } public void deployModules() { UrlDeploymentOption[] deploymentOptions = system.getOptions(UrlDeploymentOption.class); int numModules = 0; for (UrlDeploymentOption option : deploymentOptions) { numModules++; if (option.getName() == null) { option.name("app" + numModules); } deployModule(option); } } private void deployModule(UrlDeploymentOption option) { try { URL applUrl = new URL(option.getURL()); deployModule(option.getName(), applUrl.openStream()); } catch (MalformedURLException exc) { throw new TestContainerException("Problem deploying " + option, exc); } catch (IOException exc) { throw new TestContainerException("Problem deploying " + option, exc); } } private void deployModule(String applicationName, InputStream stream) { try { String warName = applicationName + ".war"; InitialDeploymentPlanBuilder builder = deploymentManager.newDeploymentPlan(); DeploymentPlan plan = builder.add(warName, stream).deploy(warName).build(); ServerDeploymentPlanResult result = deploymentManager.execute(plan).get(); UUID actionId = plan.getDeploymentActions().get(0).getId(); ServerDeploymentActionResult actionResult = result.getDeploymentActionResult(actionId); if (actionResult.getResult() != Result.EXECUTED) { throw new TestContainerException("problem deploying " + applicationName); } deployed.push(warName); } catch (ExecutionException exc) { throw new TestContainerException("Problem deploying " + applicationName, exc); } catch (InterruptedException exc) { throw new TestContainerException("Problem deploying " + applicationName, exc); } } public void cleanup() { uninstallProbe(); undeployModules(); if (server != null) { server.stop(); } } private void undeployModules() { while (!deployed.isEmpty()) { String applicationName = deployed.pop(); undeployModule(applicationName); } } private void undeployModule(String applName) { InitialDeploymentPlanBuilder builder = deploymentManager.newDeploymentPlan(); DeploymentPlan plan = builder.undeploy(applName).andRemoveUndeployed().build(); ServerDeploymentPlanResult result; try { result = deploymentManager.execute(plan).get(); } catch (InterruptedException exc) { throw new TestContainerException("problem undeploying " + applName, exc); } catch (ExecutionException exc) { throw new TestContainerException("problem undeploying " + applName, exc); } UUID actionId = plan.getDeploymentActions().get(0).getId(); ServerDeploymentActionResult actionResult = result.getDeploymentActionResult(actionId); if (actionResult.getResult() != Result.EXECUTED) { throw new TestContainerException("problem undeploying " + applName); } } @Override public TestContainer start() { String host; String httpPortNumber; CredentialsCallbackHandler callbackHandler = null; if (cm.getProperty(WILDFLY80_REMOTE_HOST_KEY) == null) { startEmbeddedServer(); host = "localhost"; httpPortNumber = Integer.toString(httpPort); } else { host = cm.getProperty(WILDFLY80_REMOTE_HOST_KEY); httpPortNumber = cm.getProperty(WILDFLY80_REMOTE_HTTP_PORT_KEY, "8080"); String username = cm.getProperty(WILDFLY80_REMOTE_USERNAME_KEY); String password = cm.getProperty(WILDFLY80_REMOTE_PASSWORD_KEY); callbackHandler = new CredentialsCallbackHandler(username, password); mgmtPort = Integer.parseInt(cm.getProperty(WILDFLY80_REMOTE_MGMT_PORT_KEY, "9990")); } try { deploymentManager = ServerDeploymentManager.Factory.create( InetAddress.getByName(host), mgmtPort, callbackHandler); String uri = String.format("http://%s:%s/Pax-Exam-Probe/", host, httpPortNumber); testDirectory.setAccessPoint(new URI(uri)); deployModules(); } catch (URISyntaxException | UnknownHostException exc) { throw new TestContainerException("Problem starting test container.", exc); } return this; } private void startEmbeddedServer() { installContainer(); cm.loadSystemProperties(WILDFLY80_SYSTEM_PROPERTIES_KEY); File tempDir = system.getTempFolder(); File dataDir = new File(tempDir, "data"); dataDir.mkdir(); File configFile = new File(configTargetDir, "standalone.xml"); if (!configFile.exists()) { throw new TestContainerException(configFile + " does not exist"); } parseServerConfiguration(configFile); System.setProperty("jboss.server.data.dir", dataDir.getAbsolutePath()); validateManagementPort(); server = EmbeddedServerFactory.create(wildFlyHome, null, null, getSystemPackages()); try { server.start(); } catch (ServerStartException exc) { throw new TestContainerException("Problem starting embedded server.", exc); } } private void validateManagementPort() { try (Socket socket = new Socket("localhost", mgmtPort)) { throw new TestContainerException(String.format( "Port %d is already taken. Check if a WildFly server is already running and stop it.", mgmtPort)); } catch (ConnectException exc) { // this is the good case, no server should be listening on the management port } catch (IOException exc) { throw new TestContainerException("Problem validating management port", exc); } } /** * * @return packages to be loaded from system class loader */ private String[] getSystemPackages() { String systemPackagesString = cm.getProperty(WILDFLY80_SYSTEM_PACKAGES_KEY, "org.jboss.logging, org.slf4j").trim(); String[] systemPackages = systemPackagesString.split(",\\s*"); return systemPackages; } public void installContainer() { System.setProperty("java.protocol.handler.pkgs", "org.ops4j.pax.url"); System.setProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager"); System.setProperty("org.jboss.logging.provider", "slf4j"); wildFlyHome = cm.getProperty("pax.exam.wildfly80.home"); if (wildFlyHome == null) { throw new TestContainerException( "System property pax.exam.wildfly80.home must be set to WildFly 8.0 install root"); } String configDirName = cm.getProperty(WILDFLY80_CONFIG_DIR_KEY, "src/test/resources/wildfly80-config"); configSourceDir = new File(configDirName); boolean overwriteConfig = Boolean.parseBoolean(cm.getProperty(WILDFLY80_CONFIG_OVERWRITE_KEY, "false")); if (isValidInstallation()) { if (overwriteConfig) { installConfiguration(); } } else { LOG.info("installing WildFly 8.0 in {}", wildFlyHome); String distUrl = cm.getProperty(WILDFLY80_DIST_URL_KEY, WILDFLY80_DIST_URL_DEFAULT); LOG.info("installing WildFly 8.0 from {} in {}", distUrl, wildFlyHome); try { URL url = new URL(distUrl); File installDir = new File(wildFlyHome); File installParent = installDir.getParentFile(); File tempInstall = new File(installParent, UUID.randomUUID().toString()); ZipInstaller installer = new ZipInstaller(url, tempInstall.getAbsolutePath()); installer.downloadAndInstall(); File unpackedRoot = tempInstall.listFiles()[0]; unpackedRoot.renameTo(installDir); installWildFlyModules(); installConfiguration(); } catch (IOException exc) { throw new TestContainerException("error during WildFly 8.0 installation", exc); } } } private void installWildFlyModules() { String modulesList = cm.getProperty(WILDFLY80_MODULES_KEY); if (modulesList == null) { return; } File addOnsDir = new File(wildFlyHome, "modules/system/add-ons/pax-exam"); addOnsDir.mkdirs(); String[] modules = modulesList.split(",\\s*"); for (String module : modules) { installWildFlyModule(module, addOnsDir); } } private void installWildFlyModule(String module, File moduleDir) { try { URL moduleUrl = new URL(module); LOG.info("installing add-on module {}", module); ZipInstaller installer = new ZipInstaller(moduleUrl, moduleDir.getAbsolutePath()); installer.downloadAndInstall(); } catch (MalformedURLException exc) { throw new TestContainerException("invalid module URL: " + module, exc); } catch (IOException exc) { throw new TestContainerException(exc); } } private boolean isValidInstallation() { boolean valid = false; File installDir = new File(wildFlyHome); if (installDir.exists()) { File moduleLoader = new File(installDir, "jboss-modules.jar"); if (!moduleLoader.exists()) { String msg = String.format("%s exists, but %s does not. " + "This does not look like a valid WildFly 8.0 installation.", wildFlyHome, moduleLoader); throw new TestContainerException(msg); } File modulesDir = new File(installDir, "modules"); File systemDir = new File(modulesDir, "system"); if (!systemDir.exists()) { String msg = String.format("%s does not exist. " + "This does not look like a valid WildFly 8.0 installation.", systemDir); throw new TestContainerException(msg); } LOG.info("using existing WildFly installation in {}", wildFlyHome); valid = true; } return valid; } /** * Copies all files in a user-defined configuration directory to the JBoss AS * configuration directory. */ private void installConfiguration() { if (!configSourceDir.exists()) { throw new TestContainerException("configuration directory " + configSourceDir + " does not exist"); } configTargetDir = new File(wildFlyHome, "standalone/configuration"); for (File configFile : configSourceDir.listFiles()) { if (!configFile.isDirectory()) { File targetFile = new File(configTargetDir, configFile.getName()); try { LOG.info("copying {} to {}", configFile, targetFile); FileUtils.copyFile(configFile, targetFile, null); } catch (IOException exc) { throw new TestContainerException("error copying config file " + configFile, exc); } } } } private void parseServerConfiguration(File serverConfig) { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder; builder = factory.newDocumentBuilder(); Document doc = builder.parse(serverConfig); XPathFactory xpf = XPathFactory.newInstance(); XPath xPath = xpf.newXPath(); String httpPortString = substituteProperties(xPath.evaluate(HTTP_PORT_XPATH, doc)); String mgmtPortString = substituteProperties(xPath.evaluate(MGMT_PORT_XPATH, doc)); String portOffsetString = System.getProperty("jboss.socket.binding.port-offset", "0"); httpPort = Integer.parseInt(httpPortString); mgmtPort = Integer.parseInt(mgmtPortString); int portOffset = Integer.parseInt(portOffsetString); httpPort += portOffset; mgmtPort += portOffset; } catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException exc) { throw new IllegalArgumentException(exc); } } public String substituteProperties(String value) { String result = value; if (value.startsWith("${") && value.endsWith("}")) { String propWithDefault = value.substring(2, value.length() - 1); int colon = propWithDefault.indexOf(':'); String defaultValue = ""; String propertyKey = propWithDefault; if (colon >= 0) { propertyKey = propWithDefault.substring(0, colon); defaultValue = propWithDefault.substring(colon + 1); } result = System.getProperty(propertyKey, defaultValue); } return result; } @Override public TestContainer stop() { cleanup(); system.clear(); return this; } @Override public String toString() { return "WildFly80"; } @Override public long installProbe(InputStream stream) { install(stream); this.warProbe = deployed.pop(); return -1; } @Override public void uninstallProbe() { if (warProbe != null) { undeployModule(warProbe); this.warProbe = null; } } }