From 7bc42de6090bf112b244c95e542e4f8a4c5e9aed Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Tue, 5 Mar 2013 23:39:58 +0000 Subject: [PATCH] update examples to 0.5.0-M2 --- global-web-fabric/pom.xml | 2 +- .../demo/GlobalWebFabricExample.groovy | 76 ----- .../brooklyn/demo/GlobalWebFabricExample.java | 95 ++++++ hadoop-and-whirr/pom.xml | 2 +- .../whirr/WebClusterWithHadoopExample.groovy | 161 ---------- .../whirr/WebClusterWithHadoopExample.java | 212 +++++++++++++ .../whirr/WebFabricWithHadoopExample.groovy | 204 ------------ .../whirr/WebFabricWithHadoopExample.java | 290 ++++++++++++++++++ .../brooklyn/extras/whirr/WhirrExample.groovy | 53 ---- .../brooklyn/extras/whirr/WhirrExample.java | 59 ++++ .../extras/whirr/WhirrHadoopExample.groovy | 38 --- .../extras/whirr/WhirrHadoopExample.java | 58 ++++ monitored-cassandra-cluster/.gitignore | 1 + monitored-cassandra-cluster/README.txt | 24 ++ monitored-cassandra-cluster/pom.xml | 49 +++ .../MonitoredCassandraClusterExample.groovy | 27 ++ .../src/main/resources/logback.xml | 5 + pom.xml | 3 +- portable-cloudfoundry/pom.xml | 2 +- .../MovableCloudFoundryClusterExample.groovy | 35 --- .../MovableCloudFoundryClusterExample.java | 47 +++ .../MovableElasticWebAppCluster.java | 57 ++++ ...=> MovableElasticWebAppClusterImpl.groovy} | 77 ++--- .../cloudfoundry/MovableEntityTrait.groovy | 21 -- ...yTraitAlt.java => MovableEntityTrait.java} | 8 +- simple-messaging-pubsub/pom.xml | 2 +- .../demo/StandaloneBrokerExample.groovy | 27 -- .../demo/StandaloneBrokerExample.java | 63 ++++ simple-open-loop-policy/README.txt | 55 ++++ .../org.marre.smsj-1.0.0-20051126.jar | Bin 0 -> 134604 bytes .../org.marre.smsj-1.0.0-20051126.pom | 9 + .../org.marre.smsj/maven-metadata-local.xml | 12 + simple-open-loop-policy/pom.xml | 133 ++++++++ .../resources/jmeter-test-plan.jmx | 125 ++++++++ .../src/main/java/brooklyn/demo/Sms.java | 45 +++ .../WebClusterDatabaseOpenLoopExample.java | 141 +++++++++ .../resources/visitors-creation-script.sql | 17 + .../src/test/java/brooklyn/demo/SmsTest.java | 32 ++ simple-web-cluster/README.txt | 4 +- simple-web-cluster/pom.xml | 2 +- .../brooklyn/demo/SingleWebServerExample.java | 64 ++++ .../demo/WebClusterDatabaseExample.java | 109 +++++++ .../demo/WebClusterDatabaseExampleApp.java | 145 +++++++++ .../WebClusterDatabaseExampleGroovy.groovy | 87 ++++++ .../java/brooklyn/demo/WebClusterExample.java | 87 ++++++ .../SingleWebServerExample.groovy | 7 +- .../WebClusterDatabaseExample.groovy | 8 +- .../WebClusterDatabaseExampleAlt.groovy | 18 +- .../WebClusterDatabaseExampleAltJava.java | 13 +- .../{ => legacy}/WebClusterExample.groovy | 12 +- .../{ => legacy}/WebClusterExampleAlt.groovy | 9 +- webapps/hello-world-hadoop-jar/pom.xml | 2 +- webapps/hello-world-hadoop/pom.xml | 2 +- webapps/hello-world-sql/pom.xml | 2 +- webapps/hello-world-webapp/pom.xml | 2 +- webapps/pom.xml | 2 +- 56 files changed, 2123 insertions(+), 719 deletions(-) delete mode 100644 global-web-fabric/src/main/java/brooklyn/demo/GlobalWebFabricExample.groovy create mode 100644 global-web-fabric/src/main/java/brooklyn/demo/GlobalWebFabricExample.java delete mode 100644 hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebClusterWithHadoopExample.groovy create mode 100644 hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebClusterWithHadoopExample.java delete mode 100644 hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebFabricWithHadoopExample.groovy create mode 100644 hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebFabricWithHadoopExample.java delete mode 100644 hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrExample.groovy create mode 100644 hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrExample.java delete mode 100644 hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrHadoopExample.groovy create mode 100644 hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrHadoopExample.java create mode 100644 monitored-cassandra-cluster/.gitignore create mode 100644 monitored-cassandra-cluster/README.txt create mode 100644 monitored-cassandra-cluster/pom.xml create mode 100644 monitored-cassandra-cluster/src/main/java/brooklyn/demo/MonitoredCassandraClusterExample.groovy create mode 100644 monitored-cassandra-cluster/src/main/resources/logback.xml delete mode 100644 portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableCloudFoundryClusterExample.groovy create mode 100644 portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableCloudFoundryClusterExample.java create mode 100644 portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableElasticWebAppCluster.java rename portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/{MovableElasticWebAppCluster.groovy => MovableElasticWebAppClusterImpl.groovy} (52%) delete mode 100644 portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableEntityTrait.groovy rename portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/{MovableEntityTraitAlt.java => MovableEntityTrait.java} (80%) delete mode 100644 simple-messaging-pubsub/src/main/java/brooklyn/demo/StandaloneBrokerExample.groovy create mode 100644 simple-messaging-pubsub/src/main/java/brooklyn/demo/StandaloneBrokerExample.java create mode 100644 simple-open-loop-policy/README.txt create mode 100644 simple-open-loop-policy/localrepo/io/brooklyn/org.marre.smsj/1.0.0-20051126/org.marre.smsj-1.0.0-20051126.jar create mode 100644 simple-open-loop-policy/localrepo/io/brooklyn/org.marre.smsj/1.0.0-20051126/org.marre.smsj-1.0.0-20051126.pom create mode 100644 simple-open-loop-policy/localrepo/io/brooklyn/org.marre.smsj/maven-metadata-local.xml create mode 100644 simple-open-loop-policy/pom.xml create mode 100644 simple-open-loop-policy/resources/jmeter-test-plan.jmx create mode 100644 simple-open-loop-policy/src/main/java/brooklyn/demo/Sms.java create mode 100644 simple-open-loop-policy/src/main/java/brooklyn/demo/WebClusterDatabaseOpenLoopExample.java create mode 100644 simple-open-loop-policy/src/main/resources/visitors-creation-script.sql create mode 100644 simple-open-loop-policy/src/test/java/brooklyn/demo/SmsTest.java create mode 100644 simple-web-cluster/src/main/java/brooklyn/demo/SingleWebServerExample.java create mode 100644 simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.java create mode 100644 simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java create mode 100644 simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleGroovy.groovy create mode 100644 simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExample.java rename simple-web-cluster/src/main/java/brooklyn/demo/{ => legacy}/SingleWebServerExample.groovy (87%) rename simple-web-cluster/src/main/java/brooklyn/demo/{ => legacy}/WebClusterDatabaseExample.groovy (91%) rename simple-web-cluster/src/main/java/brooklyn/demo/{ => legacy}/WebClusterDatabaseExampleAlt.groovy (88%) rename simple-web-cluster/src/main/java/brooklyn/demo/{ => legacy}/WebClusterDatabaseExampleAltJava.java (91%) rename simple-web-cluster/src/main/java/brooklyn/demo/{ => legacy}/WebClusterExample.groovy (91%) rename simple-web-cluster/src/main/java/brooklyn/demo/{ => legacy}/WebClusterExampleAlt.groovy (92%) diff --git a/global-web-fabric/pom.xml b/global-web-fabric/pom.xml index ce6e09a..c8173da 100644 --- a/global-web-fabric/pom.xml +++ b/global-web-fabric/pom.xml @@ -7,7 +7,7 @@ io.brooklyn.example brooklyn-examples-parent - 0.5.0-SNAPSHOT + 0.5.0-M2 ../pom.xml diff --git a/global-web-fabric/src/main/java/brooklyn/demo/GlobalWebFabricExample.groovy b/global-web-fabric/src/main/java/brooklyn/demo/GlobalWebFabricExample.groovy deleted file mode 100644 index 1ae05a2..0000000 --- a/global-web-fabric/src/main/java/brooklyn/demo/GlobalWebFabricExample.groovy +++ /dev/null @@ -1,76 +0,0 @@ -package brooklyn.demo - -import groovy.transform.InheritConstructors - -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -import brooklyn.config.BrooklynProperties -import brooklyn.entity.basic.AbstractApplication -import brooklyn.entity.basic.Attributes -import brooklyn.entity.basic.Entities -import brooklyn.entity.dns.geoscaling.GeoscalingDnsService -import brooklyn.entity.group.DynamicFabric -import brooklyn.entity.proxy.AbstractController; -import brooklyn.entity.webapp.ElasticJavaWebAppService -import brooklyn.event.basic.DependentConfiguration -import brooklyn.extras.cloudfoundry.CloudFoundryJavaWebAppCluster -import brooklyn.launcher.BrooklynLauncher -import brooklyn.location.Location -import brooklyn.location.basic.LocationRegistry -import brooklyn.util.CommandLineUtil - -@InheritConstructors -class GlobalWebFabricExample extends AbstractApplication { - - public static final Logger log = LoggerFactory.getLogger(GlobalWebFabricExample.class); - - public static final String WAR_PATH = "classpath://hello-world-webapp.war"; - - static final List DEFAULT_LOCATIONS = [ - "aws-ec2:eu-west-1", - "aws-ec2:ap-southeast-1", - "aws-ec2:us-west-1", -// "cloudfoundry:https://api.aws.af.cm/", - ]; - - static BrooklynProperties config = BrooklynProperties.Factory.newDefault() - - - DynamicFabric webFabric = new DynamicFabric(this, name: "Web Fabric", factory: new ElasticJavaWebAppService.Factory()); - - GeoscalingDnsService geoDns = new GeoscalingDnsService(this, name: "GeoScaling DNS", - username: config.getFirst("brooklyn.geoscaling.username", failIfNone:true), - password: config.getFirst("brooklyn.geoscaling.password", failIfNone:true), - primaryDomainName: config.getFirst("brooklyn.geoscaling.primaryDomain", failIfNone:true), - smartSubdomainName: 'brooklyn'); - - { - //specify the WAR file to use - webFabric.setConfig(ElasticJavaWebAppService.ROOT_WAR, WAR_PATH); - //load-balancer instances must run on 80 to work with GeoDNS (default is 8000) - webFabric.setConfig(AbstractController.PROXY_HTTP_PORT, 80); - //CloudFoundry requires to be told what URL it should listen to, which is chosen by the GeoDNS service - webFabric.setConfig(CloudFoundryJavaWebAppCluster.HOSTNAME_TO_USE_FOR_URL, - DependentConfiguration.attributeWhenReady(geoDns, Attributes.HOSTNAME)); - - //tell GeoDNS what to monitor - geoDns.setTargetEntityProvider(webFabric); - } - - - public static void main(String[] argv) { - GlobalWebFabricExample app = new GlobalWebFabricExample(name: 'Brooklyn Global Web Fabric Example'); - - ArrayList args = new ArrayList(Arrays.asList(argv)); - BrooklynLauncher.newLauncher(). - webconsolePort( CommandLineUtil.getCommandLineOption(args, "--port", "8081+") ). - managing(app). - launch(); - - List locations = new LocationRegistry().getLocationsById(args ?: DEFAULT_LOCATIONS); - app.start(locations); - Entities.dumpInfo(app); - } - -} diff --git a/global-web-fabric/src/main/java/brooklyn/demo/GlobalWebFabricExample.java b/global-web-fabric/src/main/java/brooklyn/demo/GlobalWebFabricExample.java new file mode 100644 index 0000000..8a51b1d --- /dev/null +++ b/global-web-fabric/src/main/java/brooklyn/demo/GlobalWebFabricExample.java @@ -0,0 +1,95 @@ +package brooklyn.demo; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Arrays; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.StringConfigMap; +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.dns.geoscaling.GeoscalingDnsService; +import brooklyn.entity.group.DynamicFabric; +import brooklyn.entity.proxy.AbstractController; +import brooklyn.entity.proxying.BasicEntitySpec; +import brooklyn.entity.webapp.ElasticJavaWebAppService; +import brooklyn.event.basic.DependentConfiguration; +import brooklyn.extras.cloudfoundry.CloudFoundryJavaWebAppCluster; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.launcher.BrooklynServerDetails; +import brooklyn.location.Location; +import brooklyn.location.basic.LocationRegistry; +import brooklyn.location.basic.PortRanges; +import brooklyn.util.CommandLineUtil; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +public class GlobalWebFabricExample extends ApplicationBuilder { + + public static final Logger log = LoggerFactory.getLogger(GlobalWebFabricExample.class); + + public static final String WAR_PATH = "classpath://hello-world-webapp.war"; + + static final List DEFAULT_LOCATIONS = ImmutableList.of( + "aws-ec2:eu-west-1", + "aws-ec2:ap-southeast-1", + "aws-ec2:us-west-1" +// "cloudfoundry:https://api.aws.af.cm/", + ); + + protected void doBuild() { + StringConfigMap config = getManagementContext().getConfig(); + + GeoscalingDnsService geoDns = createChild(BasicEntitySpec.newInstance(GeoscalingDnsService.class) + .displayName("GeoScaling DNS") + .configure("username", checkNotNull(config.getFirst("brooklyn.geoscaling.username"), "username")) + .configure("password", checkNotNull(config.getFirst("brooklyn.geoscaling.password"), "password")) + .configure("primaryDomainName", checkNotNull(config.getFirst("brooklyn.geoscaling.primaryDomain"), "primaryDomain")) + .configure("smartSubdomainName", "brooklyn")); + + DynamicFabric webFabric = createChild(BasicEntitySpec.newInstance(DynamicFabric.class) + .displayName("Web Fabric") + .configure(DynamicFabric.FACTORY, new ElasticJavaWebAppService.Factory()) + + //specify the WAR file to use + .configure(ElasticJavaWebAppService.ROOT_WAR, WAR_PATH) + + //load-balancer instances must run on 80 to work with GeoDNS (default is 8000) + .configure(AbstractController.PROXY_HTTP_PORT, PortRanges.fromInteger(80)) + + //CloudFoundry requires to be told what URL it should listen to, which is chosen by the GeoDNS service + .configure(CloudFoundryJavaWebAppCluster.HOSTNAME_TO_USE_FOR_URL, + DependentConfiguration.attributeWhenReady(geoDns, Attributes.HOSTNAME))); + + //tell GeoDNS what to monitor + geoDns.setTargetEntityProvider(webFabric); + } + + public static void main(String[] argv) { + List args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String locations = CommandLineUtil.getCommandLineOption(args, "--locations", Joiner.on(",").join(DEFAULT_LOCATIONS)); + + BrooklynServerDetails server = BrooklynLauncher.newLauncher() + .webconsolePort(port) + .launch(); + + // TODO instead use server.getManagementContext().getLocationRegistry().resolve(location) + List locs = new LocationRegistry().getLocationsById(Arrays.asList(locations)); + + StartableApplication app = new GlobalWebFabricExample() + .appDisplayName("Brooklyn Global Web Fabric Example") + .manage(server.getManagementContext()); + + app.start(locs); + + Entities.dumpInfo(app); + } +} diff --git a/hadoop-and-whirr/pom.xml b/hadoop-and-whirr/pom.xml index 866e197..d8f0850 100644 --- a/hadoop-and-whirr/pom.xml +++ b/hadoop-and-whirr/pom.xml @@ -8,7 +8,7 @@ io.brooklyn.example brooklyn-examples-parent - 0.5.0-SNAPSHOT + 0.5.0-M2 ../pom.xml diff --git a/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebClusterWithHadoopExample.groovy b/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebClusterWithHadoopExample.groovy deleted file mode 100644 index 84271a4..0000000 --- a/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebClusterWithHadoopExample.groovy +++ /dev/null @@ -1,161 +0,0 @@ -package brooklyn.extras.whirr - -import groovy.transform.InheritConstructors - -import java.io.File -import java.net.InetAddress -import java.util.List - -import org.apache.log4j.lf5.util.StreamUtils; -import org.apache.whirr.service.hadoop.HadoopCluster -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -import brooklyn.config.BrooklynProperties -import brooklyn.entity.Entity -import brooklyn.entity.basic.AbstractApplication -import brooklyn.entity.basic.DynamicGroup -import brooklyn.entity.basic.Entities -import brooklyn.entity.trait.Startable -import brooklyn.entity.webapp.ControlledDynamicWebAppCluster; -import brooklyn.entity.webapp.DynamicWebAppCluster -import brooklyn.entity.webapp.jboss.JBoss7Server -import brooklyn.event.SensorEvent -import brooklyn.event.SensorEventListener -import brooklyn.event.adapter.HttpResponseContext -import brooklyn.extras.whirr.hadoop.WhirrHadoopCluster -import brooklyn.launcher.BrooklynLauncher -import brooklyn.location.Location -import brooklyn.location.basic.LocationRegistry -import brooklyn.management.Task -import brooklyn.policy.autoscaling.AutoScalerPolicy -import brooklyn.policy.basic.AbstractPolicy -import brooklyn.util.CommandLineUtil -import brooklyn.util.ResourceUtils; - -import com.google.common.base.Charsets -import com.google.common.collect.Iterables -import com.google.common.io.Files - -/** - * Starts hadoop and a webapp using hadoop in the location supplied (just one location), - * configuring the webapp to connect to hadoop - */ -@InheritConstructors -public class WebClusterWithHadoopExample extends AbstractApplication { - - private static final Logger log = LoggerFactory.getLogger(WebClusterWithHadoopExample.class); - - static final List DEFAULT_LOCATIONS = [ - // note the 1d: we need our machines to be in the same availability zone - // so they can see each other on the internal IPs (where hadoop binds) - // (cross-site magic is shown in the hadoop fabric example) - // ((also note some availability zones don't work, that's amazon for you...)) - // most other clouds "just work" :) - "aws-ec2:us-east-1d", - ]; - - public static final String WAR_PATH = "classpath://hello-world-hadoop-webapp.war"; - - static BrooklynProperties config = BrooklynProperties.Factory.newDefault() - - WhirrHadoopCluster hadoopCluster = new WhirrHadoopCluster(this, size: 2, memory: 2048, name: "Whirr Hadoop Cluster"); - { - // specify hadoop version (1.0.2 has a nice, smaller hadoop client jar) - hadoopCluster.addRecipeLine("whirr.hadoop.version=1.0.2"); - // for this example we'll allow access from anywhere - hadoopCluster.addRecipeLine("whirr.client-cidrs=0.0.0.0/0"); - hadoopCluster.addRecipeLine("whirr.firewall-rules=8020,8021,50010"); - } - - ControlledDynamicWebAppCluster webCluster = new ControlledDynamicWebAppCluster(this, war: WAR_PATH); - { - webCluster.addPolicy(AutoScalerPolicy.builder() - .metric(DynamicWebAppCluster.AVERAGE_REQUESTS_PER_SECOND) - .sizeRange(1, 5) - .metricRange(10, 100) - .build()) - } - - DynamicGroup webVms = new DynamicGroup(this, name: "Web VMs", { it in JBoss7Server }); - - void start(Collection locations) { - Iterables.getOnlyElement(locations); - log.debug("starting "+this); - super.start(locations); - - // start the web cluster, and hadoop cluster, in the single location (above) - // then register a policy to configure the appservers to use hadoop - log.debug("started "+this+", now starting policy"); - PrepVmsForHadoop.newPolicyFromGroupToHadoop(webVms, hadoopCluster); - } - - public static class PrepVmsForHadoop extends AbstractPolicy { - private static final Logger log = LoggerFactory.getLogger(WebClusterWithHadoopExample.class); - - WhirrHadoopCluster hadoopCluster; - String hadoopSiteXmlContents; - Set configuredIds = []; - - public PrepVmsForHadoop(WhirrHadoopCluster hadoopCluster) { - this.hadoopCluster = hadoopCluster; - } - - public void start() { - //read the contents, and disable socks proxy since we're in the same cluster - hadoopSiteXmlContents = new File("${System.getProperty('user.home')}/.whirr/"+ - hadoopCluster.clusterSpec.clusterName+"/hadoop-site.xml").text - hadoopSiteXmlContents = hadoopSiteXmlContents.replaceAll("<\\?xml.*\\?>\\s*", ""); - hadoopSiteXmlContents = hadoopSiteXmlContents.replaceAll("hadoop.socks.server", "ignore.hadoop.socks.server"); - - subscriptionTracker.subscribeToMembers(entity, Startable.SERVICE_UP, - { SensorEvent evt -> - log.debug "hadoop set up policy recieved {}", evt - if (evt.value && !configuredIds.contains(evt.source.id)) - setupMachine(evt.source) - } as SensorEventListener); - } - - public void setupMachine(Entity e) { - try { - if (!e.getAttribute(Startable.SERVICE_UP)) return; - if (!configuredIds.add(e.id)) return; - if (log.isDebugEnabled()) log.debug "setting up machine for hadoop at {}", e - - URL updateConfig = new URL(e.getAttribute(JBoss7Server.ROOT_URL)+ - "configure.jsp?"+ - "key=brooklyn.example.hadoop.site.xml.contents"+"&"+ - "value="+URLEncoder.encode(hadoopSiteXmlContents)); - def result = new HttpResponseContext(updateConfig.openConnection()); - if (log.isDebugEnabled()) log.debug "http config update for {} got: {}", e, result.content - } catch (Exception err) { - log.warn "unable to configure {} for hadoop: {}", e, err - configuredIds.remove(e.id); - } - } - - public static PrepVmsForHadoop newPolicyFromGroupToHadoop(DynamicGroup target, WhirrHadoopCluster hadoopCluster) { - log.debug "creating policy for hadoop clusters target {} hadoop ", target, hadoopCluster - PrepVmsForHadoop prepVmsForHadoop = new PrepVmsForHadoop(hadoopCluster); - target.addPolicy(prepVmsForHadoop); - prepVmsForHadoop.start(); - log.debug "running policy over existing members {}", target.members - target.members.each { prepVmsForHadoop.setupMachine(it) } - return prepVmsForHadoop; - } - } - - public static void main(String[] argv) { - ArrayList args = new ArrayList(Arrays.asList(argv)); - int port = CommandLineUtil.getCommandLineOptionInt(args, "--port", 8081); - List locations = new LocationRegistry().getLocationsById(args ?: DEFAULT_LOCATIONS) - log.info("starting WebClusterWithHadoop, locations {}, mgmt on port {}", locations, port) - - WebClusterWithHadoopExample app = new WebClusterWithHadoopExample(name: 'Brooklyn Global Web Fabric with Hadoop Example'); - - BrooklynLauncher.manage(app, port) - app.start(locations) - Entities.dumpInfo(app) - } - -} diff --git a/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebClusterWithHadoopExample.java b/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebClusterWithHadoopExample.java new file mode 100644 index 0000000..ec8696e --- /dev/null +++ b/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebClusterWithHadoopExample.java @@ -0,0 +1,212 @@ +package brooklyn.extras.whirr; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URLEncoder; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.StringConfigMap; +import brooklyn.entity.Entity; +import brooklyn.entity.Group; +import brooklyn.entity.basic.AbstractApplication; +import brooklyn.entity.basic.DynamicGroup; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.proxying.BasicEntitySpec; +import brooklyn.entity.trait.Startable; +import brooklyn.entity.webapp.ControlledDynamicWebAppCluster; +import brooklyn.entity.webapp.DynamicWebAppCluster; +import brooklyn.entity.webapp.jboss.JBoss7Server; +import brooklyn.event.SensorEvent; +import brooklyn.event.SensorEventListener; +import brooklyn.event.feed.http.HttpPollValue; +import brooklyn.extras.whirr.hadoop.WhirrHadoopCluster; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.launcher.BrooklynServerDetails; +import brooklyn.location.Location; +import brooklyn.policy.autoscaling.AutoScalerPolicy; +import brooklyn.policy.basic.AbstractPolicy; +import brooklyn.util.CommandLineUtil; +import brooklyn.util.exceptions.Exceptions; + +import com.google.common.base.Charsets; +import com.google.common.base.Joiner; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.common.io.Files; + +/** + * Starts hadoop and a webapp using hadoop in the location supplied (just one location), + * configuring the webapp to connect to hadoop + */ +public class WebClusterWithHadoopExample extends AbstractApplication implements StartableApplication { + + private static final Logger log = LoggerFactory.getLogger(WebClusterWithHadoopExample.class); + + static final List DEFAULT_LOCATIONS = ImmutableList.of( + // note the 1d: we need our machines to be in the same availability zone + // so they can see each other on the internal IPs (where hadoop binds) + // (cross-site magic is shown in the hadoop fabric example) + // ((also note some availability zones don't work, that's amazon for you...)) + // most other clouds "just work" :) + "aws-ec2:us-east-1c"); + + public static final String WAR_PATH = "classpath://hello-world-hadoop-webapp.war"; + + private WhirrHadoopCluster hadoopCluster; + private ControlledDynamicWebAppCluster webCluster; + private DynamicGroup webVms; + + public WebClusterWithHadoopExample() { + } + + @Override + public void postConstruct() { + StringConfigMap config = getManagementSupport().getManagementContext(true).getConfig(); + + hadoopCluster = getEntityManager().createEntity(BasicEntitySpec.newInstance(WhirrHadoopCluster.class) + .parent(this) + .configure("size", 2) + .configure("memory", 2048) + .configure("name", "Whirr Hadoop Cluster")); + + // TODO Can't just set RECIPE config in spec, because that overwrites defaults in WhirrHadoopCluster! + // specify hadoop version (1.0.2 has a nice, smaller hadoop client jar) + hadoopCluster.addRecipeLine("whirr.hadoop.version=1.0.2"); + // for this example we'll allow access from anywhere + hadoopCluster.addRecipeLine("whirr.client-cidrs=0.0.0.0/0"); + hadoopCluster.addRecipeLine("whirr.firewall-rules=8020,8021,50010"); + + webCluster = getEntityManager().createEntity(BasicEntitySpec.newInstance(ControlledDynamicWebAppCluster.class) + .parent(this) + .configure("war", WAR_PATH) + .policy(AutoScalerPolicy.builder() + .metric(DynamicWebAppCluster.AVERAGE_REQUESTS_PER_SECOND) + .sizeRange(1, 5) + .metricRange(10, 100) + .build())); + + webVms = getEntityManager().createEntity(BasicEntitySpec.newInstance(DynamicGroup.class) + .parent(this) + .displayName("Web VMs") + .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(JBoss7Server.class))); + } + + @Override + public void start(Collection locations) { + Iterables.getOnlyElement(locations); + log.debug("starting "+this); + super.start(locations); + + // start the web cluster, and hadoop cluster, in the single location (above) + // then register a policy to configure the appservers to use hadoop + log.debug("started "+this+", now starting policy"); + PrepVmsForHadoop.newPolicyFromGroupToHadoop(webVms, hadoopCluster); + } + + public static class PrepVmsForHadoop extends AbstractPolicy { + WhirrHadoopCluster hadoopCluster; + String hadoopSiteXmlContents; + Set configuredIds = Sets.newLinkedHashSet(); + + public PrepVmsForHadoop(WhirrHadoopCluster hadoopCluster) { + this.hadoopCluster = hadoopCluster; + } + + public void start() { + //read the contents, and disable socks proxy since we're in the same cluster + try { + File hadoopSiteXmlFile = new File(System.getProperty("user.home")+"/.whirr/"+ + hadoopCluster.getClusterSpec().getClusterName()+"/hadoop-site.xml"); + hadoopSiteXmlContents = Files.toString(hadoopSiteXmlFile, Charsets.UTF_8); + hadoopSiteXmlContents = hadoopSiteXmlContents.replaceAll("<\\?xml.*\\?>\\s*", ""); + hadoopSiteXmlContents = hadoopSiteXmlContents.replaceAll("hadoop.socks.server", "ignore.hadoop.socks.server"); + + subscribeToMembers((Group)entity, Startable.SERVICE_UP, new SensorEventListener() { + @Override public void onEvent(SensorEvent event) { + log.debug("hadoop set up policy recieved {}", event); + if (event.getValue() != null && !configuredIds.contains(event.getSource().getId())) + setupMachine(event.getSource()); + }}); + } catch (IOException e) { + throw Exceptions.propagate(e); + } + } + + public void setupMachine(Entity e) { + try { + if (!e.getAttribute(Startable.SERVICE_UP)) return; + if (!configuredIds.add(e.getId())) return; + if (log.isDebugEnabled()) log.debug("setting up machine for hadoop at {}", e); + + URI updateConfigUri = new URI(e.getAttribute(JBoss7Server.ROOT_URL)+ + "configure.jsp?"+ + "key=brooklyn.example.hadoop.site.xml.contents"+"&"+ + "value="+URLEncoder.encode(hadoopSiteXmlContents)); + + HttpPollValue result = executeGet(updateConfigUri); + if (log.isDebugEnabled()) log.debug("http config update for {} got: {}, {}", new Object[] {e, result.getResponseCode(), new String(result.getContent())}); + } catch (Exception err) { + log.warn("unable to configure "+e+" for hadoop", err); + configuredIds.remove(e.getId()); + } + } + + private HttpPollValue executeGet(URI uri) throws ClientProtocolException, IOException { + DefaultHttpClient httpClient = new DefaultHttpClient(); + HttpGet httpGet = new HttpGet(uri); + HttpResponse httpResponse = httpClient.execute(httpGet); + try { + return new HttpPollValue(httpResponse); + } finally { + EntityUtils.consume(httpResponse.getEntity()); + } + } + + public static PrepVmsForHadoop newPolicyFromGroupToHadoop(DynamicGroup target, WhirrHadoopCluster hadoopCluster) { + log.debug("creating policy for hadoop clusters target {} hadoop ", target, hadoopCluster); + PrepVmsForHadoop prepVmsForHadoop = new PrepVmsForHadoop(hadoopCluster); + target.addPolicy(prepVmsForHadoop); + prepVmsForHadoop.start(); + log.debug("running policy over existing members {}", target.getMembers()); + for (Entity member : target.getMembers()) { + prepVmsForHadoop.setupMachine(member); + } + return prepVmsForHadoop; + } + } + + public static void main(String[] argv) { + List args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", Joiner.on(",").join(DEFAULT_LOCATIONS)); + + BrooklynServerDetails server = BrooklynLauncher.newLauncher() + .webconsolePort(port) + .launch(); + + Location loc = server.getManagementContext().getLocationRegistry().resolve(location); + + StartableApplication app = server.getManagementContext().getEntityManager().createEntity(BasicEntitySpec.newInstance(StartableApplication.class) + .displayName("Brooklyn Global Web Fabric with Hadoop Example") + .impl(WebClusterWithHadoopExample.class)); + Entities.startManagement(app, server.getManagementContext()); + app.start(ImmutableList.of(loc)); + + Entities.dumpInfo(app); + } +} diff --git a/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebFabricWithHadoopExample.groovy b/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebFabricWithHadoopExample.groovy deleted file mode 100644 index f235999..0000000 --- a/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebFabricWithHadoopExample.groovy +++ /dev/null @@ -1,204 +0,0 @@ -package brooklyn.extras.whirr - -import groovy.transform.InheritConstructors - -import org.apache.whirr.service.hadoop.HadoopCluster -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -import brooklyn.config.BrooklynProperties -import brooklyn.entity.Entity -import brooklyn.entity.basic.AbstractApplication -import brooklyn.entity.basic.Attributes -import brooklyn.entity.basic.DynamicGroup -import brooklyn.entity.basic.Entities -import brooklyn.entity.dns.geoscaling.GeoscalingDnsService -import brooklyn.entity.group.DynamicFabric -import brooklyn.entity.trait.Startable -import brooklyn.entity.webapp.ElasticJavaWebAppService -import brooklyn.entity.webapp.jboss.JBoss7Server -import brooklyn.event.SensorEvent -import brooklyn.event.SensorEventListener -import brooklyn.event.adapter.HttpResponseContext -import brooklyn.event.basic.DependentConfiguration -import brooklyn.extras.cloudfoundry.CloudFoundryJavaWebAppCluster -import brooklyn.extras.whirr.hadoop.WhirrHadoopCluster -import brooklyn.launcher.BrooklynLauncher -import brooklyn.location.Location -import brooklyn.location.basic.LocationRegistry -import brooklyn.location.basic.SshMachineLocation -import brooklyn.management.Task -import brooklyn.policy.basic.AbstractPolicy -import brooklyn.util.CommandLineUtil -import brooklyn.util.task.ParallelTask - -import com.google.common.base.Charsets -import com.google.common.collect.Iterables -import com.google.common.collect.Lists -import com.google.common.io.Files - -/** - * Starts hadoop in the first location supplied, and the hadoop-friendly webapp in all other locations. - * Webapp get configured via the configure.jsp page, plus supplying the proxy command, to connect to hadoop. - */ -@InheritConstructors -public class WebFabricWithHadoopExample extends AbstractApplication { - - private static final Logger log = LoggerFactory.getLogger(WebFabricWithHadoopExample.class); - - static final List DEFAULT_LOCATIONS = [ - // hadoop location - "aws-ec2:eu-west-1", - - //web locations - "aws-ec2:eu-west-1", - "aws-ec2:ap-southeast-1", - "aws-ec2:us-west-1", - - // cloudfoundry seems to have a timeout in upload time - // (in any case we don't have a clean way to initiate the proxy settings in there) -// "cloudfoundry:https://api.aws.af.cm/", - ]; - - public static final String WAR_PATH = "classpath://hello-world-hadoop-webapp.war"; - - static BrooklynProperties config = BrooklynProperties.Factory.newDefault() - - WhirrHadoopCluster hadoopCluster = new WhirrHadoopCluster(this, size: 2, memory: 2048, name: "Whirr Hadoop Cluster"); - { hadoopCluster.addRecipeLine("whirr.hadoop.version=1.0.2"); } - - DynamicFabric webFabric = new DynamicFabric(this, name: "Web Fabric", factory: new ElasticJavaWebAppService.Factory()); - - GeoscalingDnsService geoDns = new GeoscalingDnsService(this, name: "GeoScaling DNS", - username: config.getFirst("brooklyn.geoscaling.username", failIfNone:true), - password: config.getFirst("brooklyn.geoscaling.password", failIfNone:true), - primaryDomainName: config.getFirst("brooklyn.geoscaling.primaryDomain", failIfNone:true), - smartSubdomainName: 'brooklyn'); - - { - //specify the WAR file to use - webFabric.setConfig(ElasticJavaWebAppService.ROOT_WAR, WAR_PATH); - //load-balancer instances must run on 80 to work with GeoDNS (default is 8000) - webFabric.setConfig(AbstractController.PROXY_HTTP_PORT, 80); - //CloudFoundry requires to be told what URL it should listen to, which is chosen by the GeoDNS service - webFabric.setConfig(CloudFoundryJavaWebAppCluster.HOSTNAME_TO_USE_FOR_URL, - DependentConfiguration.attributeWhenReady(geoDns, Attributes.HOSTNAME)); - - //tell GeoDNS what to monitor - geoDns.setTargetEntityProvider(webFabric); - } - - DynamicGroup webVms = new DynamicGroup(this, name: "Web VMs", { it in JBoss7Server }); - - void start(Collection locations) { - Location hadoopLocation = Iterables.getFirst(locations, null); - if (hadoopLocation==null) throw new IllegalStateException("location required to start $this"); - // start hadoop in first, web in others (unless there is just one location supplied) - List webLocations = Lists.newArrayList(Iterables.skip(locations, 1)) ?: [hadoopLocation]; - - Task starts = executionContext.submit(new ParallelTask( - { webFabric.start(webLocations) }, - { hadoopCluster.start([hadoopLocation]); - // collect the hadoop-site.xml and feed it to all existing and new appservers, - // and start the proxies there - PrepVmsForHadoop.newPolicyFromGroupToHadoop(webVms, hadoopCluster); - } )); - starts.blockUntilEnded(); - } - - public static class PrepVmsForHadoop extends AbstractPolicy { - private static final Logger log = LoggerFactory.getLogger(WebFabricWithHadoopExample.class); - - WhirrHadoopCluster hadoopCluster; - Set configuredIds = [] - - public PrepVmsForHadoop(WhirrHadoopCluster hadoopCluster) { - this.hadoopCluster = hadoopCluster; - } - - public static PrepVmsForHadoop newPolicyFromGroupToHadoop(DynamicGroup target, WhirrHadoopCluster hadoopCluster) { - log.debug "creating policy for hadoop clusters target {} hadoop ", target, hadoopCluster - PrepVmsForHadoop prepVmsForHadoop = new PrepVmsForHadoop(hadoopCluster); - target.addPolicy(prepVmsForHadoop); - prepVmsForHadoop.start(); - log.debug "running policy over existing members {}", target.members - target.members.each { prepVmsForHadoop.setupMachine(it) } - return prepVmsForHadoop; - } - - public void start() { - subscriptionTracker.subscribeToMembers(entity, Startable.SERVICE_UP, - { SensorEvent evt -> - log.debug "hadoop set up policy recieved {}", evt - if (evt.value) setupMachine(evt.source) } as SensorEventListener); - } - public void setupMachine(Entity e) { - try { - if (log.isDebugEnabled()) log.debug "setting up machine for hadoop at {}", e - if (!e.getAttribute(Startable.SERVICE_UP)) return; - if (!configuredIds.add(e.id)) return; - SshMachineLocation ssh = Iterables.getOnlyElement(e.locations); - //would prefer to extract content from HadoopNameNodeClusterActionHandler (but that class would need refactoring) - ssh.copyTo(new File("${System.getProperty('user.home')}/.whirr/"+hadoopCluster.clusterSpec.clusterName+"/hadoop-site.xml"), "/tmp/hadoop-site.xml"); - - File identity = hadoopCluster.clusterSpec.getPrivateKeyFile(); - if (identity == null){ - identity = File.createTempFile("hadoop", "key"); - identity.deleteOnExit(); - Files.write(hadoopCluster.clusterSpec.getPrivateKey(), identity, Charsets.UTF_8); - } - if (log.isDebugEnabled()) log.debug "http config update for {}, identity file: {}", e, identity - ssh.copyTo(identity, "/tmp/hadoop-proxy-private-key"); - - //copied from HadoopProxy, would prefer to reference (but again refactoring there is needed) - String user = hadoopCluster.clusterSpec.getClusterUser(); - InetAddress namenode = HadoopCluster.getNamenodePublicAddress(hadoopCluster.cluster); - String server = namenode.getHostName(); - String proxyCommand = [ "ssh", - "-i", "/tmp/hadoop-proxy-private-key", - "-o", "ConnectTimeout=10", - "-o", "ServerAliveInterval=60", - "-o", "StrictHostKeyChecking=no", - "-o", "UserKnownHostsFile=/dev/null", - "-o", "StrictHostKeyChecking=no", - "-N", - "-D 6666", - String.format("%s@%s", user, server) ].join(" "); - if (log.isDebugEnabled()) log.debug "http config update for {}, proxy command: {}", e, proxyCommand - - ssh.copyTo(new StringReader(""" -while [ true ] ; do - date - echo starting proxy for hadoop to """+String.format("%s@%s", user, server)+""" - nohup """+proxyCommand+""" - echo proxy ended -done -"""), "/tmp/hadoop-proxy-forever.sh"); - ssh.run("chmod 600 /tmp/hadoop-proxy-private-key ; chmod +x /tmp/hadoop-proxy-forever.sh ; nohup /tmp/hadoop-proxy-forever.sh &"); - - URL updateConfig = new URL(e.getAttribute(JBoss7Server.ROOT_URL)+ - "configure.jsp?key=brooklyn.example.hadoop.site.xml.url&value=file:///tmp/hadoop-site.xml"); - - def result = new HttpResponseContext(updateConfig.openConnection()); - if (log.isDebugEnabled()) log.debug "http config update for {} got: {}", e, result.content - } catch (Exception err) { - log.warn "unable to configure {} for hadoop: {}", e, err - configuredIds.remove(e.id); - } - } - } - - public static void main(String[] argv) { - ArrayList args = new ArrayList(Arrays.asList(argv)); - int port = CommandLineUtil.getCommandLineOptionInt(args, "--port", 8081); - List locations = new LocationRegistry().getLocationsById(args ?: DEFAULT_LOCATIONS) - log.info("starting WebFabricWithHadoop, locations {}, mgmt on port {}", locations, port) - - WebFabricWithHadoopExample app = new WebFabricWithHadoopExample(name: 'Brooklyn Global Web Fabric with Hadoop Example'); - - BrooklynLauncher.manage(app, port) - app.start(locations) - Entities.dumpInfo(app) - } - -} diff --git a/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebFabricWithHadoopExample.java b/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebFabricWithHadoopExample.java new file mode 100644 index 0000000..aa503a6 --- /dev/null +++ b/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WebFabricWithHadoopExample.java @@ -0,0 +1,290 @@ +package brooklyn.extras.whirr; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.net.InetAddress; +import java.net.URI; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.util.EntityUtils; +import org.apache.whirr.service.hadoop.HadoopCluster; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.StringConfigMap; +import brooklyn.entity.Entity; +import brooklyn.entity.Group; +import brooklyn.entity.basic.AbstractApplication; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.DynamicGroup; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.dns.geoscaling.GeoscalingDnsService; +import brooklyn.entity.group.DynamicFabric; +import brooklyn.entity.proxy.AbstractController; +import brooklyn.entity.proxying.BasicEntitySpec; +import brooklyn.entity.trait.Startable; +import brooklyn.entity.webapp.ElasticJavaWebAppService; +import brooklyn.entity.webapp.jboss.JBoss7Server; +import brooklyn.event.SensorEvent; +import brooklyn.event.SensorEventListener; +import brooklyn.event.basic.DependentConfiguration; +import brooklyn.event.feed.http.HttpPollValue; +import brooklyn.extras.cloudfoundry.CloudFoundryJavaWebAppCluster; +import brooklyn.extras.whirr.hadoop.WhirrHadoopCluster; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.launcher.BrooklynServerDetails; +import brooklyn.location.Location; +import brooklyn.location.basic.LocationRegistry; +import brooklyn.location.basic.PortRanges; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.management.Task; +import brooklyn.policy.basic.AbstractPolicy; +import brooklyn.util.CommandLineUtil; +import brooklyn.util.task.ParallelTask; + +import com.google.common.base.Charsets; +import com.google.common.base.Joiner; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.common.io.Files; + +/** + * Starts hadoop in the first location supplied, and the hadoop-friendly webapp in all other locations. + * Webapp get configured via the configure.jsp page, plus supplying the proxy command, to connect to hadoop. + */ +public class WebFabricWithHadoopExample extends AbstractApplication implements StartableApplication { + + private static final Logger log = LoggerFactory.getLogger(WebFabricWithHadoopExample.class); + + static final List DEFAULT_LOCATIONS = ImmutableList.of( + // hadoop location + "aws-ec2:eu-west-1", + + //web locations + "aws-ec2:eu-west-1", + "aws-ec2:ap-southeast-1", + "aws-ec2:us-west-1" + + // cloudfoundry seems to have a timeout in upload time + // (in any case we don't have a clean way to initiate the proxy settings in there) +// "cloudfoundry:https://api.aws.af.cm/", + ); + + public static final String WAR_PATH = "classpath://hello-world-hadoop-webapp.war"; + + private WhirrHadoopCluster hadoopCluster; + private GeoscalingDnsService geoDns; + private DynamicFabric webFabric; + private DynamicGroup webVms; + + public WebFabricWithHadoopExample() { + } + + @Override + public void postConstruct() { + StringConfigMap config = getManagementSupport().getManagementContext(true).getConfig(); + + hadoopCluster = getEntityManager().createEntity(BasicEntitySpec.newInstance(WhirrHadoopCluster.class) + .parent(this) + .configure("size", 2) + .configure("memory", 2048) + .configure("name", "Whirr Hadoop Cluster")); + + // TODO Can't just set RECIPE config in spec, because that overwrites defaults in WhirrHadoopCluster! + // specify hadoop version (1.0.2 has a nice, smaller hadoop client jar) + hadoopCluster.addRecipeLine("whirr.hadoop.version=1.0.2"); + + GeoscalingDnsService geoDns = getEntityManager().createEntity(BasicEntitySpec.newInstance(GeoscalingDnsService.class) + .parent(this) + .displayName("GeoScaling DNS") + .configure("username", checkNotNull(config.getFirst("brooklyn.geoscaling.username"), "username")) + .configure("password", checkNotNull(config.getFirst("brooklyn.geoscaling.password"), "password")) + .configure("primaryDomainName", checkNotNull(config.getFirst("brooklyn.geoscaling.primaryDomain"), "primaryDomain")) + .configure("smartSubdomainName", "brooklyn")); + + webFabric = getEntityManager().createEntity(BasicEntitySpec.newInstance(DynamicFabric.class) + .parent(this) + .displayName("Web Fabric") + .configure("factory", new ElasticJavaWebAppService.Factory()) + //specify the WAR file to use + .configure(ElasticJavaWebAppService.ROOT_WAR, WAR_PATH) + //load-balancer instances must run on 80 to work with GeoDNS (default is 8000) + .configure(AbstractController.PROXY_HTTP_PORT, PortRanges.fromInteger(80)) + //CloudFoundry requires to be told what URL it should listen to, which is chosen by the GeoDNS service + .configure(CloudFoundryJavaWebAppCluster.HOSTNAME_TO_USE_FOR_URL, + DependentConfiguration.attributeWhenReady(geoDns, Attributes.HOSTNAME)) + ); +// .policy(AutoScalerPolicy.builder() +// .metric(DynamicWebAppCluster.AVERAGE_REQUESTS_PER_SECOND) +// .sizeRange(1, 5) +// .metricRange(10, 100) +// .build())); + + webVms = getEntityManager().createEntity(BasicEntitySpec.newInstance(DynamicGroup.class) + .parent(this) + .displayName("Web VMs") + .configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(JBoss7Server.class))); + + //tell GeoDNS what to monitor + geoDns.setTargetEntityProvider(webFabric); + } + + @Override + public void start(Collection locations) { + if (locations.isEmpty()) throw new IllegalStateException("location required to start "+this); + final Location hadoopLocation = Iterables.getFirst(locations, null); + // start hadoop in first, web in others (unless there is just one location supplied) + final List webLocations = (locations.size() > 1) ? ImmutableList.copyOf(Iterables.skip(locations, 1)) : ImmutableList.of(hadoopLocation); + + Task> starts = getExecutionContext().submit(new ParallelTask( + new Runnable() { + public void run() { + webFabric.start(webLocations); + } + }, + new Runnable() { + public void run() { + hadoopCluster.start(ImmutableList.of(hadoopLocation)); + // collect the hadoop-site.xml and feed it to all existing and new appservers, + // and start the proxies there + PrepVmsForHadoop.newPolicyFromGroupToHadoop(webVms, hadoopCluster); + } + })); + starts.blockUntilEnded(); + } + + public static class PrepVmsForHadoop extends AbstractPolicy { + WhirrHadoopCluster hadoopCluster; + Set configuredIds = Sets.newLinkedHashSet(); + + public PrepVmsForHadoop(WhirrHadoopCluster hadoopCluster) { + this.hadoopCluster = hadoopCluster; + } + + public static PrepVmsForHadoop newPolicyFromGroupToHadoop(DynamicGroup target, WhirrHadoopCluster hadoopCluster) { + log.debug("creating policy for hadoop clusters target {} hadoop ", target, hadoopCluster); + PrepVmsForHadoop prepVmsForHadoop = new PrepVmsForHadoop(hadoopCluster); + target.addPolicy(prepVmsForHadoop); + prepVmsForHadoop.start(); + log.debug("running policy over existing members {}", target.getMembers()); + for (Entity member : target.getMembers()) { + prepVmsForHadoop.setupMachine(member); + } + return prepVmsForHadoop; + } + + public void start() { + subscribeToMembers((Group)entity, Startable.SERVICE_UP, new SensorEventListener() { + @Override public void onEvent(SensorEvent event) { + log.debug("hadoop set up policy recieved {}", event); + if (event.getValue() != null) { + setupMachine(event.getSource()); + } + }}); + } + + public void setupMachine(Entity e) { + try { + if (log.isDebugEnabled()) log.debug("setting up machine for hadoop at {}", e); + if (!e.getAttribute(Startable.SERVICE_UP)) return; + if (!configuredIds.add(e.getId())) return; + SshMachineLocation ssh = (SshMachineLocation) Iterables.getOnlyElement(e.getLocations()); + // TODO would prefer to extract content from HadoopNameNodeClusterActionHandler (but that class would need refactoring) + ssh.copyTo(new File(System.getProperty("user.home")+"/.whirr/"+hadoopCluster.getClusterSpec().getClusterName()+"/hadoop-site.xml"), "/tmp/hadoop-site.xml"); + + File identity = hadoopCluster.getClusterSpec().getPrivateKeyFile(); + if (identity == null){ + identity = File.createTempFile("hadoop", "key"); + identity.deleteOnExit(); + Files.write(hadoopCluster.getClusterSpec().getPrivateKey(), identity, Charsets.UTF_8); + } + if (log.isDebugEnabled()) log.debug("http config update for {}, identity file: {}", e, identity); + ssh.copyTo(identity, "/tmp/hadoop-proxy-private-key"); + + //copied from HadoopProxy, would prefer to reference (but again refactoring there is needed) + String user = hadoopCluster.getClusterSpec().getClusterUser(); + InetAddress namenode = HadoopCluster.getNamenodePublicAddress(hadoopCluster.getCluster()); + String server = namenode.getHostName(); + String proxyCommand = Joiner.on(" ").join(ImmutableList.of( + "ssh", + "-i", "/tmp/hadoop-proxy-private-key", + "-o", "ConnectTimeout=10", + "-o", "ServerAliveInterval=60", + "-o", "StrictHostKeyChecking=no", + "-o", "UserKnownHostsFile=/dev/null", + "-o", "StrictHostKeyChecking=no", + "-N", + "-D 6666", + String.format("%s@%s", user, server))); + if (log.isDebugEnabled()) log.debug("http config update for {}, proxy command: {}", e, proxyCommand); + + String hadoopProxyForeverContent = + "while [ true ] ; do"+"\n"+ + " date"+"\n"+ + " echo starting proxy for hadoop to "+String.format("%s@%s", user, server)+"\n"+ + " nohup "+proxyCommand+"\n"+ + " echo proxy ended"+"\n"+ + "done"+"\n"; + + ssh.copyTo(new StringReader(hadoopProxyForeverContent), "/tmp/hadoop-proxy-forever.sh"); + + ssh.run("chmod 600 /tmp/hadoop-proxy-private-key ; chmod +x /tmp/hadoop-proxy-forever.sh ; nohup /tmp/hadoop-proxy-forever.sh &"); + + URI updateConfigUri = new URI(e.getAttribute(JBoss7Server.ROOT_URL)+ + "configure.jsp?key=brooklyn.example.hadoop.site.xml.url&value=file:///tmp/hadoop-site.xml"); + + HttpPollValue result = executeGet(updateConfigUri); + if (log.isDebugEnabled()) log.debug("http config update for {} got: {}, {}", new Object[] {e, result.getResponseCode(), new String(result.getContent())}); + } catch (Exception err) { + log.warn("unable to configure "+e+" for hadoop", err); + configuredIds.remove(e.getId()); + } + } + + private HttpPollValue executeGet(URI uri) throws ClientProtocolException, IOException { + DefaultHttpClient httpClient = new DefaultHttpClient(); + HttpGet httpGet = new HttpGet(uri); + HttpResponse httpResponse = httpClient.execute(httpGet); + try { + return new HttpPollValue(httpResponse); + } finally { + EntityUtils.consume(httpResponse.getEntity()); + } + } + } + + public static void main(String[] argv) { + List args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", Joiner.on(",").join(DEFAULT_LOCATIONS)); + + BrooklynServerDetails server = BrooklynLauncher.newLauncher() + .webconsolePort(port) + .launch(); + + List locs = new LocationRegistry().getLocationsById(ImmutableList.of(location)); + + StartableApplication app = server.getManagementContext().getEntityManager().createEntity(BasicEntitySpec.newInstance(StartableApplication.class) + .displayName("Brooklyn Global Web Fabric with Hadoop Example") + .impl(WebFabricWithHadoopExample.class)); + Entities.startManagement(app, server.getManagementContext()); + + log.info("starting WebFabricWithHadoop, locations {}, mgmt on port {}", locs, port); + app.start(locs); + + Entities.dumpInfo(app); + } +} diff --git a/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrExample.groovy b/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrExample.groovy deleted file mode 100644 index 1e0dd6d..0000000 --- a/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrExample.groovy +++ /dev/null @@ -1,53 +0,0 @@ -package brooklyn.extras.whirr - -import java.util.List; - -import brooklyn.entity.basic.AbstractApplication -import brooklyn.entity.basic.Entities; - -import org.slf4j.LoggerFactory -import org.slf4j.Logger -import brooklyn.launcher.BrooklynLauncher -import brooklyn.location.Location; -import brooklyn.location.basic.CommandLineLocations -import brooklyn.location.basic.FixedListMachineProvisioningLocation; -import brooklyn.location.basic.LocalhostMachineProvisioningLocation; -import brooklyn.util.CommandLineUtil; -import brooklyn.extras.whirr.core.WhirrCluster - -public class WhirrExample extends AbstractApplication { - - private static final Logger LOG = LoggerFactory.getLogger(WhirrExample.class); - - public static final List DEFAULT_LOCATIONS = [ "aws-ec2:eu-west-1" ] - - public static final String RECIPE = ''' -whirr.cluster-name=brooklyn-whirr -whirr.hardware-min-ram=1024 -whirr.instance-templates=1 noop, 1 elasticsearch -''' - - WhirrCluster cluster = new WhirrCluster(this, recipe: RECIPE) - - public static void main(String[] argv) { - ArrayList args = new ArrayList(Arrays.asList(argv)); - int port = CommandLineUtil.getCommandLineOptionInt(args, "--port", 8081); - List locations = CommandLineLocations.getLocationsById(args ?: DEFAULT_LOCATIONS) - - try { - def app = new WhirrExample() - - BrooklynLauncher.manage(app, port) - app.start(locations) - Entities.dumpInfo(app) - LOG.info("Press return to shut down the cluster") - System.in.read() //wait for the user to type a key - app.stop() - } catch (Throwable e) { - LOG.error("Failed to start: "+e, e); - Thread.sleep(600); - throw e; - } - } - -} diff --git a/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrExample.java b/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrExample.java new file mode 100644 index 0000000..0a44d6d --- /dev/null +++ b/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrExample.java @@ -0,0 +1,59 @@ +package brooklyn.extras.whirr; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.proxying.BasicEntitySpec; +import brooklyn.extras.whirr.core.WhirrCluster; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.launcher.BrooklynServerDetails; +import brooklyn.location.Location; +import brooklyn.util.CommandLineUtil; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +public class WhirrExample extends ApplicationBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(WhirrExample.class); + + public static final String DEFAULT_LOCATION = "aws-ec2:eu-west-1"; + + public static final String RECIPE = + "whirr.cluster-name=brooklyn-whirr"+"\n"+ + "whirr.hardware-min-ram=1024"+"\n"+ + "whirr.instance-templates=1 noop, 1 elasticsearch"+"\n"; + + protected void doBuild() { + WhirrCluster cluster = createChild(BasicEntitySpec.newInstance(WhirrCluster.class) + .configure("recipe", RECIPE)); + } + + public static void main(String[] argv) throws Exception { + List args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", DEFAULT_LOCATION); + + BrooklynServerDetails server = BrooklynLauncher.newLauncher() + .webconsolePort(port) + .launch(); + + Location loc = server.getManagementContext().getLocationRegistry().resolve(location); + + StartableApplication app = new WhirrExample() + .manage(server.getManagementContext()); + + app.start(ImmutableList.of(loc)); + + Entities.dumpInfo(app); + + LOG.info("Press return to shut down the cluster"); + System.in.read(); //wait for the user to type a key + app.stop(); + } +} diff --git a/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrHadoopExample.groovy b/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrHadoopExample.groovy deleted file mode 100644 index 5d0d620..0000000 --- a/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrHadoopExample.groovy +++ /dev/null @@ -1,38 +0,0 @@ -package brooklyn.extras.whirr - -import java.util.List; - -import brooklyn.entity.basic.AbstractApplication -import brooklyn.entity.basic.Entities; -import brooklyn.extras.whirr.core.WhirrCluster -import brooklyn.launcher.BrooklynLauncher -import brooklyn.location.Location; -import brooklyn.location.basic.CommandLineLocations -import brooklyn.location.basic.LocationRegistry; -import brooklyn.util.CommandLineUtil; - -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import brooklyn.extras.whirr.hadoop.WhirrHadoopCluster - -public class WhirrHadoopExample extends AbstractApplication { - - private static final Logger LOG = LoggerFactory.getLogger(WhirrHadoopExample.class); - - public static final String DEFAULT_LOCATION = "aws-ec2:eu-west-1" - - WhirrCluster cluster = new WhirrHadoopCluster(this, size: 2, memory: 2048, name: "brooklyn-hadoop-example") - - public static void main(String[] argv) { - ArrayList args = new ArrayList(Arrays.asList(argv)); - int port = CommandLineUtil.getCommandLineOptionInt(args, "--port", 8081); - List locations = new LocationRegistry().getLocationsById(args ?: [DEFAULT_LOCATION]) - - def app = new WhirrHadoopExample() - - BrooklynLauncher.manage(app, port) - app.start(locations) - Entities.dumpInfo(app) - } - -} diff --git a/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrHadoopExample.java b/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrHadoopExample.java new file mode 100644 index 0000000..104d716 --- /dev/null +++ b/hadoop-and-whirr/src/main/java/brooklyn/extras/whirr/WhirrHadoopExample.java @@ -0,0 +1,58 @@ +package brooklyn.extras.whirr; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.proxying.BasicEntitySpec; +import brooklyn.extras.whirr.core.WhirrCluster; +import brooklyn.extras.whirr.hadoop.WhirrHadoopCluster; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.launcher.BrooklynServerDetails; +import brooklyn.location.Location; +import brooklyn.util.CommandLineUtil; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +public class WhirrHadoopExample extends ApplicationBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(WhirrHadoopExample.class); + + public static final String DEFAULT_LOCATION = "aws-ec2:eu-west-1"; + + @Override + protected void doBuild() { + WhirrCluster cluster = createChild(BasicEntitySpec.newInstance(WhirrHadoopCluster.class) + .displayName("brooklyn-hadoop-example") + .configure("size", 2) + .configure("memory", 2048)); + } + + public static void main(String[] argv) throws Exception { + List args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", DEFAULT_LOCATION); + + BrooklynServerDetails server = BrooklynLauncher.newLauncher() + .webconsolePort(port) + .launch(); + + Location loc = server.getManagementContext().getLocationRegistry().resolve(location); + + StartableApplication app = new WhirrHadoopExample() + .manage(server.getManagementContext()); + + app.start(ImmutableList.of(loc)); + + Entities.dumpInfo(app); + + LOG.info("Press return to shut down the cluster"); + System.in.read(); //wait for the user to type a key + app.stop(); + } +} diff --git a/monitored-cassandra-cluster/.gitignore b/monitored-cassandra-cluster/.gitignore new file mode 100644 index 0000000..7a8040d --- /dev/null +++ b/monitored-cassandra-cluster/.gitignore @@ -0,0 +1 @@ +resources/lib/ \ No newline at end of file diff --git a/monitored-cassandra-cluster/README.txt b/monitored-cassandra-cluster/README.txt new file mode 100644 index 0000000..6048867 --- /dev/null +++ b/monitored-cassandra-cluster/README.txt @@ -0,0 +1,24 @@ +Instructions for running examples +================================= + +The commands below assume that the `brooklyn` script is already on your $PATH, and you are in the "examples" directory: + + cd simple-messaging-pubsub + export BROOKLYN_CLASSPATH=$(pwd)/target/classes + + # Launches a qpid broker on localhost + brooklyn -v launch --app brooklyn.demo.StandaloneBrokerExample --location localhost + + # You can get the broker's URL from the brooklyn web-console at http://localhost:8081 + # by looking at the broker entity's sensors or from the verbose output from the application startup + URL="amqp://guest:guest@/localhost?brokerlist='tcp://localhost:5672'" + + # Test subscribing, to receive a message from the broker + java -cp "./resources/lib/*:./target/classes" brooklyn.demo.Subscribe ${URL} + + # Test publishing a message to the broker + java -cp "./resources/lib/*:./target/classes" brooklyn.demo.Publish ${URL} + +--- + +For more information, please visit: http://brooklyncentral.github.com/use/examples/messaging/ diff --git a/monitored-cassandra-cluster/pom.xml b/monitored-cassandra-cluster/pom.xml new file mode 100644 index 0000000..3523315 --- /dev/null +++ b/monitored-cassandra-cluster/pom.xml @@ -0,0 +1,49 @@ + + 4.0.0 + jar + brooklyn-example-monitored-cassandra-cluster + Brooklyn Cassandra Cluster with Monitoring Example + + + io.brooklyn.example + brooklyn-examples-parent + 0.5.0-M2 + ../pom.xml + + + + + io.brooklyn + brooklyn-all + ${project.version} + provided + + + + com.google.guava + guava + + + + + + + maven-clean-plugin + + + + ${project.basedir} + + ${project.artifactId}/ + brooklyn*.log + brooklyn*.log.* + stacktrace.log + resources/lib/ + + + + + + + + diff --git a/monitored-cassandra-cluster/src/main/java/brooklyn/demo/MonitoredCassandraClusterExample.groovy b/monitored-cassandra-cluster/src/main/java/brooklyn/demo/MonitoredCassandraClusterExample.groovy new file mode 100644 index 0000000..df7fe9d --- /dev/null +++ b/monitored-cassandra-cluster/src/main/java/brooklyn/demo/MonitoredCassandraClusterExample.groovy @@ -0,0 +1,27 @@ +package brooklyn.demo + +import java.util.List + +import brooklyn.entity.basic.AbstractApplication +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.nosql.cassandra.CassandraCluster +import brooklyn.entity.proxying.BasicEntitySpec; +import brooklyn.entity.webapp.jboss.JBoss7Server; +import brooklyn.location.Location + +/** Cassandra Application */ +public class MonitoredCassandraClusterExample extends ApplicationBuilder { + + /** + * For overriding, to create and wire together entities. + */ + protected void doBuild() { + createChild(BasicEntitySpec.newInstance(CassandraCluster.class) + .configure("initialSize", "2") + .configure("clusterName", "CassandraDemo") + .configure("jmxPort", "11099+") + .configure("rmiServerPort", "9001+") + .configure("thriftPort", "9160+")); + } + +} diff --git a/monitored-cassandra-cluster/src/main/resources/logback.xml b/monitored-cassandra-cluster/src/main/resources/logback.xml new file mode 100644 index 0000000..25f3755 --- /dev/null +++ b/monitored-cassandra-cluster/src/main/resources/logback.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/pom.xml b/pom.xml index 6c29dd4..7e68390 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ io.brooklyn brooklyn-parent - 0.5.0-SNAPSHOT + 0.5.0-M2 ../pom.xml @@ -26,6 +26,7 @@ hadoop-and-whirr portable-cloudfoundry simple-messaging-pubsub + monitored-cassandra-cluster diff --git a/portable-cloudfoundry/pom.xml b/portable-cloudfoundry/pom.xml index 1dde430..886c7f9 100644 --- a/portable-cloudfoundry/pom.xml +++ b/portable-cloudfoundry/pom.xml @@ -7,7 +7,7 @@ io.brooklyn.example brooklyn-examples-parent - 0.5.0-SNAPSHOT + 0.5.0-M2 ../pom.xml diff --git a/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableCloudFoundryClusterExample.groovy b/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableCloudFoundryClusterExample.groovy deleted file mode 100644 index c3394fe..0000000 --- a/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableCloudFoundryClusterExample.groovy +++ /dev/null @@ -1,35 +0,0 @@ -package brooklyn.example.cloudfoundry; - -import groovy.transform.InheritConstructors -import brooklyn.entity.basic.AbstractApplication -import brooklyn.entity.basic.Entities -import brooklyn.launcher.BrooklynLauncher -import brooklyn.location.Location -import brooklyn.location.basic.LocationRegistry -import brooklyn.util.CommandLineUtil - -@InheritConstructors -public class MovableCloudFoundryClusterExample extends AbstractApplication implements MovableEntityTrait { - - public static final String DEFAULT_LOCATION = "cloudfoundry" - public static final String WAR_FILE_URL = "classpath://hello-world-webapp.war"; - - MovableElasticWebAppCluster websvc = new MovableElasticWebAppCluster(this, war: WAR_FILE_URL); - - @Override - public String move(String location) { - websvc.move(location); - } - - public static void main(String[] argv) { - ArrayList args = new ArrayList(Arrays.asList(argv)); - int port = CommandLineUtil.getCommandLineOptionInt(args, "--port", 8081); - List locations = new LocationRegistry().getLocationsById(args ?: [DEFAULT_LOCATION]) - - MovableCloudFoundryClusterExample app = new MovableCloudFoundryClusterExample(name:'Movable Web Cluster') - - BrooklynLauncher.manage(app, port) - app.start(locations) - Entities.dumpInfo(app) - } -} diff --git a/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableCloudFoundryClusterExample.java b/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableCloudFoundryClusterExample.java new file mode 100644 index 0000000..85374a2 --- /dev/null +++ b/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableCloudFoundryClusterExample.java @@ -0,0 +1,47 @@ +package brooklyn.example.cloudfoundry; + +import java.util.List; + +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.proxying.BasicEntitySpec; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.launcher.BrooklynServerDetails; +import brooklyn.location.Location; +import brooklyn.util.CommandLineUtil; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +public class MovableCloudFoundryClusterExample extends ApplicationBuilder { + + public static final String DEFAULT_LOCATION = "cloudfoundry"; + public static final String WAR_FILE_URL = "classpath://hello-world-webapp.war"; + + @Override + protected void doBuild() { + createChild(BasicEntitySpec.newInstance(MovableElasticWebAppCluster.class) + .configure("war", WAR_FILE_URL)); + } + + public static void main(String[] argv) { + List args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", DEFAULT_LOCATION); + + BrooklynServerDetails server = BrooklynLauncher.newLauncher() + .webconsolePort(port) + .launch(); + + Location loc = server.getManagementContext().getLocationRegistry().resolve(location); + + StartableApplication app = new MovableCloudFoundryClusterExample() + .appDisplayName("Movable Web Cluster") + .manage(server.getManagementContext()); + + app.start(ImmutableList.of(loc)); + + Entities.dumpInfo(app); + } +} diff --git a/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableElasticWebAppCluster.java b/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableElasticWebAppCluster.java new file mode 100644 index 0000000..fc2f76b --- /dev/null +++ b/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableElasticWebAppCluster.java @@ -0,0 +1,57 @@ +package brooklyn.example.cloudfoundry; + +import java.util.Collection; + +import brooklyn.entity.Effector; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.Description; +import brooklyn.entity.basic.MethodEffector; +import brooklyn.entity.basic.NamedParameter; +import brooklyn.entity.proxying.ImplementedBy; +import brooklyn.entity.trait.Startable; +import brooklyn.entity.webapp.JavaWebAppService; +import brooklyn.event.basic.BasicAttributeSensor; +import brooklyn.event.basic.BasicConfigKey; +import brooklyn.util.flags.SetFromFlag; + +@ImplementedBy(MovableElasticWebAppClusterImpl.class) +public interface MovableElasticWebAppCluster extends Entity, Startable, MovableEntityTrait { + + // this advertises that this config key is easily available on this entity, + // either by passing (war: "classpath://...") in the constructor or by setConfig(ROOT_WAR). + // as a config variable, it will be inherited by children, so the children web app entities will pick it up. + @SetFromFlag("war") + public static final BasicConfigKey ROOT_WAR = JavaWebAppService.ROOT_WAR; + + @SetFromFlag("ttl") + public static final BasicConfigKey TIME_TO_LIVE_SECONDS = new BasicConfigKey( + Long.class, "movable.time.to.live", "Time to keep demoted cluster alive (should exceed GeoDNS TTL; default 0)", 0L); + + public static final BasicAttributeSensor PRIMARY_SVC_ENTITY_ID = new BasicAttributeSensor( + String.class, "movable.primary.id", "Entity ID of primary web-app service"); + + public static final BasicAttributeSensor> SECONDARY_SVC_ENTITY_IDS = new BasicAttributeSensor( + Collection.class, "movable.secondary.ids", "Entity IDs of secondary web-app services"); + + public static final Effector CREATE_SECONDARY_IN_LOCATION = new MethodEffector(MovableElasticWebAppCluster.class, "createSecondaryInLocation"); + public static final Effector PROMOTE_SECONDARY = new MethodEffector(MovableElasticWebAppCluster.class, "promoteSecondary"); + public static final Effector DESTROY_SECONDARY = new MethodEffector(MovableElasticWebAppCluster.class, "destroySecondary"); + + /** creates a new secondary instance, in the given location, returning the ID of the secondary created and started */ + @Description("create a new secondary instance in the given location") + public String createSecondaryInLocation( + @NamedParameter("location") @Description("the location where to start the secondary") String l); + + /** promotes the indicated secondary, + * returning the ID of the former-primary which has been demoted */ + @Description("promote the indicated secondary to primary (demoting the existing primary)") + public String promoteSecondary( + @NamedParameter("idOfSecondaryToPromote") @Description("ID of secondary entity to promote") + String idOfSecondaryToPromote); + + /** destroys the indicated secondary */ + @Description("destroy the indicated secondary") + public void destroySecondary( + @NamedParameter("idOfSecondaryToDestroy") @Description("ID of secondary entity to destroy") + String idOfSecondaryToDestroy); +} diff --git a/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableElasticWebAppCluster.groovy b/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableElasticWebAppClusterImpl.groovy similarity index 52% rename from portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableElasticWebAppCluster.groovy rename to portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableElasticWebAppClusterImpl.groovy index 1be85d9..77508a8 100644 --- a/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableElasticWebAppCluster.groovy +++ b/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableElasticWebAppClusterImpl.groovy @@ -1,49 +1,33 @@ package brooklyn.example.cloudfoundry -import groovy.lang.MetaClass -import groovy.transform.InheritConstructors - -import java.util.Collection - import org.slf4j.Logger import org.slf4j.LoggerFactory -import brooklyn.enricher.basic.SensorPropagatingEnricher; import brooklyn.entity.Effector import brooklyn.entity.Entity import brooklyn.entity.basic.AbstractEntity -import brooklyn.entity.basic.Description import brooklyn.entity.basic.Entities import brooklyn.entity.basic.EntityLocal -import brooklyn.entity.basic.MethodEffector -import brooklyn.entity.basic.NamedParameter import brooklyn.entity.trait.Startable import brooklyn.entity.trait.StartableMethods import brooklyn.entity.webapp.ElasticJavaWebAppService -import brooklyn.entity.webapp.JavaWebAppService -import brooklyn.event.basic.BasicAttributeSensor -import brooklyn.event.basic.BasicConfigKey import brooklyn.location.Location import brooklyn.location.basic.LocationRegistry -import brooklyn.util.flags.SetFromFlag +import brooklyn.util.task.Tasks import com.google.common.collect.Iterables -@InheritConstructors -class MovableElasticWebAppCluster extends AbstractEntity implements Startable, MovableEntityTrait { +public class MovableElasticWebAppClusterImpl extends AbstractEntity implements MovableElasticWebAppCluster { - public static final Logger log = LoggerFactory.getLogger(MovableElasticWebAppCluster.class); + public static final Logger log = LoggerFactory.getLogger(MovableElasticWebAppClusterImpl.class); - // this advertises that this config key is easily available on this entity, - // either by passing (war: "classpath://...") in the constructor or by setConfig(ROOT_WAR). - // as a config variable, it will be inherited by children, so the children web app entities will pick it up. - @SetFromFlag("war") - public static final BasicConfigKey ROOT_WAR = JavaWebAppService.ROOT_WAR; + public MovableElasticWebAppClusterImpl() { + } - public static final BasicAttributeSensor PRIMARY_SVC_ENTITY_ID = - [ String, "movable.primary.id", "Entity ID of primary web-app service" ]; - public static final BasicAttributeSensor> SECONDARY_SVC_ENTITY_IDS = - [ Collection, "movable.secondary.ids", "Entity IDs of secondary web-app services" ]; + @Deprecated // use EntityManager.createEntity() or ApplicationBuilder.createChild() + public MovableElasticWebAppClusterImpl(Map flags, Entity parent) { + super(flags, parent); + } @Override public void start(Collection locations) { @@ -61,9 +45,11 @@ class MovableElasticWebAppCluster extends AbstractEntity implements Startable, M public EntityLocal createClusterIn(Location location) { //TODO the policy // app.web.cluster.addPolicy(app.policy) - return new ElasticJavaWebAppService.Factory(). - newFactoryForLocation(location). - newEntity([:], this); + EntityLocal result = new ElasticJavaWebAppService.Factory() + .newFactoryForLocation(location) + .newEntity([:], this); + Entities.manage(result); + return result; } @Override @@ -82,28 +68,17 @@ class MovableElasticWebAppCluster extends AbstractEntity implements Startable, M * then destroying the old-primary-now-secondary (X) */ - public static final Effector CREATE_SECONDARY_IN_LOCATION = new MethodEffector(this.&createSecondaryInLocation); - public static final Effector PROMOTE_SECONDARY = new MethodEffector(this.&promoteSecondary); - public static final Effector DESTROY_SECONDARY = new MethodEffector(this.&destroySecondary); - - /** creates a new secondary instance, in the given location, returning the ID of the secondary created and started */ - @Description("create a new secondary instance in the given location") - public String createSecondaryInLocation( - @NamedParameter("location") @Description("the location where to start the secondary") - String l) { + @Override + public String createSecondaryInLocation(String l) { Location location = new LocationRegistry().resolve(l); Entity svc = createClusterIn(location); - Entities.start(managementContext, svc, [location]); + Entities.start(svc, [location]); setAttribute(SECONDARY_SVC_ENTITY_IDS, (getAttribute(SECONDARY_SVC_ENTITY_IDS) ?: []) + svc.id); return svc.id; } - /** promotes the indicated secondary, - * returning the ID of the former-primary which has been demoted */ - @Description("promote the indicated secondary to primary (demoting the existing primary)") - public String promoteSecondary( - @NamedParameter("idOfSecondaryToPromote") @Description("ID of secondary entity to promote") - String idOfSecondaryToPromote) { + @Override + public String promoteSecondary(String idOfSecondaryToPromote) { Collection currentSecondaryIds = getAttribute(SECONDARY_SVC_ENTITY_IDS) if (!currentSecondaryIds.contains(idOfSecondaryToPromote)) throw new IllegalStateException("Cannot promote unknown secondary $idOfSecondaryToPromote "+ @@ -118,11 +93,8 @@ class MovableElasticWebAppCluster extends AbstractEntity implements Startable, M return primaryId; } - /** destroys the indicated secondary */ - @Description("destroy the indicated secondary") - public void destroySecondary( - @NamedParameter("idOfSecondaryToDestroy") @Description("ID of secondary entity to destroy") - String idOfSecondaryToDestroy) { + @Override + public void destroySecondary(String idOfSecondaryToDestroy) { Collection currentSecondaryIds = getAttribute(SECONDARY_SVC_ENTITY_IDS) if (!currentSecondaryIds.contains(idOfSecondaryToDestroy)) throw new IllegalStateException("Cannot promote unknown secondary $idOfSecondaryToDestroy "+ @@ -131,14 +103,17 @@ class MovableElasticWebAppCluster extends AbstractEntity implements Startable, M currentSecondaryIds.remove(idOfSecondaryToDestroy); setAttribute(SECONDARY_SVC_ENTITY_IDS, currentSecondaryIds); - Entity secondary = getManagementContext().getEntityManager().getEntity(idOfSecondaryToDestroy); - Entities.destroy(managementContext, secondary); + Entity secondary = getEntityManager().getEntity(idOfSecondaryToDestroy); + Entities.destroy(secondary); } @Override public String move(String location) { String newPrimary = createSecondaryInLocation(location); String oldPrimary = promoteSecondary(newPrimary); + long ttl = getConfig(TIME_TO_LIVE_SECONDS); + if (ttl>0) + Tasks.withBlockingDetails("waiting for TTL to destroy old primary") { Thread.sleep(ttl*1000); } destroySecondary(oldPrimary); return newPrimary; } diff --git a/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableEntityTrait.groovy b/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableEntityTrait.groovy deleted file mode 100644 index 7434583..0000000 --- a/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableEntityTrait.groovy +++ /dev/null @@ -1,21 +0,0 @@ -package brooklyn.example.cloudfoundry; - -import brooklyn.entity.Effector -import brooklyn.entity.basic.Description -import brooklyn.entity.basic.MethodEffector -import brooklyn.entity.basic.NamedParameter - -public interface MovableEntityTrait { - - Effector MOVE = new MethodEffector(MovableEntityTrait.&move); - - /** Effectively move the entity to the new location. - * A new entity may be created (and the old destroyed) to effect this. - * @param location the new location where the entity should running - * @return the entity ID of the primary entity (after the move) in the specified location */ - @Description("Effectively move the entity to the new location.") - public String move( - @NamedParameter("location") @Description("The new location where the entity should be running") - String location); - -} diff --git a/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableEntityTraitAlt.java b/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableEntityTrait.java similarity index 80% rename from portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableEntityTraitAlt.java rename to portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableEntityTrait.java index 1013810..5f5afe2 100644 --- a/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableEntityTraitAlt.java +++ b/portable-cloudfoundry/src/main/java/brooklyn/example/cloudfoundry/MovableEntityTrait.java @@ -5,13 +5,9 @@ import brooklyn.entity.basic.MethodEffector; import brooklyn.entity.basic.NamedParameter; -/** - * This defines an identical ``move`` effector as MovableEntityTrait, - * but shows examples of a pure-java syntax. - */ -public interface MovableEntityTraitAlt { +public interface MovableEntityTrait { - Effector MOVE = new MethodEffector(MovableEntityTraitAlt.class, "move"); + Effector MOVE = new MethodEffector(MovableEntityTrait.class, "move"); /** Effectively move the entity to the new location. * A new entity may be created (and the old destroyed) to effect this. diff --git a/simple-messaging-pubsub/pom.xml b/simple-messaging-pubsub/pom.xml index fc0ed51..c8c3643 100644 --- a/simple-messaging-pubsub/pom.xml +++ b/simple-messaging-pubsub/pom.xml @@ -7,7 +7,7 @@ io.brooklyn.example brooklyn-examples-parent - 0.5.0-SNAPSHOT + 0.5.0-M2 ../pom.xml diff --git a/simple-messaging-pubsub/src/main/java/brooklyn/demo/StandaloneBrokerExample.groovy b/simple-messaging-pubsub/src/main/java/brooklyn/demo/StandaloneBrokerExample.groovy deleted file mode 100644 index 329825f..0000000 --- a/simple-messaging-pubsub/src/main/java/brooklyn/demo/StandaloneBrokerExample.groovy +++ /dev/null @@ -1,27 +0,0 @@ -package brooklyn.demo - -import java.util.List - -import brooklyn.entity.basic.AbstractApplication -import brooklyn.entity.messaging.amqp.AmqpServer -import brooklyn.entity.messaging.qpid.QpidBroker -import brooklyn.location.Location - -/** Qpid Broker Application */ -public class StandaloneBrokerExample extends AbstractApplication { - - public static final String CUSTOM_CONFIG_PATH = "classpath://custom-config.xml" - public static final String PASSWD_PATH = "classpath://passwd" - public static final String QPID_BDBSTORE_JAR_PATH = "classpath://qpid-bdbstore-0.14.jar" - public static final String BDBSTORE_JAR_PATH = "classpath://je-5.0.34.jar" - - // Configure the Qpid broker entity - QpidBroker broker = new QpidBroker(this, - amqpPort:5672, - amqpVersion:AmqpServer.AMQP_0_10, - runtimeFiles:[ (QpidBroker.CONFIG_XML):CUSTOM_CONFIG_PATH, - (QpidBroker.PASSWD):PASSWD_PATH, - ("lib/opt/qpid-bdbstore-0.14.jar"):QPID_BDBSTORE_JAR_PATH, - ("lib/opt/je-5.0.34.jar"):BDBSTORE_JAR_PATH ], - queue:"testQueue") -} diff --git a/simple-messaging-pubsub/src/main/java/brooklyn/demo/StandaloneBrokerExample.java b/simple-messaging-pubsub/src/main/java/brooklyn/demo/StandaloneBrokerExample.java new file mode 100644 index 0000000..602a26c --- /dev/null +++ b/simple-messaging-pubsub/src/main/java/brooklyn/demo/StandaloneBrokerExample.java @@ -0,0 +1,63 @@ +package brooklyn.demo; + +import java.util.List; + +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.messaging.amqp.AmqpServer; +import brooklyn.entity.messaging.qpid.QpidBroker; +import brooklyn.entity.proxying.BasicEntitySpec; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.launcher.BrooklynServerDetails; +import brooklyn.location.Location; +import brooklyn.util.CommandLineUtil; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; + +/** Qpid Broker Application */ +public class StandaloneBrokerExample extends ApplicationBuilder { + + public static final String CUSTOM_CONFIG_PATH = "classpath://custom-config.xml"; + public static final String PASSWD_PATH = "classpath://passwd"; + public static final String QPID_BDBSTORE_JAR_PATH = "classpath://qpid-bdbstore-0.14.jar"; + public static final String BDBSTORE_JAR_PATH = "classpath://je-5.0.34.jar"; + + public static final String DEFAULT_LOCATION = "localhost"; + + protected void doBuild() { + // Configure the Qpid broker entity + QpidBroker broker = createChild(BasicEntitySpec.newInstance(QpidBroker.class) + .configure("amqpPort", 5672) + .configure("amqpVersion", AmqpServer.AMQP_0_10) + .configure("runtimeFiles", ImmutableMap.builder() + .put(QpidBroker.CONFIG_XML, CUSTOM_CONFIG_PATH) + .put(QpidBroker.PASSWD, PASSWD_PATH) + .put("lib/opt/qpid-bdbstore-0.14.jar", QPID_BDBSTORE_JAR_PATH) + .put("lib/opt/je-5.0.34.jar", BDBSTORE_JAR_PATH) + .build()) + .configure("queue", "testQueue")); + } + + public static void main(String[] argv) { + List args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", DEFAULT_LOCATION); + + BrooklynServerDetails server = BrooklynLauncher.newLauncher() + .webconsolePort(port) + .launch(); + + Location loc = server.getManagementContext().getLocationRegistry().resolve(location); + + StartableApplication app = new StandaloneBrokerExample() + .appDisplayName("Qpid app") + .manage(server.getManagementContext()); + + app.start(ImmutableList.of(loc)); + + Entities.dumpInfo(app); + } +} diff --git a/simple-open-loop-policy/README.txt b/simple-open-loop-policy/README.txt new file mode 100644 index 0000000..65fb390 --- /dev/null +++ b/simple-open-loop-policy/README.txt @@ -0,0 +1,55 @@ +Instructions for Running Examples +================================= + +The commands below assume that the `brooklyn` script is on your $PATH, this project has been built, +and you are in this directory. Adjust to taste for other configurations. + + export BROOKLYN_CLASSPATH=$(pwd)/target/classes + + # Three-tier: auto-scaling app-server cluster fronted by nginx, MySql backend wired up, on localhost + brooklyn launch --app brooklyn.demo.WebClusterDatabaseOpenLoopExample --location localhost + +The above requires passwordless `ssh localhost` and requires `gcc` to build `nginx`. +You could instead target your favourite cloud, where this has been tried and tested: + + +Redistributable embedded example: + + # To build a redistributable tar.gz with a start.sh script + # which invokes the `main` method in the example class to start + # (the redistributable will be at: target/brooklyn-*-bin.tar.gz ) + mvn clean assembly:assembly + +For more information, please visit: + + http://brooklyncentral.github.com/use/examples/webcluster/ + + +Developer Notes +=============== + +This example sends an SMS message when the cluster has reached its max size (and where the auto-scaler policy +would continue to increase the size if it were not capped). The message is sent using clickatell.com, +using http://smsj.sourceforge.net. + +Because smsj is not available on maven central, a custom local maven repo has been built (and checked in to git): + + wget http://sourceforge.net/projects/smsj/files/smsj/smsj-snapshot-20051126/smsj-20051126.jar/download + + mvn org.apache.maven.plugins:maven-install-plugin:2.3.1:install-file \ + -Dfile=smsj-20051126.jar \ + -DgroupId=io.brooklyn \ + -DartifactId=org.marre.smsj \ + -Dversion=1.0.0-20051126 \ + -Dpackaging=jar \ + -DlocalRepositoryPath=localrepo + +And in the pom, this local repo is referenced: + + + + brooklyn-examples-localrepo + file://${basedir}/localrepo + + + diff --git a/simple-open-loop-policy/localrepo/io/brooklyn/org.marre.smsj/1.0.0-20051126/org.marre.smsj-1.0.0-20051126.jar b/simple-open-loop-policy/localrepo/io/brooklyn/org.marre.smsj/1.0.0-20051126/org.marre.smsj-1.0.0-20051126.jar new file mode 100644 index 0000000000000000000000000000000000000000..a076a473490334e224e31d5301e1ad6c10767cdd GIT binary patch literal 134604 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≈}lb}&77oTkEcuYR7{QSDE7w){5R6y|x$OEbI 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 literal 0 HcmV?d00001 diff --git a/simple-open-loop-policy/localrepo/io/brooklyn/org.marre.smsj/1.0.0-20051126/org.marre.smsj-1.0.0-20051126.pom b/simple-open-loop-policy/localrepo/io/brooklyn/org.marre.smsj/1.0.0-20051126/org.marre.smsj-1.0.0-20051126.pom new file mode 100644 index 0000000..7361303 --- /dev/null +++ b/simple-open-loop-policy/localrepo/io/brooklyn/org.marre.smsj/1.0.0-20051126/org.marre.smsj-1.0.0-20051126.pom @@ -0,0 +1,9 @@ + + + 4.0.0 + io.brooklyn + org.marre.smsj + 1.0.0-20051126 + POM was created from install:install-file + diff --git a/simple-open-loop-policy/localrepo/io/brooklyn/org.marre.smsj/maven-metadata-local.xml b/simple-open-loop-policy/localrepo/io/brooklyn/org.marre.smsj/maven-metadata-local.xml new file mode 100644 index 0000000..4611327 --- /dev/null +++ b/simple-open-loop-policy/localrepo/io/brooklyn/org.marre.smsj/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + io.brooklyn + org.marre.smsj + + 1.0.0-20051126 + + 1.0.0-20051126 + + 20130206095028 + + diff --git a/simple-open-loop-policy/pom.xml b/simple-open-loop-policy/pom.xml new file mode 100644 index 0000000..9f93ccd --- /dev/null +++ b/simple-open-loop-policy/pom.xml @@ -0,0 +1,133 @@ + + 4.0.0 + jar + brooklyn-example-simple-open-loop-policy + Brooklyn Simple Web Cluster Example + + + + io.brooklyn.example + brooklyn-examples-parent + 0.5.0-M2 + ../pom.xml + + + + + brooklyn-examples-localrepo + file://${basedir}/localrepo + + + + + + io.brooklyn + org.marre.smsj + 1.0.0-20051126 + + + io.brooklyn + brooklyn-all + ${project.version} + + + io.brooklyn + brooklyn-logback-xml + ${project.version} + true + + + + io.brooklyn + brooklyn-test-support + ${project.version} + test + + + + + + + maven-dependency-plugin + + + + copy + process-classes + + copy + + + + + + ${project.groupId} + brooklyn-example-hello-world-webapp + ${project.version} + war + true + target/classes + hello-world-webapp.war + + + ${project.groupId} + brooklyn-example-hello-world-sql-webapp + ${project.version} + war + true + target/classes + hello-world-sql-webapp.war + + + + + + + + + maven-clean-plugin + + + + ${project.basedir} + + ${project.artifactId}/ + brooklyn*.log + brooklyn*.log.* + stacktrace.log + + + + + + + + + + + + + diff --git a/simple-open-loop-policy/resources/jmeter-test-plan.jmx b/simple-open-loop-policy/resources/jmeter-test-plan.jmx new file mode 100644 index 0000000..5487b8f --- /dev/null +++ b/simple-open-loop-policy/resources/jmeter-test-plan.jmx @@ -0,0 +1,125 @@ + + + + + + false + false + + + + + + + + continue + + false + -1 + + 8 + 1 + 1326116677000 + 1326116677000 + false + + + + + + 20 + + + + + + + localhost + 8000 + + + + + + GET + true + false + true + false + false + + + + + + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + + + + + + + + diff --git a/simple-open-loop-policy/src/main/java/brooklyn/demo/Sms.java b/simple-open-loop-policy/src/main/java/brooklyn/demo/Sms.java new file mode 100644 index 0000000..abaa506 --- /dev/null +++ b/simple-open-loop-policy/src/main/java/brooklyn/demo/Sms.java @@ -0,0 +1,45 @@ +package brooklyn.demo; + +import java.io.IOException; + +import org.marre.SmsSender; +import org.marre.sms.SmsException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Sms { + + private static final Logger LOG = LoggerFactory.getLogger(Sms.class); + + private final String username; + private final String password; + private final String apiid; + private final String sender; + + public Sms(String username, String password, String apiid) { + this.username = username; + this.password = password; + this.apiid = apiid; + this.sender = null; + } + + /** + * + * @param receiver International number to reciever without leading "+" + * @param msg The message that you want to send + * @throws IOException + * @throws SmsException + */ + public void sendSms(String receiver, String msg) throws SmsException, IOException { + // Send SMS with clickatell + SmsSender smsSender = SmsSender.getClickatellSender(username, password, apiid); + + smsSender.connect(); + try { + String msgid = smsSender.sendTextSms(msg, receiver, sender); + LOG.debug("Sent SMS via {}@clickatell to {}, msg {}; id {}", new Object[] {username, receiver, msg, msgid}); + } finally { + smsSender.disconnect(); + } + } +} diff --git a/simple-open-loop-policy/src/main/java/brooklyn/demo/WebClusterDatabaseOpenLoopExample.java b/simple-open-loop-policy/src/main/java/brooklyn/demo/WebClusterDatabaseOpenLoopExample.java new file mode 100644 index 0000000..3ac75e4 --- /dev/null +++ b/simple-open-loop-policy/src/main/java/brooklyn/demo/WebClusterDatabaseOpenLoopExample.java @@ -0,0 +1,141 @@ +package brooklyn.demo; + +import static brooklyn.entity.java.JavaEntityMethods.javaSysProp; +import static brooklyn.event.basic.DependentConfiguration.attributeWhenReady; +import static brooklyn.event.basic.DependentConfiguration.formatString; + +import java.io.IOException; +import java.util.List; + +import org.marre.sms.SmsException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.enricher.basic.SensorPropagatingEnricher; +import brooklyn.enricher.basic.SensorTransformingEnricher; +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.database.mysql.MySqlNode; +import brooklyn.entity.proxying.BasicEntitySpec; +import brooklyn.entity.webapp.ControlledDynamicWebAppCluster; +import brooklyn.entity.webapp.DynamicWebAppCluster; +import brooklyn.entity.webapp.JavaWebAppService; +import brooklyn.entity.webapp.WebAppService; +import brooklyn.entity.webapp.WebAppServiceConstants; +import brooklyn.event.SensorEvent; +import brooklyn.event.SensorEventListener; +import brooklyn.event.basic.BasicAttributeSensor; +import brooklyn.event.basic.BasicNotificationSensor; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.launcher.BrooklynServerDetails; +import brooklyn.location.Location; +import brooklyn.location.basic.PortRanges; +import brooklyn.policy.autoscaling.AutoScalerPolicy; +import brooklyn.policy.autoscaling.MaxPoolSizeReachedEvent; +import brooklyn.util.CommandLineUtil; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +/** + * Launches a 3-tier app with nginx, clustered jboss, and mysql. + **/ +public class WebClusterDatabaseOpenLoopExample extends ApplicationBuilder { + + public static final Logger LOG = LoggerFactory.getLogger(WebClusterDatabaseOpenLoopExample.class); + + public static final String WAR_PATH = "classpath://hello-world-sql-webapp.war"; + + public static final String DB_SETUP_SQL_URL = "classpath://visitors-creation-script.sql"; + + public static final String DB_TABLE = "visitors"; + public static final String DB_USERNAME = "brooklyn"; + public static final String DB_PASSWORD = "br00k11n"; + + // TODO Replace these with your details, for a clickatell.com account that has credit to send SMS messages + public static final String CLICKATELL_USERNAME = "brooklyndemo.uk"; + public static final String CLICKATELL_PASSWORD = "NAYLLHRLZEBYYA"; + public static final String CLICKATELL_APIID = "3411519"; + public static final String SMS_RECEIVER = "441234567890"; + + public static final BasicAttributeSensor APPSERVERS_COUNT = new BasicAttributeSensor(Integer.class, + "appservers.count", "Number of app servers deployed"); + + public static final BasicNotificationSensor MAX_SIZE_REACHED = new BasicNotificationSensor( + MaxPoolSizeReachedEvent.class, "cluster.maxPoolSizeReached", ""); + + protected void doBuild() { + MySqlNode mysql = createChild(BasicEntitySpec.newInstance(MySqlNode.class) + .configure("creationScriptUrl", DB_SETUP_SQL_URL)); + + ControlledDynamicWebAppCluster web = createChild(BasicEntitySpec.newInstance(ControlledDynamicWebAppCluster.class) + .configure(WebAppService.HTTP_PORT, PortRanges.fromString("8080+")) + .configure(JavaWebAppService.ROOT_WAR, WAR_PATH) + .configure(javaSysProp("brooklyn.example.db.url"), + formatString("jdbc:%s%s?user=%s\\&password=%s", + attributeWhenReady(mysql, MySqlNode.MYSQL_URL), + DB_TABLE, DB_USERNAME, DB_PASSWORD))); + + // Subscribe not notifications about the auto-scaler policy reaching its max cluster size + getManagementContext().getSubscriptionManager().subscribe( + web.getCluster(), + MAX_SIZE_REACHED, + new SensorEventListener() { + @Override public void onEvent(SensorEvent event) { + onMaxPoolSizeReached(event.getValue()); + } + }); + + // simple scaling policy + web.getCluster().addPolicy(AutoScalerPolicy.builder(). + metric(DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW_PER_NODE). + metricRange(10, 100). + sizeRange(1, 5). + maxSizeReachedSensor(MAX_SIZE_REACHED). +// maxReachedNotificationDelay(1000). + build()); + + // expose some KPI's + getApp().addEnricher(SensorPropagatingEnricher.newInstanceListeningTo(web, + WebAppServiceConstants.ROOT_URL, + DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW)); + getApp().addEnricher(new SensorTransformingEnricher(web, + DynamicWebAppCluster.GROUP_SIZE, APPSERVERS_COUNT, Functions.identity())); + } + + private void onMaxPoolSizeReached(MaxPoolSizeReachedEvent event) { + String msg = "Max-pool-size-reached: "+event; + System.out.println("*** "+msg); + + try { + new Sms(CLICKATELL_USERNAME, CLICKATELL_PASSWORD, CLICKATELL_APIID).sendSms(SMS_RECEIVER, msg); + } catch (SmsException e) { + LOG.warn("Error sending SMS message: "+e.getMessage(), e); + } catch (IOException e) { + LOG.warn("Error sending SMS message", e); + } + } + + public static void main(String[] argv) { + List args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", "localhost"); + + BrooklynServerDetails server = BrooklynLauncher.newLauncher() + .webconsolePort(port) + .launch(); + + Location loc = server.getManagementContext().getLocationRegistry().resolve(location); + + StartableApplication app = new WebClusterDatabaseOpenLoopExample() + .appDisplayName("Brooklyn WebApp Cluster with Database example") + .manage(server.getManagementContext()); + + app.start(ImmutableList.of(loc)); + + Entities.dumpInfo(app); + } +} diff --git a/simple-open-loop-policy/src/main/resources/visitors-creation-script.sql b/simple-open-loop-policy/src/main/resources/visitors-creation-script.sql new file mode 100644 index 0000000..f3bdd4a --- /dev/null +++ b/simple-open-loop-policy/src/main/resources/visitors-creation-script.sql @@ -0,0 +1,17 @@ +create database visitors; +use visitors; +create user 'brooklyn' identified by 'br00k11n'; +grant usage on *.* to 'brooklyn'@'%' identified by 'br00k11n'; +# ''@localhost is sometimes set up, overriding brooklyn@'%', so do a second explicit grant +grant usage on *.* to 'brooklyn'@'localhost' identified by 'br00k11n'; +grant all privileges on visitors.* to 'brooklyn'@'%'; +flush privileges; + +CREATE TABLE MESSAGES ( + id INT NOT NULL AUTO_INCREMENT, + NAME VARCHAR(30) NOT NULL, + MESSAGE VARCHAR(400) NOT NULL, + PRIMARY KEY (ID) + ); + +INSERT INTO MESSAGES values (default, 'Isaac Asimov', 'I grew up in Brooklyn' ); diff --git a/simple-open-loop-policy/src/test/java/brooklyn/demo/SmsTest.java b/simple-open-loop-policy/src/test/java/brooklyn/demo/SmsTest.java new file mode 100644 index 0000000..486fbcc --- /dev/null +++ b/simple-open-loop-policy/src/test/java/brooklyn/demo/SmsTest.java @@ -0,0 +1,32 @@ +package brooklyn.demo; + +import org.marre.sms.SmsException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +public class SmsTest { + + private static final Logger LOG = LoggerFactory.getLogger(SmsTest.class); + + @Test(groups="Integration") + public void testSendSms() throws Exception { + // http://api.clickatell.com/http/sendmsg?user=brooklyndemo.uk&password=PASSWORD&api_id=3411519&to=447709428472&text=Message + // Note this account has *no* credit card details; sending a message will result in a standard message from Clickatell.com, + // rather than receiving the actual message body. + // + // TODO It's started failing with "Error 301, No Credit Left" + // How to commit something that passes but without exposing credit card details?! + Sms sender = new Sms("brooklyndemo.uk", "NAYLLHRLZEBYYA", "3411519"); + try { + sender.sendSms("447709428472", "test sms sent in brooklyn"); + } catch (SmsException e) { + if (e.toString().contains("Error 301, No Credit Left")) { + // fair enough; let's not fail the test for this reason + LOG.warn("Cannot test SMS because no credit left on account: "+e.toString()); + } else { + throw e; + } + } + } +} diff --git a/simple-web-cluster/README.txt b/simple-web-cluster/README.txt index 19c13bc..0b238a4 100644 --- a/simple-web-cluster/README.txt +++ b/simple-web-cluster/README.txt @@ -27,8 +27,8 @@ Other examples: # A simple app: just load-balancer and appservers brooklyn launch --app brooklyn.demo.WebClusterExample --location localhost - # Pure Java three-tier example - brooklyn launch --app brooklyn.demo.WebClusterDatabaseExampleAltJava --location localhost + # Three-tier example + brooklyn launch --app brooklyn.demo.WebClusterDatabaseExample --location localhost Redistributable embedded example: diff --git a/simple-web-cluster/pom.xml b/simple-web-cluster/pom.xml index 9e4452f..253a9bd 100644 --- a/simple-web-cluster/pom.xml +++ b/simple-web-cluster/pom.xml @@ -9,7 +9,7 @@ io.brooklyn.example brooklyn-examples-parent - 0.5.0-SNAPSHOT + 0.5.0-M2 ../pom.xml diff --git a/simple-web-cluster/src/main/java/brooklyn/demo/SingleWebServerExample.java b/simple-web-cluster/src/main/java/brooklyn/demo/SingleWebServerExample.java new file mode 100644 index 0000000..baf55fb --- /dev/null +++ b/simple-web-cluster/src/main/java/brooklyn/demo/SingleWebServerExample.java @@ -0,0 +1,64 @@ +package brooklyn.demo; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.proxying.BasicEntitySpec; +import brooklyn.entity.webapp.jboss.JBoss7Server; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.launcher.BrooklynServerDetails; +import brooklyn.location.Location; +import brooklyn.util.CommandLineUtil; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +/** This example starts one web app on 8080, waits for a keypress, then stops it. */ +public class SingleWebServerExample extends ApplicationBuilder { + + public static final Logger LOG = LoggerFactory.getLogger(SingleWebServerExample.class); + + public static final String DEFAULT_LOCATION = "localhost"; + + private static final String WAR_PATH = "classpath://hello-world-webapp.war"; + + @Override + protected void doBuild() { + createChild(BasicEntitySpec.newInstance(JBoss7Server.class) + .configure("war", WAR_PATH) + .configure("httpPort", 8080)); + } + + // Shows how to use ApplicationBuilder without sub-classing, but for CLI usage one should sub-class + public static void main(String[] argv) throws Exception { + List args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", DEFAULT_LOCATION); + + BrooklynServerDetails server = BrooklynLauncher.newLauncher() + .webconsolePort(port) + .launch(); + + // TODO Want to parse, to handle multiple locations + Location loc = server.getManagementContext().getLocationRegistry().resolve(location); + + StartableApplication app = ApplicationBuilder.builder() + .child(BasicEntitySpec.newInstance(JBoss7Server.class) + .configure("war", WAR_PATH) + .configure("httpPort", 8080)) + .manage(); + JBoss7Server web = (JBoss7Server) Iterables.getOnlyElement(app.getChildren()); + + LOG.info("created, now starting..."); + app.start(ImmutableList.of(loc)); + + Entities.dumpInfo(app); + LOG.info("started, visit "+web.getAttribute(JBoss7Server.ROOT_URL)); + } +} diff --git a/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.java b/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.java new file mode 100644 index 0000000..e40c731 --- /dev/null +++ b/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.java @@ -0,0 +1,109 @@ +package brooklyn.demo; + +import static brooklyn.entity.java.JavaEntityMethods.javaSysProp; +import static brooklyn.event.basic.DependentConfiguration.attributeWhenReady; +import static brooklyn.event.basic.DependentConfiguration.formatString; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.enricher.basic.SensorPropagatingEnricher; +import brooklyn.enricher.basic.SensorTransformingEnricher; +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.database.mysql.MySqlNode; +import brooklyn.entity.proxying.BasicEntitySpec; +import brooklyn.entity.webapp.ControlledDynamicWebAppCluster; +import brooklyn.entity.webapp.DynamicWebAppCluster; +import brooklyn.entity.webapp.JavaWebAppService; +import brooklyn.entity.webapp.WebAppService; +import brooklyn.entity.webapp.WebAppServiceConstants; +import brooklyn.event.basic.BasicAttributeSensor; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.launcher.BrooklynServerDetails; +import brooklyn.location.Location; +import brooklyn.location.basic.PortRanges; +import brooklyn.policy.autoscaling.AutoScalerPolicy; +import brooklyn.util.CommandLineUtil; + +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +/** + * Launches a 3-tier app with nginx, clustered jboss, and mysql. + **/ +public class WebClusterDatabaseExample extends ApplicationBuilder { + + public static final Logger LOG = LoggerFactory.getLogger(WebClusterDatabaseExample.class); + + public static final String WAR_PATH = "classpath://hello-world-sql-webapp.war"; + + public static final String DB_SETUP_SQL_URL = "classpath://visitors-creation-script.sql"; + + public static final String DB_TABLE = "visitors"; + public static final String DB_USERNAME = "brooklyn"; + public static final String DB_PASSWORD = "br00k11n"; + + public static final BasicAttributeSensor APPSERVERS_COUNT = new BasicAttributeSensor(Integer.class, + "appservers.count", "Number of app servers deployed"); + + protected void doBuild() { + MySqlNode mysql = createChild(BasicEntitySpec.newInstance(MySqlNode.class) + .configure("creationScriptUrl", DB_SETUP_SQL_URL)); + + ControlledDynamicWebAppCluster web = createChild(BasicEntitySpec.newInstance(ControlledDynamicWebAppCluster.class) + .configure(WebAppService.HTTP_PORT, PortRanges.fromString("8080+")) + .configure(JavaWebAppService.ROOT_WAR, WAR_PATH) + .configure(javaSysProp("brooklyn.example.db.url"), + formatString("jdbc:%s%s?user=%s\\&password=%s", + attributeWhenReady(mysql, MySqlNode.MYSQL_URL), + DB_TABLE, DB_USERNAME, DB_PASSWORD)) ); + + // simple scaling policy + web.getCluster().addPolicy(AutoScalerPolicy.builder(). + metric(DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW_PER_NODE). + metricRange(10, 100). + sizeRange(1, 5). + build()); + + // expose some KPI's + getApp().addEnricher(SensorPropagatingEnricher.newInstanceListeningTo(web, + WebAppServiceConstants.ROOT_URL, + DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW)); + getApp().addEnricher(new SensorTransformingEnricher(web, + DynamicWebAppCluster.GROUP_SIZE, APPSERVERS_COUNT, Functions.identity())); + } + + public static void main(String[] argv) { + // TODO want to crate a simpler way to do the boilerplate psv main; suggestion here: +// BrooklynApplicationCli cli = BrooklynApplicationCli.newInstance(). +// application(new WebClusterDatabaseExample() +// .appDisplayName("Brooklyn WebApp Cluster with Database example")). +// defaultLocations("localhost"). +// start(); +// +// Entities.dumpInfo(cli.getApplication()); + + List args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", "localhost"); + + BrooklynServerDetails server = BrooklynLauncher.newLauncher() + .webconsolePort(port) + .launch(); + + Location loc = server.getManagementContext().getLocationRegistry().resolve(location); + + StartableApplication app = new WebClusterDatabaseExample() + .appDisplayName("Brooklyn WebApp Cluster with Database example") + .manage(server.getManagementContext()); + + app.start(ImmutableList.of(loc)); + + Entities.dumpInfo(app); + } +} diff --git a/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java b/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java new file mode 100644 index 0000000..36bf526 --- /dev/null +++ b/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java @@ -0,0 +1,145 @@ +package brooklyn.demo; + +import static brooklyn.event.basic.DependentConfiguration.attributeWhenReady; +import static brooklyn.event.basic.DependentConfiguration.formatString; + +import java.util.Arrays; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.catalog.CatalogConfig; +import brooklyn.config.ConfigKey; +import brooklyn.enricher.basic.SensorPropagatingEnricher; +import brooklyn.enricher.basic.SensorTransformingEnricher; +import brooklyn.entity.basic.AbstractApplication; +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.database.mysql.MySqlNode; +import brooklyn.entity.group.DynamicCluster; +import brooklyn.entity.java.JavaEntityMethods; +import brooklyn.entity.proxying.BasicEntitySpec; +import brooklyn.entity.webapp.ControlledDynamicWebAppCluster; +import brooklyn.entity.webapp.DynamicWebAppCluster; +import brooklyn.entity.webapp.JavaWebAppService; +import brooklyn.entity.webapp.WebAppService; +import brooklyn.entity.webapp.WebAppServiceConstants; +import brooklyn.event.AttributeSensor; +import brooklyn.event.basic.BasicAttributeSensor; +import brooklyn.event.basic.BasicConfigKey; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.launcher.BrooklynServerDetails; +import brooklyn.location.Location; +import brooklyn.location.basic.PortRanges; +import brooklyn.policy.autoscaling.AutoScalerPolicy; +import brooklyn.util.CommandLineUtil; + +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +/** + * Launches a 3-tier app with nginx, clustered jboss, and mysql. + *

+ * Demonstrates how to define a new Application Entity class (reusable and extensible), + * as opposed to just using the builder as in {@link WebClusterDatabaseExample}. + * With an app, when we define public static sensors and runtime config _on the app class_ + * (not the builder) they can be discovered at runtime. + *

+ * This variant also increases minimum size to 2. + * Note the policy min size must have the same value, + * otherwise it fights with cluster set up trying to reduce the cluster size! + **/ +public class WebClusterDatabaseExampleApp extends AbstractApplication implements StartableApplication { + + public static final Logger LOG = LoggerFactory.getLogger(WebClusterDatabaseExampleApp.class); + + public static final String DEFAULT_LOCATION = "localhost"; + + public static final String DEFAULT_WAR_PATH = "classpath://hello-world-sql-webapp.war"; + + @CatalogConfig(label="WAR (URL)") + public static final ConfigKey WAR_PATH = new BasicConfigKey(String.class, + "app.war", "URL to the application archive which should be deployed", + DEFAULT_WAR_PATH); + + // TODO to expose in catalog we need to let the keystore url be specified (not hard) + // and also confirm that this works for nginx (might be a bit fiddly); + // booleans in the gui are working (With checkbox) +// @CatalogConfig(label="HTTPS") + public static final ConfigKey USE_HTTPS = new BasicConfigKey(Boolean.class, + "app.https", "Whether the application should use HTTPS only or just HTTP only (default)", false); + + public static final String DEFAULT_DB_SETUP_SQL_URL = "classpath://visitors-creation-script.sql"; + + @CatalogConfig(label="DB Setup SQL (URL)") + public static final ConfigKey DB_SETUP_SQL_URL = new BasicConfigKey(String.class, + "app.db_sql", "URL to the SQL script to set up the database", + DEFAULT_DB_SETUP_SQL_URL); + + public static final String DB_TABLE = "visitors"; + public static final String DB_USERNAME = "brooklyn"; + public static final String DB_PASSWORD = "br00k11n"; + + BasicAttributeSensor APPSERVERS_COUNT = new BasicAttributeSensor(Integer.class, + "appservers.count", "Number of app servers deployed"); + public static final AttributeSensor REQUESTS_PER_SECOND_IN_WINDOW = + WebAppServiceConstants.REQUESTS_PER_SECOND_IN_WINDOW; + public static final AttributeSensor ROOT_URL = WebAppServiceConstants.ROOT_URL; + + @Override + public void postConstruct() { + super.postConstruct(); + + MySqlNode mysql = (MySqlNode) addChild( + getEntityManager().createEntity( + BasicEntitySpec.newInstance(MySqlNode.class) + .configure(MySqlNode.CREATION_SCRIPT_URL, getConfig(DB_SETUP_SQL_URL))) ); + + ControlledDynamicWebAppCluster web = (ControlledDynamicWebAppCluster) addChild( + getEntityManager().createEntity( + BasicEntitySpec.newInstance(ControlledDynamicWebAppCluster.class) + .configure(WebAppService.HTTP_PORT, PortRanges.fromString("8080+")) + .configure(JavaWebAppService.ROOT_WAR, getConfig(WAR_PATH)) + .configure(JavaEntityMethods.javaSysProp("brooklyn.example.db.url"), + formatString("jdbc:%s%s?user=%s\\&password=%s", + attributeWhenReady(mysql, MySqlNode.MYSQL_URL), DB_TABLE, DB_USERNAME, DB_PASSWORD)) + .configure(DynamicCluster.INITIAL_SIZE, 2) + .configure(WebAppService.ENABLED_PROTOCOLS, Arrays.asList(getConfig(USE_HTTPS) ? "https" : "http")) )); + + web.getCluster().addPolicy(AutoScalerPolicy.builder(). + metric(DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW_PER_NODE). + metricRange(10, 100). + sizeRange(2, 5). + build()); + + addEnricher(SensorPropagatingEnricher.newInstanceListeningTo(web, + WebAppServiceConstants.ROOT_URL, + DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW)); + addEnricher(new SensorTransformingEnricher(web, + DynamicWebAppCluster.GROUP_SIZE, APPSERVERS_COUNT, Functions.identity())); + } + + public static void main(String[] argv) { + List args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", DEFAULT_LOCATION); + + BrooklynServerDetails server = BrooklynLauncher.newLauncher() + .webconsolePort(port) + .launch(); + + Location loc = server.getManagementContext().getLocationRegistry().resolve(location); + + StartableApplication app = ApplicationBuilder.builder(WebClusterDatabaseExampleApp.class) + .displayName("Brooklyn WebApp Cluster with Database example") + .manage(server.getManagementContext()); + + app.start(ImmutableList.of(loc)); + + Entities.dumpInfo(app); + } + +} diff --git a/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleGroovy.groovy b/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleGroovy.groovy new file mode 100644 index 0000000..722e1c0 --- /dev/null +++ b/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleGroovy.groovy @@ -0,0 +1,87 @@ +package brooklyn.demo; + +import static brooklyn.entity.java.JavaEntityMethods.javaSysProp; +import static brooklyn.event.basic.DependentConfiguration.attributeWhenReady; +import static brooklyn.event.basic.DependentConfiguration.formatString; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.database.mysql.MySqlNode; +import brooklyn.entity.proxying.BasicEntitySpec; +import brooklyn.entity.webapp.ControlledDynamicWebAppCluster; +import brooklyn.entity.webapp.DynamicWebAppCluster; +import brooklyn.entity.webapp.jboss.JBoss7Server; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.launcher.BrooklynServerDetails; +import brooklyn.location.Location; +import brooklyn.policy.autoscaling.AutoScalerPolicy; +import brooklyn.util.CommandLineUtil; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +/** + * Launches a 3-tier app with nginx, clustered jboss, and mysql. + *

+ * This variant of {@link WebClusterDatabaseExample} demonstrates Groovy language conveniences. + **/ +public class WebClusterDatabaseExampleGroovy extends ApplicationBuilder { + + public static final Logger LOG = LoggerFactory.getLogger(WebClusterDatabaseExampleGroovy.class); + + public static final String DEFAULT_LOCATION = "localhost"; + + public static final String WAR_PATH = "classpath://hello-world-sql-webapp.war"; + + public static final String DB_SETUP_SQL_URL = "classpath://visitors-creation-script.sql"; + + public static final String DB_TABLE = "visitors"; + public static final String DB_USERNAME = "brooklyn"; + public static final String DB_PASSWORD = "br00k11n"; + + protected void doBuild() { + MySqlNode mysql = createChild(MySqlNode, + creationScriptUrl: DB_SETUP_SQL_URL); + + ControlledDynamicWebAppCluster web = createChild(ControlledDynamicWebAppCluster, + war: WAR_PATH, + httpPort: "8080+", + (javaSysProp("brooklyn.example.db.url")): + formatString("jdbc:%s%s?user=%s\\&password=%s", + attributeWhenReady(mysql, MySqlNode.MYSQL_URL), + DB_TABLE, DB_USERNAME, DB_PASSWORD)); + + web.getCluster().addPolicy(AutoScalerPolicy.builder(). + metric(DynamicWebAppCluster.AVERAGE_REQUESTS_PER_SECOND). + sizeRange(1, 5). + metricRange(10, 100). + build()); + } + + public static void main(String[] argv) { + List args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", DEFAULT_LOCATION); + + BrooklynServerDetails server = BrooklynLauncher.newLauncher() + .webconsolePort(port) + .launch(); + + Location loc = server.getManagementContext().getLocationRegistry().resolve(location); + + StartableApplication app = new WebClusterDatabaseExampleGroovy() + .appDisplayName("Brooklyn WebApp Cluster with Database example") + .manage(server.getManagementContext()); + + app.start(ImmutableList.of(loc)); + + Entities.dumpInfo(app); + } + +} diff --git a/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExample.java b/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExample.java new file mode 100644 index 0000000..f0b7b50 --- /dev/null +++ b/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExample.java @@ -0,0 +1,87 @@ +package brooklyn.demo; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.BrooklynProperties; +import brooklyn.entity.basic.ApplicationBuilder; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.proxy.nginx.NginxController; +import brooklyn.entity.proxying.BasicEntitySpec; +import brooklyn.entity.webapp.ControlledDynamicWebAppCluster; +import brooklyn.entity.webapp.DynamicWebAppCluster; +import brooklyn.entity.webapp.jboss.JBoss7Server; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.launcher.BrooklynServerDetails; +import brooklyn.location.Location; +import brooklyn.policy.autoscaling.AutoScalerPolicy; +import brooklyn.util.CommandLineUtil; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +/** + * Launches a clustered and load-balanced set of web servers. + * Demonstrates syntax, so many of the options used here are the defaults. + * (So the class could be much simpler, as in WebClusterExampleAlt.) + *

+ * Requires: + * -Xmx512m -Xms128m -XX:MaxPermSize=256m + * and brooklyn-all jar, and this jar or classes dir, on classpath. + **/ +public class WebClusterExample extends ApplicationBuilder { + public static final Logger LOG = LoggerFactory.getLogger(WebClusterExample.class); + + static BrooklynProperties config = BrooklynProperties.Factory.newDefault(); + + public static final String DEFAULT_LOCATION = "localhost"; + + public static final String WAR_PATH = "classpath://hello-world-webapp.war"; + + private NginxController nginxController; + private ControlledDynamicWebAppCluster web; + + protected void doBuild() { + nginxController = createChild(BasicEntitySpec.newInstance(NginxController.class) + //.configure("domain", "webclusterexample.brooklyn.local") + .configure("port", "8000+")); + + web = createChild(ControlledDynamicWebAppCluster.Spec.newInstance() + .displayName("WebApp cluster") + .controller(nginxController) + .initialSize(1) + .memberSpec(BasicEntitySpec.newInstance(JBoss7Server.class) + .configure("httpPort", "8080+") + .configure("war", WAR_PATH))); + + web.getCluster().addPolicy(AutoScalerPolicy.builder() + .metric(DynamicWebAppCluster.AVERAGE_REQUESTS_PER_SECOND) + .sizeRange(1, 5) + .metricRange(10, 100) + .build()); + } + + public static void main(String[] argv) { + List args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", DEFAULT_LOCATION); + + BrooklynServerDetails server = BrooklynLauncher.newLauncher() + .webconsolePort(port) + .launch(); + + // TODO Want to parse, to handle multiple locations + Location loc = server.getManagementContext().getLocationRegistry().resolve(location); + + StartableApplication app = new WebClusterExample() + .appDisplayName("Brooklyn WebApp Cluster example") + .manage(server.getManagementContext()); + + app.start(ImmutableList.of(loc)); + + Entities.dumpInfo(app); + } +} diff --git a/simple-web-cluster/src/main/java/brooklyn/demo/SingleWebServerExample.groovy b/simple-web-cluster/src/main/java/brooklyn/demo/legacy/SingleWebServerExample.groovy similarity index 87% rename from simple-web-cluster/src/main/java/brooklyn/demo/SingleWebServerExample.groovy rename to simple-web-cluster/src/main/java/brooklyn/demo/legacy/SingleWebServerExample.groovy index 776fa2c..872a72a 100644 --- a/simple-web-cluster/src/main/java/brooklyn/demo/SingleWebServerExample.groovy +++ b/simple-web-cluster/src/main/java/brooklyn/demo/legacy/SingleWebServerExample.groovy @@ -1,12 +1,11 @@ -package brooklyn.demo; - -import java.util.List +package brooklyn.demo.legacy; import org.slf4j.Logger import org.slf4j.LoggerFactory import brooklyn.entity.basic.AbstractApplication import brooklyn.entity.webapp.jboss.JBoss7Server +import brooklyn.entity.webapp.jboss.JBoss7ServerImpl import brooklyn.location.basic.CommandLineLocations import brooklyn.location.basic.LocalhostMachineProvisioningLocation @@ -18,7 +17,7 @@ public class SingleWebServerExample extends AbstractApplication { public static final List DEFAULT_LOCATIONS = [ CommandLineLocations.newLocalhostLocation() ] private static final String WAR_PATH = "classpath://hello-world-webapp.war" - JBoss7Server web = new JBoss7Server(this, war: WAR_PATH, httpPort: 8080) + JBoss7Server web = new JBoss7ServerImpl(this, war: WAR_PATH, httpPort: 8080) public static void main(String[] args) { SingleWebServerExample app = new SingleWebServerExample(); diff --git a/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.groovy b/simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterDatabaseExample.groovy similarity index 91% rename from simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.groovy rename to simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterDatabaseExample.groovy index 6d6cac0..ab9092b 100644 --- a/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExample.groovy +++ b/simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterDatabaseExample.groovy @@ -1,4 +1,4 @@ -package brooklyn.demo +package brooklyn.demo.legacy import static brooklyn.entity.java.JavaEntityMethods.javaSysProp import static brooklyn.entity.webapp.WebAppServiceConstants.HTTP_PORT @@ -12,7 +12,9 @@ import org.slf4j.LoggerFactory import brooklyn.entity.basic.AbstractApplication import brooklyn.entity.basic.Entities import brooklyn.entity.database.mysql.MySqlNode +import brooklyn.entity.database.mysql.MySqlNodeImpl import brooklyn.entity.webapp.ControlledDynamicWebAppCluster +import brooklyn.entity.webapp.ControlledDynamicWebAppClusterImpl import brooklyn.entity.webapp.DynamicWebAppCluster import brooklyn.launcher.BrooklynLauncher import brooklyn.launcher.BrooklynServerDetails @@ -39,8 +41,8 @@ public class WebClusterDatabaseExample extends AbstractApplication { public static final String DB_PASSWORD = "br00k11n" - ControlledDynamicWebAppCluster web = new ControlledDynamicWebAppCluster(this, war: WAR_PATH); - MySqlNode mysql = new MySqlNode(this, creationScriptUrl: DB_SETUP_SQL_URL); + ControlledDynamicWebAppCluster web = new ControlledDynamicWebAppClusterImpl(this, war: WAR_PATH); + MySqlNode mysql = new MySqlNodeImpl(this, creationScriptUrl: DB_SETUP_SQL_URL); { web.configure(HTTP_PORT, "8080+"). diff --git a/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleAlt.groovy b/simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterDatabaseExampleAlt.groovy similarity index 88% rename from simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleAlt.groovy rename to simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterDatabaseExampleAlt.groovy index 6e4f371..98cd100 100644 --- a/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleAlt.groovy +++ b/simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterDatabaseExampleAlt.groovy @@ -1,7 +1,4 @@ -package brooklyn.demo - -import java.util.List -import java.util.Map +package brooklyn.demo.legacy import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -11,11 +8,14 @@ import brooklyn.entity.Entity import brooklyn.entity.basic.AbstractApplication import brooklyn.entity.basic.Entities import brooklyn.entity.database.mysql.MySqlNode -import brooklyn.entity.proxy.nginx.NginxController +import brooklyn.entity.database.mysql.MySqlNodeImpl +import brooklyn.entity.proxy.nginx.NginxControllerImpl import brooklyn.entity.webapp.ControlledDynamicWebAppCluster +import brooklyn.entity.webapp.ControlledDynamicWebAppClusterImpl import brooklyn.entity.webapp.DynamicWebAppCluster import brooklyn.entity.webapp.JavaWebAppService import brooklyn.entity.webapp.jboss.JBoss7Server +import brooklyn.entity.webapp.jboss.JBoss7ServerImpl import brooklyn.event.basic.DependentConfiguration import brooklyn.launcher.BrooklynLauncher import brooklyn.location.Location @@ -69,10 +69,10 @@ INSERT INTO MESSAGES values (default, 'Isaac Asimov', 'I grew up in Brooklyn' ); setConfig(JavaWebAppService.ROOT_WAR, WAR_PATH) } - MySqlNode mysql = new MySqlNode(this, creationScriptContents: DB_SETUP_SQL); + MySqlNode mysql = new MySqlNodeImpl(this, creationScriptContents: DB_SETUP_SQL); protected JavaWebAppService newWebServer(Map flags, Entity cluster) { - JBoss7Server jb7 = new JBoss7Server(flags).configure(httpPort: "8000+"); + JBoss7Server jb7 = new JBoss7ServerImpl(flags).configure(httpPort: "8000+"); jb7.setConfig(JBoss7Server.JAVA_SYSPROPS, ["brooklyn.example.db.url": //"jdbc:mysql://localhost/visitors?user=brooklyn&password=br00k11n" DependentConfiguration.valueWhenAttributeReady(mysql, MySqlNode.MYSQL_URL, @@ -81,8 +81,8 @@ INSERT INTO MESSAGES values (default, 'Isaac Asimov', 'I grew up in Brooklyn' ); return jb7; } - ControlledDynamicWebAppCluster web = new ControlledDynamicWebAppCluster(this, - controller: new NginxController(port: 8080), + ControlledDynamicWebAppCluster web = new ControlledDynamicWebAppClusterImpl(this, + controller: new NginxControllerImpl(port: 8080), factory: this.&newWebServer ) AutoScalerPolicy policy = AutoScalerPolicy.builder() diff --git a/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleAltJava.java b/simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterDatabaseExampleAltJava.java similarity index 91% rename from simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleAltJava.java rename to simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterDatabaseExampleAltJava.java index 3a0f9ef..e8f9bb8 100644 --- a/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleAltJava.java +++ b/simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterDatabaseExampleAltJava.java @@ -1,4 +1,4 @@ -package brooklyn.demo; +package brooklyn.demo.legacy; import static brooklyn.event.basic.DependentConfiguration.valueWhenAttributeReady; @@ -16,8 +16,11 @@ import brooklyn.config.BrooklynProperties; import brooklyn.entity.basic.AbstractApplication; import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.EntityLocal; import brooklyn.entity.database.mysql.MySqlNode; +import brooklyn.entity.database.mysql.MySqlNodeImpl; import brooklyn.entity.webapp.ControlledDynamicWebAppCluster; +import brooklyn.entity.webapp.ControlledDynamicWebAppClusterImpl; import brooklyn.entity.webapp.DynamicWebAppCluster; import brooklyn.entity.webapp.WebAppService; import brooklyn.entity.webapp.jboss.JBoss7Server; @@ -65,11 +68,11 @@ public WebClusterDatabaseExampleAltJava(Map props) { super(props); } - ControlledDynamicWebAppCluster web = new ControlledDynamicWebAppCluster(this); - MySqlNode mysql = new MySqlNode(this); + ControlledDynamicWebAppCluster web = new ControlledDynamicWebAppClusterImpl(this); + MySqlNodeImpl mysql = new MySqlNodeImpl(this); - { - web.setConfig(ControlledDynamicWebAppCluster.ROOT_WAR, WAR_PATH); + { + ((EntityLocal)web).setConfig(ControlledDynamicWebAppCluster.ROOT_WAR, WAR_PATH); mysql.setConfig(MySqlNode.CREATION_SCRIPT_URL, DB_SETUP_SQL_URL); web.getFactory().setConfig(WebAppService.HTTP_PORT, "8080+"); Map jvmSysProps = new LinkedHashMap(); diff --git a/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExample.groovy b/simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterExample.groovy similarity index 91% rename from simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExample.groovy rename to simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterExample.groovy index 81f0930..ae4aea4 100644 --- a/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExample.groovy +++ b/simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterExample.groovy @@ -1,7 +1,4 @@ -package brooklyn.demo - -import java.util.List -import java.util.Map +package brooklyn.demo.legacy import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -10,12 +7,13 @@ import brooklyn.config.BrooklynProperties import brooklyn.entity.basic.AbstractApplication import brooklyn.entity.basic.Entities import brooklyn.entity.proxy.nginx.NginxController +import brooklyn.entity.proxy.nginx.NginxControllerImpl import brooklyn.entity.webapp.ControlledDynamicWebAppCluster +import brooklyn.entity.webapp.ControlledDynamicWebAppClusterImpl import brooklyn.entity.webapp.DynamicWebAppCluster import brooklyn.entity.webapp.jboss.JBoss7ServerFactory import brooklyn.launcher.BrooklynLauncher import brooklyn.location.Location -import brooklyn.location.basic.CommandLineLocations import brooklyn.location.basic.LocationRegistry import brooklyn.policy.autoscaling.AutoScalerPolicy import brooklyn.util.CommandLineUtil @@ -43,13 +41,13 @@ public class WebClusterExample extends AbstractApplication { } - NginxController nginxController = new NginxController(this, + NginxController nginxController = new NginxControllerImpl(this, // domain: 'webclusterexample.brooklyn.local', port:"8000+") JBoss7ServerFactory jbossFactory = new JBoss7ServerFactory(httpPort: "8080+", war: WAR_PATH); - ControlledDynamicWebAppCluster web = new ControlledDynamicWebAppCluster(this, + ControlledDynamicWebAppCluster web = new ControlledDynamicWebAppClusterImpl(this, name: "WebApp cluster", controller: nginxController, initialSize: 1, diff --git a/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExampleAlt.groovy b/simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterExampleAlt.groovy similarity index 92% rename from simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExampleAlt.groovy rename to simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterExampleAlt.groovy index 64b087d..4852254 100644 --- a/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterExampleAlt.groovy +++ b/simple-web-cluster/src/main/java/brooklyn/demo/legacy/WebClusterExampleAlt.groovy @@ -1,20 +1,17 @@ -package brooklyn.demo +package brooklyn.demo.legacy import groovy.transform.InheritConstructors -import java.util.List -import java.util.Map - import org.slf4j.Logger import org.slf4j.LoggerFactory import brooklyn.entity.basic.AbstractApplication import brooklyn.entity.basic.Entities import brooklyn.entity.webapp.ControlledDynamicWebAppCluster +import brooklyn.entity.webapp.ControlledDynamicWebAppClusterImpl import brooklyn.entity.webapp.DynamicWebAppCluster import brooklyn.launcher.BrooklynLauncher import brooklyn.location.Location -import brooklyn.location.basic.CommandLineLocations import brooklyn.location.basic.LocationRegistry import brooklyn.policy.autoscaling.AutoScalerPolicy import brooklyn.util.CommandLineUtil @@ -35,7 +32,7 @@ public class WebClusterExampleAlt extends AbstractApplication { public static final String WAR_PATH = "classpath://hello-world-webapp.war" - ControlledDynamicWebAppCluster web = new ControlledDynamicWebAppCluster(this, war: WAR_PATH); + ControlledDynamicWebAppCluster web = new ControlledDynamicWebAppClusterImpl(this, war: WAR_PATH); { web.addPolicy(AutoScalerPolicy.builder() .metric(DynamicWebAppCluster.AVERAGE_REQUESTS_PER_SECOND) diff --git a/webapps/hello-world-hadoop-jar/pom.xml b/webapps/hello-world-hadoop-jar/pom.xml index 5da2a51..5c5087e 100644 --- a/webapps/hello-world-hadoop-jar/pom.xml +++ b/webapps/hello-world-hadoop-jar/pom.xml @@ -20,7 +20,7 @@ io.brooklyn.example brooklyn-examples-webapps-parent - 0.5.0-SNAPSHOT + 0.5.0-M2 ../pom.xml diff --git a/webapps/hello-world-hadoop/pom.xml b/webapps/hello-world-hadoop/pom.xml index a27a1b0..174cb15 100644 --- a/webapps/hello-world-hadoop/pom.xml +++ b/webapps/hello-world-hadoop/pom.xml @@ -7,7 +7,7 @@ io.brooklyn.example brooklyn-examples-webapps-parent - 0.5.0-SNAPSHOT + 0.5.0-M2 ../pom.xml diff --git a/webapps/hello-world-sql/pom.xml b/webapps/hello-world-sql/pom.xml index ed3690d..3a9e514 100644 --- a/webapps/hello-world-sql/pom.xml +++ b/webapps/hello-world-sql/pom.xml @@ -7,7 +7,7 @@ io.brooklyn.example brooklyn-examples-webapps-parent - 0.5.0-SNAPSHOT + 0.5.0-M2 ../pom.xml diff --git a/webapps/hello-world-webapp/pom.xml b/webapps/hello-world-webapp/pom.xml index 07c31fd..a6837ca 100644 --- a/webapps/hello-world-webapp/pom.xml +++ b/webapps/hello-world-webapp/pom.xml @@ -7,7 +7,7 @@ io.brooklyn.example brooklyn-examples-webapps-parent - 0.5.0-SNAPSHOT + 0.5.0-M2 ../pom.xml diff --git a/webapps/pom.xml b/webapps/pom.xml index 966ec38..ab4b0b8 100644 --- a/webapps/pom.xml +++ b/webapps/pom.xml @@ -12,7 +12,7 @@ io.brooklyn.example brooklyn-examples-parent - 0.5.0-SNAPSHOT + 0.5.0-M2 ../pom.xml