This is an automated email from the ASF dual-hosted git repository. heneveld pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git
commit 9361fa64a999490b9b67fe748f3a25dab8317732 Author: Alex Heneveld <[email protected]> AuthorDate: Tue Aug 27 00:33:00 2019 +0200 create and use install/run dirs on windows --- .../camp/brooklyn/WindowsYamlLiveTest.java | 35 +++--- .../brooklyn/location/ssh/CanResolveOnBoxDir.java | 27 +++++ .../brooklyn/location/ssh/SshMachineLocation.java | 23 +++- .../base/AbstractSoftwareProcessDriver.java | 123 +++++++++++++++++++-- .../base/AbstractSoftwareProcessSshDriver.java | 100 ----------------- .../base/AbstractSoftwareProcessWinRmDriver.java | 26 ++--- .../software/base/VanillaWindowsProcess.java | 41 +++++++ .../lifecycle/MachineLifecycleEffectorTasks.java | 23 +--- .../location/winrm/WinRmMachineLocation.java | 17 ++- .../core/internal/winrm/winrm4j/Winrm4jTool.java | 8 +- 10 files changed, 257 insertions(+), 166 deletions(-) diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WindowsYamlLiveTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WindowsYamlLiveTest.java index 6e1a0a1..974f9f6 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WindowsYamlLiveTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/WindowsYamlLiveTest.java @@ -38,6 +38,7 @@ import org.apache.brooklyn.entity.software.base.test.location.WindowsTestFixture import org.apache.brooklyn.location.winrm.WinRmMachineLocation; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.core.task.TaskPredicates; +import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes; import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; import org.apache.brooklyn.util.text.StringPredicates; import org.apache.brooklyn.util.text.Strings; @@ -151,13 +152,13 @@ public class WindowsYamlLiveTest extends AbstractWindowsYamlTest { public void testPowershellMinimalist() throws Exception { Map<String, String> cmds = ImmutableMap.<String, String>builder() .put("myarg", "myval") - .put("launch.powershell.command", "\"& c:\\\\exit0.ps1\"") - .put("checkRunning.powershell.command", "\"& c:\\\\exit0.bat\"") + .put("launch.powershell.command", JavaStringEscapes.wrapJavaString("& \"$Env:INSTALL_DIR\\exit0.ps1\"")) + .put("checkRunning.powershell.command", JavaStringEscapes.wrapJavaString("& \"$Env:INSTALL_DIR\\exit0.bat\"")) .build(); Map<String, List<String>> stdouts = ImmutableMap.of(); - runWindowsApp(cmds, stdouts, null); + runWindowsApp(cmds, stdouts, true, null); } @Test(groups="Live") @@ -182,7 +183,7 @@ public class WindowsYamlLiveTest extends AbstractWindowsYamlTest { .put("winrm: pre-launch-command.*", ImmutableList.of("myval")) .build(); - runWindowsApp(cmds, stdouts, null); + runWindowsApp(cmds, stdouts, false, null); } @Test(groups="Live") @@ -207,7 +208,7 @@ public class WindowsYamlLiveTest extends AbstractWindowsYamlTest { .put("winrm: pre-launch-command.*", ImmutableList.of("myval")) .build(); - runWindowsApp(cmds, stdouts, null); + runWindowsApp(cmds, stdouts, false, null); } @Test(groups="Live") @@ -227,7 +228,7 @@ public class WindowsYamlLiveTest extends AbstractWindowsYamlTest { Map<String, List<String>> stdouts = ImmutableMap.of(); - runWindowsApp(cmds, stdouts, "winrm: pre-install-command.*"); + runWindowsApp(cmds, stdouts, false, "winrm: pre-install-command.*"); } // FIXME Failing to match the expected exception, but looks fine! Needs more investigation. @@ -248,7 +249,7 @@ public class WindowsYamlLiveTest extends AbstractWindowsYamlTest { Map<String, List<String>> stdouts = ImmutableMap.of(); - runWindowsApp(cmds, stdouts, "winrm: is-running-command.*"); + runWindowsApp(cmds, stdouts, false, "winrm: is-running-command.*"); } // FIXME Needs more work to get the stop's task that failed, so can assert got the right error message @@ -269,29 +270,29 @@ public class WindowsYamlLiveTest extends AbstractWindowsYamlTest { Map<String, List<String>> stdouts = ImmutableMap.of(); - runWindowsApp(cmds, stdouts, "winrm: stop-command.*"); + runWindowsApp(cmds, stdouts, false, "winrm: stop-command.*"); } - protected void runWindowsApp(Map<String, String> commands, Map<String, List<String>> stdouts, String taskRegexFailed) throws Exception { + protected void runWindowsApp(Map<String, String> commands, Map<String, List<String>> stdouts, boolean useInstallDir, String taskRegexFailed) throws Exception { String cmdFailed = (taskRegexFailed == null) ? null : TASK_REGEX_TO_COMMAND.get(taskRegexFailed); List<String> yaml = Lists.newArrayList(); yaml.addAll(yamlLocation); + String prefix = useInstallDir ? "" : "c:\\"; yaml.addAll(ImmutableList.of( "services:", "- type: org.apache.brooklyn.entity.software.base.VanillaWindowsProcess", " brooklyn.config:", - " onbox.base.dir.skipResolution: true", " templates.preinstall:", " classpath://org/apache/brooklyn/camp/brooklyn/echoFreemarkerMyarg.bat: c:\\echoFreemarkerMyarg.bat", " classpath://org/apache/brooklyn/camp/brooklyn/echoFreemarkerMyarg.ps1: c:\\echoFreemarkerMyarg.ps1", " files.preinstall:", - " classpath://org/apache/brooklyn/camp/brooklyn/echoArg.bat: c:\\echoArg.bat", - " classpath://org/apache/brooklyn/camp/brooklyn/echoMyArg.ps1: c:\\echoMyArg.ps1", - " classpath://org/apache/brooklyn/camp/brooklyn/exit0.bat: c:\\exit0.bat", - " classpath://org/apache/brooklyn/camp/brooklyn/exit1.bat: c:\\exit1.bat", - " classpath://org/apache/brooklyn/camp/brooklyn/exit0.ps1: c:\\exit0.ps1", - " classpath://org/apache/brooklyn/camp/brooklyn/exit1.ps1: c:\\exit1.ps1", + " classpath://org/apache/brooklyn/camp/brooklyn/echoArg.bat: "+prefix+"echoArg.bat", + " classpath://org/apache/brooklyn/camp/brooklyn/echoMyArg.ps1: "+prefix+"echoMyArg.ps1", + " classpath://org/apache/brooklyn/camp/brooklyn/exit0.bat: "+prefix+"exit0.bat", + " classpath://org/apache/brooklyn/camp/brooklyn/exit1.bat: "+prefix+"exit1.bat", + " classpath://org/apache/brooklyn/camp/brooklyn/exit0.ps1: "+prefix+"exit0.ps1", + " classpath://org/apache/brooklyn/camp/brooklyn/exit1.ps1: "+prefix+"exit1.ps1", "")); for (Map.Entry<String, String> entry : commands.entrySet()) { @@ -363,7 +364,7 @@ public class WindowsYamlLiveTest extends AbstractWindowsYamlTest { private void assertPhaseStreamEquals(Entity entity, String phase, String stream, Predicate<String> check) { Optional<Task<?>> t = findTaskOrSubTask(entity, TaskPredicates.displayNameSatisfies(StringPredicates.startsWith("winrm: "+phase))); - Asserts.assertThat(BrooklynTaskTags.stream(t.get(), stream).getStreamContentsAbbreviated().trim(), check); + Asserts.assertThat(BrooklynTaskTags.stream(t.get(), stream).streamContents.get().trim(), check); } @Override diff --git a/core/src/main/java/org/apache/brooklyn/location/ssh/CanResolveOnBoxDir.java b/core/src/main/java/org/apache/brooklyn/location/ssh/CanResolveOnBoxDir.java new file mode 100644 index 0000000..a53a319 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/location/ssh/CanResolveOnBoxDir.java @@ -0,0 +1,27 @@ +/* + * 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.location.ssh; + +import org.apache.brooklyn.api.entity.Entity; + +public interface CanResolveOnBoxDir { + + String resolveOnBoxDirFor(Entity entity, String unresolvedPath); + +} diff --git a/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java b/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java index 909cc2b..ada35d0 100644 --- a/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java +++ b/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java @@ -44,6 +44,7 @@ import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.location.MachineDetails; import org.apache.brooklyn.api.location.MachineLocation; import org.apache.brooklyn.api.location.PortRange; @@ -57,6 +58,7 @@ import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.config.ConfigUtils; import org.apache.brooklyn.core.config.MapConfigKey; import org.apache.brooklyn.core.config.Sanitizer; +import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks; import org.apache.brooklyn.core.entity.BrooklynConfigKeys; import org.apache.brooklyn.core.location.AbstractMachineLocation; import org.apache.brooklyn.core.location.BasicMachineDetails; @@ -80,8 +82,10 @@ import org.apache.brooklyn.util.core.internal.ssh.SshException; import org.apache.brooklyn.util.core.internal.ssh.SshTool; import org.apache.brooklyn.util.core.internal.ssh.sshj.SshjTool; import org.apache.brooklyn.util.core.mutex.WithMutexes; +import org.apache.brooklyn.util.core.task.DynamicTasks; import org.apache.brooklyn.util.core.task.ScheduledTask; import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; import org.apache.brooklyn.util.core.task.system.internal.ExecWithLoggingHelpers; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.KeyTransformingLoadingCache.KeyTransformingSameTypeLoadingCache; @@ -128,7 +132,7 @@ import com.google.common.reflect.TypeToken; * Additionally there are routines to copyTo, copyFrom; and installTo (which tries a curl, and falls back to copyTo * in event the source is accessible by the caller only). */ -public class SshMachineLocation extends AbstractMachineLocation implements MachineLocation, PortSupplier, WithMutexes, Closeable { +public class SshMachineLocation extends AbstractMachineLocation implements MachineLocation, PortSupplier, WithMutexes, Closeable, CanResolveOnBoxDir { private static final Logger LOG = LoggerFactory.getLogger(SshMachineLocation.class); private static final Logger logSsh = LoggerFactory.getLogger(BrooklynLogging.SSH_IO); @@ -1040,4 +1044,21 @@ public class SshMachineLocation extends AbstractMachineLocation implements Machi return mutexes().hasMutex(mutexId); } + @Override + public String resolveOnBoxDirFor(Entity entity, String unresolvedPath) { + ProcessTaskWrapper<Integer> baseTask = SshEffectorTasks.ssh( + BashCommands.alternatives("mkdir -p \"${BASE_DIR}\"", + BashCommands.chain( + BashCommands.sudo("mkdir -p \"${BASE_DIR}\""), + BashCommands.sudo("chown "+getUser()+" \"${BASE_DIR}\""))), + "cd ~", + "cd ${BASE_DIR}", + "echo BASE_DIR_RESULT':'`pwd`:BASE_DIR_RESULT") + .environmentVariable("BASE_DIR", unresolvedPath) + .requiringExitCodeZero() + .summary("initializing on-box base dir "+unresolvedPath).newTask(); + DynamicTasks.queueIfPossible(baseTask).orSubmitAsync(entity); + return Strings.getFragmentBetween(baseTask.block().getStdout(), "BASE_DIR_RESULT:", ":BASE_DIR_RESULT"); + } + } diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessDriver.java index b7748df..f945aeb 100644 --- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessDriver.java +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessDriver.java @@ -47,6 +47,7 @@ import org.apache.brooklyn.core.entity.BrooklynConfigKeys; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic; +import org.apache.brooklyn.core.feed.ConfigToAttributes; import org.apache.brooklyn.entity.software.base.lifecycle.MachineLifecycleEffectorTasks; import org.apache.brooklyn.entity.software.base.lifecycle.MachineLifecycleEffectorTasks.CloseableLatch; import org.apache.brooklyn.util.collections.MutableMap; @@ -58,6 +59,7 @@ import org.apache.brooklyn.util.core.text.TemplateProcessor; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.stream.ReaderInputStream; +import org.apache.brooklyn.util.text.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,6 +79,13 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr protected final ResourceUtils resource; protected final Location location; + // we cache these for efficiency and in case the entity becomes unmanaged + private volatile String installDir; + private volatile String runDir; + private volatile String expandedInstallDir; + + private final Object installDirSetupMutex = new Object(); + public AbstractSoftwareProcessDriver(EntityLocal entity, Location location) { this.entity = checkNotNull(entity, "entity"); this.location = checkNotNull(location, "location"); @@ -441,6 +450,10 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr } } + protected String mergePaths(String ...s) { + return Os.mergePathsUnix(s); + } + private void applyFnToResourcesAppendToList( Map<String, String> resources, final Function<SourceAndDestination, Task<?>> function, String destinationParentDir, final List<TaskAdaptable<?>> tasks) { @@ -448,7 +461,7 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr for (Map.Entry<String, String> entry : resources.entrySet()) { final String source = checkNotNull(entry.getKey(), "Missing source for resource"); String target = checkNotNull(entry.getValue(), "Missing destination for resource"); - final String destination = Os.isAbsolutish(target) ? target : Os.mergePathsUnix(destinationParentDir, target); + final String destination = Os.isAbsolutish(target) ? target : mergePaths(destinationParentDir, target); // if source is a directory then copy all files underneath. // e.g. /tmp/a/{b,c/d}, source = /tmp/a, destination = dir/a/b and dir/a/c/d. @@ -463,7 +476,7 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (attrs.isRegularFile()) { Path relativePath = file.subpath(startElements, file.getNameCount()); - tasks.add(function.apply(new SourceAndDestination(file.toString(), Os.mergePathsUnix(destination, relativePath.toString())))); + tasks.add(function.apply(new SourceAndDestination(file.toString(), mergePaths(destination, relativePath.toString())))); } return FileVisitResult.CONTINUE; } @@ -562,18 +575,19 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr } /** - * @param template URI of file to template and copy, e.g. file://.., http://.., classpath://.. + * @param templateUrl URI of file to template and copy, e.g. file://.., http://.., classpath://.. * @param target Destination on server. * @param extraSubstitutions Extra substitutions for the templater to use, for example * "foo" -> "bar", and in a template ${foo}. * @return The exit code of the SSH command run. */ - public int copyTemplate(String template, String target, boolean createParent, Map<String, ?> extraSubstitutions) { - String data = processTemplate(template, extraSubstitutions); + public int copyTemplate(String templateUrl, String target, boolean createParent, Map<String, ?> extraSubstitutions) { + log.debug("Processing template "+templateUrl+" and copying to "+target+" on "+getLocation()+" for "+getEntity()); + String data = processTemplate(templateUrl, extraSubstitutions); return copyResource(MutableMap.<Object,Object>of(), new StringReader(data), target, createParent); } - public abstract int copyResource(Map<Object,Object> sshFlags, String source, String target, boolean createParentDir); + public abstract int copyResource(Map<Object,Object> sshFlags, String sourceUrl, String target, boolean createParentDir); public abstract int copyResource(Map<Object,Object> sshFlags, InputStream source, String target, boolean createParentDir); @@ -595,8 +609,8 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr return copyResource(MutableMap.of(), resource, target); } - public int copyResource(String resource, String target, boolean createParentDir) { - return copyResource(MutableMap.of(), resource, target, createParentDir); + public int copyResource(String resourceUrl, String target, boolean createParentDir) { + return copyResource(MutableMap.of(), resourceUrl, target, createParentDir); } @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -677,6 +691,95 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr return envSerializer.serialize(env); } - public abstract String getRunDir(); - public abstract String getInstallDir(); + + protected void setInstallDir(String installDir) { + this.installDir = installDir; + entity.sensors().set(SoftwareProcess.INSTALL_DIR, installDir); + } + + public String getInstallDir() { + if (installDir != null) return installDir; + + String existingVal = getEntity().getAttribute(SoftwareProcess.INSTALL_DIR); + if (Strings.isNonBlank(existingVal)) { // e.g. on rebind + installDir = existingVal; + return installDir; + } + + synchronized (installDirSetupMutex) { + // previously we looked at sensor value, but we shouldn't as it might have been converted from the config key value + // *before* we computed the install label, or that label may have changed since previous install; now force a recompute + setInstallLabel(); + + // set it null first so that we force a recompute + setInstallDir(null); + setInstallDir(Os.tidyPath(ConfigToAttributes.apply(getEntity(), SoftwareProcess.INSTALL_DIR))); + return installDir; + } + } + + protected void setInstallLabel() { + if (((EntityInternal)getEntity()).config().getLocalRaw(SoftwareProcess.INSTALL_UNIQUE_LABEL).isPresentAndNonNull()) return; + getEntity().config().set(SoftwareProcess.INSTALL_UNIQUE_LABEL, + getEntity().getEntityType().getSimpleName()+ + (Strings.isNonBlank(getVersion()) ? "_"+getVersion() : "")+ + (Strings.isNonBlank(getInstallLabelExtraSalt()) ? "_"+getInstallLabelExtraSalt() : "") ); + } + + /** allows subclasses to return extra salt (ie unique hash) + * for cases where install dirs need to be distinct e.g. based on extra plugins being placed in the install dir; + * {@link #setInstallLabel()} uses entity-type simple name and version already + * <p> + * this salt should not be too long and must not contain invalid path chars. + * a hash code of other relevant info is not a bad choice. + **/ + protected String getInstallLabelExtraSalt() { + return null; + } + + protected void setRunDir(String runDir) { + this.runDir = runDir; + entity.sensors().set(SoftwareProcess.RUN_DIR, runDir); + } + + public String getRunDir() { + if (runDir != null) return runDir; + + String existingVal = getEntity().getAttribute(SoftwareProcess.RUN_DIR); + if (Strings.isNonBlank(existingVal)) { // e.g. on rebind + runDir = existingVal; + return runDir; + } + + setRunDir(Os.tidyPath(ConfigToAttributes.apply(getEntity(), SoftwareProcess.RUN_DIR))); + return runDir; + } + + public void setExpandedInstallDir(String val) { + String oldVal = getEntity().getAttribute(SoftwareProcess.EXPANDED_INSTALL_DIR); + if (Strings.isNonBlank(oldVal) && !oldVal.equals(val)) { + log.info("Resetting expandedInstallDir (to "+val+" from "+oldVal+") for "+getEntity()); + } + + expandedInstallDir = val; + getEntity().sensors().set(SoftwareProcess.EXPANDED_INSTALL_DIR, val); + } + + public String getExpandedInstallDir() { + if (expandedInstallDir != null) return expandedInstallDir; + + String existingVal = getEntity().getAttribute(SoftwareProcess.EXPANDED_INSTALL_DIR); + if (Strings.isNonBlank(existingVal)) { // e.g. on rebind + expandedInstallDir = existingVal; + return expandedInstallDir; + } + + String untidiedVal = ConfigToAttributes.apply(getEntity(), SoftwareProcess.EXPANDED_INSTALL_DIR); + if (Strings.isNonBlank(untidiedVal)) { + setExpandedInstallDir(Os.tidyPath(untidiedVal)); + return expandedInstallDir; + } else { + throw new IllegalStateException("expandedInstallDir is null; most likely install was not called for "+getEntity()); + } + } } diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java index 9ff370c..376577b 100644 --- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessSshDriver.java @@ -81,13 +81,6 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP public static final Logger log = LoggerFactory.getLogger(AbstractSoftwareProcessSshDriver.class); public static final Logger logSsh = LoggerFactory.getLogger(BrooklynLogging.SSH_IO); - // we cache these for efficiency and in case the entity becomes unmanaged - private volatile String installDir; - private volatile String runDir; - private volatile String expandedInstallDir; - - private final Object installDirSetupMutex = new Object(); - protected volatile DownloadResolver resolver; @Override @@ -130,99 +123,6 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP return (SshMachineLocation) super.getLocation(); } - protected void setInstallDir(String installDir) { - this.installDir = installDir; - entity.sensors().set(SoftwareProcess.INSTALL_DIR, installDir); - } - - @Override - public String getInstallDir() { - if (installDir != null) return installDir; - - String existingVal = getEntity().getAttribute(SoftwareProcess.INSTALL_DIR); - if (Strings.isNonBlank(existingVal)) { // e.g. on rebind - installDir = existingVal; - return installDir; - } - - synchronized (installDirSetupMutex) { - // previously we looked at sensor value, but we shouldn't as it might have been converted from the config key value - // *before* we computed the install label, or that label may have changed since previous install; now force a recompute - setInstallLabel(); - - // set it null first so that we force a recompute - setInstallDir(null); - setInstallDir(Os.tidyPath(ConfigToAttributes.apply(getEntity(), SoftwareProcess.INSTALL_DIR))); - return installDir; - } - } - - protected void setInstallLabel() { - if (((EntityInternal)getEntity()).config().getLocalRaw(SoftwareProcess.INSTALL_UNIQUE_LABEL).isPresentAndNonNull()) return; - getEntity().config().set(SoftwareProcess.INSTALL_UNIQUE_LABEL, - getEntity().getEntityType().getSimpleName()+ - (Strings.isNonBlank(getVersion()) ? "_"+getVersion() : "")+ - (Strings.isNonBlank(getInstallLabelExtraSalt()) ? "_"+getInstallLabelExtraSalt() : "") ); - } - - /** allows subclasses to return extra salt (ie unique hash) - * for cases where install dirs need to be distinct e.g. based on extra plugins being placed in the install dir; - * {@link #setInstallLabel()} uses entity-type simple name and version already - * <p> - * this salt should not be too long and must not contain invalid path chars. - * a hash code of other relevant info is not a bad choice. - **/ - protected String getInstallLabelExtraSalt() { - return null; - } - - protected void setRunDir(String runDir) { - this.runDir = runDir; - entity.sensors().set(SoftwareProcess.RUN_DIR, runDir); - } - - @Override - public String getRunDir() { - if (runDir != null) return runDir; - - String existingVal = getEntity().getAttribute(SoftwareProcess.RUN_DIR); - if (Strings.isNonBlank(existingVal)) { // e.g. on rebind - runDir = existingVal; - return runDir; - } - - setRunDir(Os.tidyPath(ConfigToAttributes.apply(getEntity(), SoftwareProcess.RUN_DIR))); - return runDir; - } - - public void setExpandedInstallDir(String val) { - String oldVal = getEntity().getAttribute(SoftwareProcess.EXPANDED_INSTALL_DIR); - if (Strings.isNonBlank(oldVal) && !oldVal.equals(val)) { - log.info("Resetting expandedInstallDir (to "+val+" from "+oldVal+") for "+getEntity()); - } - - expandedInstallDir = val; - getEntity().sensors().set(SoftwareProcess.EXPANDED_INSTALL_DIR, val); - } - - public String getExpandedInstallDir() { - if (expandedInstallDir != null) return expandedInstallDir; - - String existingVal = getEntity().getAttribute(SoftwareProcess.EXPANDED_INSTALL_DIR); - if (Strings.isNonBlank(existingVal)) { // e.g. on rebind - expandedInstallDir = existingVal; - return expandedInstallDir; - } - - String untidiedVal = ConfigToAttributes.apply(getEntity(), SoftwareProcess.EXPANDED_INSTALL_DIR); - if (Strings.isNonBlank(untidiedVal)) { - setExpandedInstallDir(Os.tidyPath(untidiedVal)); - return expandedInstallDir; - } else { - throw new IllegalStateException("expandedInstallDir is null; most likely install was not called for "+getEntity()); - } - } - public SshMachineLocation getMachine() { return getLocation(); } public String getHostname() { return entity.getAttribute(Attributes.HOSTNAME); } public String getAddress() { return entity.getAttribute(Attributes.ADDRESS); } diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessWinRmDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessWinRmDriver.java index 0303313..eca10a1 100644 --- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessWinRmDriver.java +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessWinRmDriver.java @@ -58,7 +58,6 @@ import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; public abstract class AbstractSoftwareProcessWinRmDriver extends AbstractSoftwareProcessDriver implements NativeWindowsScriptRunner { private static final Logger LOG = LoggerFactory.getLogger(AbstractSoftwareProcessWinRmDriver.class); @@ -74,6 +73,10 @@ public abstract class AbstractSoftwareProcessWinRmDriver extends AbstractSoftwar entity.sensors().set(WINDOWS_PASSWORD, location.config().get(WinRmMachineLocation.PASSWORD)); } + protected String mergePaths(String ...s) { + return super.mergePaths(s).replaceAll("/", "\\\\"); + } + protected WinRmExecuteHelper newScript(String command, String psCommand, String phase, String taskNamePrefix) { return newScript(command, psCommand, phase, taskNamePrefix, null); } @@ -284,28 +287,17 @@ public abstract class AbstractSoftwareProcessWinRmDriver extends AbstractSoftwar } @Override - public String getRunDir() { - // TODO: This needs to be tidied, and read from the appropriate flags (if set) - return "$HOME\\brooklyn-managed-processes\\apps\\" + entity.getApplicationId() + "\\entities\\" + getEntityVersionLabel()+"_"+entity.getId(); - } - - @Override - public String getInstallDir() { - // TODO: This needs to be tidied, and read from the appropriate flags (if set) - return "$HOME\\brooklyn-managed-processes\\installs\\" + entity.getApplicationId() + "\\" + getEntityVersionLabel()+"_"+entity.getId(); - } - - @Override - public int copyResource(Map<Object, Object> sshFlags, String source, String target, boolean createParentDir) { + public int copyResource(Map<Object, Object> sshFlags, String sourceUrl, String target, boolean createParentDir) { if (createParentDir) { createDirectory(getDirectory(target), "Creating resource directory"); } InputStream stream = null; try { - Tasks.setBlockingDetails("retrieving resource "+source+" for copying across"); - stream = resource.getResourceFromUrl(source); - Tasks.setBlockingDetails("copying resource "+source+" to server"); + Tasks.setBlockingDetails("retrieving resource "+sourceUrl+" for copying across"); + stream = resource.getResourceFromUrl(sourceUrl); + Tasks.setBlockingDetails("copying resource "+sourceUrl+" to server"); + LOG.debug("Copying "+sourceUrl+" to "+target+" on "+getLocation()+" for "+getEntity()); return copyTo(stream, target); } catch (Exception e) { throw Exceptions.propagate(e); diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcess.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcess.java index e772f24..7879d58 100644 --- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcess.java +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcess.java @@ -30,12 +30,53 @@ import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.config.ConfigInheritance; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.BrooklynConfigKeys; +import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey; import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.core.sensor.TemplatedStringAttributeSensorAndConfigKey; +import org.apache.brooklyn.util.core.flags.SetFromFlag; import org.apache.brooklyn.util.time.Duration; @Catalog(name="Vanilla Windows Process", description="A basic Windows entity configured with scripts, e.g. for launch, check-running and stop") @ImplementedBy(VanillaWindowsProcessImpl.class) public interface VanillaWindowsProcess extends AbstractVanillaProcess { + + @SetFromFlag("installDir") + AttributeSensorAndConfigKey<String,String> INSTALL_DIR = new TemplatedStringAttributeSensorAndConfigKey( + "install.dir", + "Directory in which this software will be installed (if downloading/unpacking artifacts explicitly); uses FreeMarker templating format", + "${" + + "config['"+BrooklynConfigKeys.ONBOX_BASE_DIR.getName()+"']!" + + "config['"+BrooklynConfigKeys.BROOKLYN_DATA_DIR.getName()+"']!" + + "'ERROR-ONBOX_BASE_DIR-not-set'" + + "}" + + "\\" + + "installs\\" + + // the var?? tests if it exists, passing value to ?string(if_present,if_absent) + // the ! provides a default value afterwards, which is never used, but is required for parsing + // when the config key is not available; + // thus the below prefers the install.unique_label, but falls back to simple name + // plus a version identifier *if* the version is explicitly set + "${(config['install.unique_label']??)?string(config['install.unique_label']!'X'," + + "(entity.entityType.simpleName)+" + + "((config['install.version']??)?string('_'+(config['install.version']!'X'),''))" + + ")}"); + + @SetFromFlag("runDir") + AttributeSensorAndConfigKey<String,String> RUN_DIR = new TemplatedStringAttributeSensorAndConfigKey( + "run.dir", + "Directory from which this software to be run; uses FreeMarker templating format", + "${" + + "config['"+BrooklynConfigKeys.ONBOX_BASE_DIR.getName()+"']!" + + "config['"+BrooklynConfigKeys.BROOKLYN_DATA_DIR.getName()+"']!" + + "'ERROR-ONBOX_BASE_DIR-not-set'" + + "}" + + "\\" + + "apps\\${entity.applicationId}\\" + + "entities\\${entity.entityType.simpleName}_" + + "${entity.id}"); + + // 3389 is RDP; 5985 is WinRM (3389 isn't used by Brooklyn, but useful for the end-user subsequently) ConfigKey<Collection<Integer>> REQUIRED_OPEN_LOGIN_PORTS = ConfigKeys.newConfigKeyWithDefault( SoftwareProcess.REQUIRED_OPEN_LOGIN_PORTS, diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/MachineLifecycleEffectorTasks.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/MachineLifecycleEffectorTasks.java index 26c9ac4..721c3a0 100644 --- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/MachineLifecycleEffectorTasks.java +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/MachineLifecycleEffectorTasks.java @@ -44,7 +44,6 @@ import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.config.Sanitizer; import org.apache.brooklyn.core.effector.EffectorBody; import org.apache.brooklyn.core.effector.Effectors; -import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks; import org.apache.brooklyn.core.entity.Attributes; import org.apache.brooklyn.core.entity.BrooklynConfigKeys; import org.apache.brooklyn.core.entity.Entities; @@ -74,6 +73,7 @@ import org.apache.brooklyn.entity.software.base.SoftwareProcess.StopSoftwarePara import org.apache.brooklyn.entity.software.base.SoftwareProcess.StopSoftwareParameters.StopMode; import org.apache.brooklyn.entity.stock.EffectorStartableImpl.StartParameters; import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation; +import org.apache.brooklyn.location.ssh.CanResolveOnBoxDir; import org.apache.brooklyn.location.ssh.SshMachineLocation; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; @@ -81,14 +81,11 @@ import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.core.task.DynamicTasks; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.core.task.ValueResolverIterator; -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.net.UserAndHostAndPort; import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.repeat.Repeater; -import org.apache.brooklyn.util.ssh.BashCommands; -import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -561,6 +558,7 @@ public abstract class MachineLifecycleEffectorTasks { if (base==null) base = machine.getConfig(BrooklynConfigKeys.BROOKLYN_DATA_DIR); if (base==null) base = entity.getManagementContext().getConfig().getConfig(BrooklynConfigKeys.BROOKLYN_DATA_DIR); if (base==null) base = "~/brooklyn-managed-processes"; + if (base.equals("~")) base="."; if (base.startsWith("~/")) base = "."+base.substring(1); @@ -569,21 +567,8 @@ public abstract class MachineLifecycleEffectorTasks { if (log.isDebugEnabled()) log.debug("Skipping on-box base dir resolution for "+entity+" at "+machine); if (!Os.isAbsolutish(base)) base = "~/"+base; resolvedBase = Os.tidyPath(base); - } else if (machine instanceof SshMachineLocation) { - SshMachineLocation ms = (SshMachineLocation)machine; - ProcessTaskWrapper<Integer> baseTask = SshEffectorTasks.ssh( - BashCommands.alternatives("mkdir -p \"${BASE_DIR}\"", - BashCommands.chain( - BashCommands.sudo("mkdir -p \"${BASE_DIR}\""), - BashCommands.sudo("chown "+ms.getUser()+" \"${BASE_DIR}\""))), - "cd ~", - "cd ${BASE_DIR}", - "echo BASE_DIR_RESULT':'`pwd`:BASE_DIR_RESULT") - .environmentVariable("BASE_DIR", base) - .requiringExitCodeZero() - .summary("initializing on-box base dir "+base).newTask(); - DynamicTasks.queueIfPossible(baseTask).orSubmitAsync(entity); - resolvedBase = Strings.getFragmentBetween(baseTask.block().getStdout(), "BASE_DIR_RESULT:", ":BASE_DIR_RESULT"); + } else if (machine instanceof CanResolveOnBoxDir) { + resolvedBase = ((CanResolveOnBoxDir)machine).resolveOnBoxDirFor(entity, base); } if (resolvedBase==null) { if (!Os.isAbsolutish(base)) base = "~/"+base; diff --git a/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java index 9471096..0fd93b7 100644 --- a/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java +++ b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java @@ -32,6 +32,7 @@ import java.util.Set; import javax.annotation.Nullable; +import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.location.MachineDetails; import org.apache.brooklyn.api.location.MachineLocation; import org.apache.brooklyn.api.location.OsDetails; @@ -40,19 +41,24 @@ import org.apache.brooklyn.config.ConfigKey.HasConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.config.ConfigUtils; import org.apache.brooklyn.core.config.Sanitizer; +import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks; import org.apache.brooklyn.core.entity.BrooklynConfigKeys; import org.apache.brooklyn.core.location.AbstractMachineLocation; import org.apache.brooklyn.core.location.access.PortForwardManager; import org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver; import org.apache.brooklyn.core.mgmt.ManagementContextInjectable; +import org.apache.brooklyn.location.ssh.CanResolveOnBoxDir; import org.apache.brooklyn.util.core.ClassLoaderUtils; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.core.internal.ssh.SshTool; import org.apache.brooklyn.util.core.internal.winrm.WinRmTool; import org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse; import org.apache.brooklyn.util.core.internal.winrm.winrm4j.Winrm4jTool; +import org.apache.brooklyn.util.core.task.DynamicTasks; +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.ssh.BashCommands; import org.apache.brooklyn.util.stream.Streams; import org.apache.brooklyn.util.text.Strings; import org.apache.commons.codec.binary.Base64; @@ -71,7 +77,7 @@ import com.google.common.collect.Iterables; import com.google.common.net.HostAndPort; import com.google.common.reflect.TypeToken; -public class WinRmMachineLocation extends AbstractMachineLocation implements MachineLocation { +public class WinRmMachineLocation extends AbstractMachineLocation implements MachineLocation, CanResolveOnBoxDir { private static final Logger LOG = LoggerFactory.getLogger(WinRmMachineLocation.class); @@ -492,4 +498,13 @@ public class WinRmMachineLocation extends AbstractMachineLocation implements Mac // )); } + @Override + public String resolveOnBoxDirFor(Entity entity, String unresolvedPath) { + // TODO this is simplistic, writes at c:\ for HOME + if (unresolvedPath.startsWith("./") || unresolvedPath.startsWith("~/")) { + unresolvedPath = "C:\\"+unresolvedPath.substring(2); + } + return unresolvedPath.replaceAll("/", "\\"); + } + } diff --git a/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/winrm4j/Winrm4jTool.java b/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/winrm4j/Winrm4jTool.java index 2ea8318..b7ff665 100644 --- a/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/winrm4j/Winrm4jTool.java +++ b/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/winrm4j/Winrm4jTool.java @@ -144,7 +144,12 @@ public class Winrm4jTool implements org.apache.brooklyn.util.core.internal.winrm byte[] inputData = new byte[chunkSize]; int bytesRead; int expectedFileSize = 0; + int i=0; while ((bytesRead = source.read(inputData)) > 0) { + i++; + + LOG.debug("Copying chunk "+i+" to "+destination+" on "+host); + byte[] chunk; if (bytesRead == chunkSize) { chunk = inputData; @@ -156,7 +161,8 @@ public class Winrm4jTool implements org.apache.brooklyn.util.core.internal.winrm " -value ([System.Convert]::FromBase64String(\"" + new String(Base64.encodeBase64(chunk)) + "\"))}")); expectedFileSize += bytesRead; } - + LOG.debug("Finished copying to "+destination+" on "+host); + return new org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse("", "", 0); } catch (java.io.IOException e) { throw propagate(e, "Failed copying to server at "+destination);
