/*
* 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 java.util.LinkedList;
import java.util.List;
import java.util.Map;
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.entity.Entities;
import org.apache.brooklyn.core.objs.BrooklynObjectInternal.ConfigurationSupportInternal;
import org.apache.brooklyn.entity.software.base.lifecycle.ScriptHelper;
import org.apache.brooklyn.location.ssh.SshMachineLocation;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.file.ArchiveUtils;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.net.Urls;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.ssh.BashCommands;
import org.apache.brooklyn.util.text.Identifiers;
import org.apache.brooklyn.util.text.Strings;
import com.google.common.collect.ImmutableMap;
public class VanillaSoftwareProcessSshDriver extends AbstractSoftwareProcessSshDriver implements VanillaSoftwareProcessDriver {
public VanillaSoftwareProcessSshDriver(EntityLocal entity, SshMachineLocation machine) {
super(entity, machine);
}
String downloadedFilename = null;
/**
* Needed because the download url and install commands are likely different for different VanillaSoftwareProcesses!
* This is particularly true for YAML entities. We take a hash of the download_url, install_command and environment variables.
* We thus assume any templating of the script has already been done by this point.
*/
@Override
protected String getInstallLabelExtraSalt() {
// run non-blocking in case a value set later is used (e.g. a port)
Integer hash = hashCodeIfResolved(SoftwareProcess.DOWNLOAD_URL.getConfigKey(),
VanillaSoftwareProcess.INSTALL_COMMAND, SoftwareProcess.SHELL_ENVIRONMENT);
// if any of the above blocked then we must make a unique install label,
// as other yet-unknown config is involved
if (hash==null) return Identifiers.makeRandomId(8);
// a user-friendly hash is nice, but tricky since it would have to be short;
// go with a random one unless it's totally blank
if (hash==0) return "default";
return Identifiers.makeIdFromHash(hash);
}
private Integer hashCodeIfResolved(ConfigKey<?> ...keys) {
int hash = 0;
for (ConfigKey<?> k: keys) {
Maybe<?> value = ((ConfigurationSupportInternal)getEntity().config()).getNonBlocking(k);
if (value.isAbsent()) return null;
hash = hash*31 + (value.get()==null ? 0 : value.get().hashCode());
}
return hash;
}
@Override
public void install() {
Maybe<Object> url = getEntity().getConfigRaw(SoftwareProcess.DOWNLOAD_URL, true);
if (url.isPresentAndNonNull()) {
DownloadResolver resolver = Entities.newDownloader(this);
List<String> urls = resolver.getTargets();
downloadedFilename = resolver.getFilename();
List<String> commands = new LinkedList<String>();
commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, downloadedFilename));
commands.addAll(ArchiveUtils.installCommands(downloadedFilename));
int result = newScript(ImmutableMap.of(INSTALL_INCOMPLETE, true), INSTALLING)
.failOnNonZeroResultCode(false)
.body.append(commands)
.execute();
if (result!=0) {
// could not install at remote machine; try resolving URL here and copying across
for (String urlI: urls) {
result = ArchiveUtils.install(getMachine(), urlI, Urls.mergePaths(getInstallDir(), downloadedFilename));
if (result==0)
break;
}
if (result != 0)
throw new IllegalStateException("Error installing archive: " + downloadedFilename);
}
}
// If downloadUrl did partial install (see INSTALL_INCOMPLETE above) then always execute install so mark it as completed.
String installCommand = getEntity().getConfig(VanillaSoftwareProcess.INSTALL_COMMAND);
if (url.isPresentAndNonNull() && Strings.isBlank(installCommand)) installCommand = "# mark as complete";
if (Strings.isNonBlank(installCommand)) {
newScript(INSTALLING)
.failOnNonZeroResultCode()
.environmentVariablesReset(getShellEnvironment())
.body.append(installCommand)
.execute();
}
}
@Override
public void customize() {
if (downloadedFilename != null) {
newScript(CUSTOMIZING)
.failOnNonZeroResultCode()
// don't set vars yet -- it resolves dependencies (e.g. DB) which we don't want until we start
.environmentVariablesReset()
.body.append(ArchiveUtils.extractCommands(downloadedFilename, getInstallDir()))
.execute();
}
String customizeCommand = getEntity().getConfig(VanillaSoftwareProcess.CUSTOMIZE_COMMAND);
if (Strings.isNonBlank(customizeCommand)) {
newScript(CUSTOMIZING)
.failOnNonZeroResultCode()
.body.append(customizeCommand)
.execute();
}
}
@Override
public Map<String, String> getShellEnvironment() {
return MutableMap.copyOf(super.getShellEnvironment()).add("PID_FILE", getPidFile());
}
public String getPidFile() {
// TODO see note in VanillaSoftwareProcess about PID_FILE as a config key
// if (getEntity().getConfigRaw(PID_FILE, includeInherited)) ...
return Os.mergePathsUnix(getRunDir(), PID_FILENAME);
}
@Override
public void launch() {
newScript(LAUNCHING)
.failOnNonZeroResultCode()
.body.append(getEntity().getConfig(VanillaSoftwareProcess.LAUNCH_COMMAND))
.execute();
}
@Override
public boolean isRunning() {
String customCommand = getEntity().getConfig(VanillaSoftwareProcess.CHECK_RUNNING_COMMAND);
ScriptHelper script = null;
if (customCommand == null) {
script = newScript(MutableMap.of(USE_PID_FILE, getPidFile()), CHECK_RUNNING);
} else {
// TODO: template substitutions?
script = newScript(CHECK_RUNNING).body.append(customCommand);
}
return script.execute() == 0;
}
@Override
public void stop() {
String customCommand = getEntity().getConfig(VanillaSoftwareProcess.STOP_COMMAND);
ScriptHelper script = null;
if (customCommand == null) {
script = newScript(MutableMap.of(USE_PID_FILE, getPidFile()), STOPPING);
} else {
// TODO: template substitutions?
script = newScript(STOPPING).body.append(customCommand);
}
script.execute();
}
}