From a9ad442de4d5576df24cf81bb4b07d66de3e46ed Mon Sep 17 00:00:00 2001 From: Andrew Kennedy Date: Tue, 3 Jun 2014 22:43:42 +0100 Subject: [PATCH 01/10] Initial NodeJS entity --- .../webapp/JavaWebAppSoftwareProcessImpl.java | 31 ----- .../entity/webapp/WebAppServiceMethods.java | 52 ++++++-- .../entity/webapp/jboss/JBoss7ServerImpl.java | 5 +- .../webapp/nodejs/NodeJsWebAppDriver.java | 9 ++ .../webapp/nodejs/NodeJsWebAppService.java | 19 +++ .../nodejs/NodeJsWebAppSoftwareProcess.java | 15 +++ .../NodeJsWebAppSoftwareProcessImpl.java | 54 ++++++++ .../webapp/nodejs/NodeJsWebAppSshDriver.java | 125 ++++++++++++++++++ 8 files changed, 269 insertions(+), 41 deletions(-) create mode 100644 software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppDriver.java create mode 100644 software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java create mode 100644 software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcess.java create mode 100644 software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcessImpl.java create mode 100644 software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/JavaWebAppSoftwareProcessImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/JavaWebAppSoftwareProcessImpl.java index 2ea3f48ca7..5c89497e6e 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/JavaWebAppSoftwareProcessImpl.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/JavaWebAppSoftwareProcessImpl.java @@ -34,11 +34,9 @@ import brooklyn.entity.annotation.EffectorParam; import brooklyn.entity.basic.SoftwareProcessImpl; import brooklyn.entity.java.JavaAppUtils; -import brooklyn.location.access.BrooklynAccessUtils; import com.google.common.base.Throwables; import com.google.common.collect.Sets; -import com.google.common.net.HostAndPort; public abstract class JavaWebAppSoftwareProcessImpl extends SoftwareProcessImpl implements JavaWebAppService, JavaWebAppSoftwareProcess { @@ -178,33 +176,4 @@ protected void doStop() { setAttribute(REQUESTS_PER_SECOND_LAST, 0D); setAttribute(REQUESTS_PER_SECOND_IN_WINDOW, 0D); } - - protected Set getEnabledProtocols() { - return getAttribute(JavaWebAppSoftwareProcess.ENABLED_PROTOCOLS); - } - - protected boolean isProtocolEnabled(String protocol) { - for (String contender : getEnabledProtocols()) { - if (protocol.equalsIgnoreCase(contender)) { - return true; - } - } - return false; - } - - protected String inferBrooklynAccessibleRootUrl() { - if (isProtocolEnabled("https")) { - Integer rawPort = getAttribute(HTTPS_PORT); - checkNotNull(rawPort, "HTTPS_PORT sensors not set for %s; is an acceptable port available?", this); - HostAndPort hp = BrooklynAccessUtils.getBrooklynAccessibleAddress(this, rawPort); - return String.format("https://%s:%s/", hp.getHostText(), hp.getPort()); - } else if (isProtocolEnabled("http")) { - Integer rawPort = getAttribute(HTTP_PORT); - checkNotNull(rawPort, "HTTP_PORT sensors not set for %s; is an acceptable port available?", this); - HostAndPort hp = BrooklynAccessUtils.getBrooklynAccessibleAddress(this, rawPort); - return String.format("http://%s:%s/", hp.getHostText(), hp.getPort()); - } else { - throw new IllegalStateException("HTTP and HTTPS protocols not enabled for "+this+"; enabled protocols are "+getEnabledProtocols()); - } - } } diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/WebAppServiceMethods.java b/software/webapp/src/main/java/brooklyn/entity/webapp/WebAppServiceMethods.java index 71048306df..c6fbe29a61 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/WebAppServiceMethods.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/WebAppServiceMethods.java @@ -18,36 +18,72 @@ */ package brooklyn.entity.webapp; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Set; import java.util.concurrent.TimeUnit; import brooklyn.enricher.RollingTimeWindowMeanEnricher; import brooklyn.enricher.TimeFractionDeltaEnricher; import brooklyn.enricher.TimeWeightedDeltaEnricher; +import brooklyn.entity.Entity; import brooklyn.entity.basic.EntityLocal; +import brooklyn.location.access.BrooklynAccessUtils; import brooklyn.util.time.Duration; +import com.google.common.net.HostAndPort; + public class WebAppServiceMethods implements WebAppServiceConstants { - + public static final Duration DEFAULT_WINDOW_DURATION = Duration.TEN_SECONDS; - + public static void connectWebAppServerPolicies(EntityLocal entity) { connectWebAppServerPolicies(entity, DEFAULT_WINDOW_DURATION); } - + public static void connectWebAppServerPolicies(EntityLocal entity, Duration windowPeriod) { entity.addEnricher(TimeWeightedDeltaEnricher.getPerSecondDeltaEnricher(entity, REQUEST_COUNT, REQUESTS_PER_SECOND_LAST)); - + if (windowPeriod!=null) { - entity.addEnricher(new RollingTimeWindowMeanEnricher(entity, REQUESTS_PER_SECOND_LAST, + entity.addEnricher(new RollingTimeWindowMeanEnricher(entity, REQUESTS_PER_SECOND_LAST, REQUESTS_PER_SECOND_IN_WINDOW, windowPeriod)); } - + entity.addEnricher(new TimeFractionDeltaEnricher(entity, TOTAL_PROCESSING_TIME, PROCESSING_TIME_FRACTION_LAST, TimeUnit.MILLISECONDS)); - + if (windowPeriod!=null) { - entity.addEnricher(new RollingTimeWindowMeanEnricher(entity, PROCESSING_TIME_FRACTION_LAST, + entity.addEnricher(new RollingTimeWindowMeanEnricher(entity, PROCESSING_TIME_FRACTION_LAST, PROCESSING_TIME_FRACTION_IN_WINDOW, windowPeriod)); } } + + public static Set getEnabledProtocols(Entity entity) { + return entity.getAttribute(WebAppService.ENABLED_PROTOCOLS); + } + + public static boolean isProtocolEnabled(Entity entity, String protocol) { + for (String contender : getEnabledProtocols(entity)) { + if (protocol.equalsIgnoreCase(contender)) { + return true; + } + } + return false; + } + + public static String inferBrooklynAccessibleRootUrl(Entity entity) { + if (isProtocolEnabled(entity, "https")) { + Integer rawPort = entity.getAttribute(HTTPS_PORT); + checkNotNull(rawPort, "HTTPS_PORT sensors not set for %s; is an acceptable port available?", entity); + HostAndPort hp = BrooklynAccessUtils.getBrooklynAccessibleAddress(entity, rawPort); + return String.format("https://%s:%s/", hp.getHostText(), hp.getPort()); + } else if (isProtocolEnabled(entity, "http")) { + Integer rawPort = entity.getAttribute(HTTP_PORT); + checkNotNull(rawPort, "HTTP_PORT sensors not set for %s; is an acceptable port available?", entity); + HostAndPort hp = BrooklynAccessUtils.getBrooklynAccessibleAddress(entity, rawPort); + return String.format("http://%s:%s/", hp.getHostText(), hp.getPort()); + } else { + throw new IllegalStateException("HTTP and HTTPS protocols not enabled for "+entity+"; enabled protocols are "+getEnabledProtocols(entity)); + } + } } diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java index df592590f4..2cb1303881 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java @@ -27,6 +27,7 @@ import brooklyn.entity.Entity; import brooklyn.entity.webapp.HttpsSslConfig; import brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl; +import brooklyn.entity.webapp.WebAppServiceMethods; import brooklyn.event.feed.http.HttpFeed; import brooklyn.event.feed.http.HttpPollConfig; import brooklyn.event.feed.http.HttpValueFunctions; @@ -173,11 +174,11 @@ public int getDeploymentTimeoutSecs() { } public boolean isHttpEnabled() { - return isProtocolEnabled("HTTP"); + return WebAppServiceMethods.isProtocolEnabled(this, "HTTP"); } public boolean isHttpsEnabled() { - return isProtocolEnabled("HTTPS"); + return WebAppServiceMethods.isProtocolEnabled(this, "HTTPS"); } public Integer getHttpPort() { diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppDriver.java new file mode 100644 index 0000000000..00c908d374 --- /dev/null +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppDriver.java @@ -0,0 +1,9 @@ +package brooklyn.entity.webapp.nodejs; + +import brooklyn.entity.basic.SoftwareProcessDriver; + +public interface NodeJsWebAppDriver extends SoftwareProcessDriver { + + Integer getHttpPort(); + +} diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java new file mode 100644 index 0000000000..19c8c3e9a7 --- /dev/null +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java @@ -0,0 +1,19 @@ +package brooklyn.entity.webapp.nodejs; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.webapp.WebAppService; +import brooklyn.util.flags.SetFromFlag; + +public interface NodeJsWebAppService extends WebAppService { + + @SetFromFlag("gitRepoUrl") + ConfigKey APP_GIT_REPOSITORY_URL = ConfigKeys.newStringConfigKey("nodejs.gitRepo.url", "The Git repository where the application is hosted"); + + @SetFromFlag("appFileName") + ConfigKey APP_FILE = ConfigKeys.newStringConfigKey("nodejs.app.fileName", "The NodeJS application file to start", "app.js"); + + @SetFromFlag("appName") + ConfigKey APP_NAME = ConfigKeys.newStringConfigKey("nodejs.app.name", "The name of the NodeJS application"); + +} diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcess.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcess.java new file mode 100644 index 0000000000..999440d93a --- /dev/null +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcess.java @@ -0,0 +1,15 @@ +package brooklyn.entity.webapp.nodejs; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.util.flags.SetFromFlag; + +public interface NodeJsWebAppSoftwareProcess extends SoftwareProcess, NodeJsWebAppService { + + ConfigKey SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "stable"); + + @SetFromFlag("appUser") + ConfigKey APP_USER = ConfigKeys.newStringConfigKey("nodejs.app.user", "The user to run the NodeJS application as", "webapp"); + +} diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcessImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcessImpl.java new file mode 100644 index 0000000000..d0d2207bd8 --- /dev/null +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcessImpl.java @@ -0,0 +1,54 @@ +package brooklyn.entity.webapp.nodejs; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.annotation.Effector; +import brooklyn.entity.annotation.EffectorParam; +import brooklyn.entity.basic.SoftwareProcessImpl; +import brooklyn.entity.java.JavaAppUtils; +import brooklyn.entity.webapp.WebAppServiceMethods; +import brooklyn.location.access.BrooklynAccessUtils; + +import com.google.common.base.Throwables; +import com.google.common.collect.Sets; +import com.google.common.net.HostAndPort; + +public abstract class NodeJsWebAppSoftwareProcessImpl extends SoftwareProcessImpl implements NodeJsWebAppSoftwareProcess { + + private static final Logger LOG = LoggerFactory.getLogger(NodeJsWebAppSoftwareProcessImpl.class); + + public NodeJsWebAppSoftwareProcessImpl() { + super(); + } + + @Override + protected void connectSensors() { + super.connectSensors(); + + WebAppServiceMethods.connectWebAppServerPolicies(this); + } + + public NodeJsWebAppDriver getDriver() { + return (NodeJsWebAppDriver) super.getDriver(); + } + + @Override + protected void doStop() { + super.doStop(); + // zero our workrate derived workrates. + // TODO might not be enough, as policy may still be executing and have a record of historic vals; should remove policies + // (also not sure we want this; implies more generally a responsibility for sensors to announce things when disconnected, + // vs them just showing the last known value...) + setAttribute(REQUESTS_PER_SECOND_LAST, 0D); + setAttribute(REQUESTS_PER_SECOND_IN_WINDOW, 0D); + } + +} diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java new file mode 100644 index 0000000000..611b3b7e15 --- /dev/null +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java @@ -0,0 +1,125 @@ +package brooklyn.entity.webapp.nodejs; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.webapp.WebAppService; +import brooklyn.entity.webapp.WebAppServiceMethods; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.os.Os; +import brooklyn.util.ssh.BashCommands; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +public abstract class NodeJsWebAppSshDriver extends AbstractSoftwareProcessSshDriver implements NodeJsWebAppDriver { + + public NodeJsWebAppSshDriver(NodeJsWebAppSoftwareProcessImpl entity, SshMachineLocation machine) { + super(entity, machine); + } + + public NodeJsWebAppSoftwareProcessImpl getEntity() { + return (NodeJsWebAppSoftwareProcessImpl) super.getEntity(); + } + + @Override + public Integer getHttpPort() { + return entity.getAttribute(Attributes.HTTP_PORT); + } + + protected String inferRootUrl() { + return WebAppServiceMethods.inferBrooklynAccessibleRootUrl(getEntity()); + } + + @Override + public void postLaunch() { + String rootUrl = inferRootUrl(); + entity.setAttribute(WebAppService.ROOT_URL, rootUrl); + } + + protected Map getPortMap() { + return ImmutableMap.of("httpPort", entity.getAttribute(WebAppService.HTTP_PORT)); + } + + @Override + public Set getPortsUsed() { + return ImmutableSet.builder() + .addAll(super.getPortsUsed()) + .addAll(getPortMap().values()) + .build(); + } + + @Override + public void install() { + log.debug("Installing {}", getEntity()); + + List commands = ImmutableList.builder() + .add(BashCommands.installPackage(MutableMap.of("yum", "git nodejs npm"), null)) + .add(BashCommands.sudo("npm install -g n")) + .add(BashCommands.sudo("n " + getEntity().getConfig(SoftwareProcess.SUGGESTED_VERSION))) + .add(BashCommands.sudo("useradd -mrU " + getEntity().getConfig(NodeJsWebAppSoftwareProcess.APP_USER))) + .build(); + + newScript(INSTALLING) + .body.append(commands) + .execute(); + } + + @Override + public void customize() { + log.debug("Customising {}", getEntity()); + + String appUser = getEntity().getConfig(NodeJsWebAppSoftwareProcess.APP_USER); + String appName = getEntity().getConfig(NodeJsWebAppService.APP_NAME); + + List commands = ImmutableList.builder() + .add(String.format("git clone %s %s", getEntity().getConfig(NodeJsWebAppService.APP_GIT_REPOSITORY_URL), appName)) + .add(BashCommands.sudo(String.format("chown -R %1$s:%1$s %2$s", appUser, appName))) + .build(); + + newScript(CUSTOMIZING) + .body.append(commands) + .execute(); + } + + @Override + public void launch() { + log.debug("Launching {}", getEntity()); + + String appUser = getEntity().getConfig(NodeJsWebAppSoftwareProcess.APP_USER); + String appName = getEntity().getConfig(NodeJsWebAppService.APP_NAME); + + List commands = ImmutableList.builder() + .add(String.format("cd %s", Os.mergePathsUnix(getRunDir(), appName))) + .add(BashCommands.sudoAsUser(appUser, "nohup node " + getEntity().getConfig(NodeJsWebAppService.APP_FILE) + " &")) + .build(); + + newScript(LAUNCHING) + .body.append(commands) + .execute(); + } + + @Override + public boolean isRunning() { + return newScript(CHECK_RUNNING).execute() == 0; + } + + @Override + public void stop() { + newScript(STOPPING).execute(); + } + + @Override + public Map getShellEnvironment() { + return MutableMap.builder().putAll(super.getShellEnvironment()) + .put("PORT", Integer.toString(getHttpPort())) + .build(); + } + +} From ef23cc19395d8f9b0bf1129635e79a091b7abf5b Mon Sep 17 00:00:00 2001 From: Andrew Kennedy Date: Thu, 24 Jul 2014 16:49:50 +0100 Subject: [PATCH 02/10] Updated NodeJS entity --- .../webapp/nodejs/NodeJsWebAppService.java | 26 +++++- .../nodejs/NodeJsWebAppServiceImpl.java | 67 +++++++++++++++ .../nodejs/NodeJsWebAppSoftwareProcess.java | 15 ---- .../NodeJsWebAppSoftwareProcessImpl.java | 54 ------------ .../webapp/nodejs/NodeJsWebAppSshDriver.java | 84 +++++++++++-------- 5 files changed, 143 insertions(+), 103 deletions(-) create mode 100644 software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java delete mode 100644 software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcess.java delete mode 100644 software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcessImpl.java diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java index 19c8c3e9a7..6ed2580f88 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java @@ -1,19 +1,43 @@ package brooklyn.entity.webapp.nodejs; +import java.util.List; + import brooklyn.config.ConfigKey; import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.proxying.ImplementedBy; import brooklyn.entity.webapp.WebAppService; import brooklyn.util.flags.SetFromFlag; -public interface NodeJsWebAppService extends WebAppService { +import com.google.common.collect.ImmutableList; +import com.google.common.reflect.TypeToken; + +@ImplementedBy(NodeJsWebAppServiceImpl.class) +public interface NodeJsWebAppService extends SoftwareProcess, WebAppService { + + ConfigKey SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "stable"); @SetFromFlag("gitRepoUrl") ConfigKey APP_GIT_REPOSITORY_URL = ConfigKeys.newStringConfigKey("nodejs.gitRepo.url", "The Git repository where the application is hosted"); + @SetFromFlag("archiveUrl") + ConfigKey APP_ARCHIVE_URL = ConfigKeys.newStringConfigKey("nodejs.archive.url", "The URL where the application archive is hosted"); + @SetFromFlag("appFileName") ConfigKey APP_FILE = ConfigKeys.newStringConfigKey("nodejs.app.fileName", "The NodeJS application file to start", "app.js"); @SetFromFlag("appName") ConfigKey APP_NAME = ConfigKeys.newStringConfigKey("nodejs.app.name", "The name of the NodeJS application"); + @SetFromFlag("appCommand") + ConfigKey APP_COMMAND = ConfigKeys.newStringConfigKey("nodejs.app.command", "Command to start the NodeJS application", "node"); + + @SetFromFlag("nodePackages") + ConfigKey> NODE_PACKAGE_LIST = ConfigKeys.newConfigKey(new TypeToken>() { }, + "nodejs.packages", "The NPM packages to install", ImmutableList.of()); + + ConfigKey SERVICE_UP_PATH = ConfigKeys.newStringConfigKey("nodejs.serviceUp.path", "Path to use when checking the NodeJS application is running", "/"); + + Integer getHttpPort(); + } diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java new file mode 100644 index 0000000000..802727e8d2 --- /dev/null +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java @@ -0,0 +1,67 @@ +package brooklyn.entity.webapp.nodejs; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.SoftwareProcessImpl; +import brooklyn.entity.webapp.WebAppServiceMethods; +import brooklyn.event.feed.http.HttpFeed; +import brooklyn.event.feed.http.HttpPollConfig; +import brooklyn.event.feed.http.HttpValueFunctions; + +import com.google.common.base.Predicates; + +public class NodeJsWebAppServiceImpl extends SoftwareProcessImpl implements NodeJsWebAppService { + + private static final Logger LOG = LoggerFactory.getLogger(NodeJsWebAppService.class); + + private transient HttpFeed httpFeed; + + @Override + public Class getDriverInterface() { + return NodeJsWebAppDriver.class; + } + + @Override + public NodeJsWebAppDriver getDriver() { + return (NodeJsWebAppDriver) super.getDriver(); + } + + @Override + protected void connectSensors() { + super.connectSensors(); + + String nodeJsUrl = WebAppServiceMethods.inferBrooklynAccessibleRootUrl(this); + LOG.info("Connecting to {}", nodeJsUrl); + + httpFeed = HttpFeed.builder() + .entity(this) + .baseUri(nodeJsUrl) + .poll(new HttpPollConfig(SERVICE_UP) + .suburl(getConfig(NodeJsWebAppService.SERVICE_UP_PATH)) + .checkSuccess(Predicates.alwaysTrue()) + .onSuccess(HttpValueFunctions.responseCodeEquals(200)) + .setOnException(false)) + .build(); + + WebAppServiceMethods.connectWebAppServerPolicies(this); + } + + @Override + public void disconnectSensors() { + if (httpFeed != null) httpFeed.stop(); + super.disconnectSensors(); + } + + @Override + protected void doStop() { + super.doStop(); + + setAttribute(REQUESTS_PER_SECOND_LAST, 0D); + setAttribute(REQUESTS_PER_SECOND_IN_WINDOW, 0D); + } + + @Override + public Integer getHttpPort() { return getAttribute(HTTP_PORT); } + +} diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcess.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcess.java deleted file mode 100644 index 999440d93a..0000000000 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcess.java +++ /dev/null @@ -1,15 +0,0 @@ -package brooklyn.entity.webapp.nodejs; - -import brooklyn.config.ConfigKey; -import brooklyn.entity.basic.ConfigKeys; -import brooklyn.entity.basic.SoftwareProcess; -import brooklyn.util.flags.SetFromFlag; - -public interface NodeJsWebAppSoftwareProcess extends SoftwareProcess, NodeJsWebAppService { - - ConfigKey SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "stable"); - - @SetFromFlag("appUser") - ConfigKey APP_USER = ConfigKeys.newStringConfigKey("nodejs.app.user", "The user to run the NodeJS application as", "webapp"); - -} diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcessImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcessImpl.java deleted file mode 100644 index d0d2207bd8..0000000000 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftwareProcessImpl.java +++ /dev/null @@ -1,54 +0,0 @@ -package brooklyn.entity.webapp.nodejs; - -import static com.google.common.base.Preconditions.checkNotNull; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import brooklyn.entity.annotation.Effector; -import brooklyn.entity.annotation.EffectorParam; -import brooklyn.entity.basic.SoftwareProcessImpl; -import brooklyn.entity.java.JavaAppUtils; -import brooklyn.entity.webapp.WebAppServiceMethods; -import brooklyn.location.access.BrooklynAccessUtils; - -import com.google.common.base.Throwables; -import com.google.common.collect.Sets; -import com.google.common.net.HostAndPort; - -public abstract class NodeJsWebAppSoftwareProcessImpl extends SoftwareProcessImpl implements NodeJsWebAppSoftwareProcess { - - private static final Logger LOG = LoggerFactory.getLogger(NodeJsWebAppSoftwareProcessImpl.class); - - public NodeJsWebAppSoftwareProcessImpl() { - super(); - } - - @Override - protected void connectSensors() { - super.connectSensors(); - - WebAppServiceMethods.connectWebAppServerPolicies(this); - } - - public NodeJsWebAppDriver getDriver() { - return (NodeJsWebAppDriver) super.getDriver(); - } - - @Override - protected void doStop() { - super.doStop(); - // zero our workrate derived workrates. - // TODO might not be enough, as policy may still be executing and have a record of historic vals; should remove policies - // (also not sure we want this; implies more generally a responsibility for sensors to announce things when disconnected, - // vs them just showing the last known value...) - setAttribute(REQUESTS_PER_SECOND_LAST, 0D); - setAttribute(REQUESTS_PER_SECOND_IN_WINDOW, 0D); - } - -} diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java index 611b3b7e15..f65397672b 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java @@ -4,47 +4,52 @@ import java.util.Map; import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver; import brooklyn.entity.basic.Attributes; import brooklyn.entity.basic.SoftwareProcess; import brooklyn.entity.webapp.WebAppService; -import brooklyn.entity.webapp.WebAppServiceMethods; import brooklyn.location.basic.SshMachineLocation; +import brooklyn.util.collections.MutableList; import brooklyn.util.collections.MutableMap; +import brooklyn.util.file.ArchiveUtils; import brooklyn.util.os.Os; import brooklyn.util.ssh.BashCommands; +import brooklyn.util.text.Strings; -import com.google.common.collect.ImmutableList; +import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +public class NodeJsWebAppSshDriver extends AbstractSoftwareProcessSshDriver implements NodeJsWebAppDriver { -public abstract class NodeJsWebAppSshDriver extends AbstractSoftwareProcessSshDriver implements NodeJsWebAppDriver { + private static final Logger LOG = LoggerFactory.getLogger(NodeJsWebAppService.class); - public NodeJsWebAppSshDriver(NodeJsWebAppSoftwareProcessImpl entity, SshMachineLocation machine) { + public NodeJsWebAppSshDriver(NodeJsWebAppServiceImpl entity, SshMachineLocation machine) { super(entity, machine); } - public NodeJsWebAppSoftwareProcessImpl getEntity() { - return (NodeJsWebAppSoftwareProcessImpl) super.getEntity(); + public NodeJsWebAppServiceImpl getEntity() { + return (NodeJsWebAppServiceImpl) super.getEntity(); } @Override public Integer getHttpPort() { - return entity.getAttribute(Attributes.HTTP_PORT); - } - - protected String inferRootUrl() { - return WebAppServiceMethods.inferBrooklynAccessibleRootUrl(getEntity()); + return getEntity().getAttribute(Attributes.HTTP_PORT); } @Override public void postLaunch() { - String rootUrl = inferRootUrl(); + String rootUrl = String.format("http://%s:%d/", getHostname(), getHttpPort()); entity.setAttribute(WebAppService.ROOT_URL, rootUrl); } protected Map getPortMap() { - return ImmutableMap.of("httpPort", entity.getAttribute(WebAppService.HTTP_PORT)); + return ImmutableMap.of("http", getEntity().getAttribute(WebAppService.HTTP_PORT)); } @Override @@ -57,15 +62,23 @@ public Set getPortsUsed() { @Override public void install() { - log.debug("Installing {}", getEntity()); - - List commands = ImmutableList.builder() - .add(BashCommands.installPackage(MutableMap.of("yum", "git nodejs npm"), null)) + List packages = getEntity().getConfig(NodeJsWebAppService.NODE_PACKAGE_LIST); + LOG.info("Installing Node.JS {} {}", getEntity().getConfig(SoftwareProcess.SUGGESTED_VERSION), Iterables.toString(packages)); + + List commands = MutableList.builder() + .add(BashCommands.INSTALL_CURL) + .add(BashCommands.ifExecutableElse0("apt-get", BashCommands.chain( + BashCommands.installPackage("python-software-properties python g++ make"), + BashCommands.sudo("add-apt-repository ppa:chris-lea/node.js")))) + .add(BashCommands.installPackage(MutableMap.of("yum", "git nodejs npm", "apt", "git-core nodejs"), null)) .add(BashCommands.sudo("npm install -g n")) .add(BashCommands.sudo("n " + getEntity().getConfig(SoftwareProcess.SUGGESTED_VERSION))) - .add(BashCommands.sudo("useradd -mrU " + getEntity().getConfig(NodeJsWebAppSoftwareProcess.APP_USER))) .build(); + if (packages != null && packages.size() > 0) { + commands.add(BashCommands.sudo("npm install -g " + Joiner.on(' ').join(packages))); + } + newScript(INSTALLING) .body.append(commands) .execute(); @@ -73,15 +86,21 @@ public void install() { @Override public void customize() { - log.debug("Customising {}", getEntity()); + List commands = Lists.newLinkedList(); - String appUser = getEntity().getConfig(NodeJsWebAppSoftwareProcess.APP_USER); + String gitRepoUrl = getEntity().getConfig(NodeJsWebAppService.APP_GIT_REPOSITORY_URL); + String archiveUrl = getEntity().getConfig(NodeJsWebAppService.APP_ARCHIVE_URL); String appName = getEntity().getConfig(NodeJsWebAppService.APP_NAME); - List commands = ImmutableList.builder() - .add(String.format("git clone %s %s", getEntity().getConfig(NodeJsWebAppService.APP_GIT_REPOSITORY_URL), appName)) - .add(BashCommands.sudo(String.format("chown -R %1$s:%1$s %2$s", appUser, appName))) - .build(); + if (Strings.isNonBlank(gitRepoUrl) && Strings.isNonBlank(archiveUrl)) { + throw new IllegalStateException("Only one of Git or archive URL must be set"); + } else if (Strings.isNonBlank(gitRepoUrl)) { + commands.add(String.format("git clone %s %s", gitRepoUrl, 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"); + } newScript(CUSTOMIZING) .body.append(commands) @@ -90,29 +109,28 @@ public void customize() { @Override public void launch() { - log.debug("Launching {}", getEntity()); + List commands = Lists.newLinkedList(); - String appUser = getEntity().getConfig(NodeJsWebAppSoftwareProcess.APP_USER); String appName = getEntity().getConfig(NodeJsWebAppService.APP_NAME); + String appFile = getEntity().getConfig(NodeJsWebAppService.APP_FILE); + String appCommand = getEntity().getConfig(NodeJsWebAppService.APP_COMMAND); - List commands = ImmutableList.builder() - .add(String.format("cd %s", Os.mergePathsUnix(getRunDir(), appName))) - .add(BashCommands.sudoAsUser(appUser, "nohup node " + getEntity().getConfig(NodeJsWebAppService.APP_FILE) + " &")) - .build(); + commands.add(String.format("cd %s", Os.mergePathsUnix(getRunDir(), appName))); + commands.add(BashCommands.sudo("nohup " + appCommand + " " + appFile + " &")); - newScript(LAUNCHING) + newScript(MutableMap.of(USE_PID_FILE, true), LAUNCHING) .body.append(commands) .execute(); } @Override public boolean isRunning() { - return newScript(CHECK_RUNNING).execute() == 0; + return newScript(MutableMap.of(USE_PID_FILE, true), CHECK_RUNNING).execute() == 0; } @Override public void stop() { - newScript(STOPPING).execute(); + newScript(MutableMap.of(USE_PID_FILE, true), STOPPING).execute(); } @Override From a68f4295322a820fa8a2b4922a551091c8e21ed7 Mon Sep 17 00:00:00 2001 From: Andrew Kennedy Date: Thu, 31 Jul 2014 15:37:30 +0100 Subject: [PATCH 03/10] Change root URL to use default --- .../entity/webapp/nodejs/NodeJsWebAppServiceImpl.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java index 802727e8d2..b5fa4c9055 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java @@ -5,11 +5,14 @@ import brooklyn.entity.basic.SoftwareProcessImpl; import brooklyn.entity.webapp.WebAppServiceMethods; +import brooklyn.event.feed.ConfigToAttributes; import brooklyn.event.feed.http.HttpFeed; import brooklyn.event.feed.http.HttpPollConfig; import brooklyn.event.feed.http.HttpValueFunctions; +import brooklyn.location.access.BrooklynAccessUtils; import com.google.common.base.Predicates; +import com.google.common.net.HostAndPort; public class NodeJsWebAppServiceImpl extends SoftwareProcessImpl implements NodeJsWebAppService { @@ -31,7 +34,10 @@ public NodeJsWebAppDriver getDriver() { protected void connectSensors() { super.connectSensors(); - String nodeJsUrl = WebAppServiceMethods.inferBrooklynAccessibleRootUrl(this); + ConfigToAttributes.apply(this); + + HostAndPort accessible = BrooklynAccessUtils.getBrooklynAccessibleAddress(this, getAttribute(HTTP_PORT)); + String nodeJsUrl = String.format("http://%s:%d", accessible.getHostText(), accessible.getPort()); LOG.info("Connecting to {}", nodeJsUrl); httpFeed = HttpFeed.builder() From a694716ff09f21da8fa5f0192d92dd61d3ebc21a Mon Sep 17 00:00:00 2001 From: Andrew Kennedy Date: Thu, 31 Jul 2014 16:21:46 +0100 Subject: [PATCH 04/10] Added simple integration and live tests --- .../nodejs/NodeJsWebAppEc2LiveTest.java | 41 ++++++++++ .../NodeJsWebAppFixtureIntegrationTest.java | 44 ++++++++++ .../NodeJsWebAppSimpleIntegrationTest.java | 82 +++++++++++++++++++ .../nodejs/NodeJsWebAppSoftlayerLiveTest.java | 48 +++++++++++ 4 files changed, 215 insertions(+) create mode 100644 software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppEc2LiveTest.java create mode 100644 software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppFixtureIntegrationTest.java create mode 100644 software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSimpleIntegrationTest.java create mode 100644 software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftlayerLiveTest.java diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppEc2LiveTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppEc2LiveTest.java new file mode 100644 index 0000000000..7738516dc1 --- /dev/null +++ b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppEc2LiveTest.java @@ -0,0 +1,41 @@ +package brooklyn.entity.webapp.nodejs; + +import static brooklyn.entity.webapp.nodejs.NodeJsWebAppFixtureIntegrationTest.*; +import static org.testng.Assert.assertNotNull; + +import brooklyn.entity.AbstractEc2LiveTest; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.location.Location; +import brooklyn.test.Asserts; +import brooklyn.test.HttpTestUtils; + +import com.google.common.collect.ImmutableList; + +/** + * A simple test of installing+running on AWS-EC2, using various OS distros and versions. + */ +public class NodeJsWebAppEc2LiveTest extends AbstractEc2LiveTest { + + @Override + protected void doTest(Location loc) throws Exception { + final NodeJsWebAppService server = app.createAndManageChild(EntitySpec.create(NodeJsWebAppService.class) + .configure("gitRepoUrl", GIT_REPO_URL) + .configure("appFileName", APP_FILE) + .configure("appName", APP_NAME)); + + app.start(ImmutableList.of(loc)); + + String url = server.getAttribute(NodeJsWebAppService.ROOT_URL); + + HttpTestUtils.assertHttpStatusCodeEventuallyEquals(url, 200); + HttpTestUtils.assertContentContainsText(url, "Hello"); + + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + assertNotNull(server.getAttribute(NodeJsWebAppService.REQUEST_COUNT)); + assertNotNull(server.getAttribute(NodeJsWebAppService.ERROR_COUNT)); + assertNotNull(server.getAttribute(NodeJsWebAppService.TOTAL_PROCESSING_TIME)); + }}); + } + +} diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppFixtureIntegrationTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppFixtureIntegrationTest.java new file mode 100644 index 0000000000..af6a5930bf --- /dev/null +++ b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppFixtureIntegrationTest.java @@ -0,0 +1,44 @@ +package brooklyn.entity.webapp.nodejs; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.DataProvider; + +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.entity.webapp.AbstractWebAppFixtureIntegrationTest; +import brooklyn.entity.webapp.WebAppService; +import brooklyn.location.basic.PortRanges; +import brooklyn.test.entity.TestApplication; + +public class NodeJsWebAppFixtureIntegrationTest extends AbstractWebAppFixtureIntegrationTest { + + private static final Logger LOG = LoggerFactory.getLogger(NodeJsWebAppFixtureIntegrationTest.class); + + public static final String GIT_REPO_URL = "https://github.com/grkvlt/node-hello-world.git"; + public static final String APP_FILE = "app.js"; + public static final String APP_NAME = "node-hello-world"; + + @DataProvider(name = "basicEntities") + public Object[][] basicEntities() { + TestApplication nodejsApp = newTestApplication(); + NodeJsWebAppService nodejs = nodejsApp.createAndManageChild(EntitySpec.create(NodeJsWebAppService.class) + .configure(NodeJsWebAppService.HTTP_PORT, PortRanges.fromString(DEFAULT_HTTP_PORT)) + .configure("gitRepoUrl", GIT_REPO_URL) + .configure("appFileName", APP_FILE) + .configure("appName", APP_NAME)); + + return new WebAppService[][] { + new WebAppService[] { nodejs } + }; + } + + public static void main(String ...args) throws Exception { + NodeJsWebAppFixtureIntegrationTest t = new NodeJsWebAppFixtureIntegrationTest(); + t.setUp(); + t.testReportsServiceDownWhenKilled((SoftwareProcess) t.basicEntities()[0][0]); + t.shutdownApp(); + t.shutdownMgmt(); + } + +} diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSimpleIntegrationTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSimpleIntegrationTest.java new file mode 100644 index 0000000000..1ad8cf8cd8 --- /dev/null +++ b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSimpleIntegrationTest.java @@ -0,0 +1,82 @@ +package brooklyn.entity.webapp.nodejs; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.fail; + +import java.net.ServerSocket; +import java.util.Iterator; + +import org.jclouds.util.Throwables2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.location.PortRange; +import brooklyn.location.basic.LocalhostMachineProvisioningLocation; +import brooklyn.location.basic.PortRanges; +import brooklyn.test.entity.TestApplication; +import brooklyn.util.net.Networking; + +import com.google.common.collect.ImmutableList; + +/** + * This tests the operation of the {@link NodeJsWebAppService} entity. + * + * FIXME this test is largely superseded by WebApp*IntegrationTest which tests inter alia Tomcat + */ +public class NodeJsWebAppSimpleIntegrationTest { + @SuppressWarnings("unused") + private static final Logger LOG = LoggerFactory.getLogger(NodeJsWebAppSimpleIntegrationTest.class); + + /** don't use 8080 since that is commonly used by testing software; use different from other tests. */ + static PortRange DEFAULT_HTTP_PORT_RANGE = PortRanges.fromString("7880-7980"); + + private TestApplication app; + private NodeJsWebAppService nodejs; + private int httpPort; + + @BeforeMethod(alwaysRun=true) + public void pickFreePort() { + for (Iterator iter = DEFAULT_HTTP_PORT_RANGE.iterator(); iter.hasNext();) { + Integer port = iter.next(); + if (Networking.isPortAvailable(port)) { + httpPort = port; + return; + } + } + fail("someone is already listening on ports "+DEFAULT_HTTP_PORT_RANGE+"; tests assume that port is free on localhost"); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (app != null) Entities.destroyAll(app.getManagementContext()); + } + + @Test(groups="Integration") + public void detectFailureIfTomcanodejsantBindToPort() throws Exception { + ServerSocket listener = new ServerSocket(httpPort); + try { + app = ApplicationBuilder.newManagedApp(TestApplication.class); + nodejs = app.createAndManageChild(EntitySpec.create(NodeJsWebAppService.class).configure("httpPort", httpPort)); + + try { + nodejs.start(ImmutableList.of(app.getManagementContext().getLocationManager().manage(new LocalhostMachineProvisioningLocation()))); + fail("Should have thrown start-exception"); + } catch (Exception e) { + // LocalhostMachineProvisioningLocation does NetworkUtils.isPortAvailable, so get -1 + IllegalArgumentException iae = Throwables2.getFirstThrowableOfType(e, IllegalArgumentException.class); + if (iae == null || iae.getMessage() == null || !iae.getMessage().equals("port for httpPort is null")) throw e; + } finally { + nodejs.stop(); + } + assertFalse(nodejs.getAttribute(NodeJsWebAppServiceImpl.SERVICE_UP)); + } finally { + listener.close(); + } + } +} diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftlayerLiveTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftlayerLiveTest.java new file mode 100644 index 0000000000..3aa13cafd6 --- /dev/null +++ b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftlayerLiveTest.java @@ -0,0 +1,48 @@ +package brooklyn.entity.webapp.nodejs; + +import static brooklyn.entity.webapp.nodejs.NodeJsWebAppFixtureIntegrationTest.*; +import static org.testng.Assert.assertNotNull; + +import org.testng.annotations.Test; + +import brooklyn.entity.AbstractSoftlayerLiveTest; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.location.Location; +import brooklyn.test.Asserts; +import brooklyn.test.HttpTestUtils; + +import com.google.common.collect.ImmutableList; + +/** + * A simple test of installing+running on Softlayer, using various OS distros and versions. + */ +public class NodeJsWebAppSoftlayerLiveTest extends AbstractSoftlayerLiveTest { + + @Override + protected void doTest(Location loc) throws Exception { + final NodeJsWebAppService server = app.createAndManageChild(EntitySpec.create(NodeJsWebAppService.class) + .configure("gitRepoUrl", GIT_REPO_URL) + .configure("appFileName", APP_FILE) + .configure("appName", APP_NAME)); + + app.start(ImmutableList.of(loc)); + + String url = server.getAttribute(NodeJsWebAppService.ROOT_URL); + + HttpTestUtils.assertHttpStatusCodeEventuallyEquals(url, 200); + HttpTestUtils.assertContentContainsText(url, "Hello"); + + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + assertNotNull(server.getAttribute(NodeJsWebAppService.REQUEST_COUNT)); + assertNotNull(server.getAttribute(NodeJsWebAppService.ERROR_COUNT)); + assertNotNull(server.getAttribute(NodeJsWebAppService.TOTAL_PROCESSING_TIME)); + }}); + } + + @Test(groups = {"Live", "Live-sanity"}) + @Override + public void test_Ubuntu_12_0_4() throws Exception { + super.test_Ubuntu_12_0_4(); + } +} From 7b42a27530ba38cd68fba100734a31dc11d0ef17 Mon Sep 17 00:00:00 2001 From: Andrew Kennedy Date: Thu, 31 Jul 2014 17:52:29 +0100 Subject: [PATCH 05/10] Add APACHE-2.0 license header --- .../webapp/nodejs/NodeJsWebAppDriver.java | 18 ++++++++++++++++++ .../webapp/nodejs/NodeJsWebAppService.java | 18 ++++++++++++++++++ .../webapp/nodejs/NodeJsWebAppServiceImpl.java | 18 ++++++++++++++++++ .../webapp/nodejs/NodeJsWebAppSshDriver.java | 18 ++++++++++++++++++ .../webapp/nodejs/NodeJsWebAppEc2LiveTest.java | 18 ++++++++++++++++++ .../NodeJsWebAppFixtureIntegrationTest.java | 18 ++++++++++++++++++ .../NodeJsWebAppSimpleIntegrationTest.java | 18 ++++++++++++++++++ .../nodejs/NodeJsWebAppSoftlayerLiveTest.java | 18 ++++++++++++++++++ 8 files changed, 144 insertions(+) diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppDriver.java index 00c908d374..7638018943 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppDriver.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppDriver.java @@ -1,3 +1,21 @@ +/* + * 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 brooklyn.entity.webapp.nodejs; import brooklyn.entity.basic.SoftwareProcessDriver; diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java index 6ed2580f88..afc28175b0 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java @@ -1,3 +1,21 @@ +/* + * 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 brooklyn.entity.webapp.nodejs; import java.util.List; diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java index b5fa4c9055..c42bae5327 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java @@ -1,3 +1,21 @@ +/* + * 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 brooklyn.entity.webapp.nodejs; import org.slf4j.Logger; diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java index f65397672b..d9e81b7503 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java @@ -1,3 +1,21 @@ +/* + * 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 brooklyn.entity.webapp.nodejs; import java.util.List; diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppEc2LiveTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppEc2LiveTest.java index 7738516dc1..d89c94e6e4 100644 --- a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppEc2LiveTest.java +++ b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppEc2LiveTest.java @@ -1,3 +1,21 @@ +/* + * 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 brooklyn.entity.webapp.nodejs; import static brooklyn.entity.webapp.nodejs.NodeJsWebAppFixtureIntegrationTest.*; diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppFixtureIntegrationTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppFixtureIntegrationTest.java index af6a5930bf..3b24c14b53 100644 --- a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppFixtureIntegrationTest.java +++ b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppFixtureIntegrationTest.java @@ -1,3 +1,21 @@ +/* + * 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 brooklyn.entity.webapp.nodejs; import org.slf4j.Logger; diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSimpleIntegrationTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSimpleIntegrationTest.java index 1ad8cf8cd8..34ebb3a80a 100644 --- a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSimpleIntegrationTest.java +++ b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSimpleIntegrationTest.java @@ -1,3 +1,21 @@ +/* + * 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 brooklyn.entity.webapp.nodejs; import static org.testng.Assert.assertFalse; diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftlayerLiveTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftlayerLiveTest.java index 3aa13cafd6..ce4de92433 100644 --- a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftlayerLiveTest.java +++ b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSoftlayerLiveTest.java @@ -1,3 +1,21 @@ +/* + * 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 brooklyn.entity.webapp.nodejs; import static brooklyn.entity.webapp.nodejs.NodeJsWebAppFixtureIntegrationTest.*; From a26970a20b0d11d11150aa6c02ac72dafd0504be Mon Sep 17 00:00:00 2001 From: Andrew Kennedy Date: Mon, 11 Aug 2014 15:26:08 +0100 Subject: [PATCH 06/10] Add example YAML --- .../webapp/nodejs/NodeJsWebAppService.java | 6 +++ .../nodejs/NodeJsWebAppServiceImpl.java | 5 ++- .../entity/webapp/nodejs/nodejs-todo.yaml | 42 +++++++++++++++++++ .../webapp/nodejs/nodejs-word-finder.yaml | 34 +++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-todo.yaml create mode 100644 software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-word-finder.yaml diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java index afc28175b0..d4fe793f4d 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java @@ -21,10 +21,13 @@ import java.util.List; import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.Attributes; import brooklyn.entity.basic.ConfigKeys; import brooklyn.entity.basic.SoftwareProcess; import brooklyn.entity.proxying.ImplementedBy; import brooklyn.entity.webapp.WebAppService; +import brooklyn.location.PortRange; +import brooklyn.location.basic.PortRanges; import brooklyn.util.flags.SetFromFlag; import com.google.common.collect.ImmutableList; @@ -35,6 +38,9 @@ public interface NodeJsWebAppService extends SoftwareProcess, WebAppService { ConfigKey SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "stable"); + @SetFromFlag("httpPort") + ConfigKey HTTP_PORT = ConfigKeys.newConfigKeyWithDefault(Attributes.HTTP_PORT.getConfigKey(), PortRanges.fromInteger(3000)); + @SetFromFlag("gitRepoUrl") ConfigKey APP_GIT_REPOSITORY_URL = ConfigKeys.newStringConfigKey("nodejs.gitRepo.url", "The Git repository where the application is hosted"); diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java index c42bae5327..83aace7461 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java @@ -21,6 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import brooklyn.entity.basic.Attributes; import brooklyn.entity.basic.SoftwareProcessImpl; import brooklyn.entity.webapp.WebAppServiceMethods; import brooklyn.event.feed.ConfigToAttributes; @@ -54,7 +55,7 @@ protected void connectSensors() { ConfigToAttributes.apply(this); - HostAndPort accessible = BrooklynAccessUtils.getBrooklynAccessibleAddress(this, getAttribute(HTTP_PORT)); + HostAndPort accessible = BrooklynAccessUtils.getBrooklynAccessibleAddress(this, getAttribute(Attributes.HTTP_PORT)); String nodeJsUrl = String.format("http://%s:%d", accessible.getHostText(), accessible.getPort()); LOG.info("Connecting to {}", nodeJsUrl); @@ -86,6 +87,6 @@ protected void doStop() { } @Override - public Integer getHttpPort() { return getAttribute(HTTP_PORT); } + public Integer getHttpPort() { return getAttribute(Attributes.HTTP_PORT); } } diff --git a/software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-todo.yaml b/software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-todo.yaml new file mode 100644 index 0000000000..879a9c1b38 --- /dev/null +++ b/software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-todo.yaml @@ -0,0 +1,42 @@ +# 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. + +id: nodejs-todo-application +name: "Node.JS Todo Application" +origin: "https://github.com/amirrajan/nodejs-todo/" +locations: +- jclouds:softlayer:ams01 +services: +- serviceType: brooklyn.entity.nosql.redis.RedisStore + id: redis +- serviceType: brooklyn.entity.webapp.nodejs.NodeJsWebAppService + id: nodejs + name: "Node.JS" + brooklyn.config: + gitRepoUrl: + "https://github.com/amirrajan/nodejs-todo/" + appFileName: server.js + appName: nodejs-todo + nodePackages: + - express + - ejs + - redis + env: + REDISTOGO_URL: > + $brooklyn:formatString("redis://%s:%d/", + component("redis").attributeWhenReady("host.name"), + component("redis").attributeWhenReady("redis.port")) diff --git a/software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-word-finder.yaml b/software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-word-finder.yaml new file mode 100644 index 0000000000..5af00fcc13 --- /dev/null +++ b/software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-word-finder.yaml @@ -0,0 +1,34 @@ +# 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. + +id: nodejs-word-finder-application +name: "Node.JS Word Finder Application" +origin: "https://github.com/amirrajan/word-finder/" +locations: +- jclouds:softlayer:ams01 +services: +- serviceType: brooklyn.entity.webapp.nodejs.NodeJsWebAppService + id: nodejs + name: "Node.JS" + brooklyn.config: + gitRepoUrl: + "https://github.com/amirrajan/word-finder/" + appFileName: server.js + appName: word-finder + nodePackages: + - express + - ejs \ No newline at end of file From a97b7682cca2660ff66abc8f49c80666061ad3f1 Mon Sep 17 00:00:00 2001 From: Andrew Kennedy Date: Fri, 15 Aug 2014 16:36:32 +0100 Subject: [PATCH 07/10] Updated test code with example Todo application --- software/webapp/pom.xml | 8 +- .../webapp/nodejs/NodeJsWebAppService.java | 4 + .../webapp/src/main/resources/jetty-logo.svg | 88 ------------------ .../webapp/src/main/resources/nodejs-logo.png | Bin 0 -> 9620 bytes .../webapp/nodejs/NodeJsTodoApplication.java | 56 +++++++++++ .../nodejs/NodeJsWebAppEc2LiveTest.java | 8 ++ .../NodeJsWebAppFixtureIntegrationTest.java | 4 - .../NodeJsWebAppSimpleIntegrationTest.java | 22 ++--- .../webapp/nodejs/nodejs-hello-world.yaml | 31 ++++++ 9 files changed, 113 insertions(+), 108 deletions(-) delete mode 100644 software/webapp/src/main/resources/jetty-logo.svg create mode 100644 software/webapp/src/main/resources/nodejs-logo.png create mode 100644 software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsTodoApplication.java create mode 100644 software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-hello-world.yaml diff --git a/software/webapp/pom.xml b/software/webapp/pom.xml index 43f95ac1e5..a7aa1807d3 100644 --- a/software/webapp/pom.xml +++ b/software/webapp/pom.xml @@ -111,7 +111,6 @@ mx4j mx4j-tools - ${mx4j.version} test @@ -141,6 +140,13 @@ tests test + + io.brooklyn + brooklyn-software-nosql + ${project.version} + test + + io.brooklyn diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java index d4fe793f4d..7e6b8404bd 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java @@ -20,6 +20,7 @@ import java.util.List; +import brooklyn.catalog.Catalog; import brooklyn.config.ConfigKey; import brooklyn.entity.basic.Attributes; import brooklyn.entity.basic.ConfigKeys; @@ -33,6 +34,9 @@ import com.google.common.collect.ImmutableList; import com.google.common.reflect.TypeToken; +@Catalog(name="Node.JS Application", + description="Node.JS Web Application", + iconUrl="classpath:///nodejs-logo.png") @ImplementedBy(NodeJsWebAppServiceImpl.class) public interface NodeJsWebAppService extends SoftwareProcess, WebAppService { diff --git a/software/webapp/src/main/resources/jetty-logo.svg b/software/webapp/src/main/resources/jetty-logo.svg deleted file mode 100644 index 5f8d6fd716..0000000000 --- a/software/webapp/src/main/resources/jetty-logo.svg +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/software/webapp/src/main/resources/nodejs-logo.png b/software/webapp/src/main/resources/nodejs-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c6c7b97bf1aa5a603c0a45f4f88545222d6f5f38 GIT binary patch literal 9620 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 literal 0 HcmV?d00001 diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsTodoApplication.java b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsTodoApplication.java new file mode 100644 index 0000000000..32043d5970 --- /dev/null +++ b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsTodoApplication.java @@ -0,0 +1,56 @@ +/* + * 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 brooklyn.entity.webapp.nodejs; + +import static brooklyn.event.basic.DependentConfiguration.attributeWhenReady; +import brooklyn.catalog.Catalog; +import brooklyn.entity.basic.AbstractApplication; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.nosql.redis.RedisStore; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.event.basic.DependentConfiguration; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Node.JS Todo Application + */ +@Catalog(name="NodeJS Todo", + description="Node.JS Todo Application.", + iconUrl="classpath://nodejs-logo.png") +public class NodeJsTodoApplication extends AbstractApplication implements StartableApplication { + + @Override + public void init() { + RedisStore redis = addChild(EntitySpec.create(RedisStore.class)); + + addChild(EntitySpec.create(NodeJsWebAppService.class) + .configure(NodeJsWebAppService.APP_GIT_REPOSITORY_URL, "https://github.com/amirrajan/nodejs-todo/") + .configure(NodeJsWebAppService.APP_FILE, "server.js") + .configure(NodeJsWebAppService.APP_NAME, "nodejs-todo") + .configure(NodeJsWebAppService.NODE_PACKAGE_LIST, ImmutableList.of("express", "ejs", "redis")) + .configure(SoftwareProcess.SHELL_ENVIRONMENT, ImmutableMap.of( + "REDISTOGO_URL", DependentConfiguration.formatString("redis://%s:%d/", + attributeWhenReady(redis, Attributes.HOSTNAME), attributeWhenReady(redis, RedisStore.REDIS_PORT))))); + } + +} diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppEc2LiveTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppEc2LiveTest.java index d89c94e6e4..ac8e32d2c5 100644 --- a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppEc2LiveTest.java +++ b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppEc2LiveTest.java @@ -21,6 +21,8 @@ import static brooklyn.entity.webapp.nodejs.NodeJsWebAppFixtureIntegrationTest.*; import static org.testng.Assert.assertNotNull; +import org.testng.annotations.Test; + import brooklyn.entity.AbstractEc2LiveTest; import brooklyn.entity.proxying.EntitySpec; import brooklyn.location.Location; @@ -56,4 +58,10 @@ protected void doTest(Location loc) throws Exception { }}); } + @Test(groups = {"Live", "Live-sanity"}) + @Override + public void test_Ubuntu_12_0() throws Exception { + super.test_Ubuntu_12_0(); + } + } diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppFixtureIntegrationTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppFixtureIntegrationTest.java index 3b24c14b53..fa56a34e09 100644 --- a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppFixtureIntegrationTest.java +++ b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppFixtureIntegrationTest.java @@ -18,8 +18,6 @@ */ package brooklyn.entity.webapp.nodejs; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.testng.annotations.DataProvider; import brooklyn.entity.basic.SoftwareProcess; @@ -31,8 +29,6 @@ public class NodeJsWebAppFixtureIntegrationTest extends AbstractWebAppFixtureIntegrationTest { - private static final Logger LOG = LoggerFactory.getLogger(NodeJsWebAppFixtureIntegrationTest.class); - public static final String GIT_REPO_URL = "https://github.com/grkvlt/node-hello-world.git"; public static final String APP_FILE = "app.js"; public static final String APP_NAME = "node-hello-world"; diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSimpleIntegrationTest.java b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSimpleIntegrationTest.java index 34ebb3a80a..54e6c7fa8c 100644 --- a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSimpleIntegrationTest.java +++ b/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSimpleIntegrationTest.java @@ -25,15 +25,13 @@ import java.util.Iterator; import org.jclouds.util.Throwables2; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import brooklyn.entity.basic.ApplicationBuilder; import brooklyn.entity.basic.Entities; import brooklyn.entity.proxying.EntitySpec; +import brooklyn.location.LocationSpec; import brooklyn.location.PortRange; import brooklyn.location.basic.LocalhostMachineProvisioningLocation; import brooklyn.location.basic.PortRanges; @@ -44,15 +42,10 @@ /** * This tests the operation of the {@link NodeJsWebAppService} entity. - * - * FIXME this test is largely superseded by WebApp*IntegrationTest which tests inter alia Tomcat */ public class NodeJsWebAppSimpleIntegrationTest { - @SuppressWarnings("unused") - private static final Logger LOG = LoggerFactory.getLogger(NodeJsWebAppSimpleIntegrationTest.class); - /** don't use 8080 since that is commonly used by testing software; use different from other tests. */ - static PortRange DEFAULT_HTTP_PORT_RANGE = PortRanges.fromString("7880-7980"); + private static PortRange DEFAULT_PORT_RANGE = PortRanges.fromString("3000-3099"); private TestApplication app; private NodeJsWebAppService nodejs; @@ -60,14 +53,14 @@ public class NodeJsWebAppSimpleIntegrationTest { @BeforeMethod(alwaysRun=true) public void pickFreePort() { - for (Iterator iter = DEFAULT_HTTP_PORT_RANGE.iterator(); iter.hasNext();) { + for (Iterator iter = DEFAULT_PORT_RANGE.iterator(); iter.hasNext();) { Integer port = iter.next(); if (Networking.isPortAvailable(port)) { httpPort = port; return; } } - fail("someone is already listening on ports "+DEFAULT_HTTP_PORT_RANGE+"; tests assume that port is free on localhost"); + fail("someone is already listening on ports "+DEFAULT_PORT_RANGE+"; tests assume that port is free on localhost"); } @AfterMethod(alwaysRun=true) @@ -76,14 +69,13 @@ public void tearDown() throws Exception { } @Test(groups="Integration") - public void detectFailureIfTomcanodejsantBindToPort() throws Exception { + public void detectFailureIfNodeJsBindToPort() throws Exception { ServerSocket listener = new ServerSocket(httpPort); try { - app = ApplicationBuilder.newManagedApp(TestApplication.class); + app = TestApplication.Factory.newManagedInstanceForTests(); nodejs = app.createAndManageChild(EntitySpec.create(NodeJsWebAppService.class).configure("httpPort", httpPort)); - try { - nodejs.start(ImmutableList.of(app.getManagementContext().getLocationManager().manage(new LocalhostMachineProvisioningLocation()))); + nodejs.start(ImmutableList.of(app.getManagementContext().getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)))); fail("Should have thrown start-exception"); } catch (Exception e) { // LocalhostMachineProvisioningLocation does NetworkUtils.isPortAvailable, so get -1 diff --git a/software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-hello-world.yaml b/software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-hello-world.yaml new file mode 100644 index 0000000000..0ac274ece6 --- /dev/null +++ b/software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-hello-world.yaml @@ -0,0 +1,31 @@ +# 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. + +id: nodejs-hello-world-application +name: "Node.JS Hello World Application" +origin: "https://github.com/grkvlt/node-hello-world.git/" +locations: +- jclouds:softlayer:ams01 +services: +- serviceType: brooklyn.entity.webapp.nodejs.NodeJsWebAppService + id: nodejs + name: "Node.JS" + brooklyn.config: + gitRepoUrl: + "https://github.com/grkvlt/node-hello-world.git" + appFileName: app.js + appName: node-hello-world \ No newline at end of file From 2ce9c52811dacb21a1f10ea1fbb66e95cd2e83c2 Mon Sep 17 00:00:00 2001 From: Andrew Kennedy Date: Fri, 15 Aug 2014 16:48:14 +0100 Subject: [PATCH 08/10] Added command line config key to replace entire Node.JS start command string --- .../entity/webapp/nodejs/NodeJsWebAppService.java | 5 ++++- .../entity/webapp/nodejs/NodeJsWebAppSshDriver.java | 13 +++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java index 7e6b8404bd..1711563ce6 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java @@ -58,7 +58,10 @@ public interface NodeJsWebAppService extends SoftwareProcess, WebAppService { ConfigKey APP_NAME = ConfigKeys.newStringConfigKey("nodejs.app.name", "The name of the NodeJS application"); @SetFromFlag("appCommand") - ConfigKey APP_COMMAND = ConfigKeys.newStringConfigKey("nodejs.app.command", "Command to start the NodeJS application", "node"); + ConfigKey APP_COMMAND = ConfigKeys.newStringConfigKey("nodejs.app.command", "Command to start the NodeJS application (defaults to node)", "node"); + + @SetFromFlag("appCommandLine") + ConfigKey APP_COMMAND_LINE = ConfigKeys.newStringConfigKey("nodejs.app.commandLine", "Replacement command line to start the NodeJS application (ignores command and file if set)"); @SetFromFlag("nodePackages") ConfigKey> NODE_PACKAGE_LIST = ConfigKeys.newConfigKey(new TypeToken>() { }, diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java index d9e81b7503..24c858100b 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java @@ -67,7 +67,7 @@ public void postLaunch() { } protected Map getPortMap() { - return ImmutableMap.of("http", getEntity().getAttribute(WebAppService.HTTP_PORT)); + return ImmutableMap.of("http", getHttpPort()); } @Override @@ -111,13 +111,13 @@ public void customize() { 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"); + 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)); } 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"); + throw new IllegalStateException("At least one of Git or archive URL must be set for " + getEntity()); } newScript(CUSTOMIZING) @@ -132,9 +132,14 @@ public void launch() { 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; + } commands.add(String.format("cd %s", Os.mergePathsUnix(getRunDir(), appName))); - commands.add(BashCommands.sudo("nohup " + appCommand + " " + appFile + " &")); + commands.add(BashCommands.sudo("nohup " + appCommandLine + " &")); newScript(MutableMap.of(USE_PID_FILE, true), LAUNCHING) .body.append(commands) From d2e63ca7672aaabfb5f5ad3a8d8a4e78e5e100ea Mon Sep 17 00:00:00 2001 From: Andrew Kennedy Date: Wed, 20 Aug 2014 12:29:06 +0100 Subject: [PATCH 09/10] Moved test blueprints to example and launcher projects --- .../src/main/java/brooklyn/demo}/NodeJsTodoApplication.java | 3 ++- software/webapp/pom.xml | 6 ------ .../launcher/src/test/resources}/nodejs-todo.yaml | 0 .../launcher/src/test/resources}/nodejs-word-finder.yaml | 0 4 files changed, 2 insertions(+), 7 deletions(-) rename {software/webapp/src/test/java/brooklyn/entity/webapp/nodejs => examples/simple-web-cluster/src/main/java/brooklyn/demo}/NodeJsTodoApplication.java (96%) rename {software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs => usage/launcher/src/test/resources}/nodejs-todo.yaml (100%) rename {software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs => usage/launcher/src/test/resources}/nodejs-word-finder.yaml (100%) diff --git a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsTodoApplication.java b/examples/simple-web-cluster/src/main/java/brooklyn/demo/NodeJsTodoApplication.java similarity index 96% rename from software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsTodoApplication.java rename to examples/simple-web-cluster/src/main/java/brooklyn/demo/NodeJsTodoApplication.java index 32043d5970..cba766cc8b 100644 --- a/software/webapp/src/test/java/brooklyn/entity/webapp/nodejs/NodeJsTodoApplication.java +++ b/examples/simple-web-cluster/src/main/java/brooklyn/demo/NodeJsTodoApplication.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package brooklyn.entity.webapp.nodejs; +package brooklyn.demo; import static brooklyn.event.basic.DependentConfiguration.attributeWhenReady; import brooklyn.catalog.Catalog; @@ -26,6 +26,7 @@ import brooklyn.entity.basic.StartableApplication; import brooklyn.entity.nosql.redis.RedisStore; import brooklyn.entity.proxying.EntitySpec; +import brooklyn.entity.webapp.nodejs.NodeJsWebAppService; import brooklyn.event.basic.DependentConfiguration; import com.google.common.collect.ImmutableList; diff --git a/software/webapp/pom.xml b/software/webapp/pom.xml index a7aa1807d3..5a44cc2162 100644 --- a/software/webapp/pom.xml +++ b/software/webapp/pom.xml @@ -140,12 +140,6 @@ tests test - - io.brooklyn - brooklyn-software-nosql - ${project.version} - test - diff --git a/software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-todo.yaml b/usage/launcher/src/test/resources/nodejs-todo.yaml similarity index 100% rename from software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-todo.yaml rename to usage/launcher/src/test/resources/nodejs-todo.yaml diff --git a/software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-word-finder.yaml b/usage/launcher/src/test/resources/nodejs-word-finder.yaml similarity index 100% rename from software/webapp/src/test/resources/brooklyn/entity/webapp/nodejs/nodejs-word-finder.yaml rename to usage/launcher/src/test/resources/nodejs-word-finder.yaml From e18474f3e438ec5867447033c045f01d1f25cd0e Mon Sep 17 00:00:00 2001 From: Andrew Kennedy Date: Wed, 20 Aug 2014 18:07:59 +0100 Subject: [PATCH 10/10] Update example blueprint for Node.JS Todo application --- .../brooklyn/demo/NodeJsTodoApplication.java | 6 ++-- .../resources/brooklyn/demo}/nodejs-todo.yaml | 13 ++++++- .../webapp/nodejs/NodeJsWebAppSshDriver.java | 2 +- .../test/resources/nodejs-word-finder.yaml | 34 ------------------- 4 files changed, 17 insertions(+), 38 deletions(-) rename {usage/launcher/src/test/resources => examples/simple-web-cluster/src/main/resources/brooklyn/demo}/nodejs-todo.yaml (82%) delete mode 100644 usage/launcher/src/test/resources/nodejs-word-finder.yaml diff --git a/examples/simple-web-cluster/src/main/java/brooklyn/demo/NodeJsTodoApplication.java b/examples/simple-web-cluster/src/main/java/brooklyn/demo/NodeJsTodoApplication.java index cba766cc8b..54eb00802d 100644 --- a/examples/simple-web-cluster/src/main/java/brooklyn/demo/NodeJsTodoApplication.java +++ b/examples/simple-web-cluster/src/main/java/brooklyn/demo/NodeJsTodoApplication.java @@ -26,6 +26,7 @@ import brooklyn.entity.basic.StartableApplication; import brooklyn.entity.nosql.redis.RedisStore; import brooklyn.entity.proxying.EntitySpec; +import brooklyn.entity.trait.Startable; import brooklyn.entity.webapp.nodejs.NodeJsWebAppService; import brooklyn.event.basic.DependentConfiguration; @@ -48,10 +49,11 @@ public void init() { .configure(NodeJsWebAppService.APP_GIT_REPOSITORY_URL, "https://github.com/amirrajan/nodejs-todo/") .configure(NodeJsWebAppService.APP_FILE, "server.js") .configure(NodeJsWebAppService.APP_NAME, "nodejs-todo") - .configure(NodeJsWebAppService.NODE_PACKAGE_LIST, ImmutableList.of("express", "ejs", "redis")) + .configure(NodeJsWebAppService.NODE_PACKAGE_LIST, ImmutableList.of("express", "ejs", "jasmine-node", "underscore", "method-override", "cookie-parser", "express-session", "body-parser", "cookie-session", "redis", "redis-url", "connect")) .configure(SoftwareProcess.SHELL_ENVIRONMENT, ImmutableMap.of( "REDISTOGO_URL", DependentConfiguration.formatString("redis://%s:%d/", - attributeWhenReady(redis, Attributes.HOSTNAME), attributeWhenReady(redis, RedisStore.REDIS_PORT))))); + attributeWhenReady(redis, Attributes.HOSTNAME), attributeWhenReady(redis, RedisStore.REDIS_PORT)))) + .configure(SoftwareProcess.LAUNCH_LATCH, attributeWhenReady(redis, Startable.SERVICE_UP))); } } diff --git a/usage/launcher/src/test/resources/nodejs-todo.yaml b/examples/simple-web-cluster/src/main/resources/brooklyn/demo/nodejs-todo.yaml similarity index 82% rename from usage/launcher/src/test/resources/nodejs-todo.yaml rename to examples/simple-web-cluster/src/main/resources/brooklyn/demo/nodejs-todo.yaml index 879a9c1b38..8bf3f7f741 100644 --- a/usage/launcher/src/test/resources/nodejs-todo.yaml +++ b/examples/simple-web-cluster/src/main/resources/brooklyn/demo/nodejs-todo.yaml @@ -23,20 +23,31 @@ locations: services: - serviceType: brooklyn.entity.nosql.redis.RedisStore id: redis + name: "Redis" - serviceType: brooklyn.entity.webapp.nodejs.NodeJsWebAppService id: nodejs name: "Node.JS" brooklyn.config: gitRepoUrl: - "https://github.com/amirrajan/nodejs-todo/" + "https://github.com/grkvlt/nodejs-todo/" appFileName: server.js appName: nodejs-todo nodePackages: - express - ejs + - jasmine-node + - underscore + - method-override + - cookie-parser + - express-session + - body-parser + - cookie-session - redis + - redis-url + - connect env: REDISTOGO_URL: > $brooklyn:formatString("redis://%s:%d/", component("redis").attributeWhenReady("host.name"), component("redis").attributeWhenReady("redis.port")) + launch.latch: $brooklyn:component("redis").attributeWhenReady("service.isUp") \ No newline at end of file diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java index 24c858100b..839d434a04 100644 --- a/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java +++ b/software/webapp/src/main/java/brooklyn/entity/webapp/nodejs/NodeJsWebAppSshDriver.java @@ -139,7 +139,7 @@ public void launch() { } commands.add(String.format("cd %s", Os.mergePathsUnix(getRunDir(), appName))); - commands.add(BashCommands.sudo("nohup " + appCommandLine + " &")); + commands.add("nohup " + appCommandLine + " > console.out 2>&1 &"); newScript(MutableMap.of(USE_PID_FILE, true), LAUNCHING) .body.append(commands) diff --git a/usage/launcher/src/test/resources/nodejs-word-finder.yaml b/usage/launcher/src/test/resources/nodejs-word-finder.yaml deleted file mode 100644 index 5af00fcc13..0000000000 --- a/usage/launcher/src/test/resources/nodejs-word-finder.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# 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. - -id: nodejs-word-finder-application -name: "Node.JS Word Finder Application" -origin: "https://github.com/amirrajan/word-finder/" -locations: -- jclouds:softlayer:ams01 -services: -- serviceType: brooklyn.entity.webapp.nodejs.NodeJsWebAppService - id: nodejs - name: "Node.JS" - brooklyn.config: - gitRepoUrl: - "https://github.com/amirrajan/word-finder/" - appFileName: server.js - appName: word-finder - nodePackages: - - express - - ejs \ No newline at end of file