All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version
/*
 * 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 org.apache.brooklyn.util.JavaGroovyEquivalents.elvis;
import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolver;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.BrooklynLogging;
import org.apache.brooklyn.core.effector.EffectorTasks;
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;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.feed.ConfigToAttributes;
import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
import org.apache.brooklyn.entity.software.base.lifecycle.NaiveScriptRunner;
import org.apache.brooklyn.entity.software.base.lifecycle.ScriptHelper;
import org.apache.brooklyn.location.ssh.SshMachineLocation;
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.json.ShellEnvironmentSerializer;
import org.apache.brooklyn.util.core.mutex.WithMutexes;
import org.apache.brooklyn.util.core.task.DynamicTasks;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.ssh.BashCommands;
import org.apache.brooklyn.util.stream.KnownSizeInputStream;
import org.apache.brooklyn.util.stream.Streams;
import org.apache.brooklyn.util.text.StringPredicates;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Joiner;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * An abstract SSH implementation of the {@link AbstractSoftwareProcessDriver}.
 *
 * This provides conveniences for clients implementing the install/customize/launch/isRunning/stop lifecycle
 * over SSH.  These conveniences include checking whether software is already installed,
 * creating/using a PID file for some operations, and reading ssh-specific config from the entity
 * to override/augment ssh flags on the session.
 */
