| /* |
| * 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.brooklynnode; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static java.lang.String.format; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.InputStream; |
| import java.net.InetAddress; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks; |
| import org.apache.brooklyn.core.entity.drivers.downloads.DownloadSubstituters; |
| import org.apache.brooklyn.entity.brooklynnode.BrooklynNode.ExistingFileBehaviour; |
| import org.apache.brooklyn.entity.java.JavaSoftwareProcessSshDriver; |
| import org.apache.brooklyn.location.ssh.SshMachineLocation; |
| import org.apache.brooklyn.util.collections.MutableMap; |
| import org.apache.brooklyn.util.core.file.ArchiveBuilder; |
| import org.apache.brooklyn.util.core.file.ArchiveUtils; |
| import org.apache.brooklyn.util.core.internal.ssh.SshTool; |
| import org.apache.brooklyn.util.core.task.DynamicTasks; |
| import org.apache.brooklyn.util.net.Networking; |
| 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 org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.base.Objects; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Lists; |
| |
| public class BrooklynNodeSshDriver extends JavaSoftwareProcessSshDriver implements BrooklynNodeDriver { |
| |
| private static final Logger log = LoggerFactory.getLogger(BrooklynNodeSshDriver.class); |
| |
| public BrooklynNodeSshDriver(BrooklynNodeImpl entity, SshMachineLocation machine) { |
| super(entity, machine); |
| } |
| |
| @Override |
| public BrooklynNodeImpl getEntity() { |
| return (BrooklynNodeImpl) super.getEntity(); |
| } |
| |
| public String getBrooklynHome() { |
| return getRunDir(); |
| } |
| |
| @Override |
| protected String getLogFileLocation() { |
| return Os.mergePathsUnix(getRunDir(), "console"); |
| } |
| |
| protected String getPidFile() { |
| return Os.mergePathsUnix(getRunDir(), "pid_java"); |
| } |
| |
| @Override |
| protected String getInstallLabelExtraSalt() { |
| String downloadUrl = entity.getConfig(BrooklynNode.DOWNLOAD_URL); |
| String uploadUrl = entity.getConfig(BrooklynNode.DISTRO_UPLOAD_URL); |
| if (Objects.equal(downloadUrl, BrooklynNode.DOWNLOAD_URL.getConfigKey().getDefaultValue()) && |
| Objects.equal(uploadUrl, BrooklynNode.DISTRO_UPLOAD_URL.getDefaultValue())) { |
| // if both are at the default value, then no salt |
| return null; |
| } |
| return Identifiers.makeIdFromHash(Objects.hashCode(downloadUrl, uploadUrl)); |
| } |
| |
| @Override |
| public String getArchiveNameFormat() { |
| String subpath = entity.getConfig(BrooklynNode.SUBPATH_IN_ARCHIVE); |
| if (subpath==null) { |
| // assume the dir name is `basename-VERSION` where download link is `basename-VERSION-dist.tar.gz` |
| String uploadUrl = entity.getConfig(BrooklynNode.DISTRO_UPLOAD_URL); |
| String origDownloadName = uploadUrl; |
| if (origDownloadName==null) |
| origDownloadName = entity.getAttribute(BrooklynNode.DOWNLOAD_URL); |
| if (origDownloadName!=null) { |
| // BasicDownloadResolver makes it crazy hard to get the template-evaluated value of DOWNLOAD_URL |
| origDownloadName = DownloadSubstituters.substitute(origDownloadName, DownloadSubstituters.getBasicEntitySubstitutions(this)); |
| origDownloadName = Urls.decode(origDownloadName); |
| origDownloadName = Urls.getBasename(origDownloadName); |
| String downloadName = origDownloadName; |
| downloadName = Strings.removeFromEnd(downloadName, ".tar.gz"); |
| downloadName = Strings.removeFromEnd(downloadName, ".tgz"); |
| downloadName = Strings.removeFromEnd(downloadName, ".zip"); |
| if (!downloadName.equals(origDownloadName)) { |
| downloadName = Strings.removeFromEnd(downloadName, "-dist"); |
| subpath = downloadName; |
| } |
| } |
| } |
| if (subpath==null) subpath = "brooklyn-dist-%s"; |
| return subpath; |
| } |
| |
| @Override |
| public void clearInstallDir() { |
| super.setInstallDir(null); |
| super.setExpandedInstallDir(null); |
| } |
| |
| @Override |
| public void install() { |
| String uploadUrl = entity.getConfig(BrooklynNode.DISTRO_UPLOAD_URL); |
| |
| // Need to explicitly give file, because for snapshot URLs you don't get a clean filename from the URL. |
| // This filename is used to generate the first URL to try: [BROOKLYN_VERSION_BELOW] |
| // file://$HOME/.brooklyn/repository/BrooklynNode/1.0.0/brooklynnode-0.8.0-snapshot.tar.gz |
| // (DOWNLOAD_URL overrides this and has a default which comes from maven) |
| List<String> urls = resolver.getTargets(); |
| String saveAs = resolver.getFilename(); |
| |
| newScript("createInstallDir") |
| .body.append("mkdir -p "+getInstallDir()) |
| .failOnNonZeroResultCode() |
| .execute(); |
| |
| List<String> commands = Lists.newArrayList(); |
| // TODO use machine.installTo ... but that only works w a single location currently |
| if (uploadUrl != null) { |
| // Only upload if not already installed |
| boolean exists = newScript("checkIfInstalled") |
| .body.append("cd "+getInstallDir(), "test -f BROOKLYN") |
| .execute() == 0; |
| if (!exists) { |
| InputStream distroStream = resource.getResourceFromUrl(uploadUrl); |
| getMachine().copyTo(distroStream, getInstallDir()+"/"+saveAs); |
| } |
| } else { |
| commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)); |
| } |
| commands.add(BashCommands.INSTALL_TAR); |
| commands.add("tar xzfv " + saveAs); |
| |
| newScript(INSTALLING). |
| failOnNonZeroResultCode(). |
| body.append(commands).execute(); |
| } |
| |
| @Override |
| public void customize() { |
| newScript(CUSTOMIZING) |
| .failOnNonZeroResultCode() |
| .body.append( |
| // workaround for AMP distribution placing everything in the root of this archive, but |
| // brooklyn distribution placing everything in a subdirectory: check to see if subdirectory |
| // with expected name exists; symlink to same directory if it doesn't |
| // FIXME remove when all downstream usages don't use this |
| format("[ -d %1$s ] || ln -s . %1$s", getExpandedInstallDir(), getExpandedInstallDir()), |
| |
| // previously we only copied bin,conf and set BROOKLYN_HOME to the install dir; |
| // but that does not play nicely if installing dists other than brooklyn |
| // (such as what is built by our artifact) |
| format("cp -R %s/* .", getExpandedInstallDir()), |
| "mkdir -p ./lib/dropins/") |
| .execute(); |
| |
| SshMachineLocation machine = getMachine(); |
| BrooklynNode entity = getEntity(); |
| |
| String brooklynGlobalPropertiesRemotePath = entity.getConfig(BrooklynNode.BROOKLYN_GLOBAL_PROPERTIES_REMOTE_PATH); |
| String brooklynGlobalPropertiesContents = entity.getConfig(BrooklynNode.BROOKLYN_GLOBAL_PROPERTIES_CONTENTS); |
| String brooklynGlobalPropertiesUri = entity.getConfig(BrooklynNode.BROOKLYN_GLOBAL_PROPERTIES_URI); |
| |
| String brooklynLocalPropertiesRemotePath = processTemplateContents(entity.getConfig(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_REMOTE_PATH)); |
| String brooklynLocalPropertiesContents = entity.getConfig(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_CONTENTS); |
| String brooklynLocalPropertiesUri = entity.getConfig(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_URI); |
| |
| String brooklynCatalogInitialBomRemotePath = processTemplateContents(entity.getConfig(BrooklynNode.BROOKLYN_CATALOG_INITIAL_BOM_REMOTE_PATH)); |
| String brooklynCatalogInitialBomUri = entity.getConfig(BrooklynNode.BROOKLYN_CATALOG_INITIAL_BOM_URI); |
| String brooklynCatalogInitialBomContents = entity.getConfig(BrooklynNode.BROOKLYN_CATALOG_INITIAL_BOM_CONTENTS); |
| |
| // Override the ~/.brooklyn/brooklyn.properties if required |
| if (brooklynGlobalPropertiesContents != null || brooklynGlobalPropertiesUri != null) { |
| ExistingFileBehaviour onExisting = entity.getConfig(BrooklynNode.ON_EXISTING_PROPERTIES_FILE); |
| Integer checkExists = DynamicTasks.queue(SshEffectorTasks.ssh("ls \""+brooklynGlobalPropertiesRemotePath+"\"").allowingNonZeroExitCode()).get(); |
| boolean doUpload = true; |
| if (checkExists==0) { |
| switch (onExisting) { |
| case USE_EXISTING: doUpload = false; break; |
| case OVERWRITE: break; |
| case DO_NOT_USE: |
| throw new IllegalStateException("Properties file "+brooklynGlobalPropertiesContents+" already exists and "+ |
| "even though it is not being used, content for it was supplied"); |
| case FAIL: |
| throw new IllegalStateException("Properties file "+brooklynGlobalPropertiesContents+" already exists and "+ |
| BrooklynNode.ON_EXISTING_PROPERTIES_FILE+" response is to fail"); |
| default: |
| throw new IllegalStateException("Properties file "+brooklynGlobalPropertiesContents+" already exists and "+ |
| BrooklynNode.ON_EXISTING_PROPERTIES_FILE+" response "+onExisting+" is unknown"); |
| } |
| } |
| if (onExisting==ExistingFileBehaviour.DO_NOT_USE) { |
| log.warn("Global properties supplied when told not to use them; no global properties exists, so it will be installed, but it will not be used."); |
| } |
| if (doUpload) |
| uploadFileContents(brooklynGlobalPropertiesContents, brooklynGlobalPropertiesUri, brooklynGlobalPropertiesRemotePath); |
| } |
| |
| // Upload a local-brooklyn.properties if required |
| if (brooklynLocalPropertiesContents != null || brooklynLocalPropertiesUri != null) { |
| uploadFileContents(brooklynLocalPropertiesContents, brooklynLocalPropertiesUri, brooklynLocalPropertiesRemotePath); |
| } |
| |
| // Upload a local-catalog.bom if required |
| if (brooklynCatalogInitialBomContents != null || brooklynCatalogInitialBomUri != null) { |
| uploadFileContents(brooklynCatalogInitialBomContents, brooklynCatalogInitialBomUri, brooklynCatalogInitialBomRemotePath); |
| } |
| |
| // Copy additional resources to the server |
| for (Map.Entry<String,String> entry : getEntity().getAttribute(BrooklynNode.COPY_TO_RUNDIR).entrySet()) { |
| Map<String, String> substitutions = ImmutableMap.of("RUN", getRunDir()); |
| String localResource = entry.getKey(); |
| String remotePath = entry.getValue(); |
| String resolvedRemotePath = remotePath; |
| for (Map.Entry<String,String> substitution : substitutions.entrySet()) { |
| String key = substitution.getKey(); |
| String val = substitution.getValue(); |
| resolvedRemotePath = resolvedRemotePath.replace("${"+key+"}", val).replace("$"+key, val); |
| } |
| machine.copyTo(MutableMap.of("permissions", "0600"), resource.getResourceFromUrl(localResource), resolvedRemotePath); |
| } |
| |
| for (Object entry : getEntity().getClasspath()) { |
| String filename = null; |
| String url = null; |
| |
| if (entry instanceof String) { |
| url = (String) entry; |
| } else { |
| if (entry instanceof Map) { |
| url = (String) ((Map) entry).get("url"); |
| filename = (String) ((Map) entry).get("filename"); |
| } |
| } |
| checkNotNull(url, "url"); |
| |
| // If a local folder, then create archive from contents first |
| if (Urls.isDirectory(url)) { |
| File jarFile = ArchiveBuilder.jar().addDirContentsAt(new File(url), "").create(); |
| url = jarFile.getAbsolutePath(); |
| } |
| |
| if (filename == null) { |
| // Determine filename |
| filename = getFilename(url); |
| } |
| ArchiveUtils.deploy(MutableMap.<String, Object>of(), url, machine, getRunDir(), Os.mergePaths(getRunDir(), "lib", "dropins"), filename); |
| } |
| |
| String cmd = entity.getConfig(BrooklynNode.EXTRA_CUSTOMIZATION_SCRIPT); |
| if (Strings.isNonBlank(cmd)) { |
| DynamicTasks.queueIfPossible( SshEffectorTasks.ssh(cmd).summary("Bespoke BrooklynNode customization script") |
| .requiringExitCodeZero() ) |
| .orSubmitAndBlock(getEntity()); |
| } |
| } |
| |
| private String getFilename(String url) { |
| String destFile = url.contains("?") ? url.substring(0, url.indexOf('?')) : url; |
| destFile = destFile.substring(destFile.lastIndexOf('/') + 1); |
| return destFile; |
| } |
| |
| @Override |
| public void launch() { |
| String app = getEntity().getAttribute(BrooklynNode.APP); |
| String locations = getEntity().getAttribute(BrooklynNode.LOCATIONS); |
| boolean hasLocalBrooklynProperties = getEntity().getConfig(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_CONTENTS) != null || getEntity().getConfig(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_URI) != null; |
| boolean hasLocalCatalogInitialBom = getEntity().getConfig(BrooklynNode.BROOKLYN_CATALOG_INITIAL_BOM_CONTENTS) != null || getEntity().getConfig(BrooklynNode.BROOKLYN_CATALOG_INITIAL_BOM_URI) != null; |
| String localBrooklynPropertiesPath = processTemplateContents(getEntity().getConfig(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_REMOTE_PATH)); |
| String localCatalogInitialBomPath = processTemplateContents(getEntity().getConfig(BrooklynNode.BROOKLYN_CATALOG_INITIAL_BOM_REMOTE_PATH)); |
| InetAddress bindAddress = getEntity().getAttribute(BrooklynNode.WEB_CONSOLE_BIND_ADDRESS); |
| InetAddress publicAddress = getEntity().getAttribute(BrooklynNode.WEB_CONSOLE_PUBLIC_ADDRESS); |
| |
| String cmd = entity.getConfig(BrooklynNode.LAUNCH_COMMAND); |
| if (Strings.isBlank(cmd)) cmd = "./bin/brooklyn"; |
| cmd = "nohup " + cmd + " launch"; |
| if (app != null) { |
| cmd += " --app "+app; |
| } |
| if (locations != null) { |
| cmd += " --locations "+locations; |
| } |
| if (entity.getConfig(BrooklynNode.ON_EXISTING_PROPERTIES_FILE)==ExistingFileBehaviour.DO_NOT_USE) { |
| cmd += " --noGlobalBrooklynProperties"; |
| } |
| if (hasLocalBrooklynProperties) { |
| cmd += " --localBrooklynProperties "+localBrooklynPropertiesPath; |
| } |
| if (hasLocalCatalogInitialBom) { |
| cmd += " --catalogInitial "+localCatalogInitialBomPath; |
| } |
| Integer webPort = null; |
| if (getEntity().isHttpProtocolEnabled("http")) { |
| webPort = getEntity().getAttribute(BrooklynNode.HTTP_PORT); |
| Networking.checkPortsValid(ImmutableMap.of("webPort", webPort)); |
| } else if (getEntity().isHttpProtocolEnabled("https")) { |
| webPort = getEntity().getAttribute(BrooklynNode.HTTPS_PORT); |
| Networking.checkPortsValid(ImmutableMap.of("webPort", webPort)); |
| } |
| if (webPort!=null) { |
| cmd += " --port "+webPort; |
| } else if (getEntity().getEnabledHttpProtocols().isEmpty()) { |
| // TODO sensors will probably not work in this mode |
| cmd += " --noConsole"; |
| } else { |
| throw new IllegalStateException("Unknown web protocol in "+BrooklynNode.ENABLED_HTTP_PROTOCOLS+" " |
| + "("+getEntity().getEnabledHttpProtocols()+"); expecting 'http' or 'https'"); |
| } |
| |
| if (bindAddress != null) { |
| cmd += " --bindAddress "+bindAddress.getHostAddress(); |
| } |
| if (publicAddress != null) { |
| cmd += " --publicAddress "+publicAddress.getHostName(); |
| } |
| if (getEntity().getAttribute(BrooklynNode.NO_WEB_CONSOLE_AUTHENTICATION)) { |
| cmd += " --noConsoleSecurity"; |
| } |
| if (Strings.isNonBlank(getEntity().getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS))) { |
| cmd += " "+getEntity().getConfig(BrooklynNode.EXTRA_LAUNCH_PARAMETERS); |
| } |
| cmd += format(" >> %s/console 2>&1 </dev/null &", getRunDir()); |
| |
| log.info("Starting brooklyn on {} using command {}", getMachine(), cmd); |
| |
| // relies on brooklyn script creating pid file |
| newScript(ImmutableMap.of("usePidFile", |
| entity.getConfig(BrooklynNode.LAUNCH_COMMAND_CREATES_PID_FILE) ? false : getPidFile()), |
| LAUNCHING). |
| body.append( |
| format("export BROOKLYN_CLASSPATH=%s", getRunDir()+"/lib/\"*\""), |
| format("export BROOKLYN_HOME=%s", getBrooklynHome()), |
| format(cmd) |
| ).failOnNonZeroResultCode().execute(); |
| } |
| |
| @Override |
| public boolean isRunning() { |
| Map<String,String> flags = ImmutableMap.of("usePidFile", getPidFile()); |
| int result = newScript(flags, CHECK_RUNNING).execute(); |
| return result == 0; |
| } |
| |
| @Override |
| public void stop() { |
| Map<String,String> flags = ImmutableMap.of("usePidFile", getPidFile()); |
| newScript(flags, STOPPING).execute(); |
| } |
| |
| @Override |
| public void kill() { |
| Map<String,String> flags = ImmutableMap.of("usePidFile", getPidFile()); |
| newScript(flags, KILLING).execute(); |
| } |
| |
| @Override |
| public Map<String, String> getShellEnvironment() { |
| Map<String, String> orig = super.getShellEnvironment(); |
| String origClasspath = orig.get("CLASSPATH"); |
| String newClasspath = (origClasspath == null ? "" : origClasspath+":") + |
| getRunDir()+"/conf/" + ":" + |
| getRunDir()+"/lib/\"*\""; |
| Map<String,String> results = new LinkedHashMap<String,String>(); |
| results.putAll(orig); |
| results.put("BROOKLYN_CLASSPATH", newClasspath); |
| results.put("BROOKLYN_HOME", getBrooklynHome()); |
| results.put("RUN", getRunDir()); |
| return results; |
| } |
| |
| private void uploadFileContents(String contents, String alternativeUri, String remotePath) { |
| checkNotNull(remotePath, "remotePath"); |
| SshMachineLocation machine = getMachine(); |
| String tempRemotePath = String.format("%s/upload.tmp", getRunDir()); |
| |
| if (contents == null && alternativeUri == null) { |
| throw new IllegalStateException("No contents supplied for file " + remotePath); |
| } |
| InputStream stream = contents != null |
| ? new ByteArrayInputStream(contents.getBytes()) |
| : resource.getResourceFromUrl(alternativeUri); |
| Map<String, String> flags = MutableMap.of(SshTool.PROP_PERMISSIONS.getName(), "0600"); |
| machine.copyTo(flags, stream, tempRemotePath); |
| newScript(CUSTOMIZING) |
| .failOnNonZeroResultCode() |
| .body.append( |
| format("mkdir -p %s", remotePath.subSequence(0, remotePath.lastIndexOf("/"))), |
| format("cp -p %s %s", tempRemotePath, remotePath), |
| format("rm -f %s", tempRemotePath)) |
| .execute(); |
| } |
| } |