| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.brooklyn.test.framework; |
| |
| import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.ON_FIRE; |
| import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.RUNNING; |
| import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.STARTING; |
| import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.STOPPED; |
| import static org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.setExpectedState; |
| import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.checkActualAgainstAssertions; |
| import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions; |
| import static org.apache.brooklyn.util.text.Strings.isNonBlank; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ExecutionException; |
| |
| import org.apache.brooklyn.api.location.Location; |
| import org.apache.brooklyn.api.mgmt.Task; |
| import org.apache.brooklyn.core.location.Machines; |
| import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; |
| import org.apache.brooklyn.location.winrm.WinRmMachineLocation; |
| import org.apache.brooklyn.util.collections.MutableList; |
| import org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse; |
| import org.apache.brooklyn.util.core.task.DynamicTasks; |
| import org.apache.brooklyn.util.core.task.TaskBuilder; |
| import org.apache.brooklyn.util.core.task.Tasks; |
| import org.apache.brooklyn.util.exceptions.Exceptions; |
| import org.apache.brooklyn.util.exceptions.ReferenceWithError; |
| import org.apache.brooklyn.util.repeat.Repeater; |
| import org.apache.brooklyn.util.text.Strings; |
| import org.apache.brooklyn.util.time.Duration; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.collect.Iterables; |
| |
| public class TestWinrmCommandImpl extends TargetableTestComponentImpl implements TestWinrmCommand { |
| |
| // FIXME Do we want dest-path? |
| // FIXME Do I need COMPUTER_NAME in flags? |
| |
| private static final Logger LOG = LoggerFactory.getLogger(TestWinrmCommandImpl.class); |
| |
| private static final int A_LINE = 80; |
| |
| @Override |
| public void start(Collection<? extends Location> locations) { |
| setExpectedState(this, STARTING); |
| execute(); |
| } |
| |
| @Override |
| public void stop() { |
| LOG.debug("{} Stopping simple command", this); |
| setUpAndRunState(false, STOPPED); |
| } |
| |
| @Override |
| public void restart() { |
| LOG.debug("{} Restarting simple command", this); |
| execute(); |
| } |
| |
| private String shorten(String text) { |
| return Strings.maxlenWithEllipsis(text, A_LINE); |
| } |
| |
| @SuppressWarnings("serial") |
| private static class MarkerException extends Exception { |
| public MarkerException(Throwable cause) { |
| super(cause); |
| } |
| } |
| |
| public void execute() { |
| try { |
| checkConfig(); |
| final WinRmMachineLocation machineLocation = |
| Machines.findUniqueMachineLocation(resolveTarget().getLocations(), WinRmMachineLocation.class).get(); |
| final Duration timeout = getRequiredConfig(TIMEOUT); |
| final Duration backoffToPeriod = getRequiredConfig(BACKOFF_TO_PERIOD); |
| |
| // TODO use TestFrameworkAssertions (or use Repeater in the same way as that does)? |
| // See identical comment in `TestSshCommandImpl`. |
| ReferenceWithError<Boolean> result = Repeater.create("Running winrm-command tests") |
| .limitTimeTo(timeout) |
| .limitIterationsTo(getRequiredConfig(ITERATION_LIMIT)) |
| .backoffTo((backoffToPeriod != null) ? backoffToPeriod : Duration.millis(500)) |
| .until(new Callable<Boolean>() { |
| @Override |
| public Boolean call() throws Exception { |
| try { |
| WinRmToolResponse result = execute(machineLocation); |
| handle(result); |
| } catch (AssertionError e) { |
| // Repeater will only handle Exceptions gracefully. Other Throwables are thrown |
| // immediately, so the AssertionError thrown by handle causes early exit. |
| throw new MarkerException(e); |
| } |
| return true; |
| } |
| }) |
| .runKeepingError(); |
| |
| if (!result.hasError()) { |
| setUpAndRunState(true, RUNNING); |
| } else { |
| setUpAndRunState(false, ON_FIRE); |
| Throwable error = result.getError(); |
| if (error instanceof MarkerException) { |
| error = error.getCause(); |
| } |
| throw Exceptions.propagate(error); |
| } |
| |
| } catch (Throwable t) { |
| setUpAndRunState(false, ON_FIRE); |
| throw Exceptions.propagate(t); |
| } |
| } |
| |
| private WinRmToolResponse execute(WinRmMachineLocation machineLocation) { |
| WinRmToolResponse result = null; |
| String psScript = getConfig(PS_SCRIPT); |
| String command = getConfig(COMMAND); |
| |
| if (isNonBlank(psScript)) { |
| result = executePsScript(machineLocation, psScript); |
| } else if (isNonBlank(command)) { |
| result = executeCommand(machineLocation, command); |
| } else { |
| // should have been caught by checkConfig() earlier; maybe someone reconfigured it on-the-fly?! |
| throw illegal("Must specify exactly one of", PS_SCRIPT.getName(), "and", COMMAND.getName()); |
| } |
| |
| return result; |
| } |
| |
| protected void checkConfig() { |
| String psScript = getConfig(PS_SCRIPT); |
| String command = getConfig(COMMAND); |
| |
| if (!(isNonBlank(psScript) ^ isNonBlank(command))) { |
| String psScriptName = PS_SCRIPT.getName(); |
| String commandName = COMMAND.getName(); |
| throw illegal("Must specify exactly one of", psScriptName, "and", commandName); |
| } |
| } |
| |
| protected void handle(WinRmToolResponse result) { |
| LOG.debug("{}, Result is {}\nwith output [\n{}\n] and error [\n{}\n]", new Object[] { |
| this, result.getStatusCode(), shorten(result.getStdOut()), shorten(result.getStdErr()) |
| }); |
| TestFrameworkAssertions.AssertionSupport support = new TestFrameworkAssertions.AssertionSupport(); |
| for (Map<String, Object> assertion : exitCodeAssertions()) { |
| checkActualAgainstAssertions(support, assertion, "exit code", result.getStatusCode()); |
| } |
| for (Map<String, Object> assertion : getAssertions(this, ASSERT_OUT)) { |
| checkActualAgainstAssertions(support, assertion, "stdout", result.getStdOut()); |
| } |
| for (Map<String, Object> assertion : getAssertions(this, ASSERT_ERR)) { |
| checkActualAgainstAssertions(support, assertion, "stderr", result.getStdErr()); |
| } |
| support.validate(); |
| } |
| |
| private WinRmToolResponse executeCommand(final WinRmMachineLocation machine, final String command) { |
| TaskBuilder<WinRmToolResponse> tb = Tasks.<WinRmToolResponse>builder().displayName("winrm-exec").body( |
| new Callable<WinRmToolResponse>() { |
| @Override |
| public WinRmToolResponse call() throws Exception { |
| return machine.executeCommand(command); |
| } |
| }); |
| return execute(tb, command); |
| } |
| |
| private WinRmToolResponse executePsScript(final WinRmMachineLocation machine, final String psScript) { |
| TaskBuilder<WinRmToolResponse> tb = Tasks.<WinRmToolResponse>builder().displayName("winrm-exec").body( |
| new Callable<WinRmToolResponse>() { |
| @Override |
| public WinRmToolResponse call() throws Exception { |
| // FIXME Do I need COMPUTER_NAME in flags? |
| return machine.executePsScript(psScript); |
| } |
| }); |
| return execute(tb, psScript); |
| } |
| |
| private WinRmToolResponse execute(TaskBuilder<WinRmToolResponse> tb, String cmdIn) { |
| try { |
| ByteArrayOutputStream stdin = new ByteArrayOutputStream(); |
| if (cmdIn != null) { |
| stdin.write(cmdIn.getBytes()); |
| } |
| tb.tag(BrooklynTaskTags.tagForStreamSoft(BrooklynTaskTags.STREAM_STDIN, stdin)); |
| } catch (IOException e) { |
| LOG.warn("Error registering stream "+BrooklynTaskTags.STREAM_STDIN+" on "+tb+": "+e, e); |
| } |
| |
| ByteArrayOutputStream stdout = new ByteArrayOutputStream(); |
| tb.tag(BrooklynTaskTags.tagForStreamSoft(BrooklynTaskTags.STREAM_STDOUT, stdout)); |
| ByteArrayOutputStream stderr = new ByteArrayOutputStream(); |
| tb.tag(BrooklynTaskTags.tagForStreamSoft(BrooklynTaskTags.STREAM_STDERR, stderr)); |
| |
| Task<WinRmToolResponse> task = tb.build(); |
| |
| WinRmToolResponse result; |
| DynamicTasks.queueIfPossible(task).orSubmitAndBlock().getTask().blockUntilEnded(); |
| try { |
| result = task.get(); |
| if (result.getStdOut() != null) stdout.write(result.getStdOut().getBytes()); |
| if (result.getStdErr() != null) stderr.write(result.getStdErr().getBytes()); |
| } catch (InterruptedException | ExecutionException e) { |
| throw Exceptions.propagate(e); |
| } catch (IOException e) { |
| // Should not happen, as just using ByteArrayOutputStream |
| throw Exceptions.propagate(e); |
| } |
| |
| return result; |
| } |
| |
| private IllegalArgumentException illegal(String message, String... messages) { |
| Iterable<String> allmsgs = Iterables.concat(MutableList.of(this.toString() + ":", message), Arrays.asList(messages)); |
| return new IllegalArgumentException(Joiner.on(' ').join(allmsgs)); |
| } |
| |
| private List<Map<String, Object>> exitCodeAssertions() { |
| |
| List<Map<String, Object>> assertStatus = getAssertions(this, ASSERT_STATUS); |
| List<Map<String, Object>> assertOut = getAssertions(this, ASSERT_OUT); |
| List<Map<String, Object>> assertErr = getAssertions(this, ASSERT_ERR); |
| |
| List<Map<String, Object>> result; |
| if (assertStatus.isEmpty() && assertOut.isEmpty() && assertErr.isEmpty()) { |
| Map<String, Object> shouldSucceed = DEFAULT_ASSERTION; |
| result = MutableList.of(shouldSucceed); |
| } else { |
| result = assertStatus; |
| } |
| return result; |
| } |
| |
| } |