public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareProcessDriver implements NaiveScriptRunner {

    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
    public void prepare() {
        // Check if we should create a download resolver?
        String downloadUrl = getEntity().config().get(SoftwareProcess.DOWNLOAD_URL);
        if (Strings.isNonEmpty(downloadUrl)) {
            resolver = Entities.newDownloader(this);
            String formatString = getArchiveNameFormat();
            if (Strings.isNonEmpty(formatString)) {
                setExpandedInstallDir(Os.mergePaths(getInstallDir(), resolver.getUnpackedDirectoryName(String.format(formatString, getVersion()))));
            } else {
                setExpandedInstallDir(getInstallDir());
            }
        }
    }

    /** include this flag in newScript creation to prevent entity-level flags from being included;
     * any SSH-specific flags passed to newScript override flags from the entity,
     * and flags from the entity override flags on the location
     * (where there aren't conflicts, flags from all three are used however) */
    public static final String IGNORE_ENTITY_SSH_FLAGS = SshEffectorTasks.IGNORE_ENTITY_SSH_FLAGS.getName();

    public AbstractSoftwareProcessSshDriver(EntityLocal entity, SshMachineLocation machine) {
        super(entity, machine);

        // FIXME this assumes we own the location, and causes warnings about configuring location after deployment;
        // better would be to wrap the ssh-execution-provider to supply these flags
        if (getSshFlags()!=null && !getSshFlags().isEmpty())
            machine.configure(getSshFlags());

        // ensure these are set using the routines below, not a global ConfigToAttributes.apply()
        getInstallDir();
        getRunDir();
    }

    /** returns location (tighten type, since we know it is an ssh machine location here) */
    @Override
    public SshMachineLocation getLocation() {
        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
     * 

* 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); } public String getSubnetHostname() { return entity.getAttribute(Attributes.SUBNET_HOSTNAME); } public String getSubnetAddress() { return entity.getAttribute(Attributes.SUBNET_ADDRESS); } protected Map getSshFlags() { return SshEffectorTasks.getSshFlags(getEntity(), getMachine()); } /** * @deprecated since 0.10.0 This method will become private in a future release. */ @Deprecated public int execute(String command, String summaryForLogging) { return execute(ImmutableList.of(command), summaryForLogging); } /** * @deprecated since 0.10.0 This method will become private in a future release. */ @Override @Deprecated public int execute(List script, String summaryForLogging) { return execute(Maps.newLinkedHashMap(), script, summaryForLogging); } /** * @deprecated since 0.10.0 This method will become private in a future release. */ @SuppressWarnings({ "rawtypes", "unchecked" }) @Override @Deprecated public int execute(Map flags2, List script, String summaryForLogging) { // TODO replace with SshEffectorTasks.ssh ?; remove the use of flags // TODO log the stdin/stdout/stderr upon error Map flags = Maps.newLinkedHashMap(); if (!flags2.containsKey(IGNORE_ENTITY_SSH_FLAGS)) { flags.putAll(getSshFlags()); } flags.putAll(flags2); Map environment = (Map) flags.get("env"); if (environment == null) { // Important only to call getShellEnvironment() if env was not supplied; otherwise it // could cause us to resolve config (e.g. block for attributeWhenReady) too early. environment = getShellEnvironment(); } if (Tasks.current()!=null) { // attach tags here, as well as in ScriptHelper, because they may have just been read from the driver if (environment!=null) { Tasks.addTagDynamically(BrooklynTaskTags.tagForEnvStream(BrooklynTaskTags.STREAM_ENV, environment)); } if (BrooklynTaskTags.stream(Tasks.current(), BrooklynTaskTags.STREAM_STDIN)==null) { Tasks.addTagDynamically(BrooklynTaskTags.tagForStreamSoft(BrooklynTaskTags.STREAM_STDIN, Streams.byteArrayOfString(Strings.join(script, "\n")))); } if (BrooklynTaskTags.stream(Tasks.current(), BrooklynTaskTags.STREAM_STDOUT)==null) { ByteArrayOutputStream stdout = new ByteArrayOutputStream(); Tasks.addTagDynamically(BrooklynTaskTags.tagForStreamSoft(BrooklynTaskTags.STREAM_STDOUT, stdout)); ByteArrayOutputStream stderr = new ByteArrayOutputStream(); Tasks.addTagDynamically(BrooklynTaskTags.tagForStreamSoft(BrooklynTaskTags.STREAM_STDERR, stderr)); flags.put("out", stdout); flags.put("err", stderr); } } if (!flags.containsKey("logPrefix")) flags.put("logPrefix", ""+entity.getId()+"@"+getLocation().getDisplayName()); return getMachine().execScript(flags, summaryForLogging, script, environment); } @Override public void copyPreInstallResources() { final WithMutexes mutexSupport = getLocation().mutexes(); String mutexId = "installation lock at host"; mutexSupport.acquireMutex(mutexId, "pre-installation lock at host for files and templates"); try { super.copyPreInstallResources(); } catch (Exception e) { log.warn("Error copying pre-install resources", e); throw Exceptions.propagate(e); } finally { mutexSupport.releaseMutex(mutexId); } } @Override public void copyInstallResources() { final WithMutexes mutexSupport = getLocation().mutexes(); String mutexId = "installation lock at host"; mutexSupport.acquireMutex(mutexId, "installation lock at host for files and templates"); try { super.copyInstallResources(); } catch (Exception e) { log.warn("Error copying install resources", e); throw Exceptions.propagate(e); } finally { mutexSupport.releaseMutex(mutexId); } } @Override public void copyCustomizeResources() { final WithMutexes mutexSupport = getLocation().mutexes(); String mutexId = "installation lock at host"; mutexSupport.acquireMutex(mutexId, "installation lock at host for files and templates"); try { super.copyCustomizeResources(); } catch (Exception e) { log.warn("Error copying customize resources", e); throw Exceptions.propagate(e); } finally { mutexSupport.releaseMutex(mutexId); } } private void executeSuccessfully(ConfigKey configKey, String label) { if(Strings.isNonBlank(getEntity().getConfig(configKey))) { log.debug("Executing {} on entity {}", label, entity.getDisplayName()); int result = execute(ImmutableList.of(getEntity().getConfig(configKey)), label); if (0 != result) { log.debug("Executing {} failed with return code {}", label, result); throw new IllegalStateException("commands for " + configKey.getName() + " failed with return code " + result); } } } @Override public void runPreInstallCommand() { executeSuccessfully(BrooklynConfigKeys.PRE_INSTALL_COMMAND, "running pre-install commands"); } @Override public void runPostInstallCommand() { executeSuccessfully(BrooklynConfigKeys.POST_INSTALL_COMMAND, "running post-install commands"); } @Override public void runPreCustomizeCommand() { executeSuccessfully(BrooklynConfigKeys.PRE_CUSTOMIZE_COMMAND, "running pre-customize commands"); } @Override public void runPostCustomizeCommand() { executeSuccessfully(BrooklynConfigKeys.POST_CUSTOMIZE_COMMAND, "running post-customize commands"); } @Override public void runPreLaunchCommand() { executeSuccessfully(BrooklynConfigKeys.PRE_LAUNCH_COMMAND, "running pre-launch commands"); } @Override public void runPostLaunchCommand() { executeSuccessfully(BrooklynConfigKeys.POST_LAUNCH_COMMAND, "running post-launch commands"); } /** * The environment variables to be set when executing the commands (for install, run, check running, etc). * @see SoftwareProcess#SHELL_ENVIRONMENT */ public Map getShellEnvironment() { Map env = entity.getConfig(SoftwareProcess.SHELL_ENVIRONMENT); ShellEnvironmentSerializer envSerializer = new ShellEnvironmentSerializer(((EntityInternal)entity).getManagementContext()); return envSerializer.serialize(env); } /** * @param sshFlags Extra flags to be used when making an SSH connection to the entity's machine. * If the map contains the key {@link #IGNORE_ENTITY_SSH_FLAGS} then only the * given flags are used. Otherwise, the given flags are combined with (and take * precendence over) the flags returned by {@link #getSshFlags()}. * @param source URI of file to copy, e.g. file://.., http://.., classpath://.. * @param target Destination on server, relative to {@link #getRunDir()} if not absolute path * @param createParentDir Whether to create the parent target directory, if it doesn't already exist * @return The exit code of the SSH command run */ @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public int copyResource(Map sshFlags, String source, String target, boolean createParentDir) { // TODO use SshTasks.put instead, better logging Map flags = Maps.newLinkedHashMap(); if (!sshFlags.containsKey(IGNORE_ENTITY_SSH_FLAGS)) { flags.putAll(getSshFlags()); } flags.putAll(sshFlags); String destination = Os.isAbsolutish(target) ? target : Os.mergePathsUnix(getRunDir(), target); if (createParentDir) { // don't use File.separator because it's remote machine's format, rather than local machine's int lastSlashIndex = destination.lastIndexOf("/"); String parent = (lastSlashIndex > 0) ? destination.substring(0, lastSlashIndex) : null; if (parent != null) { getMachine().execCommands("createParentDir", ImmutableList.of("mkdir -p "+parent)); } } int result = getMachine().installTo(resource, flags, source, destination); if (result == 0) { if (log.isDebugEnabled()) { log.debug("Copied file for {}: {} to {} - result {}", new Object[] { entity, source, destination, result }); } } return result; } /** * Input stream will be closed automatically. *

* If using {@link SshjTool} usage, consider using {@link KnownSizeInputStream} to avoid having * to write out stream once to find its size! * * @see #copyResource(Map, String, String) for parameter descriptions. */ @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public int copyResource(Map sshFlags, InputStream source, String target, boolean createParentDir) { Map flags = Maps.newLinkedHashMap(); if (!sshFlags.containsKey(IGNORE_ENTITY_SSH_FLAGS)) { flags.putAll(getSshFlags()); } flags.putAll(sshFlags); String destination = Os.isAbsolutish(target) ? target : Os.mergePathsUnix(getRunDir(), target); if (createParentDir) { // don't use File.separator because it's remote machine's format, rather than local machine's int lastSlashIndex = destination.lastIndexOf("/"); String parent = (lastSlashIndex > 0) ? destination.substring(0, lastSlashIndex) : null; if (parent != null) { getMachine().execCommands("createParentDir", ImmutableList.of("mkdir -p "+parent)); } } // TODO SshMachineLocation.copyTo currently doesn't log warn on non-zero or set blocking details // (because delegated to by installTo, for multiple calls). So do it here for now. int result; String prevBlockingDetails = Tasks.setBlockingDetails("copying resource to server at "+destination); try { result = getMachine().copyTo(flags, source, destination); } finally { Tasks.setBlockingDetails(prevBlockingDetails); } if (result == 0) { log.debug("copying stream complete; {} on {}", new Object[] { destination, getMachine() }); } else { log.warn("copying stream failed; {} on {}: {}", new Object[] { destination, getMachine(), result }); } return result; } public void checkNoHostnameBug() { try { ProcessTaskWrapper hostnameTask = DynamicTasks.queue(SshEffectorTasks.ssh("echo FOREMARKER; hostname; echo AFTMARKER")).block(); String stdout = Strings.getFragmentBetween(hostnameTask.getStdout(), "FOREMARKER", "AFTMARKER"); if (hostnameTask.getExitCode() == 0 && Strings.isNonBlank(stdout)) { String hostname = stdout.trim(); if (hostname.equals("(none)")) { String newHostname = "br-"+getEntity().getId().toLowerCase(); log.info("Detected no-hostname bug with hostname "+hostname+" for "+getEntity()+"; renaming "+getMachine()+" to hostname "+newHostname); DynamicTasks.queue(SshEffectorTasks.ssh(BashCommands.setHostname(newHostname, null))).block(); } } else { log.debug("Hostname could not be determined for location "+EffectorTasks.findSshMachine()+"; not doing no-hostname bug check"); } } catch (Exception e) { Exceptions.propagateIfFatal(e); log.warn("Error checking/fixing no-hostname bug (continuing): "+e, e); } } public static final String INSTALLING = "installing"; public static final String CUSTOMIZING = "customizing"; public static final String LAUNCHING = "launching"; public static final String CHECK_RUNNING = "check-running"; public static final String STOPPING = "stopping"; public static final String KILLING = "killing"; public static final String RESTARTING = "restarting"; public static final String PID_FILENAME = "pid.txt"; /* Flags */ /** * Use a PID file, created by launching, and reading it for check-running, * stopping and killing. The value can be true or a path to a file to * use; either relative to RUN_DIR or an absolute path. */ public static final String USE_PID_FILE = "usePidFile"; /** * Define the process owner if not the same as the brooklyn user. Both stopping and * killing will sudo to this user before issuing the kill command. Only valid * if USE_PID_FILE is also set. */ public static final String PROCESS_OWNER = "processOwner"; /** * Marks the script as having a customised setup, to prevent the default headers and footers being * added to the list of commands. */ public static final String NON_STANDARD_LAYOUT = "nonStandardLayout"; /** * Prevents creation of the $INSTALL_DIR/BROOKLYN marker file after installing * phase finishes, to allow further installation phases to execute. */ public static final String INSTALL_INCOMPLETE = "installIncomplete"; /** * Enable shell debugging output via set -x prepended to the command header. */ public static final String DEBUG = "debug"; /** Permitted flags for {@link #newScript(Map, String)}. */ public static final List VALID_FLAGS = ImmutableList.of(USE_PID_FILE, PROCESS_OWNER, NON_STANDARD_LAYOUT, INSTALL_INCOMPLETE, DEBUG); /** @see #newScript(Map, String) */ protected ScriptHelper newScript(String phase) { return newScript(Maps.newLinkedHashMap(), phase); } /** * Sets up a {@link ScriptHelper} to generate a script that controls the given phase * (check-running, launching etc.) including default header and * footer commands. *

* Supported flags: *

    *
  • usePidFile - true or filename to save and retrieve the PID *
  • processOwner - username that owns the running process *
  • nonStandardLayout - true to omit all default commands *
  • installIncomplete - true to prevent marking complete *
  • debug - true to enable shell debug output *
  • * * @param flags a {@link Map} of flags to control script generation * @param phase the phase to create the ScriptHelper for * * @see #newScript(String) * @see #USE_PID_FILE * @see #PROCESS_OWNER * @see #NON_STANDARD_LAYOUT * @see #INSTALL_INCOMPLETE * @see #DEBUG */ protected ScriptHelper newScript(Map flags, String phase) { if (!Entities.isManaged(getEntity())) throw new IllegalStateException(getEntity()+" is no longer managed; cannot create script to run here ("+phase+")"); if (!Iterables.all(flags.keySet(), StringPredicates.equalToAny(VALID_FLAGS))) { throw new IllegalArgumentException("Invalid flags passed: " + flags); } ScriptHelper s = new ScriptHelper(this, phase+" "+elvis(entity,this)); if (!groovyTruth(flags.get(NON_STANDARD_LAYOUT))) { if (groovyTruth(flags.get(DEBUG))) { s.header.prepend("set -x"); } if (INSTALLING.equals(phase)) { // mutexId should be global because otherwise package managers will contend with each other final String mutexId = "installation lock at host"; s.useMutex(getLocation().mutexes(), mutexId, "installing "+elvis(entity,this)); s.header.append( "export INSTALL_DIR=\""+getInstallDir()+"\"", "mkdir -p $INSTALL_DIR", "cd $INSTALL_DIR", "test -f BROOKLYN && exit 0" ); if (!groovyTruth(flags.get(INSTALL_INCOMPLETE))) { s.footer.append("date > $INSTALL_DIR/BROOKLYN"); } // don't set vars during install phase, prevent dependency resolution s.environmentVariablesReset(); } if (ImmutableSet.of(CUSTOMIZING, LAUNCHING, CHECK_RUNNING, STOPPING, KILLING, RESTARTING).contains(phase)) { s.header.append( "export INSTALL_DIR=\""+getInstallDir()+"\"", "export RUN_DIR=\""+getRunDir()+"\"", "mkdir -p $RUN_DIR", "cd $RUN_DIR" ); } } if (ImmutableSet.of(LAUNCHING, RESTARTING).contains(phase)) { s.failIfBodyEmpty(); } if (ImmutableSet.of(STOPPING, KILLING).contains(phase)) { // stopping and killing allowed to have empty body if pid file set if (!groovyTruth(flags.get(USE_PID_FILE))) s.failIfBodyEmpty(); } if (ImmutableSet.of(INSTALLING, LAUNCHING).contains(phase)) { s.updateTaskAndFailOnNonZeroResultCode(); } if (phase.equalsIgnoreCase(CHECK_RUNNING)) { s.setInessential(); s.setTransient(); s.setFlag(SshTool.PROP_CONNECT_TIMEOUT, Duration.TEN_SECONDS.toMilliseconds()); s.setFlag(SshTool.PROP_SESSION_TIMEOUT, Duration.THIRTY_SECONDS.toMilliseconds()); s.setFlag(SshTool.PROP_SSH_TRIES, 1); } if (groovyTruth(flags.get(USE_PID_FILE))) { Object usePidFile = flags.get(USE_PID_FILE); String pidFile = (usePidFile instanceof CharSequence ? usePidFile : Os.mergePathsUnix(getRunDir(), PID_FILENAME)).toString(); String processOwner = (String) flags.get(PROCESS_OWNER); if (LAUNCHING.equals(phase)) { entity.sensors().set(SoftwareProcess.PID_FILE, pidFile); s.footer.prepend("echo $! > "+pidFile); } else if (CHECK_RUNNING.equals(phase)) { // old method, for supplied service, or entity.id // "ps aux | grep ${service} | grep \$(cat ${pidFile}) > /dev/null" // new way, preferred? if (processOwner != null) { s.body.append( BashCommands.sudoAsUser(processOwner, "test -f "+pidFile) + " || exit 1", "ps -p $(" + BashCommands.sudoAsUser(processOwner, "cat "+pidFile) + ")" ); } else { s.body.append( "test -f "+pidFile+" || exit 1", "ps -p `cat "+pidFile+"`" ); } // no pid, not running; 1 is not running s.requireResultCode(Predicates.or(Predicates.equalTo(0), Predicates.equalTo(1))); } else if (STOPPING.equals(phase)) { String stopCommand = Joiner.on('\n').join("PID=$(cat "+pidFile + ")", "test -n \"$PID\" || exit 0", "SIGTERM_USED=\"\"", "for i in $(seq 1 16); do", " if ps -p $PID > /dev/null ; then", " kill $PID", " echo Attempted to stop PID $PID by sending SIGTERM.", " else", " echo Process $PID stopped successfully.", " SIGTERM_USED=\"true\"", " break", " fi", " sleep 1", "done", "if test -z $SIGTERM_USED; then", " kill -9 $PID", " echo Sent SIGKILL to $PID", "fi", "rm -f " + pidFile); if (processOwner != null) { s.body.append(BashCommands.sudoAsUser(processOwner, stopCommand)); } else { s.body.append(stopCommand); } } else if (KILLING.equals(phase)) { if (processOwner != null) { s.body.append( "export PID=$(" + BashCommands.sudoAsUser(processOwner, "cat "+pidFile) + ")", "test -n \"$PID\" || exit 0", BashCommands.sudoAsUser(processOwner, "kill -9 $PID"), BashCommands.sudoAsUser(processOwner, "rm -f "+pidFile) ); } else { s.body.append( "export PID=$(cat "+pidFile+")", "test -n \"$PID\" || exit 0", "kill -9 $PID", "rm -f "+pidFile ); } } else if (RESTARTING.equals(phase)) { if (processOwner != null) { s.footer.prepend( BashCommands.sudoAsUser(processOwner, "test -f "+pidFile) + " || exit 1", "ps -p $(" + BashCommands.sudoAsUser(processOwner, "cat "+pidFile) + ") || exit 1" ); } else { s.footer.prepend( "test -f "+pidFile+" || exit 1", "ps -p $(cat "+pidFile+") || exit 1" ); } // no pid, not running; no process; can't restart, 1 is not running } else { log.warn(USE_PID_FILE + ": script option not valid for " + s.summary); } } return s; } @Override protected void createDirectory(String directoryName, String summaryForLogging) { DynamicTasks.queue(SshEffectorTasks.ssh("mkdir -p " + directoryName).summary(summaryForLogging) .requiringExitCodeZero()).get(); } public Set getPortsUsed() { Set result = Sets.newLinkedHashSet(); result.add(22); return result; } @Override public void setup() { } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy