/* * JBoss, Home of Professional Open Source. * Copyright 2015, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.test.manualmode.management.cli; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.util.Collections; import java.util.List; import org.codehaus.plexus.util.FileUtils; import org.jboss.as.test.deployment.trivial.ServiceActivatorDeploymentUtil; import org.jboss.as.test.integration.management.base.AbstractCliTestBase; import org.jboss.as.test.integration.management.extension.EmptySubsystemParser; import org.jboss.as.test.integration.management.extension.ExtensionUtils; import org.jboss.as.test.integration.management.extension.blocker.BlockerExtension; import org.jboss.as.test.integration.management.util.CLIOpResult; import org.jboss.as.test.shared.TimeoutUtil; import org.jboss.dmr.ModelNode; import org.jboss.logging.Logger; import org.jboss.shrinkwrap.api.exporter.ZipExporter; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.jboss.stdio.SimpleStdioContextSelector; import org.jboss.stdio.StdioContext; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.wildfly.security.manager.WildFlySecurityManager; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** * Basic tests for embedded host-controller in the CLI. * * @author Ken Wills (c) 2016 Red Hat Inc. */ public class CLIEmbedHostControllerTestCase extends AbstractCliTestBase { /** * Use this if you are debugging and want to look at the original System.out output, * as the tests play tricks with it later. */ private static final PrintStream out = System.out; private static final File ROOT = new File(System.getProperty("jboss.home")); private static final String JBOSS_HOME = " --jboss-home=" + ROOT.getAbsolutePath(); private static final String STOP = "stop-embedded-host-controller"; private static final String SERVICE_ACTIVATOR_DEPLOYMENT_NAME = "service-activated.jar"; private static JavaArchive serviceActivatorDeployment; private static File serviceActivatorDeploymentFile; private static boolean uninstallStdio; public static final String JBOSS_DOMAIN_BASE_DIR = "jboss.domain.base.dir"; public static final String JBOSS_DOMAIN_CONFIG_DIR = "jboss.domain.config.dir"; public static final String JBOSS_DOMAIN_CONTENT_DIR = "jboss.domain.content.dir"; public static final String JBOSS_DOMAIN_DEPLOY_DIR = "jboss.domain.deploy.dir"; public static final String JBOSS_DOMAIN_TEMP_DIR = "jboss.domain.temp.dir"; public static final String JBOSS_DOMAIN_LOG_DIR = "jboss.domain.log.dir"; public static final String JBOSS_DOMAIN_DATA_DIR = "jboss.domain.data.dir"; private static final String[] DOMAIN_PROPS = { JBOSS_DOMAIN_BASE_DIR, JBOSS_DOMAIN_CONFIG_DIR, JBOSS_DOMAIN_CONTENT_DIR, JBOSS_DOMAIN_DEPLOY_DIR, JBOSS_DOMAIN_TEMP_DIR, JBOSS_DOMAIN_LOG_DIR, JBOSS_DOMAIN_DATA_DIR }; // Sink for embedding app (i.e. this class and the CLI) and embedded HC writes to stdout private ByteArrayOutputStream logOut; private static StdioContext initialStdioContext; @BeforeClass public static void beforeClass() throws Exception { CLIEmbedUtil.copyConfig(ROOT, "domain", "logging.properties", "logging.properties.backup", false); // Set up ability to manipulate stdout initialStdioContext = StdioContext.getStdioContext(); try { StdioContext.install(); uninstallStdio = true; } catch (IllegalStateException ignore) { // } serviceActivatorDeployment = ServiceActivatorDeploymentUtil.createServiceActivatorDeploymentArchive(SERVICE_ACTIVATOR_DEPLOYMENT_NAME, Collections.emptyMap()); File tmpDir = new File(System.getProperty("java.io.tmpdir")); serviceActivatorDeploymentFile = new File(tmpDir, SERVICE_ACTIVATOR_DEPLOYMENT_NAME); serviceActivatorDeployment.as(ZipExporter.class).exportTo(serviceActivatorDeploymentFile, true); ExtensionUtils.createExtensionModule(BlockerExtension.MODULE_NAME, BlockerExtension.class, EmptySubsystemParser.class.getPackage()); // Silly assertion just to stop IDEs complaining about field 'out' not being used. assertNotNull(out); } @AfterClass public static void afterClass() throws IOException { CLIEmbedUtil.copyConfig(ROOT, "domain", "logging.properties.backup", "logging.properties", false); try { StdioContext.setStdioContextSelector(new SimpleStdioContextSelector(initialStdioContext)); } finally { if (uninstallStdio) { StdioContext.uninstall(); } } ExtensionUtils.deleteExtensionModule(BlockerExtension.MODULE_NAME); } @Before public void setup() throws Exception { CLIEmbedUtil.copyConfig(ROOT, "domain", "logging.properties.backup", "logging.properties", false); CLIEmbedUtil.copyConfig(ROOT, "domain", "host.xml", "host-cli.xml", true); CLIEmbedUtil.copyConfig(ROOT, "domain", "domain.xml", "domain-cli.xml", true); // Capture stdout logOut = new ByteArrayOutputStream(); final StdioContext replacement = StdioContext.create(initialStdioContext.getIn(), logOut, initialStdioContext.getErr()); StdioContext.setStdioContextSelector(new SimpleStdioContextSelector(replacement)); initCLI(false); } @After public void cleanup() throws Exception { try { closeCLI(); } finally { StdioContext.setStdioContextSelector(new SimpleStdioContextSelector(initialStdioContext)); } } /** Tests logging behavior with no --std-out param */ @Test public void testStdOutDefault() throws Exception { stdoutTest(null); } /** Tests logging behavior with --std-out=discard */ @Test public void testStdOutDiscard() throws Exception { stdoutTest("discard"); } /** Tests logging behavior with --std-out=echo */ @Test public void testStdOutEcho() throws Exception { stdoutTest("echo"); } /** * This test is about confirming the CLI side and embedded server side logging to stdout * works as expected. CLI logging should always be captured in the 'logOut' sink, while * embedded server logging should only be captured if paramVal is 'echo'. * * @param paramVal value to assign to the --std-out param, or null if the param should be omitted * @throws Exception just because */ private void stdoutTest(String paramVal) throws Exception { String stdoutParam = paramVal == null ? "" : ("--std-out=" + paramVal); boolean expectServerLogging = "echo".equals(paramVal); // The app embedding the server should be able to log checkClientSideLogging(); // Use or the logging subsystem won't log anyway String line = "embed-host-controller --host-config=host-cli.xml --domain-config=domain-cli.xml " + stdoutParam + JBOSS_HOME; cli.sendLine(line); if (expectServerLogging) { checkLogging("WFLYSRV0025"); } else { checkNoLogging("WFLYSRV0025"); } assertState("running", TimeoutUtil.adjust(30000)); // The app embedding the server should still be able to log checkClientSideLogging(); // Do something that certainly creates a server-side logger after boot, // as part of executing a management op. Confirm its logging gets the // appropriate server-side behavior. cli.sendLine("/extension=" + BlockerExtension.MODULE_NAME + ":add"); if (expectServerLogging) { checkLogging(BlockerExtension.REGISTERED_MESSAGE); } else { checkNoLogging(BlockerExtension.REGISTERED_MESSAGE); } // The app embedding the server should still be able to log checkClientSideLogging(); cli.sendLine(STOP); if (expectServerLogging) { checkLogging("WFLYSRV0050"); } else { checkNoLogging("WFLYSRV0050"); } // The app embedding the server should still be able to log checkClientSideLogging(); } private void checkClientSideLogging() throws IOException { String text = "test." + System.nanoTime(); Logger.getLogger(text).error(text); checkLogging(text); } /** Confirms that low level and high level reloads work */ @Test public void testReload() throws Exception { // since this is admin-only always for now, just add/remove an extension, // and make sure it works. String line = "embed-host-controller --host-config=host-cli.xml --domain-config=domain-cli.xml " + JBOSS_HOME; cli.sendLine(line); cli.sendLine("/extension=org.wildfly.extension.io:add"); assertState("running", 0); // embedded-hc requires admin-only cli.sendLine("/host=master:reload(admin-only=true"); assertState("running", TimeoutUtil.adjust(30000)); cli.sendLine("/extension=org.wildfly.extension.io:remove"); assertState("running", 0); // High level cli.sendLine("reload --host=master --admin-only=true"); assertState("running", TimeoutUtil.adjust(30000)); } /** Confirms that the low and high level shutdown commands are not available */ @Test public void testShutdownNotAvailable() throws Exception { String line = "embed-host-controller --host-config=host-cli.xml --domain-config=domain-cli.xml " + JBOSS_HOME; cli.sendLine(line); assertTrue(cli.isConnected()); assertState("running", 0); cli.sendLine(":shutdown", true); assertState("running", 0); cli.sendLine("shutdown", true); assertState("running", 0); } @Test public void testTimeout() throws Exception { cli.sendLine("command-timeout set 60"); String line = "embed-host-controller --host-config=host-cli.xml --domain-config=domain-cli.xml " + JBOSS_HOME; cli.sendLine(line); cli.sendLine("stop-embedded-host-controller"); } /** * Test not specifying a server config works. * @throws IOException */ @Test public void testDefaultServerConfig() throws IOException { validateServerConnectivity(""); } /** * Test the -c shorthand for specifying a domain config works. * @throws IOException */ @Test public void testDashC() throws IOException { validateServerConnectivity("-c=domain-cli.xml"); } /** Tests the stop-embedded-host-controller command */ @Test public void testStopEmbeddedServer() throws IOException { validateServerConnectivity(); cli.sendLine("stop-embedded-host-controller"); assertFalse(cli.isConnected()); } /** Tests that the quit command stops any embedded server */ @Test public void testStopServerOnQuit() throws IOException { validateServerConnectivity(); cli.sendLine("quit"); assertFalse(cli.isConnected()); } /** Tests the the CommandContext terminateSession method stops any embedded server */ @Test public void testStopServerOnTerminateSession() throws IOException { validateServerConnectivity(); cli.getCommandContext().terminateSession(); assertFalse(cli.isConnected()); } /** * Tests the --help param works. * @throws IOException */ @Test public void testHelp() throws IOException { cli.sendLine("embed-host-controller --help"); checkLogging("embed-host-controller"); String line = "embed-host-controller --host-config=host-cli.xml --domain-config=domain-cli.xml " + JBOSS_HOME; cli.sendLine(line); assertTrue(cli.isConnected()); cli.sendLine("stop-embedded-host-controller --help"); checkLogging("stop-embedded-host-controller"); } @Test public void testBaseDir() throws IOException, InterruptedException { String currBaseDir = null; final String newDomain = "CLIEmbedServerTestCaseHostControllerTmp"; assertFalse(cli.isConnected()); try { // save the current value, if it has one currBaseDir = WildFlySecurityManager.getPropertyPrivileged(JBOSS_DOMAIN_BASE_DIR, null); CLIEmbedUtil.copyServerBaseDir(ROOT, "domain", newDomain, true); CLIEmbedUtil.copyConfig(ROOT, newDomain, "logging.properties.backup", "logging.properties", false); String newBaseDir = ROOT + File.separator + newDomain; setProperties(newBaseDir); final String line = "embed-host-controller --host-config=host-cli.xml --domain-config=domain-cli.xml --std-out=echo " + JBOSS_HOME; cli.sendLine(line); assertTrue(cli.isConnected()); cli.sendLine("/system-property=" + newDomain + ":add(value=" + newDomain +")"); assertProperty(newDomain, newDomain, false); // verify we've logged to the right spot at least, and directories were created File f = new File(ROOT + File.separator + newDomain + File.separator + "log" + File.separator + "host-controller.log"); // WFCORE-1187 //assertTrue(f.exists()); //assertTrue(f.length() > 0); // stop the hc, and restart it with default properties cli.sendLine("stop-embedded-host-controller"); setProperties(null); cli.sendLine(line); assertTrue(cli.isConnected()); // shouldn't be set assertProperty(newDomain, null, true); cli.sendLine("stop-embedded-host-controller"); setProperties(newBaseDir); cli.sendLine(line); assertTrue(cli.isConnected()); // shouldn't be set assertProperty(newDomain, newDomain, false); } finally { cli.sendLine("stop-embedded-host-controller"); // clean up copy FileUtils.deleteDirectory(ROOT + File.separator + newDomain); // restore the originals setProperties(currBaseDir); } } @Test public void testLogDir() throws IOException, InterruptedException { String currBaseDir = null; final String newDomain = "CLIEmbedServerTestCaseHostControllerTmp"; assertFalse(cli.isConnected()); File newLogDir = null; try { // save the current value, if it has one currBaseDir = WildFlySecurityManager.getPropertyPrivileged(JBOSS_DOMAIN_BASE_DIR, null); if (currBaseDir == null) { currBaseDir = ROOT + File.separator + "domain"; } CLIEmbedUtil.copyServerBaseDir(ROOT, "domain", newDomain, true); newLogDir = new File(ROOT + File.separator + newDomain, "newlog"); if (newLogDir.exists()) { FileUtils.deleteDirectory(newLogDir); } assertTrue(newLogDir.mkdir()); // only set log.dir WildFlySecurityManager.setPropertyPrivileged(JBOSS_DOMAIN_LOG_DIR, ROOT + File.separator + newDomain + File.separator + "newlog"); String line = "embed-host-controller --host-config=host-cli.xml --domain-config=domain-cli.xml --std-out=echo " + JBOSS_HOME; cli.sendLine(line); assertTrue(cli.isConnected()); // verify we've logged to the right spot and directories were created File f = new File(ROOT + File.separator + newDomain + File.separator + "newlog" + File.separator + "host-controller.log"); // WFCORE-1187, when this overrides logging.properties correctly, we can check this //assertTrue(f.exists()); //assertTrue(f.length() > 0); } finally { cli.sendLine("stop-embedded-host-controller"); // clean up copy FileUtils.deleteDirectory(ROOT + File.separator + newDomain); // restore the originals setProperties(currBaseDir); } } private void assertProperty(final String propertyName, final String expected, final boolean notPresent) throws IOException, InterruptedException { cli.sendLine("/system-property=" + propertyName + " :read-attribute(name=value)", true); CLIOpResult result = cli.readAllAsOpResult(); ModelNode resp = result.getResponseNode(); ModelNode stateNode = result.isIsOutcomeSuccess() ? resp.get(RESULT) : resp.get(FAILURE_DESCRIPTION); if (notPresent) { assertTrue(stateNode.asString().indexOf("WFLYCTL0216") != -1); } else { assertEquals(expected, stateNode.asString()); } } private void assertState(String expected, int timeout) throws IOException, InterruptedException { long done = timeout < 1 ? 0 : System.currentTimeMillis() + timeout; String history = ""; String state = null; do { try { cli.sendLine("/host=master:read-attribute(name=host-state)", true); CLIOpResult result = cli.readAllAsOpResult(); ModelNode resp = result.getResponseNode(); ModelNode stateNode = result.isIsOutcomeSuccess() ? resp.get(RESULT) : resp.get(FAILURE_DESCRIPTION); state = stateNode.asString(); history += state+"\n"; } catch (Exception ignored) { // history += ignored.toString()+ "--" + cli.readOutput() + "\n"; } if (expected.equals(state)) { return; } else { Thread.sleep(20); } } while (timeout > 0 && System.currentTimeMillis() < done); assertEquals(history, expected, state); } private void checkNoLogging(String line) throws IOException { String output = readLogOut(); assertFalse(output, checkLogging(output, line)); } private String readLogOut() { if (logOut.size() > 0) { String output = new String(logOut.toByteArray()).trim(); logOut.reset(); return output; } return null; } private void checkLogging(String line) throws IOException { String logOutput = readLogOut(); assertTrue(logOutput, checkLogging(logOutput, line)); } private boolean checkLogging(String logOutput, String line) throws IOException { List<String> output = CLIEmbedUtil.getOutputLines(logOutput); for (String s : output) { if (s.contains(line)) { return true; } } return false; } private void validateServerConnectivity() throws IOException { validateServerConnectivity("--host-config=host-cli.xml --domain-config=domain-cli.xml"); } private void validateServerConnectivity(String serverConfigParam) throws IOException { // does basically nothing yet, other than accepts params, and starts a server. String line = "embed-host-controller " + serverConfigParam + " " + JBOSS_HOME; cli.sendLine(line); assertTrue(cli.isConnected()); } private void setProperties(final String newBaseDir) { if (newBaseDir == null) { for (String prop : DOMAIN_PROPS) { WildFlySecurityManager.clearPropertyPrivileged(prop); } return; } WildFlySecurityManager.setPropertyPrivileged(JBOSS_DOMAIN_BASE_DIR, newBaseDir); WildFlySecurityManager.setPropertyPrivileged(JBOSS_DOMAIN_CONFIG_DIR, newBaseDir + File.separator + "configuration"); WildFlySecurityManager.setPropertyPrivileged(JBOSS_DOMAIN_DATA_DIR, newBaseDir + File.separator + "data"); WildFlySecurityManager.setPropertyPrivileged(JBOSS_DOMAIN_LOG_DIR, newBaseDir + File.separator + "log"); WildFlySecurityManager.setPropertyPrivileged(JBOSS_DOMAIN_TEMP_DIR, newBaseDir + File.separator + "tmp"); } }