blob: 06dfb0a57de366f2a80d9ba6e7f1260eccf88c27 [file] [log] [blame]
/*
* 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.webapp.nodejs;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.brooklyn.entity.webapp.WebAppServiceMethods;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver;
import org.apache.brooklyn.entity.webapp.WebAppService;
import org.apache.brooklyn.location.ssh.SshMachineLocation;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.file.ArchiveUtils;
import org.apache.brooklyn.util.net.Networking;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.ssh.BashCommands;
import org.apache.brooklyn.util.text.Strings;
public class NodeJsWebAppSshDriver extends AbstractSoftwareProcessSshDriver implements NodeJsWebAppDriver {
private static final Logger LOG = LoggerFactory.getLogger(NodeJsWebAppService.class);
public NodeJsWebAppSshDriver(NodeJsWebAppServiceImpl entity, SshMachineLocation machine) {
super(entity, machine);
}
@Override
public NodeJsWebAppServiceImpl getEntity() {
return (NodeJsWebAppServiceImpl) super.getEntity();
}
@Override
public Integer getHttpPort() {
return getEntity().getAttribute(Attributes.HTTP_PORT);
}
@Override
public String getAppDir() {
return Os.mergePaths(getRunDir(), getEntity().getConfig(NodeJsWebAppService.APP_NAME));
}
@Override
public void postLaunch() {
String rootUrl = WebAppServiceMethods.inferBrooklynAccessibleRootUrl(entity);
entity.sensors().set(Attributes.MAIN_URI, URI.create(rootUrl));
entity.sensors().set(WebAppService.ROOT_URL, rootUrl);
}
protected Map<String, Integer> getPortMap() {
return MutableMap.of("http", getHttpPort());
}
@Override
public Set<Integer> getPortsUsed() {
return ImmutableSet.<Integer>builder()
.addAll(super.getPortsUsed())
.addAll(getPortMap().values())
.build();
}
// TODO Suggest that other entities follow this pattern as well: check for port availability early
// to report failures early, and in case getShellEnvironment() tries to convert any null port numbers
// to int.
@Override
public void prepare() {
super.prepare();
Networking.checkPortsValid(getPortMap());
}
@Override
public void install() {
LOG.info("Installing Node.JS {}", getVersion());
List<String> commands = MutableList.<String>builder()
.add(BashCommands.INSTALL_CURL)
.add(BashCommands.ifExecutableElse0("apt-get", BashCommands.chain(
BashCommands.installPackage("software-properties-common python-software-properties python g++ make"),
BashCommands.sudo("add-apt-repository ppa:chris-lea/node.js"))))
.add(BashCommands.installPackage(MutableMap.of("yum", "git openssl nodejs npm", "apt", "git-core nodejs npm"), null))
.add("mkdir -p \"$HOME/.npm\"")
.add(BashCommands.sudo("npm install -g n"))
.add(BashCommands.sudo("n " + getVersion()))
.build();
newScript(INSTALLING)
.body.append(commands)
.execute();
}
@Override
public void customize() {
List<String> commands = Lists.newLinkedList();
String gitRepoUrl = getEntity().getConfig(NodeJsWebAppService.APP_GIT_REPOSITORY_URL);
String archiveUrl = getEntity().getConfig(NodeJsWebAppService.APP_ARCHIVE_URL);
String appName = getEntity().getConfig(NodeJsWebAppService.APP_NAME);
if (Strings.isNonBlank(gitRepoUrl) && Strings.isNonBlank(archiveUrl)) {
throw new IllegalStateException("Only one of Git or archive URL must be set for " + getEntity());
} else if (Strings.isNonBlank(gitRepoUrl)) {
commands.add(String.format("git clone %s %s", gitRepoUrl, appName));
commands.add(String.format("cd %s", appName));
} else if (Strings.isNonBlank(archiveUrl)) {
ArchiveUtils.deploy(archiveUrl, getMachine(), getRunDir());
} else {
throw new IllegalStateException("At least one of Git or archive URL must be set for " + getEntity());
}
commands.add(BashCommands.ifFileExistsElse1("package.json", "npm install"));
List<String> packages = getEntity().getConfig(NodeJsWebAppService.NODE_PACKAGE_LIST);
if (packages != null && packages.size() > 0) {
commands.add(BashCommands.sudo("npm install -g " + Joiner.on(' ').join(packages)));
}
newScript(CUSTOMIZING)
.body.append(commands)
.execute();
}
@Override
public void launch() {
List<String> commands = Lists.newLinkedList();
String appName = getEntity().getConfig(NodeJsWebAppService.APP_NAME);
String appFile = getEntity().getConfig(NodeJsWebAppService.APP_FILE);
String appCommand = getEntity().getConfig(NodeJsWebAppService.APP_COMMAND);
String appCommandLine = getEntity().getConfig(NodeJsWebAppService.APP_COMMAND_LINE);
if (Strings.isBlank(appCommandLine)) {
appCommandLine = appCommand + " " + appFile;
}
// Ensure global NPM modules are on Node's path.
commands.add("export NODE_PATH=\"$NODE_PATH:$(npm root -g)\"");
commands.add(String.format("cd %s", Os.mergePathsUnix(getRunDir(), appName)));
commands.add("nohup " + appCommandLine + " > console.out 2>&1 &");
newScript(MutableMap.of(USE_PID_FILE, true), LAUNCHING)
.body.append(commands)
.execute();
}
@Override
public boolean isRunning() {
return newScript(MutableMap.of(USE_PID_FILE, true), CHECK_RUNNING).execute() == 0;
}
@Override
public void stop() {
newScript(MutableMap.of(USE_PID_FILE, true), STOPPING).execute();
}
@Override
public Map<String, String> getShellEnvironment() {
return MutableMap.<String, String>builder().putAll(super.getShellEnvironment())
.put("PORT", Integer.toString(getHttpPort()))
.build();
}
}