/* * JBoss, Home of Professional Open Source. * Copyright 2016, 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.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.inject.Inject; import org.jboss.as.cli.CliInitializationException; import org.jboss.as.cli.CommandArgument; import org.jboss.as.cli.CommandContext; import org.jboss.as.cli.CommandContextFactory; import org.jboss.as.cli.CommandHandler; import org.jboss.as.cli.CommandLineException; import org.jboss.as.cli.Util; import org.jboss.as.cli.handlers.LsHandler; import org.jboss.as.cli.impl.CommandContextConfiguration; import org.jboss.as.cli.impl.CommandExecutor; import org.jboss.as.cli.impl.CommandExecutor.TimeoutCommandContext; 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.shared.TestSuiteEnvironment; import org.jboss.dmr.ModelNode; import org.junit.After; import org.junit.Test; import org.junit.Before; import org.junit.runner.RunWith; import org.wildfly.core.testrunner.WildflyTestRunner; import static org.junit.Assert.assertEquals; import org.wildfly.core.testrunner.ServerControl; import org.wildfly.core.testrunner.ServerController; /** * * @author jdenise@redhat.com */ @RunWith(WildflyTestRunner.class) @ServerControl(manual = true) public class CommandTimeoutHandlerTestCase { @Inject private ServerController container; private interface Action { void doIt(CommandContext ctx) throws CommandLineException; } private class CommandHandlerWrapper implements CommandHandler { private final CommandHandler handler; private final Action action; CommandHandlerWrapper(CommandContext ctx, CommandHandler handler, Action action) { this.handler = handler; this.action = action; } @Override public boolean isAvailable(CommandContext ctx) { return handler.isAvailable(ctx); } @Override public boolean isBatchMode(CommandContext ctx) { return handler.isBatchMode(ctx); } @Override public void handle(CommandContext ctx) throws CommandLineException { action.doIt(ctx); } @Override public CommandArgument getArgument(CommandContext ctx, String name) { return handler.getArgument(ctx, name); } @Override public boolean hasArgument(CommandContext ctx, String name) { return handler.hasArgument(ctx, name); } @Override public boolean hasArgument(CommandContext ctx, int index) { return handler.hasArgument(ctx, index); } @Override public Collection<CommandArgument> getArguments(CommandContext ctx) { return handler.getArguments(ctx); } } private static final int CONFIG_TIMEOUT = 1; private CommandContext ctx; private static final ByteArrayOutputStream consoleOutput = new ByteArrayOutputStream(); @Before public void beforeTest() throws CliInitializationException, IOException, CommandLineException { container.start(); consoleOutput.reset(); ExtensionUtils.createExtensionModule("org.wildfly.extension.blocker-test", BlockerExtension.class, EmptySubsystemParser.class.getPackage()); CommandContextConfiguration config = new CommandContextConfiguration.Builder(). setInitConsole(true).setConsoleOutput(consoleOutput). setController("remote+http://" + TestSuiteEnvironment.getServerAddress() + ":" + TestSuiteEnvironment.getServerPort()). setCommandTimeout(CONFIG_TIMEOUT).build(); ctx = CommandContextFactory.getInstance().newCommandContext(config); ctx.handle("command-timeout reset default"); ctx.handle("connect"); ctx.handle("/extension=org.wildfly.extension.blocker-test:add"); ctx.handle("command-timeout reset config"); } @After public void afterTest() throws Exception { // Just in case something went wrong. try { ctx.handle("command-timeout reset default"); if (ctx.isBatchMode()) { ctx.handle("discard-batch"); } checkInactiveOperations(); ctx.handle("/extension=org.wildfly.extension.blocker-test:remove"); } finally { ctx.terminateSession(); container.stop(); ExtensionUtils.deleteExtensionModule("org.wildfly.extension.blocker-test"); } } @Test public void testTimeoutHandler() throws CommandLineException { int t = CONFIG_TIMEOUT; checkTimeout(t, ctx); t = 4; ctx.handle("command-timeout set " + t); checkTimeout(t, ctx); t = 0; ctx.handle("command-timeout set " + t); checkTimeout(t, ctx); ctx.handle("command-timeout reset config"); checkTimeout(CONFIG_TIMEOUT, ctx); ctx.handle("command-timeout reset default"); checkTimeout((short) 0, ctx); t = 4; ctx.handle("command-timeout set " + t); checkTimeout(t, ctx); try { ctx.handle("command-timeout set a"); throw new RuntimeException("Should have failed"); } catch (CommandLineException ex) { // XXX OK, expected. } try { ctx.handle("command-timeout set -1"); throw new RuntimeException("Should have failed"); } catch (CommandLineException ex) { // XXX OK, expected. } try { ctx.handle("command-timeout reset toto"); throw new RuntimeException("Should have failed"); } catch (CommandLineException ex) { // XXX OK, expected. } } @Test public void testReloadShutdown() throws CommandLineException { ctx.handle("command-timeout set 60"); ctx.handle("reload"); } @Test public void testExecutionTimeout() throws CommandLineException { ctx.setCommandTimeout((short) 2); ctx.handle("take-your-time 1000"); try { ctx.handle("take-your-time 3000 --local"); throw new RuntimeException("Should have failed"); } catch (CommandLineException ex) { // XXX OK, expected } try { ctx.handle("/subsystem=blocker-test:block(block-point=MODEL,block-time=400000)"); throw new RuntimeException("Should have failed"); } catch (CommandLineException ex) { // XXX OK, expected } ctx.handle("command-timeout set 2"); ctx.handle("take-your-time 1000"); } @Test public void testBatchTimeout() throws CommandLineException { ctx.handle("command-timeout set 2"); ctx.handle("batch"); ctx.handle("take-your-time 2000 --local"); ctx.handle("take-your-time 2000"); try { ctx.handle("run-batch"); throw new RuntimeException("Should have failed"); } catch (CommandLineException ex) { if (!ex.getMessage().equals("Timeout exception for run-batch")) { throw new CommandLineException("Not excepected exception ", ex); } // XXX OK expected. } // For now, keep the batch after a timeout so must discard it ctx.handle("discard-batch"); } @Test public void testIfTimeout() throws CommandLineException { String prop = "test-" + System.currentTimeMillis(); ctx.handle("/system-property=" + prop + ":add(value=false)"); // This timeout is for each command ctx.handle("command-timeout set 3"); ctx.handle("if result.value==\"true\" of /system-property=" + prop + ":read-resource"); ctx.handle(":never-called-or-failed-1"); ctx.handle("else"); ctx.handle("take-your-time 2000"); ctx.handle("take-your-time 2000"); ctx.handle("end-if"); // inside an if block, timeout exception stop the block execution ctx.handle("if result.value==\"true\" of /system-property=" + prop + ":read-resource"); ctx.handle(":never-called-or-failed-2"); ctx.handle("else"); // This one will fail ctx.handle("command-timeout set 2"); ctx.handle("take-your-time 40000"); ctx.handle("/system-property=" + prop + ":write-attribute(name=value, value=true)"); try { ctx.handle("end-if"); throw new RuntimeException("Should have failed"); } catch (CommandLineException ex) { if (!ex.getMessage().equals("Timeout exception for take-your-time 40000")) { throw new CommandLineException("Not expected exception ", ex); } } //Check that the property has not been set to true. ctx.handle("if result.value==\"true\" of /system-property=" + prop + ":read-resource"); ctx.handle(":never-called-or-failed-3"); ctx.handle("end-if"); //Check that a batch that timeout in an if block stops execution ctx.handle("if result.value==\"false\" of /system-property=" + prop + ":read-resource"); ctx.handle("command-timeout set 2"); // This one should fail ctx.handle("batch"); ctx.handle("take-your-time 20000"); ctx.handle("take-your-time 20000"); ctx.handle("run-batch"); // This one should not be executed ctx.handle("/system-property=" + prop + ":write-attribute(name=value, value=true)"); try { ctx.handle("end-if"); throw new RuntimeException("Should have failed"); } catch (CommandLineException ex) { if (!ex.getMessage().equals("Timeout exception for run-batch")) { throw new CommandLineException("Not expected exception ", ex); } } //Check that the property has not been added. ctx.handle("if result.value==\"true\" of /system-property=" + prop + ":read-resource"); ctx.handle(":never-called-or-failed-4"); ctx.handle("end-if"); // Timeout in condition. ctx.handle("command-timeout set 2"); ctx.handle("if result.value!=true of take-your-time 40000"); ctx.handle("/system-property=" + prop + ":write-attribute(name=value, value=true)"); ctx.handle("else"); ctx.handle("/system-property=" + prop + ":write-attribute(name=value, value=true)"); try { ctx.handle("end-if"); throw new RuntimeException("Should have failed"); } catch (CommandLineException ex) { if (!ex.getMessage().equals("Timeout exception for if condition")) { throw new CommandLineException("Not expected exception ", ex); } } //Check that the property has not been set. ctx.handle("if result.value==\"true\" of /system-property=" + prop + ":read-resource"); ctx.handle(":never-called-or-failed-5"); ctx.handle("end-if"); ctx.handle("/system-property=" + prop + ":remove"); } @Test public void testTryTimeout() throws CommandLineException { String catchProp = "catch-" + System.currentTimeMillis(); String tryProp = "try-" + System.currentTimeMillis(); String finallyProp = "finally-" + System.currentTimeMillis(); // This timeout is for each command ctx.handle("command-timeout set 2"); ctx.handle("try"); ctx.handle("take-your-time 40000"); ctx.handle("/system-property=" + tryProp + ":add(value=true)"); ctx.handle("catch"); ctx.handle("/system-property=" + catchProp + ":add(value=true)"); ctx.handle("end-try"); // check that catch has been called. ctx.handle("if result.value!=\"true\" of /system-property=" + catchProp + ":read-resource"); ctx.handle(":never-called-or-failed-1"); ctx.handle("end-if"); //Check that the property has not been added by try. ctx.handle("if result.value==\"true\" of /system-property=" + tryProp + ":read-resource"); ctx.handle(":never-called-or-failed-2"); ctx.handle("end-if"); ctx.handle("try"); ctx.handle("/system-property=" + tryProp + ":add(value=true)"); ctx.handle("catch"); ctx.handle("/system-property=" + catchProp + ":add(value=true)"); ctx.handle("finally"); ctx.handle("take-your-time 40000"); ctx.handle("/system-property=" + finallyProp + ":add(value=true)"); try { ctx.handle("end-try"); throw new RuntimeException("Should have failed"); } catch (CommandLineException ex) { if (!ex.getMessage().equals("Timeout exception for take-your-time 40000")) { throw new CommandLineException("Not expected exception ", ex); } } // check that try has been called. ctx.handle("if result.value!=\"true\" of /system-property=" + tryProp + ":read-resource"); ctx.handle(":never-called-or-failed-3"); ctx.handle("end-if"); //Check that the property has not been added by finally. ctx.handle("if result.value==\"true\" of /system-property=" + finallyProp + ":read-resource"); ctx.handle(":never-called-or-failed-4"); ctx.handle("end-if"); ctx.handle("/system-property=" + catchProp + ":remove"); ctx.handle("/system-property=" + tryProp + ":remove"); } @Test public void testComandExecutor() throws Exception { CommandExecutor executor = new CommandExecutor(ctx); CommandHandler ls = new LsHandler(ctx); ctx.setCommandTimeout(100); // Required in order for the ctx to be in sync when calling the handler directly. ctx.handle("ls"); { List<Boolean> holder = new ArrayList<>(); CommandHandlerWrapper wrapper = new CommandHandlerWrapper(ctx, ls, (context) -> { holder.add(true); TimeoutCommandContext tc = (TimeoutCommandContext) context; tc.setLastHandlerTask(null); ls.handle(context); Future<?> future = tc.getLastTask(); if (future == null || !future.isDone()) { throw new CommandLineException("Future is not done " + future); } }); executor.execute(wrapper, 100, TimeUnit.SECONDS); if (holder.size() != 1) { throw new Exception("Handler not called"); } } { List<Object> holder = new ArrayList<>(); CommandHandlerWrapper wrapper = new CommandHandlerWrapper(ctx, ls, (context) -> { TimeoutCommandContext tc = (TimeoutCommandContext) context; tc.setLastHandlerTask(null); try { Thread.sleep(200); holder.add(null); } catch (InterruptedException ex) { holder.add(ex); Thread.currentThread().interrupt(); } try { ls.handle(context); holder.add(null); } catch (Exception ex) { // Expecting a timeout exception, // the task has already been canceled. holder.add(ex); } Future<?> future = tc.getLastTask(); holder.add(future); }); try { executor.execute(wrapper, 100, TimeUnit.MILLISECONDS); throw new RuntimeException("Should have failed"); } catch (TimeoutException ex) { // XXX OK expected. } // Wait for the task to terminate and check the various steps. int waitTime = 1000; while (holder.size() != 3 && waitTime > 0) { Thread.sleep(100); waitTime -= 100; } if (holder.size() != 3) { throw new Exception("Task didn't terminate"); } if (holder.get(0) == null) { throw new Exception("Task thread not interrupted"); } if (holder.get(1) == null) { throw new Exception("Ls has not timeouted"); } if (holder.get(2) != null) { throw new Exception("Future task is not null. Steps: " + holder); } } } private static void checkTimeout(int expected, CommandContext ctx) throws CommandLineException { consoleOutput.reset(); ctx.handle("command-timeout get"); String output = consoleOutput.toString(); assertEquals("Original Output --" + consoleOutput.toString() + "--", "" + expected + (Util.isWindows() ? "\r\n" : "\n"), output); assertEquals(ctx.getCommandTimeout(), expected); } private void checkInactiveOperations() throws Exception { // Check that no operation is still running ModelNode request = ctx.buildRequest("/core-service=management/" + "service=management-operations:read-children-resources(child-type=active-operation)"); ModelNode mn = ctx.getModelControllerClient().execute(request); String result = mn.get(Util.RESULT).toString(); if (result.contains("blocker-test")) { throw new Exception("Some requests are still running: " + result); } } }