Initial restructuring due to review comments. Removes as much as possible of the driver/lifecycle material, in response to https://github.com/apache/incubator-brooklyn/pull/1030#issuecomment-156714424
Driver related classes removed. SshEffectorTasks used where possible. The lifecycle class is slimmed down but retained as it takes care of getting hold of the MachineLocation. Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/b30489ed Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/b30489ed Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/b30489ed Branch: refs/heads/master Commit: b30489ed043da00162f5102dbb6ccfd8582d05df Parents: 014b6bf Author: Geoff Macartney <geoff.macart...@cloudsoftcorp.com> Authored: Tue Nov 17 10:41:42 2015 +0000 Committer: Geoff Macartney <geoff.macart...@cloudsoftcorp.com> Committed: Tue Nov 17 11:33:10 2015 +0000 ---------------------------------------------------------------------- .../brooklyn/test/framework/SimpleCommand.java | 9 + .../test/framework/SimpleCommandImpl.java | 226 +++++++++++++------ .../SimpleCommandLifecycleEffectorTasks.java | 23 +- .../test/framework/SimpleCommandSshDriver.java | 192 ---------------- .../test/framework/SimpleCommandTestImpl.java | 2 +- 5 files changed, 165 insertions(+), 287 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b30489ed/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommand.java ---------------------------------------------------------------------- diff --git a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommand.java b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommand.java index 3332a04..40e26a9 100644 --- a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommand.java +++ b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommand.java @@ -38,6 +38,15 @@ import static org.apache.brooklyn.core.config.ConfigKeys.newConfigKey; @ImplementedBy(SimpleCommandImpl.class) public interface SimpleCommand extends Entity, Startable { + /** + * Result of a command invocation. + */ + interface Result { + int getExitCode(); + String getStdout(); + String getStderr(); + } + @SetFromFlag(nullable = false) ConfigKey<String> DEFAULT_COMMAND = ConfigKeys.newConfigKey(String.class, "defaultCommand", "Command to invoke if no script is provided via a downloadUrl"); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b30489ed/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandImpl.java ---------------------------------------------------------------------- diff --git a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandImpl.java b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandImpl.java index 8b3d27f..6b5f87c 100644 --- a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandImpl.java +++ b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandImpl.java @@ -18,49 +18,69 @@ */ package org.apache.brooklyn.test.framework; -import org.apache.brooklyn.api.entity.drivers.DriverDependentEntity; -import org.apache.brooklyn.api.entity.drivers.EntityDriverManager; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.api.mgmt.TaskFactory; import org.apache.brooklyn.core.annotation.EffectorParam; +import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks; import org.apache.brooklyn.core.entity.AbstractEntity; import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.core.location.Locations; +import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.core.task.DynamicTasks; +import org.apache.brooklyn.util.core.task.ssh.SshPutTaskWrapper; +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.guava.Maybe; import org.apache.brooklyn.util.text.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Random; import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.*; import static org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.setExpectedState; +import static org.apache.brooklyn.util.text.Strings.isBlank; +import static org.apache.brooklyn.util.text.Strings.isNonBlank; /** * Implementation for {@link SimpleCommand}. */ -public class SimpleCommandImpl extends AbstractEntity - implements SimpleCommand, DriverDependentEntity<SimpleCommandDriver> { +public class SimpleCommandImpl extends AbstractEntity implements SimpleCommand { private static final Logger LOG = LoggerFactory.getLogger(SimpleCommandImpl.class); private static final int A_LINE = 80; - private transient SimpleCommandDriver driver; + public static final String DEFAULT_NAME = "download.sh"; - private Collection<? extends Location> locations; + private ResourceUtils resourceUtils; @Override - public SimpleCommandDriver getDriver() { - return driver; + public void init() { + super.init(); + resourceUtils = ResourceUtils.create(this); + getLifecycleEffectorTasks().attachLifecycleEffectors(this); } - @Override - public Class<SimpleCommandDriver> getDriverInterface() { - return SimpleCommandDriver.class; + protected SimpleCommandLifecycleEffectorTasks getLifecycleEffectorTasks () { + return new SimpleCommandLifecycleEffectorTasks(); } /** * Gives the opportunity to sub-classes to do additional work based on the result of the command. */ - protected void handle(SimpleCommandDriver.Result result) { + protected void handle(SimpleCommand.Result result) { LOG.debug("Result is {}\nwith output [\n{}\n] and error [\n{}\n]", new Object[] { result.getExitCode(), shorten(result.getStdout()), shorten(result.getStderr()) }); @@ -79,99 +99,155 @@ public class SimpleCommandImpl extends AbstractEntity @Override - public void init() { - super.init(); - getLifecycleEffectorTasks().attachLifecycleEffectors(this); - } - - - protected void initDriver(MachineLocation machine) { - LOG.debug("Initializing simple command driver"); - SimpleCommandDriver newDriver = doInitDriver(machine); - if (newDriver == null) { - throw new UnsupportedOperationException("cannot start "+this+" on "+machine+": no driver available"); - } - driver = newDriver; + public void start(@EffectorParam(name = "locations") Collection<? extends Location> locations) { + addLocations(locations); + setExpectedState(this, STARTING); } - protected SimpleCommandDriver doInitDriver(MachineLocation machine) { - if (driver!=null) { - if (machine.equals(driver.getLocation())) { - return driver; //just reuse - } else { - LOG.warn("driver/location change is untested for {} at {}; changing driver and continuing", this, machine); - return newDriver(machine); - } - } else { - return newDriver(machine); - } + @Override + public void stop() { + LOG.debug("Stopping simple command"); + setUpAndRunState(false, STOPPED); } - protected SimpleCommandDriver newDriver(MachineLocation machine) { - LOG.debug("Creating new simple command driver for {} from management context", machine); - EntityDriverManager entityDriverManager = getManagementContext().getEntityDriverManager(); - return entityDriverManager.build(this, machine); + @Override + public void restart() { + LOG.debug("Restarting simple command"); + setUpAndRunState(true, RUNNING); } - @Override - public void start(@EffectorParam(name = "locations") Collection<? extends Location> locations) { - this.locations = locations; - startOnLocations(); + private void setUpAndRunState(boolean up, Lifecycle status) { + sensors().set(SERVICE_UP, up); + setExpectedState(this, status); } - protected void startOnLocations() { - setExpectedState(this, STARTING); - int size = locations.size(); - LOG.debug("Starting simple command at {} locations{}", size, - size > 0 ? " beginning " + locations.iterator().next() : ""); + public void execute(MachineLocation machineLocation) { try { - execute(locations); + executeCommand(machineLocation); setUpAndRunState(true, RUNNING); - - } catch (final Exception e) { + } catch (Exception e) { setUpAndRunState(false, ON_FIRE); throw Exceptions.propagate(e); } } - private void execute(Collection<? extends Location> locations) { - SimpleCommandDriver.Result result = null; + private void executeCommand(MachineLocation machineLocation) { + + SimpleCommand.Result result = null; String downloadUrl = getConfig(DOWNLOAD_URL); + String command = getConfig(DEFAULT_COMMAND); + + String downloadName = DOWNLOAD_URL.getName(); + String commandName = DEFAULT_COMMAND.getName(); + + if (isNonBlank(downloadUrl) && isNonBlank(command)) { + throw illegal("Cannot specify both", downloadName, "and", commandName); + } + + if (isBlank(downloadUrl) && isBlank(commandName)) { + throw illegal("No", downloadName, "and no", commandName, "provided"); + } + if (Strings.isNonBlank(downloadUrl)) { String scriptDir = getConfig(SCRIPT_DIR); - result = getDriver().executeDownloadedScript(locations, downloadUrl, scriptDir); - - } else { - String command = getConfig(DEFAULT_COMMAND); - if (Strings.isBlank(command)) { - throw new IllegalArgumentException("No default command and no downloadUrl provided"); - } + String destPath = calculateDestPath(downloadUrl, scriptDir); + result = executeDownloadedScript(machineLocation, downloadUrl, destPath); + } - result = getDriver().execute(locations, command); + if (Strings.isNonBlank(command)) { + result = executeShellCommand(machineLocation, command); } + handle(result); } + private IllegalArgumentException illegal(String ...messages) { + return new IllegalArgumentException(Joiner.on(' ').join(this.toString() + ":", messages)); + } - @Override - public void stop() { - LOG.debug("Stopping simple command"); - setUpAndRunState(false, STOPPED); + private SimpleCommand.Result executeDownloadedScript(MachineLocation machineLocation, String downloadUrl, String destPath) { + + SshMachineLocation machine = getSshMachine(ImmutableList.<Location>of(machineLocation)); + + TaskFactory<?> install = SshTasks.installFromUrl(ImmutableMap.<String, Object>of(), machine, downloadUrl, destPath); + DynamicTasks.queue(install); + DynamicTasks.waitForLast(); + + machine.execCommands("make the script executable", ImmutableList.<String>of("chmod u+x " + destPath)); + + return executeShellCommand(machineLocation, destPath); } - @Override - public void restart() { - LOG.debug("Restarting simple command"); - setUpAndRunState(true, RUNNING); + + private SimpleCommand.Result executeShellCommand(MachineLocation machineLocation, String command) { + + SshMachineLocation machine = getSshMachine(ImmutableList.of(machineLocation)); + SshEffectorTasks.SshEffectorTaskFactory<Integer> etf = SshEffectorTasks.ssh(machine, command); + + LOG.debug("Creating task to execute '{}' on location {}", command, machine); + ProcessTaskWrapper<Integer> job = DynamicTasks.queue(etf); + DynamicTasks.waitForLast(); + return buildResult(job); } - private void setUpAndRunState(boolean up, Lifecycle status) { - sensors().set(SERVICE_UP, up); - setExpectedState(this, status); + + private <T> SimpleCommand.Result buildResult(final ProcessTaskWrapper<Integer> job) { + return new SimpleCommand.Result() { + + @Override + public int getExitCode() { + return job.get(); + } + + @Override + public String getStdout() { + return job.getStdout().trim(); + } + + @Override + public String getStderr() { + return job.getStderr().trim(); + } + }; } - protected SimpleCommandLifecycleEffectorTasks getLifecycleEffectorTasks () { - return new SimpleCommandLifecycleEffectorTasks(); + private SshMachineLocation getSshMachine(Collection<? extends Location> hostLocations) { + Maybe<SshMachineLocation> host = Locations.findUniqueSshMachineLocation(hostLocations); + if (host.isAbsent()) { + throw new IllegalArgumentException("No SSH machine found to run command"); + } + return host.get(); + } + + 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-" + randomDir(), scriptName); + } catch (MalformedURLException e) { + throw illegal("Malformed URL:", url); + } + } + + private String randomDir() { + return Integer.valueOf(new Random(System.currentTimeMillis()).nextInt(100000)).toString(); + } + + 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; } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b30489ed/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandLifecycleEffectorTasks.java ---------------------------------------------------------------------- diff --git a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandLifecycleEffectorTasks.java b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandLifecycleEffectorTasks.java index d044212..ae318ed 100644 --- a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandLifecycleEffectorTasks.java +++ b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandLifecycleEffectorTasks.java @@ -32,38 +32,23 @@ import java.util.Collection; public class SimpleCommandLifecycleEffectorTasks extends MachineLifecycleEffectorTasks { private static final Logger LOG = LoggerFactory.getLogger(SimpleCommandLifecycleEffectorTasks.class); - private MachineLocation location; protected Location getLocation(@Nullable Collection<? extends Location> locations) { return super.getLocation(entity().filterLocations(locations)); } - @Override - protected void preStartCustom(MachineLocation machine) { - location = machine; - super.preStartCustom(location); - LOG.debug("Performing lifecycle preStartCustom on simple command"); - entity().initDriver(location); - } - - @Override - protected void preRestartCustom() { - LOG.debug("Performing lifecycle preStartCustom on simple command"); - Asserts.notNull(location, "Cannot restart with no location"); - entity().initDriver(location); - } @Override protected String startProcessesAtMachine(Supplier<MachineLocation> machineS) { LOG.debug("Performing lifecycle startProcessesAtMachine on simple command"); - entity().getDriver().start(); - return "Started with driver " + entity().getDriver(); + MachineLocation machineLocation = machineS.get(); + entity().execute(machineLocation); + return "Started simple command on " + machineLocation; } @Override protected String stopProcessesAtMachine() { - LOG.debug("Performing lifecycle stopProcessesAtMachine on simple command"); - entity().getDriver().stop(); + LOG.debug("No action needed on simple command stopped"); return "Stopped"; } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b30489ed/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandSshDriver.java ---------------------------------------------------------------------- diff --git a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandSshDriver.java b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandSshDriver.java deleted file mode 100644 index f386b1e..0000000 --- a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandSshDriver.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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 com.google.common.base.Joiner; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import org.apache.brooklyn.api.entity.EntityLocal; -import org.apache.brooklyn.api.location.Location; -import org.apache.brooklyn.api.mgmt.TaskAdaptable; -import org.apache.brooklyn.api.mgmt.TaskFactory; -import org.apache.brooklyn.core.location.Locations; -import org.apache.brooklyn.location.ssh.SshMachineLocation; -import org.apache.brooklyn.util.collections.MutableList; -import org.apache.brooklyn.util.core.ResourceUtils; -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.ProcessTaskFactory; -import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; -import org.apache.brooklyn.util.exceptions.Exceptions; -import org.apache.brooklyn.util.exceptions.FatalRuntimeException; -import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.text.Strings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.Random; - -import static com.google.common.base.Preconditions.checkNotNull; -import static org.apache.brooklyn.util.text.Strings.isBlank; - -/** - * Driver for {@link SimpleCommand}. - */ -public class SimpleCommandSshDriver implements SimpleCommandDriver { - - private static final Logger LOG = LoggerFactory.getLogger(SimpleCommandSshDriver.class); - public static final String DEFAULT_NAME = "download.sh"; - - protected final EntityLocal entity; - protected final ResourceUtils resource; - protected final Location location; - - public SimpleCommandSshDriver(EntityLocal entity, SshMachineLocation location) { - LOG.debug("Constructing SSH driver for simple command for {} at {}", entity, location); - this.entity = checkNotNull(entity, "entity"); - this.location = checkNotNull(location, "location"); - this.resource = ResourceUtils.create(entity); - } - - @Override - public EntityLocal getEntity() { - return entity; - } - - @Override - public void start() { - LOG.debug("Performing start in SSH driver for simple command"); - invoke(); - } - - private void invoke() { - SimpleCommand simpleCommand = (SimpleCommand) getEntity(); - simpleCommand.start(ImmutableList.of(location)); - } - - @Override - public void restart() { - LOG.debug("Performing restart in SSH driver for simple command"); - invoke(); - } - - @Override - public void stop() { - LOG.debug("Performing stop in SSH driver for simple command"); - } - - @Override - public Result execute(Collection<? extends Location> hostLocations, String command) { - - SshMachineLocation machine = getSshMachine(hostLocations); - ProcessTaskFactory<Integer> taskFactory = SshTasks.newSshExecTaskFactory(machine, command); - - LOG.debug("Creating task to execute '{}' on location {}", command, machine); - final ProcessTaskWrapper<Integer> job = DynamicTasks.queue(taskFactory); - DynamicTasks.waitForLast(); - return buildResult(job); - } - - private <T> Result buildResult(final ProcessTaskWrapper<Integer> job) { - return new Result() { - - @Override - public int getExitCode() { - return job.get(); - } - - @Override - public String getStdout() { - return job.getStdout().trim(); - } - - @Override - public String getStderr() { - return job.getStderr().trim(); - } - }; - } - - @Override - public Result executeDownloadedScript(Collection<? extends Location> hostLocations, - String url, String directory) { - - SshMachineLocation machine = getSshMachine(hostLocations); - String destPath = calculateDestPath(url, directory); - - TaskFactory<?> install = SshTasks.installFromUrl(ImmutableMap.<String, Object>of(), machine, url, destPath); - DynamicTasks.queue(install); - DynamicTasks.waitForLast(); - - machine.execCommands("make the script executable", ImmutableList.<String>of("chmod u+x " + destPath)); - - return execute(hostLocations, destPath); - } - - 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-" + randomDir(), scriptName); - } catch (MalformedURLException e) { - throw Exceptions.propagate(new FatalRuntimeException("Malformed URL: " + url)); - } - } - - private String randomDir() { - return Integer.valueOf(new Random(System.currentTimeMillis()).nextInt(100000)).toString(); - } - - 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 SshMachineLocation getSshMachine(Collection<? extends Location> hostLocations) { - Maybe<SshMachineLocation> host = Locations.findUniqueSshMachineLocation(hostLocations); - if (host.isAbsent()) { - throw new IllegalArgumentException("No SSH machine found to run command"); - } - return host.get(); - } - - @Override - public Location getLocation() { - return location; - } - -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b30489ed/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandTestImpl.java ---------------------------------------------------------------------- diff --git a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandTestImpl.java b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandTestImpl.java index 261afe7..cf85b37 100644 --- a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandTestImpl.java +++ b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandTestImpl.java @@ -53,7 +53,7 @@ public class SimpleCommandTestImpl extends SimpleCommandImpl implements SimpleCo } @Override - protected void handle(SimpleCommandDriver.Result result) { + protected void handle(SimpleCommand.Result result) { AssertionSupport support = new AssertionSupport(); checkAssertions(support, exitCodeAssertions(), "exit code", result.getExitCode()); checkAssertions(support, getConfig(ASSERT_OUT), "stdout", result.getStdout());