| /* |
| * 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.entity.software.base; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static java.nio.file.FileVisitOption.FOLLOW_LINKS; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.nio.file.FileVisitResult; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.SimpleFileVisitor; |
| import java.nio.file.attribute.BasicFileAttributes; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.Callable; |
| |
| import org.apache.brooklyn.api.entity.EntityLocal; |
| import org.apache.brooklyn.api.location.Location; |
| import org.apache.brooklyn.api.mgmt.Task; |
| import org.apache.brooklyn.api.mgmt.TaskAdaptable; |
| import org.apache.brooklyn.config.ConfigKey; |
| 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; |
| import org.apache.brooklyn.util.core.ResourceUtils; |
| import org.apache.brooklyn.util.core.json.ShellEnvironmentSerializer; |
| import org.apache.brooklyn.util.core.task.DynamicTasks; |
| import org.apache.brooklyn.util.core.task.Tasks; |
| 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; |
| |
| import com.google.common.annotations.Beta; |
| import com.google.common.base.Function; |
| import com.google.common.base.Optional; |
| import com.google.common.collect.ImmutableMap; |
| |
| /** |
| * An abstract implementation of the {@link SoftwareProcessDriver}. |
| */ |
| public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDriver { |
| |
| private static final Logger log = LoggerFactory.getLogger(AbstractSoftwareProcessDriver.class); |
| |
| protected final EntityLocal entity; |
| 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"); |
| this.resource = ResourceUtils.create(entity); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.apache.brooklyn.entity.software.base.SoftwareProcessDriver#rebind() |
| */ |
| @Override |
| public void rebind() { |
| // no-op |
| } |
| |
| /** |
| * Start the entity. |
| * <p> |
| * This installs, configures and launches the application process. However, |
| * users can also call the {@link #install()}, {@link #customize()} and |
| * {@link #launch()} steps independently. The {@link #postLaunch()} will |
| * be called after the {@link #launch()} metheod is executed, but the |
| * process may not be completely initialised at this stage, so care is |
| * required when implementing these stages. |
| * <p> |
| * The {@link BrooklynConfigKeys#SKIP_ENTITY_START_IF_RUNNING} key can be set on the location |
| * or the entity to skip the startup process if the entity is already running, |
| * according to the {@link #isRunning()} method. To force the startup to be |
| * skipped, {@link BrooklynConfigKeys#SKIP_ENTITY_START} can be set on the entity. |
| * The {@link BrooklynConfigKeys#SKIP_ENTITY_INSTALLATION} key can also be used to |
| * skip the {@link #setup()}, {@link #copyInstallResources()} and |
| * {@link #install()} methods if set on the entity or location. |
| * |
| * @see #stop() |
| */ |
| @Override |
| public void start() { |
| boolean skipStart = false; |
| Optional<Boolean> locationRunning = Optional.fromNullable(getLocation().getConfig(BrooklynConfigKeys.SKIP_ENTITY_START_IF_RUNNING)); |
| Optional<Boolean> entityRunning = Optional.fromNullable(entity.getConfig(BrooklynConfigKeys.SKIP_ENTITY_START_IF_RUNNING)); |
| Optional<Boolean> entityStarted = Optional.fromNullable(entity.getConfig(BrooklynConfigKeys.SKIP_ENTITY_START)); |
| if (locationRunning.or(entityRunning).or(false)) { |
| skipStart = isRunning(); |
| } else { |
| skipStart = entityStarted.or(false); |
| } |
| |
| DynamicTasks.queue("prepare", new Runnable() { @Override public void run() { |
| prepare(); |
| }}); |
| |
| if (!skipStart) { |
| DynamicTasks.queue("install", new Runnable() { @Override public void run() { |
| Optional<Boolean> locationInstalled = Optional.fromNullable(getLocation().getConfig(BrooklynConfigKeys.SKIP_ENTITY_INSTALLATION)); |
| Optional<Boolean> entityInstalled = Optional.fromNullable(entity.getConfig(BrooklynConfigKeys.SKIP_ENTITY_INSTALLATION)); |
| |
| boolean skipInstall = locationInstalled.or(entityInstalled).or(false); |
| if (!skipInstall) { |
| DynamicTasks.queue("copy-pre-install-resources", new Runnable() { @Override public void run() { |
| try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.PRE_INSTALL_RESOURCES_LATCH)) { |
| copyPreInstallResources(); |
| } |
| }}); |
| |
| DynamicTasks.queue("pre-install", new Runnable() { @Override public void run() { |
| preInstall(); |
| }}); |
| |
| DynamicTasks.queue("pre-install-command", new Runnable() { @Override public void run() { |
| runPreInstallCommand(); |
| }}); |
| |
| DynamicTasks.queue("setup", new Runnable() { @Override public void run() { |
| try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.SETUP_LATCH)) { |
| setup(); |
| } |
| }}); |
| |
| DynamicTasks.queue("copy-install-resources", new Runnable() { @Override public void run() { |
| try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.INSTALL_RESOURCES_LATCH)) { |
| copyInstallResources(); |
| } |
| }}); |
| |
| DynamicTasks.queue("install (main)", new Runnable() { @Override public void run() { |
| try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.INSTALL_LATCH)) { |
| install(); |
| } |
| }}); |
| |
| DynamicTasks.queue("post-install-command", new Runnable() { @Override public void run() { |
| runPostInstallCommand(); |
| }}); |
| } |
| }}); |
| |
| DynamicTasks.queue("customize", new Runnable() { @Override public void run() { |
| DynamicTasks.queue("copy-pre-customize-resources", new Runnable() { @Override public void run() { |
| try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.CUSTOMIZE_RESOURCES_LATCH)) { |
| copyCustomizeResources(); |
| } |
| }}); |
| |
| DynamicTasks.queue("pre-customize-command", new Runnable() { @Override public void run() { |
| runPreCustomizeCommand(); |
| }}); |
| |
| DynamicTasks.queue("customize (main)", new Runnable() { @Override public void run() { |
| try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.CUSTOMIZE_LATCH)) { |
| customize(); |
| } |
| }}); |
| |
| DynamicTasks.queue("post-customize-command", new Runnable() { @Override public void run() { |
| runPostCustomizeCommand(); |
| }}); |
| }}); |
| |
| DynamicTasks.queue("launch", new Runnable() { @Override public void run() { |
| DynamicTasks.queue("copy-runtime-resources", new Runnable() { public void run() { |
| try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.RUNTIME_RESOURCES_LATCH)) { |
| copyRuntimeResources(); |
| } |
| }}); |
| |
| DynamicTasks.queue("pre-launch-command", new Runnable() { @Override public void run() { |
| runPreLaunchCommand(); |
| }}); |
| |
| DynamicTasks.queue("launch (main)", new Runnable() { @Override public void run() { |
| try (CloseableLatch value = waitForLatch(BrooklynConfigKeys.LAUNCH_LATCH)) { |
| launch(); |
| } |
| }}); |
| |
| DynamicTasks.queue("post-launch-command", new Runnable() { @Override public void run() { |
| runPostLaunchCommand(); |
| }}); |
| }}); |
| } |
| |
| DynamicTasks.queue("post-launch", new Runnable() { @Override public void run() { |
| postLaunch(); |
| }}); |
| } |
| |
| private CloseableLatch waitForLatch(ConfigKey<Boolean> configKey) { |
| return MachineLifecycleEffectorTasks.waitForCloseableLatch((EntityInternal)entity, configKey); |
| } |
| |
| @Override |
| public abstract void stop(); |
| |
| /** |
| * Prepare the entity instance before running any commands. Always executed during {@link #start()}. |
| */ |
| public void prepare() {} |
| |
| /** |
| * Implement this method in child classes to add some pre-install behavior |
| */ |
| public void preInstall() {} |
| |
| /** |
| * Implementations should fail if the return code is non-zero, by throwing some appropriate exception. |
| */ |
| public abstract void runPreInstallCommand(); |
| |
| /** |
| * Implementations should fail if the return code is non-zero, by throwing some appropriate exception. |
| */ |
| public abstract void setup(); |
| |
| /** |
| * Implementations should fail if the return code is non-zero, by throwing some appropriate exception. |
| */ |
| public abstract void install(); |
| |
| /** |
| * Implementations should fail if the return code is non-zero, by throwing some appropriate exception. |
| */ |
| public abstract void runPostInstallCommand(); |
| |
| /** |
| * Implementations should fail if the return code is non-zero, by throwing some appropriate exception. |
| */ |
| public abstract void runPreCustomizeCommand(); |
| |
| /** |
| * Implementations should fail if the return code is non-zero, by throwing some appropriate exception. |
| */ |
| public abstract void customize(); |
| |
| /** |
| * Implementations should fail if the return code is non-zero, by throwing some appropriate exception. |
| */ |
| public abstract void runPostCustomizeCommand(); |
| |
| /** |
| * Implementations should fail if the return code is non-zero, by throwing some appropriate exception. |
| */ |
| public abstract void runPreLaunchCommand(); |
| |
| /** |
| * Implementations should fail if the return code is non-zero, by throwing some appropriate exception. |
| */ |
| public abstract void launch(); |
| |
| /** |
| * Only run if launch is run (if start is not skipped). |
| * Implementations should fail if the return code is non-zero, by throwing some appropriate exception. |
| */ |
| public abstract void runPostLaunchCommand(); |
| |
| @Override |
| public void kill() { |
| stop(); |
| } |
| |
| /** |
| * Implement this method in child classes to add some post-launch behavior. |
| * This is run even if start is skipped and launch is not run. |
| */ |
| public void postLaunch() {} |
| |
| @Override |
| public void restart() { |
| DynamicTasks.queue("stop (best effort)", new Runnable() { |
| @Override |
| public void run() { |
| DynamicTasks.markInessential(); |
| boolean previouslyRunning = isRunning(); |
| try { |
| ServiceStateLogic.setExpectedState(getEntity(), Lifecycle.STOPPING); |
| stop(); |
| } catch (Exception e) { |
| // queue a failed task so that there is visual indication that this task had a failure, |
| // without interrupting the parent |
| if (previouslyRunning) { |
| log.warn(getEntity() + " restart: stop failed, when was previously running (ignoring)", e); |
| DynamicTasks.queue(Tasks.fail("Primary job failure (when previously running)", e)); |
| } else { |
| log.debug(getEntity() + " restart: stop failed (but was not previously running, so not a surprise)", e); |
| DynamicTasks.queue(Tasks.fail("Primary job failure (when not previously running)", e)); |
| } |
| // the above queued tasks will cause this task to be indicated as failed, with an indication of severity |
| } |
| } |
| }); |
| |
| DynamicTasks.queue("restart", new Runnable() { |
| @Override |
| public void run() { |
| try { |
| if (doFullStartOnRestart()) { |
| DynamicTasks.waitForLast(); |
| ServiceStateLogic.setExpectedState(getEntity(), Lifecycle.STARTING); |
| start(); |
| } else { |
| DynamicTasks.queue("pre-launch-command", new Runnable() { @Override public void run() { |
| ServiceStateLogic.setExpectedState(getEntity(), Lifecycle.STARTING); |
| runPreLaunchCommand(); |
| }}); |
| DynamicTasks.queue("launch (main)", new Runnable() { @Override public void run() { |
| launch(); |
| }}); |
| DynamicTasks.queue("post-launch-command", new Runnable() { @Override public void run() { |
| runPostLaunchCommand(); |
| }}); |
| DynamicTasks.queue("post-launch", new Runnable() { @Override public void run() { |
| postLaunch(); |
| }}); |
| } |
| DynamicTasks.waitForLast(); |
| } catch (Exception e) { |
| ServiceStateLogic.setExpectedState(entity, Lifecycle.ON_FIRE); |
| throw Exceptions.propagate(e); |
| } |
| } |
| }); |
| } |
| |
| @Beta |
| /** ideally restart() would take options, e.g. whether to do full start, skip installs, etc; |
| * however in the absence here is a toggle - not sure how well it works; |
| * default is false which is similar to previous behaviour (with some seemingly-obvious tidies), |
| * meaning install and configure will NOT be done on restart. */ |
| protected boolean doFullStartOnRestart() { |
| return false; |
| } |
| |
| @Override |
| public EntityLocal getEntity() { return entity; } |
| |
| @Override |
| public Location getLocation() { return location; } |
| |
| public InputStream getResource(String url) { |
| return resource.getResourceFromUrl(url); |
| } |
| |
| /** |
| * Files and templates to be copied to the server <em>before</em> pre-install. This allows the {@link #preInstall()} |
| * process to have access to all required resources. |
| * <p> |
| * Will be prefixed with the entity's {@link #getInstallDir() install directory} if relative. |
| * |
| * @see SoftwareProcess#PRE_INSTALL_FILES |
| * @see SoftwareProcess#PRE_INSTALL_TEMPLATES |
| * @see #copyRuntimeResources() |
| */ |
| public void copyPreInstallResources() { |
| copyResources(getInstallDir(), entity.getConfig(SoftwareProcess.PRE_INSTALL_FILES), entity.getConfig(SoftwareProcess.PRE_INSTALL_TEMPLATES)); |
| } |
| |
| /** |
| * Files and templates to be copied to the server <em>before</em> installation. This allows the {@link #install()} |
| * process to have access to all required resources. |
| * <p> |
| * Will be prefixed with the entity's {@link #getInstallDir() install directory} if relative. |
| * |
| * @see SoftwareProcess#INSTALL_FILES |
| * @see SoftwareProcess#INSTALL_TEMPLATES |
| * @see #copyRuntimeResources() |
| */ |
| public void copyInstallResources() { |
| // Ensure environment variables are not looked up here, otherwise sub-classes might |
| // lookup port numbers and fail with ugly error if port is not set; better to wait |
| // until in Entity's code (e.g. customize) where such checks are done explicitly. |
| copyResources(getInstallDir(), entity.getConfig(SoftwareProcess.INSTALL_FILES), entity.getConfig(SoftwareProcess.INSTALL_TEMPLATES)); |
| } |
| |
| /** |
| * Files and templates to be copied to the server <em>before</em> customize. This allows the {@link #customize()} |
| * process to have access to all required resources. |
| * <p> |
| * Will be prefixed with the entity's {@link #getInstallDir() install directory} if relative. |
| * |
| * @see SoftwareProcess#INSTALL_FILES |
| * @see SoftwareProcess#INSTALL_TEMPLATES |
| * @see #copyRuntimeResources() |
| */ |
| public void copyCustomizeResources() { |
| copyResources(getInstallDir(), entity.getConfig(SoftwareProcess.CUSTOMIZE_FILES), entity.getConfig(SoftwareProcess.CUSTOMIZE_TEMPLATES)); |
| } |
| |
| protected void copyResources(String destinationParentDir, Map<String, String> files, Map<String, String> templates) { |
| if (files == null) files = Collections.emptyMap(); |
| if (templates == null) templates = Collections.emptyMap(); |
| |
| final List<TaskAdaptable<?>> tasks = new ArrayList<>(files.size() + templates.size()); |
| applyFnToResourcesAppendToList(files, newCopyResourceFunction(), destinationParentDir, tasks); |
| applyFnToResourcesAppendToList(templates, newCopyTemplateFunction(), destinationParentDir, tasks); |
| |
| if (!tasks.isEmpty()) { |
| String oldBlockingDetails = Tasks.setBlockingDetails("Copying resources"); |
| try { |
| DynamicTasks.queue(Tasks.sequential(tasks)).getUnchecked(); |
| } finally { |
| Tasks.setBlockingDetails(oldBlockingDetails); |
| } |
| } |
| } |
| |
| 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) { |
| |
| 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 : 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. |
| final File srcFile = new File(source); |
| if (srcFile.isDirectory() && srcFile.exists()) { |
| try { |
| final Path start = srcFile.toPath(); |
| final int startElements = start.getNameCount(); |
| // Actually walking to a depth of Integer.MAX_VALUE would be interesting. |
| Files.walkFileTree(start, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() { |
| @Override |
| 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(), mergePaths(destination, relativePath.toString())))); |
| } |
| return FileVisitResult.CONTINUE; |
| } |
| }); |
| } catch (IOException e) { |
| throw Exceptions.propagate(e); |
| } |
| } else { |
| tasks.add(function.apply(new SourceAndDestination(source, destination))); |
| } |
| } |
| } |
| |
| private static class SourceAndDestination { |
| final String source; |
| final String destination; |
| private SourceAndDestination(String source, String destination) { |
| this.source = source; |
| this.destination = destination; |
| } |
| } |
| |
| private Function<SourceAndDestination, Task<?>> newCopyResourceFunction() { |
| return new Function<SourceAndDestination, Task<?>>() { |
| @Override |
| public Task<?> apply(final SourceAndDestination input) { |
| return Tasks.builder() |
| .displayName("Copying file: source=" + input.source + ", destination=" + input.destination) |
| .body(new Callable<Object>() { |
| @Override |
| public Integer call() { |
| return copyResource(input.source, input.destination, true); |
| } |
| }) |
| .build(); |
| } |
| }; |
| } |
| |
| private Function<SourceAndDestination, Task<?>> newCopyTemplateFunction() { |
| return new Function<SourceAndDestination, Task<?>>() { |
| @Override |
| public Task<?> apply(final SourceAndDestination input) { |
| return Tasks.builder() |
| .displayName("Copying template: source=" + input.source + ", destination=" + input.destination) |
| .body(new Callable<Object>() { |
| @Override |
| public Integer call() { |
| Map<String, Object> substitutions = MutableMap.copyOf(entity.config().get(SoftwareProcess.TEMPLATE_SUBSTITUTIONS)).asUnmodifiable(); |
| return copyTemplate(input.source, input.destination, true, substitutions); |
| } |
| }) |
| .build(); |
| } |
| }; |
| } |
| |
| protected abstract void createDirectory(String directoryName, String summaryForLogging); |
| |
| /** |
| * Files and templates to be copied to the server <em>after</em> customisation. This allows overwriting of |
| * existing files such as entity configuration which may be copied from the installation directory |
| * during the {@link #customize()} process. |
| * <p> |
| * Will be prefixed with the entity's {@link #getRunDir() run directory} if relative. |
| * |
| * @see SoftwareProcess#RUNTIME_FILES |
| * @see SoftwareProcess#RUNTIME_TEMPLATES |
| * @see #copyInstallResources() |
| */ |
| public void copyRuntimeResources() { |
| try { |
| copyResources(getRunDir(), entity.getConfig(SoftwareProcess.RUNTIME_FILES), entity.getConfig(SoftwareProcess.RUNTIME_TEMPLATES)); |
| } catch (Exception e) { |
| log.warn("Error copying runtime resources", e); |
| throw Exceptions.propagate(e); |
| } |
| } |
| |
| /** |
| * @param template File to template and copy. |
| * @param target Destination on server. |
| * @return The exit code the SSH command run. |
| */ |
| public int copyTemplate(File template, String target) { |
| return copyTemplate(template.toURI().toASCIIString(), target); |
| } |
| |
| /** |
| * @param template URI of file to template and copy, e.g. file://.., http://.., classpath://.. |
| * @param target Destination on server. |
| * @return The exit code of the SSH command run. |
| */ |
| public int copyTemplate(String template, String target) { |
| return copyTemplate(template, target, false, ImmutableMap.<String, String>of()); |
| } |
| |
| /** |
| * @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 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 sourceUrl, String target, boolean createParentDir); |
| |
| public abstract int copyResource(Map<Object,Object> sshFlags, InputStream source, String target, boolean createParentDir); |
| |
| /** |
| * @param file File to copy. |
| * @param target Destination on server. |
| * @return The exit code the SSH command run. |
| */ |
| public int copyResource(File file, String target) { |
| return copyResource(file.toURI().toASCIIString(), target); |
| } |
| |
| /** |
| * @param resource URI of file to copy, e.g. file://.., http://.., classpath://.. |
| * @param target Destination on server. |
| * @return The exit code of the SSH command run |
| */ |
| public int copyResource(String resource, String target) { |
| return copyResource(MutableMap.of(), resource, target); |
| } |
| |
| public int copyResource(String resourceUrl, String target, boolean createParentDir) { |
| return copyResource(MutableMap.of(), resourceUrl, target, createParentDir); |
| } |
| |
| @SuppressWarnings({ "rawtypes", "unchecked" }) |
| public int copyResource(Map sshFlags, String source, String target) { |
| return copyResource(sshFlags, source, target, false); |
| } |
| |
| /** |
| * @see #copyResource(Map, InputStream, String, boolean) |
| */ |
| public int copyResource(Reader source, String target) { |
| return copyResource(MutableMap.of(), source, target, false); |
| } |
| |
| /** |
| * @see #copyResource(Map, InputStream, String, boolean) |
| */ |
| public int copyResource(Map<Object,Object> sshFlags, Reader source, String target, boolean createParent) { |
| return copyResource(sshFlags, new ReaderInputStream(source), target, createParent); |
| } |
| |
| /** |
| * @see #copyResource(Map, InputStream, String, boolean) |
| */ |
| public int copyResource(InputStream source, String target) { |
| return copyResource(MutableMap.of(), source, target, false); |
| } |
| |
| public String getResourceAsString(String url) { |
| return resource.getResourceAsString(url); |
| } |
| |
| public String processTemplate(File templateConfigFile, Map<String,Object> extraSubstitutions) { |
| return processTemplate(templateConfigFile.toURI().toASCIIString(), extraSubstitutions); |
| } |
| |
| public String processTemplate(File templateConfigFile) { |
| return processTemplate(templateConfigFile.toURI().toASCIIString()); |
| } |
| |
| /** Takes the contents of a template file from the given URL (often a classpath://com/myco/myprod/myfile.conf or .sh) |
| * and replaces "${entity.xxx}" with the result of entity.getXxx() and similar for other driver, location; |
| * as well as replacing config keys on the management context |
| * <p> |
| * uses Freemarker templates under the covers |
| **/ |
| public String processTemplate(String templateConfigUrl) { |
| return processTemplate(templateConfigUrl, ImmutableMap.<String,String>of()); |
| } |
| |
| public String processTemplate(String templateConfigUrl, Map<String,? extends Object> extraSubstitutions) { |
| return processTemplateContents(getResourceAsString(templateConfigUrl), extraSubstitutions); |
| } |
| |
| public String processTemplateContents(String templateContents) { |
| return processTemplateContents(templateContents, ImmutableMap.<String,String>of()); |
| } |
| |
| public String processTemplateContents(String templateContents, Map<String,? extends Object> extraSubstitutions) { |
| return TemplateProcessor.processTemplateContents(templateContents, this, extraSubstitutions); |
| } |
| |
| public String getArchiveNameFormat() { |
| return getEntity().config().get(SoftwareProcess.ARCHIVE_DIRECTORY_NAME_FORMAT); |
| } |
| |
| public String getVersion() { |
| return getEntity().config().get(SoftwareProcess.SUGGESTED_VERSION); |
| } |
| |
| /** |
| * The environment variables to be set when executing the commands (for install, run, check running, etc). |
| * @see SoftwareProcess#SHELL_ENVIRONMENT |
| */ |
| public Map<String, String> getShellEnvironment() { |
| Map<String, Object> env = entity.getConfig(SoftwareProcess.SHELL_ENVIRONMENT); |
| ShellEnvironmentSerializer envSerializer = new ShellEnvironmentSerializer(((EntityInternal)entity).getManagementContext()); |
| return envSerializer.serialize(env); |
| } |
| |
| |
| 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()); |
| } |
| } |
| } |