blob: f523893e4c6c7fcd6b9281e94a4a9761bb66025d [file] [log] [blame]
/*
* 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.checkAssertions;
import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
import static org.apache.brooklyn.util.text.Strings.isBlank;
import static org.apache.brooklyn.util.text.Strings.isNonBlank;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.mgmt.TaskFactory;
import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
import org.apache.brooklyn.core.location.Machines;
import org.apache.brooklyn.location.ssh.SshMachineLocation;
import org.apache.brooklyn.test.framework.TestFrameworkAssertions.AssertionSupport;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.core.task.DynamicTasks;
import org.apache.brooklyn.util.core.task.ssh.SshTasks;
import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.text.Identifiers;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Duration;
// TODO assertions below should use TestFrameworkAssertions but that class needs to be improved to give better error messages
public class SimpleShellCommandTestImpl extends TargetableTestComponentImpl implements SimpleShellCommandTest {
private static final Logger LOG = LoggerFactory.getLogger(SimpleShellCommandTestImpl.class);
private static final int A_LINE = 80;
public static final String DEFAULT_NAME = "download.sh";
private static final String CD = "cd";
@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 void setUpAndRunState(boolean up, Lifecycle status) {
sensors().set(SERVICE_UP, up);
setExpectedState(this, status);
}
private static class Result {
int exitCode;
String stdout;
String stderr;
public Result(final ProcessTaskWrapper<Integer> job) {
exitCode = job.get();
stdout = job.getStdout().trim();
stderr = job.getStderr().trim();
}
public int getExitCode() {
return exitCode;
}
public String getStdout() {
return stdout;
}
public String getStderr() {
return stderr;
}
}
protected void handle(Result result) {
LOG.debug("{}, Result is {}\nwith output [\n{}\n] and error [\n{}\n]", new Object[] {
this, result.getExitCode(), shorten(result.getStdout()), shorten(result.getStderr())
});
ImmutableMap<String, Duration> flags = ImmutableMap.of("timeout", getConfig(TIMEOUT));
AssertionSupport support = new AssertionSupport();
checkAssertions(support, flags, exitCodeAssertions(), "exit code", Suppliers.ofInstance(result.getExitCode()));
checkAssertions(support, flags, getAssertions(this, ASSERT_OUT), "stdout", Suppliers.ofInstance(result.getStdout()));
checkAssertions(support, flags, getAssertions(this, ASSERT_ERR), "stderr", Suppliers.ofInstance(result.getStderr()));
support.validate();
}
private String shorten(String text) {
return Strings.maxlenWithEllipsis(text, A_LINE);
}
public void execute() {
try {
SshMachineLocation machineLocation =
Machines.findUniqueMachineLocation(resolveTarget().getLocations(), SshMachineLocation.class).get();
executeCommand(machineLocation);
setUpAndRunState(true, RUNNING);
} catch (Throwable t) {
setUpAndRunState(false, ON_FIRE);
throw Exceptions.propagate(t);
}
}
private void executeCommand(SshMachineLocation machineLocation) {
Result result = null;
String downloadUrl = getConfig(DOWNLOAD_URL);
String command = getConfig(COMMAND);
String downloadName = DOWNLOAD_URL.getName();
String commandName = COMMAND.getName();
if (!(isNonBlank(downloadUrl) ^ isNonBlank(command))) {
throw illegal("Must specify exactly one of", downloadName, "and", commandName);
}
if (isNonBlank(downloadUrl)) {
String scriptDir = getConfig(SCRIPT_DIR);
String scriptPath = calculateDestPath(downloadUrl, scriptDir);
result = executeDownloadedScript(machineLocation, downloadUrl, scriptPath);
}
if (isNonBlank(command)) {
result = executeShellCommand(machineLocation, command);
}
handle(result);
}
private Result executeDownloadedScript(SshMachineLocation machineLocation, String url, String scriptPath) {
TaskFactory<?> install = SshTasks.installFromUrl(ImmutableMap.<String, Object>of(), machineLocation, url, scriptPath);
DynamicTasks.queue(install);
DynamicTasks.waitForLast();
List<String> commands = new ArrayList<>();
commands.add("chmod u+x " + scriptPath);
maybeCdToRunDir(commands);
commands.add(scriptPath);
return runCommands(machineLocation, commands);
}
private Result executeShellCommand(SshMachineLocation machineLocation, String command) {
List<String> commands = new ArrayList<>();
maybeCdToRunDir(commands);
commands.add(command);
return runCommands(machineLocation, commands);
}
private void maybeCdToRunDir(List<String> commands) {
String runDir = getConfig(RUN_DIR);
if (!isBlank(runDir)) {
commands.add(CD + " " + runDir);
}
}
private Result runCommands(SshMachineLocation machine, List<String> commands) {
SshEffectorTasks.SshEffectorTaskFactory<Integer> etf = SshEffectorTasks.ssh(commands.toArray(new String[]{}))
.machine(machine);
ProcessTaskWrapper<Integer> job = DynamicTasks.queue(etf);
job.asTask().blockUntilEnded();
return new Result(job);
}
private IllegalArgumentException illegal(String message, String ...messages) {
return new IllegalArgumentException(Joiner.on(' ').join(this.toString() + ":", message, messages));
}
private String calculateDestPath(String url, String directory) {
try {
URL asUrl = new URL(url);
Iterable<String> path = Splitter.on("/").split(asUrl.getPath());
String scriptName = getLastPartOfPath(path, DEFAULT_NAME);
return Joiner.on("/").join(directory, "test-" + Identifiers.makeRandomId(8), scriptName);
} catch (MalformedURLException e) {
throw illegal("Malformed URL:", url);
}
}
private static String getLastPartOfPath(Iterable<String> path, String defaultName) {
MutableList<String> parts = MutableList.copyOf(path);
Collections.reverse(parts);
Iterator<String> it = parts.iterator();
String scriptName = null;
// strip any trailing "/" parts of URL
while (isBlank(scriptName) && it.hasNext()) {
scriptName = it.next();
}
if (isBlank(scriptName)) {
scriptName = defaultName;
}
return scriptName;
}
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;
}
}