Repository: brooklyn-library Updated Branches: refs/heads/master 8c3ad2786 -> 3b96d1d1c
Temporarily restore simple-web-cluster. A file in it is still in use at https://github.com/apache/brooklyn-library/blob/master/qa/src/test/resources/java-web-app-and-db-with-policy.bom#L54 Will need to tidy that up so the file is no longer used and then this example can be deleted again. Project: http://git-wip-us.apache.org/repos/asf/brooklyn-library/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-library/commit/24296fe8 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-library/tree/24296fe8 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-library/diff/24296fe8 Branch: refs/heads/master Commit: 24296fe813e6c707269d33652f925b81097eb8bf Parents: 8c3ad27 Author: Geoff Macartney <geoff.macart...@cloudsoftcorp.com> Authored: Fri May 12 15:29:07 2017 +0100 Committer: Geoff Macartney <geoff.macart...@cloudsoftcorp.com> Committed: Fri May 12 15:29:07 2017 +0100 ---------------------------------------------------------------------- examples/pom.xml | 1 + examples/simple-web-cluster/.gitignore | 2 + examples/simple-web-cluster/README.txt | 59 ++++++ examples/simple-web-cluster/pom.xml | 164 +++++++++++++++ .../resources/jmeter-test-plan.jmx | 143 +++++++++++++ .../src/main/assembly/assembly.xml | 74 +++++++ .../src/main/assembly/files/README.txt | 49 +++++ .../src/main/assembly/scripts/start.sh | 43 ++++ .../brooklyn/demo/NodeJsTodoApplication.java | 60 ++++++ .../brooklyn/demo/SingleWebServerExample.java | 66 ++++++ .../demo/WebClusterDatabaseExample.java | 122 +++++++++++ .../demo/WebClusterDatabaseExampleApp.java | 174 ++++++++++++++++ .../apache/brooklyn/demo/WebClusterExample.java | 95 +++++++++ .../src/main/resources/catalog.bom | 31 +++ .../src/main/resources/logback-custom.xml | 43 ++++ .../brooklyn/demo/glossy-3d-blue-web-icon.png | Bin 0 -> 46490 bytes .../apache/brooklyn/demo/nodejs-riak-todo.yaml | 46 +++++ .../org/apache/brooklyn/demo/nodejs-todo.yaml | 53 +++++ .../main/resources/visitors-creation-script.sql | 41 ++++ ...lusterDatabaseExampleAppIntegrationTest.java | 204 +++++++++++++++++++ 20 files changed, 1470 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/pom.xml ---------------------------------------------------------------------- diff --git a/examples/pom.xml b/examples/pom.xml index ac03e8e..697a3f2 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -37,6 +37,7 @@ </parent> <modules> + <module>simple-web-cluster</module> <module>webapps</module> </modules> </project> http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/.gitignore ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/.gitignore b/examples/simple-web-cluster/.gitignore new file mode 100644 index 0000000..9a3d6d6 --- /dev/null +++ b/examples/simple-web-cluster/.gitignore @@ -0,0 +1,2 @@ +brooklyn-example-simple-web-cluster/ +brooklyn-example-simple-web-cluster.tar.gz http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/README.txt ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/README.txt b/examples/simple-web-cluster/README.txt new file mode 100644 index 0000000..598a4ce --- /dev/null +++ b/examples/simple-web-cluster/README.txt @@ -0,0 +1,59 @@ +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 org.apache.brooklyn.demo.WebClusterDatabaseExample --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: + + # Same three-tier, but in Amazon California, prompting for credentials + # (the location arg can be changed for any example, of course, and other clouds are available) + export JCLOUDS_AWS_EC2_IDENTITY=AKA50M30N3S1DFR0MAW55 + export JCLOUDS_AWS_EC2_CREDENTIAL=aT0Ps3cr3tC0D3wh1chAW5w1llG1V3y0uTOus333 + brooklyn launch --app brooklyn.demo.WebClusterDatabaseExample --location aws-ec2:us-west-1 + + +Other examples: + + # A very simple app: a single web-server + brooklyn launch --app org.apache.brooklyn.demo.SingleWebServerExample --location localhost + + # A simple app: just load-balancer and appservers + brooklyn launch --app org.apache.brooklyn.demo.WebClusterExample --location localhost + + # Three-tier example + brooklyn launch --app org.apache.brooklyn.demo.WebClusterDatabaseExample --location localhost + + +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 https://brooklyn.incubator.apache.org/. + +---- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. \ No newline at end of file http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/pom.xml ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/pom.xml b/examples/simple-web-cluster/pom.xml new file mode 100644 index 0000000..ef5cd67 --- /dev/null +++ b/examples/simple-web-cluster/pom.xml @@ -0,0 +1,164 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <packaging>jar</packaging> + <artifactId>brooklyn-example-simple-web-cluster</artifactId> + <name>Brooklyn Simple Web Cluster Example</name> + + <!-- this is only needed for "mvn deploy"; the group and version can be extracted + and parent block removed to run this standalone --> + <parent> + <groupId>org.apache.brooklyn.example</groupId> + <artifactId>brooklyn-examples-parent</artifactId> + <version>0.12.0-SNAPSHOT</version> <!-- BROOKLYN_VERSION --> + <relativePath>../pom.xml</relativePath> + </parent> + + <dependencies> + <dependency> + <groupId>org.apache.brooklyn</groupId> + <artifactId>brooklyn-core</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.brooklyn</groupId> + <artifactId>brooklyn-launcher</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.brooklyn</groupId> + <artifactId>brooklyn-software-webapp</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.brooklyn</groupId> + <artifactId>brooklyn-software-database</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.brooklyn</groupId> + <artifactId>brooklyn-software-nosql</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.brooklyn</groupId> + <artifactId>brooklyn-logback-xml</artifactId> + <version>${project.version}</version> + <optional>true</optional> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + + <dependency> + <groupId>org.apache.brooklyn</groupId> + <artifactId>brooklyn-core</artifactId> + <version>${project.version}</version> + <classifier>tests</classifier> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.brooklyn</groupId> + <artifactId>brooklyn-test-support</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.testng</groupId> + <artifactId>testng</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <artifactId>maven-dependency-plugin</artifactId> + <!-- copy the WAR so it is available on the classpath for programmatic deployment --> + <executions> + <execution> + <id>copy</id> + <phase>process-classes</phase> + <goals> + <goal>copy</goal> + </goals> + <configuration> + <artifactItems> + <!-- these can fail in eclipse trying to copy _from_ target/classes. + see http://jira.codehaus.org/browse/MDEP-259 --> + <artifactItem> + <groupId>org.apache.brooklyn.example</groupId> + <artifactId>brooklyn-example-hello-world-webapp</artifactId> + <version>${project.version}</version> + <type>war</type> + <overWrite>true</overWrite> + <outputDirectory>target/classes</outputDirectory> + <destFileName>hello-world-webapp.war</destFileName> + </artifactItem> + <artifactItem> + <groupId>org.apache.brooklyn.example</groupId> + <artifactId>brooklyn-example-hello-world-sql-webapp</artifactId> + <version>${project.version}</version> + <type>war</type> + <overWrite>true</overWrite> + <outputDirectory>target/classes</outputDirectory> + <destFileName>hello-world-sql-webapp.war</destFileName> + </artifactItem> + </artifactItems> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-clean-plugin</artifactId> + <configuration> + <filesets> + <fileset> + <directory>${project.basedir}</directory> + <includes> + <include>${project.artifactId}/</include> + <include>brooklyn*.log</include> + <include>brooklyn*.log.*</include> + <include>stacktrace.log</include> + </includes> + </fileset> + </filesets> + </configuration> + </plugin> + <plugin> + <!-- optional, with this block, `mvn assembly:assembly` will build a redistributable tgz --> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <descriptors> + <descriptor>src/main/assembly/assembly.xml</descriptor> + </descriptors> + </configuration> + </plugin> + </plugins> + </build> + +</project> http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/resources/jmeter-test-plan.jmx ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/resources/jmeter-test-plan.jmx b/examples/simple-web-cluster/resources/jmeter-test-plan.jmx new file mode 100644 index 0000000..0eb1741 --- /dev/null +++ b/examples/simple-web-cluster/resources/jmeter-test-plan.jmx @@ -0,0 +1,143 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<jmeterTestPlan version="1.2" properties="2.1"> + <hashTree> + <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Brooklyn local webapp test - load for localhost:8000" enabled="true"> + <stringProp name="TestPlan.comments"></stringProp> + <boolProp name="TestPlan.functional_mode">false</boolProp> + <boolProp name="TestPlan.serialize_threadgroups">false</boolProp> + <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="TestPlan.user_define_classpath"></stringProp> + </TestPlan> + <hashTree> + <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Test Group - hitting site with 8 threads" enabled="true"> + <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> + <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> + <boolProp name="LoopController.continue_forever">false</boolProp> + <intProp name="LoopController.loops">-1</intProp> + </elementProp> + <stringProp name="ThreadGroup.num_threads">8</stringProp> + <stringProp name="ThreadGroup.ramp_time">1</stringProp> + <longProp name="ThreadGroup.start_time">1326116677000</longProp> + <longProp name="ThreadGroup.end_time">1326116677000</longProp> + <boolProp name="ThreadGroup.scheduler">false</boolProp> + <stringProp name="ThreadGroup.duration"></stringProp> + <stringProp name="ThreadGroup.delay"></stringProp> + </ThreadGroup> + <hashTree> + <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Timer - frequency 50 reqs/sec per thread (20 ms delay)" enabled="true"> + <stringProp name="ConstantTimer.delay">20</stringProp> + </ConstantTimer> + <hashTree/> + <HTTPSampler guiclass="HttpTestSampleGui" testclass="HTTPSampler" testname="HTTP Request - localhost:8000" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain">localhost</stringProp> + <stringProp name="HTTPSampler.port">8000</stringProp> + <stringProp name="HTTPSampler.connect_timeout"></stringProp> + <stringProp name="HTTPSampler.response_timeout"></stringProp> + <stringProp name="HTTPSampler.protocol"></stringProp> + <stringProp name="HTTPSampler.contentEncoding"></stringProp> + <stringProp name="HTTPSampler.path"></stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"></stringProp> + </HTTPSampler> + <hashTree/> + <CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="HTTP Cookie Manager" enabled="true"> + <collectionProp name="CookieManager.cookies"/> + <boolProp name="CookieManager.clearEachIteration">false</boolProp> + </CookieManager> + <hashTree/> + </hashTree> + <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>true</xml> + <fieldNames>false</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + <ResultCollector guiclass="SplineVisualizer" testclass="ResultCollector" testname="Spline Visualizer" enabled="true"> + <boolProp name="ResultCollector.error_logging">false</boolProp> + <objProp> + <name>saveConfig</name> + <value class="SampleSaveConfiguration"> + <time>true</time> + <latency>true</latency> + <timestamp>true</timestamp> + <success>true</success> + <label>true</label> + <code>true</code> + <message>true</message> + <threadName>true</threadName> + <dataType>true</dataType> + <encoding>false</encoding> + <assertions>true</assertions> + <subresults>true</subresults> + <responseData>false</responseData> + <samplerData>false</samplerData> + <xml>true</xml> + <fieldNames>false</fieldNames> + <responseHeaders>false</responseHeaders> + <requestHeaders>false</requestHeaders> + <responseDataOnError>false</responseDataOnError> + <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage> + <assertionsResultsToSave>0</assertionsResultsToSave> + <bytes>true</bytes> + </value> + </objProp> + <stringProp name="filename"></stringProp> + </ResultCollector> + <hashTree/> + </hashTree> + </hashTree> +</jmeterTestPlan> http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/main/assembly/assembly.xml ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/main/assembly/assembly.xml b/examples/simple-web-cluster/src/main/assembly/assembly.xml new file mode 100644 index 0000000..cbe7a08 --- /dev/null +++ b/examples/simple-web-cluster/src/main/assembly/assembly.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<assembly> + <id>bin</id> + <!-- Generates an archive containing the needed files; + can add e.g. zip to the following + (but executable bit is not preserved) --> + <formats> + <format>tar.gz</format> + </formats> + + <!-- Adds dependencies to zip package under lib directory --> + <dependencySets> + <dependencySet> + <!-- + Project artifact is not copied under library directory since + it is added to the root directory of the zip package. + --> + <useProjectArtifact>false</useProjectArtifact> + <outputDirectory>lib</outputDirectory> + <unpack>false</unpack> + </dependencySet> + </dependencySets> + + <fileSets> + <!-- + Adds startup scripts to the root directory of zip package. The startup + scripts are located to src/main/scripts directory as stated by Maven + conventions. + --> + <fileSet> + <directory>src/main/assembly/scripts</directory> + <outputDirectory></outputDirectory> + <fileMode>0755</fileMode> + <includes> + <include>*</include> + </includes> + </fileSet> + <!-- add additional files (but not marked executable --> + <fileSet> + <directory>src/main/assembly/files</directory> + <outputDirectory></outputDirectory> + <includes> + <include>*</include> + </includes> + </fileSet> + <!-- adds jar package to the root directory of zip package --> + <fileSet> + <directory>target</directory> + <outputDirectory></outputDirectory> + <includes> + <include>*.jar</include> + </includes> + </fileSet> + </fileSets> +</assembly> + http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/main/assembly/files/README.txt ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/main/assembly/files/README.txt b/examples/simple-web-cluster/src/main/assembly/files/README.txt new file mode 100644 index 0000000..cf49ea5 --- /dev/null +++ b/examples/simple-web-cluster/src/main/assembly/files/README.txt @@ -0,0 +1,49 @@ +Brooklyn Example +================ + +To use, configure your cloud credentials then run ./start.sh in this directory. +You can then access the management context in your browser, typically on localhost:8081. + + +### Cloud Credentials + +To run, you'll need to specify credentials for your preferred cloud. This can be done +in `~/.brooklyn/brooklyn.properties`: + + brooklyn.jclouds.aws-ec2.identity=AKXXXXXXXXXXXXXXXXXX + brooklyn.jclouds.aws-ec2.credential=secret01xxxxxxxxxxxxxxxxxxxxxxxxxxx + +Alternatively these can be set as shell environment parameters or JVM system properties. + +Many other clouds are supported also, as well as pre-existing machines ("bring your own nodes"), +custom endpoints for private clouds, and specifying custom keys and passphrases. +For more information see: + + https://github.com/brooklyncentral/brooklyn/blob/master/docs/use/guide/defining-applications/common-usage.md#off-the-shelf-locations + + +### Run + +Usage: + + ./start.sh [--port 8081+] location + +Where location might be `localhost` (the defaul), or `aws-ec2:us-east-1`, `openstack:endpoint`, etc. + +---- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. \ No newline at end of file http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/main/assembly/scripts/start.sh ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/main/assembly/scripts/start.sh b/examples/simple-web-cluster/src/main/assembly/scripts/start.sh new file mode 100755 index 0000000..ec4f110 --- /dev/null +++ b/examples/simple-web-cluster/src/main/assembly/scripts/start.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +if [ -z "$BROOKLYN_APP_CLASS" ] ; then + BROOKLYN_APP_CLASS=org.apache.brooklyn.demo.WebClusterDatabaseExample +fi + +if [ -z "$JAVA" ] ; then + JAVA=`which java` +fi +if [ ! -z "$JAVA_HOME" ] ; then + JAVA=$JAVA_HOME/bin/java +else + JAVA=`which java` +fi +if [ ! -x "$JAVA" ] ; then + echo Cannot find java. Set JAVA_HOME or add java to path. + exit 1 +fi + +if [[ ! `ls *.jar 2> /dev/null` || ! `ls lib/*.jar 2> /dev/null` ]] ; then + echo Command must be run from the directory where it is installed. + exit 1 +fi + +$JAVA -Xms256m -Xmx1024m -XX:MaxPermSize=1024m -classpath "*:lib/*" $BROOKLYN_APP_CLASS "$@" http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/NodeJsTodoApplication.java ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/NodeJsTodoApplication.java b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/NodeJsTodoApplication.java new file mode 100644 index 0000000..086f390 --- /dev/null +++ b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/NodeJsTodoApplication.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.demo; + +import static org.apache.brooklyn.core.sensor.DependentConfiguration.attributeWhenReady; + +import org.apache.brooklyn.api.catalog.Catalog; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.core.entity.AbstractApplication; +import org.apache.brooklyn.core.entity.Attributes; +import org.apache.brooklyn.core.entity.StartableApplication; +import org.apache.brooklyn.core.entity.trait.Startable; +import org.apache.brooklyn.core.sensor.DependentConfiguration; +import org.apache.brooklyn.entity.nosql.redis.RedisStore; +import org.apache.brooklyn.entity.software.base.SoftwareProcess; +import org.apache.brooklyn.entity.webapp.nodejs.NodeJsWebAppService; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Node.JS Todo Application + */ +@Catalog(name="NodeJS Todo", + description="Node.js is a cross-platform runtime environment for server-side and networking applications. Node.js applications are written in JavaScript", + iconUrl="classpath://nodejs-logo.png") +public class NodeJsTodoApplication extends AbstractApplication implements StartableApplication { + + @Override + public void initApp() { + RedisStore redis = addChild(EntitySpec.create(RedisStore.class)); + + addChild(EntitySpec.create(NodeJsWebAppService.class) + .configure(NodeJsWebAppService.APP_GIT_REPOSITORY_URL, "https://github.com/grkvlt/nodejs-todo/") + .configure(NodeJsWebAppService.APP_FILE, "server.js") + .configure(NodeJsWebAppService.APP_NAME, "nodejs-todo") + .configure(NodeJsWebAppService.NODE_PACKAGE_LIST, ImmutableList.of("express", "ejs", "jasmine-node", "underscore", "method-override", "cookie-parser", "express-session", "body-parser", "cookie-session", "redis", "redis-url", "connect")) + .configure(SoftwareProcess.SHELL_ENVIRONMENT, ImmutableMap.<String, Object>of( + "REDISTOGO_URL", DependentConfiguration.formatString("redis://%s:%d/", + attributeWhenReady(redis, Attributes.HOSTNAME), attributeWhenReady(redis, RedisStore.REDIS_PORT)))) + .configure(SoftwareProcess.LAUNCH_LATCH, attributeWhenReady(redis, Startable.SERVICE_UP))); + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/SingleWebServerExample.java ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/SingleWebServerExample.java b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/SingleWebServerExample.java new file mode 100644 index 0000000..cec7a7e --- /dev/null +++ b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/SingleWebServerExample.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.demo; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.core.entity.AbstractApplication; +import org.apache.brooklyn.core.entity.Attributes; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.StartableApplication; +import org.apache.brooklyn.core.location.PortRanges; +import org.apache.brooklyn.entity.webapp.JavaWebAppService; +import org.apache.brooklyn.entity.webapp.jboss.JBoss7Server; +import org.apache.brooklyn.launcher.BrooklynLauncher; +import org.apache.brooklyn.util.CommandLineUtil; + +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 AbstractApplication { + + public static final Logger LOG = LoggerFactory.getLogger(SingleWebServerExample.class); + + private static final String WAR_PATH = "classpath://hello-world-webapp.war"; + + @Override + public void initApp() { + addChild(EntitySpec.create(JBoss7Server.class) + .configure(JavaWebAppService.ROOT_WAR, WAR_PATH) + .configure(Attributes.HTTP_PORT, PortRanges.fromString("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<String> args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", "localhost"); + + BrooklynLauncher launcher = BrooklynLauncher.newInstance() + .application(EntitySpec.create(StartableApplication.class, SingleWebServerExample.class).displayName("Brooklyn WebApp example")) + .webconsolePort(port) + .location(location) + .start(); + + Entities.dumpInfo(launcher.getApplications()); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExample.java ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExample.java b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExample.java new file mode 100644 index 0000000..601537a --- /dev/null +++ b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExample.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.demo; + +import static org.apache.brooklyn.core.sensor.DependentConfiguration.attributeWhenReady; +import static org.apache.brooklyn.core.sensor.DependentConfiguration.formatString; +import static org.apache.brooklyn.entity.java.JavaEntityMethods.javaSysProp; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.entity.database.mysql.MySqlNode; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.core.entity.AbstractApplication; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.StartableApplication; +import org.apache.brooklyn.core.location.PortRanges; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.enricher.stock.Enrichers; +import org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster; +import org.apache.brooklyn.entity.webapp.DynamicWebAppCluster; +import org.apache.brooklyn.entity.webapp.JavaWebAppService; +import org.apache.brooklyn.entity.webapp.WebAppService; +import org.apache.brooklyn.entity.webapp.WebAppServiceConstants; +import org.apache.brooklyn.launcher.BrooklynLauncher; +import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy; +import org.apache.brooklyn.policy.enricher.HttpLatencyDetector; +import org.apache.brooklyn.util.CommandLineUtil; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; + +/** + * Launches a 3-tier app with nginx, clustered jboss, and mysql. + **/ +public class WebClusterDatabaseExample extends AbstractApplication { + + 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 AttributeSensor<Integer> APPSERVERS_COUNT = Sensors.newIntegerSensor( + "appservers.count", "Number of app servers deployed"); + + @Override + public void initApp() { + MySqlNode mysql = addChild(EntitySpec.create(MySqlNode.class) + .configure("creationScriptUrl", DB_SETUP_SQL_URL)); + + ControlledDynamicWebAppCluster web = addChild(EntitySpec.create(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.DATASTORE_URL), + DB_TABLE, DB_USERNAME, DB_PASSWORD)) ); + + web.enrichers().add(HttpLatencyDetector.builder(). + url(ControlledDynamicWebAppCluster.ROOT_URL). + rollup(10, TimeUnit.SECONDS). + build()); + + // simple scaling policy + web.getCluster().policies().add(AutoScalerPolicy.builder(). + metric(DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW_PER_NODE). + metricRange(10, 100). + sizeRange(1, 5). + build()); + + // expose some KPI's + enrichers().add(Enrichers.builder() + .propagating(WebAppServiceConstants.ROOT_URL, + DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW, + HttpLatencyDetector.REQUEST_LATENCY_IN_SECONDS_IN_WINDOW) + .from(web) + .build()); + + enrichers().add(Enrichers.builder() + .propagating(ImmutableMap.of(DynamicWebAppCluster.GROUP_SIZE, APPSERVERS_COUNT)) + .from(web) + .build()); + } + + public static void main(String[] argv) { + List<String> args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", "localhost"); + + BrooklynLauncher launcher = BrooklynLauncher.newInstance() + .application(EntitySpec.create(StartableApplication.class, WebClusterDatabaseExample.class).displayName("Brooklyn WebApp Cluster with Database example")) + .webconsolePort(port) + .location(location) + .start(); + + Entities.dumpInfo(launcher.getApplications()); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleApp.java ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleApp.java b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleApp.java new file mode 100644 index 0000000..5c58fee --- /dev/null +++ b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExampleApp.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.demo; + +import static org.apache.brooklyn.core.sensor.DependentConfiguration.attributeWhenReady; +import static org.apache.brooklyn.core.sensor.DependentConfiguration.formatString; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.api.catalog.Catalog; +import org.apache.brooklyn.api.catalog.CatalogConfig; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.AbstractApplication; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.StartableApplication; +import org.apache.brooklyn.core.location.PortRanges; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.enricher.stock.Enrichers; +import org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster; +import org.apache.brooklyn.entity.webapp.DynamicWebAppCluster; +import org.apache.brooklyn.entity.webapp.JavaWebAppService; +import org.apache.brooklyn.entity.webapp.WebAppService; +import org.apache.brooklyn.entity.webapp.WebAppServiceConstants; +import org.apache.brooklyn.entity.database.mysql.MySqlNode; +import org.apache.brooklyn.entity.group.DynamicCluster; +import org.apache.brooklyn.entity.java.JavaEntityMethods; +import org.apache.brooklyn.launcher.BrooklynLauncher; +import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy; +import org.apache.brooklyn.policy.enricher.HttpLatencyDetector; +import org.apache.brooklyn.util.CommandLineUtil; +import org.apache.brooklyn.util.core.BrooklynMavenArtifacts; +import org.apache.brooklyn.util.core.ResourceUtils; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; + +/** + * Launches a 3-tier app with nginx, clustered jboss, and mysql. + * <p> + * Includes some advanced features such as KPI / derived sensors, + * and annotations for use in a catalog. + * <p> + * 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! + **/ +@Catalog(name="Elastic Java Web + DB", + description="Deploys a WAR to a load-balanced elastic Java AppServer cluster, " + + "with an auto-scaling policy, " + + "wired to a database initialized with the provided SQL; " + + "defaults to a 'Hello World' chatroom app.", + iconUrl="classpath://brooklyn/demo/glossy-3d-blue-web-icon.png") +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 = ResourceUtils.create(WebClusterDatabaseExampleApp.class) + // take this war, from the classpath, or via maven if not on the classpath + .firstAvailableUrl( + "classpath://hello-world-sql-webapp.war", + BrooklynMavenArtifacts.localUrl("example", "brooklyn-example-hello-world-sql-webapp", "war")) + .or("classpath://hello-world-sql-webapp.war"); + + @CatalogConfig(label="WAR (URL)", priority=2) + public static final ConfigKey<String> WAR_PATH = ConfigKeys.newConfigKey( + "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<Boolean> USE_HTTPS = ConfigKeys.newConfigKey( + "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)", priority=1) + public static final ConfigKey<String> DB_SETUP_SQL_URL = ConfigKeys.newConfigKey( + "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"; + + public static final AttributeSensor<Integer> APPSERVERS_COUNT = Sensors.newIntegerSensor( + "appservers.count", "Number of app servers deployed"); + public static final AttributeSensor<Double> REQUESTS_PER_SECOND_IN_WINDOW = + WebAppServiceConstants.REQUESTS_PER_SECOND_IN_WINDOW; + public static final AttributeSensor<String> ROOT_URL = WebAppServiceConstants.ROOT_URL; + + @Override + public void initApp() { + MySqlNode mysql = addChild( + EntitySpec.create(MySqlNode.class) + .configure(MySqlNode.CREATION_SCRIPT_URL, Entities.getRequiredUrlConfig(this, DB_SETUP_SQL_URL))); + + ControlledDynamicWebAppCluster web = addChild( + EntitySpec.create(ControlledDynamicWebAppCluster.class) + .configure(WebAppService.HTTP_PORT, PortRanges.fromString("8080+")) + // to specify a diferrent appserver: +// .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TomcatServer.class)) + .configure(JavaWebAppService.ROOT_WAR, Entities.getRequiredUrlConfig(this, WAR_PATH)) + .configure(JavaEntityMethods.javaSysProp("brooklyn.example.db.url"), + formatString("jdbc:%s%s?user=%s\\&password=%s", + attributeWhenReady(mysql, MySqlNode.DATASTORE_URL), DB_TABLE, DB_USERNAME, DB_PASSWORD)) + .configure(DynamicCluster.INITIAL_SIZE, 2) + .configure(WebAppService.ENABLED_PROTOCOLS, ImmutableSet.of(getConfig(USE_HTTPS) ? "https" : "http")) ); + + web.enrichers().add(HttpLatencyDetector.builder() + .url(ROOT_URL) + .rollup(10, TimeUnit.SECONDS) + .build()); + + web.getCluster().policies().add(AutoScalerPolicy.builder() + .metric(DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW_PER_NODE) + .metricRange(10, 100) + .sizeRange(2, 5) + .build()); + + enrichers().add(Enrichers.builder() + .propagating(WebAppServiceConstants.ROOT_URL, + DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW, + HttpLatencyDetector.REQUEST_LATENCY_IN_SECONDS_IN_WINDOW) + .from(web) + .build()); + + enrichers().add(Enrichers.builder() + .propagating(ImmutableMap.of(DynamicWebAppCluster.GROUP_SIZE, APPSERVERS_COUNT)) + .from(web) + .build()); + } + + public static void main(String[] argv) { + List<String> args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", DEFAULT_LOCATION); + + BrooklynLauncher launcher = BrooklynLauncher.newInstance() + .application(EntitySpec.create(StartableApplication.class, WebClusterDatabaseExampleApp.class) + .displayName("Brooklyn WebApp Cluster with Database example")) + .webconsolePort(port) + .location(location) + .start(); + + Entities.dumpInfo(launcher.getApplications()); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterExample.java ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterExample.java b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterExample.java new file mode 100644 index 0000000..e5a1ae2 --- /dev/null +++ b/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterExample.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.demo; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.core.entity.AbstractApplication; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.internal.BrooklynProperties; +import org.apache.brooklyn.entity.proxy.nginx.NginxController; +import org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster; +import org.apache.brooklyn.entity.webapp.DynamicWebAppCluster; +import org.apache.brooklyn.entity.webapp.jboss.JBoss7Server; +import org.apache.brooklyn.launcher.BrooklynLauncher; +import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy; +import org.apache.brooklyn.util.CommandLineUtil; + +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.) + * <p> + * Requires: + * -Xmx512m -Xms128m -XX:MaxPermSize=256m + * and brooklyn-all jar, and this jar or classes dir, on classpath. + **/ +public class WebClusterExample extends AbstractApplication { + 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; + + @Override + public void initApp() { + nginxController = addChild(EntitySpec.create(NginxController.class) + //.configure("domain", "webclusterexample.brooklyn.local") + .configure("port", "8000+")); + + web = addChild(EntitySpec.create(ControlledDynamicWebAppCluster.class) + .displayName("WebApp cluster") + .configure(ControlledDynamicWebAppCluster.CONTROLLER, nginxController) + .configure(ControlledDynamicWebAppCluster.INITIAL_SIZE, 1) + .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class) + .configure("httpPort", "8080+") + .configure("war", WAR_PATH))); + + web.getCluster().policies().add(AutoScalerPolicy.builder() + .metric(DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW_PER_NODE) + .sizeRange(1, 5) + .metricRange(10, 100) + .build()); + } + + public static void main(String[] argv) { + List<String> args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", DEFAULT_LOCATION); + + // TODO Want to parse, to handle multiple locations + BrooklynLauncher launcher = BrooklynLauncher.newInstance() + .application(EntitySpec.create(WebClusterExample.class).displayName("Brooklyn WebApp Cluster example")) + .webconsolePort(port) + .location(location) + .start(); + + Entities.dumpInfo(launcher.getApplications()); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/main/resources/catalog.bom ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/main/resources/catalog.bom b/examples/simple-web-cluster/src/main/resources/catalog.bom new file mode 100644 index 0000000..4d5cd8c --- /dev/null +++ b/examples/simple-web-cluster/src/main/resources/catalog.bom @@ -0,0 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +brooklyn.catalog: + version: "0.12.0-SNAPSHOT" # BROOKLYN_VERSION + itemType: template + items: + - id: org.apache.brooklyn.demo.NodeJsTodoApplication + item: + services: + - type: org.apache.brooklyn.demo.NodeJsTodoApplication + name: NodeJS Todo + - id: org.apache.brooklyn.demo.WebClusterDatabaseExampleApp + item: + services: + - type: org.apache.brooklyn.demo.WebClusterDatabaseExampleApp + name: Elastic Java Web + DB http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/main/resources/logback-custom.xml ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/main/resources/logback-custom.xml b/examples/simple-web-cluster/src/main/resources/logback-custom.xml new file mode 100644 index 0000000..02a8a82 --- /dev/null +++ b/examples/simple-web-cluster/src/main/resources/logback-custom.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<included> + + <!-- + + this file demonstrates how to customise logging, base on the placeholder + logging-custom.xml included in brooklyn-logging-includes. + + --> + + <!-- include this category --> + <logger name="org.apache.brooklyn.demo" level="DEBUG"/> + + <!-- log to simple-web-cluster.log --> + <property name="logging.basename" scope="context" value="brooklyn-simple-web-cluster" /> + + <!-- + + more customisation is possible by overriding the default-included + configuration files, such as logback-main.xml (entirely replacing configuration), + or e.g. brooklyn/logback-appender-file.xml (replacing the FILE logger config). + + --> + +</included> http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/main/resources/org/apache/brooklyn/demo/glossy-3d-blue-web-icon.png ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/main/resources/org/apache/brooklyn/demo/glossy-3d-blue-web-icon.png b/examples/simple-web-cluster/src/main/resources/org/apache/brooklyn/demo/glossy-3d-blue-web-icon.png new file mode 100644 index 0000000..542a1de Binary files /dev/null and b/examples/simple-web-cluster/src/main/resources/org/apache/brooklyn/demo/glossy-3d-blue-web-icon.png differ http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/main/resources/org/apache/brooklyn/demo/nodejs-riak-todo.yaml ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/main/resources/org/apache/brooklyn/demo/nodejs-riak-todo.yaml b/examples/simple-web-cluster/src/main/resources/org/apache/brooklyn/demo/nodejs-riak-todo.yaml new file mode 100644 index 0000000..958d29d --- /dev/null +++ b/examples/simple-web-cluster/src/main/resources/org/apache/brooklyn/demo/nodejs-riak-todo.yaml @@ -0,0 +1,46 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: "Node.JS Todo Application" +origin: "https://github.com/amirrajan/nodejs-todo/" +location: + jclouds:aws-ec2:us-west-1: + imageId: us-west-1/ami-c33cdd87 +services: +- type: org.apache.brooklyn.entity.nosql.riak.RiakCluster + initialSize: 2 + id: mycluster + brooklyn.config: + provisioning.properties: + osFamily: centos + minCores: 4 + minRam: 2048 +- type: org.apache.brooklyn.entity.webapp.nodejs.NodeJsWebAppService + id: nodejs-riak1 + name: "Node.JS" + brooklyn.config: + gitRepoUrl: + "https://github.com/bostko/nodejs-todo.git" + appFileName: server.js + appName: nodejs-todo + nodePackages: + - basho-riak-client + env: + NODE_ENV: production + RIAK_NODES: > + $brooklyn:component("mycluster").attributeWhenReady("riak.cluster.nodeListPbPort") + launch.latch: $brooklyn:component("mycluster").attributeWhenReady("service.isUp") \ No newline at end of file http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/main/resources/org/apache/brooklyn/demo/nodejs-todo.yaml ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/main/resources/org/apache/brooklyn/demo/nodejs-todo.yaml b/examples/simple-web-cluster/src/main/resources/org/apache/brooklyn/demo/nodejs-todo.yaml new file mode 100644 index 0000000..6aab1db --- /dev/null +++ b/examples/simple-web-cluster/src/main/resources/org/apache/brooklyn/demo/nodejs-todo.yaml @@ -0,0 +1,53 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +id: nodejs-todo-application +name: "Node.JS Todo Application" +origin: "https://github.com/amirrajan/nodejs-todo/" +locations: +- jclouds:softlayer:ams01 +services: +- type: org.apache.brooklyn.entity.nosql.redis.RedisStore + id: redis + name: "Redis" +- type: org.apache.brooklyn.entity.webapp.nodejs.NodeJsWebAppService + id: nodejs + name: "Node.JS" + brooklyn.config: + gitRepoUrl: + "https://github.com/grkvlt/nodejs-todo/" + appFileName: server.js + appName: nodejs-todo + nodePackages: + - express + - ejs + - jasmine-node + - underscore + - method-override + - cookie-parser + - express-session + - body-parser + - cookie-session + - redis + - redis-url + - connect + env: + REDISTOGO_URL: > + $brooklyn:formatString("redis://%s:%d/", + component("redis").attributeWhenReady("host.subnet.hostname"), + component("redis").attributeWhenReady("redis.port")) + launch.latch: $brooklyn:component("redis").attributeWhenReady("service.isUp") \ No newline at end of file http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/main/resources/visitors-creation-script.sql ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/main/resources/visitors-creation-script.sql b/examples/simple-web-cluster/src/main/resources/visitors-creation-script.sql new file mode 100644 index 0000000..2422f8f --- /dev/null +++ b/examples/simple-web-cluster/src/main/resources/visitors-creation-script.sql @@ -0,0 +1,41 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. +-- +create database visitors; +use visitors; + +# not necessary to create user if we grant (and not supported in some dialects) +# 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 BIGINT 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' ); http://git-wip-us.apache.org/repos/asf/brooklyn-library/blob/24296fe8/examples/simple-web-cluster/src/test/java/org/apache/brooklyn/demo/RebindWebClusterDatabaseExampleAppIntegrationTest.java ---------------------------------------------------------------------- diff --git a/examples/simple-web-cluster/src/test/java/org/apache/brooklyn/demo/RebindWebClusterDatabaseExampleAppIntegrationTest.java b/examples/simple-web-cluster/src/test/java/org/apache/brooklyn/demo/RebindWebClusterDatabaseExampleAppIntegrationTest.java new file mode 100644 index 0000000..2dfbb81 --- /dev/null +++ b/examples/simple-web-cluster/src/test/java/org/apache/brooklyn/demo/RebindWebClusterDatabaseExampleAppIntegrationTest.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.demo; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.sensor.Enricher; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.EntityAsserts; +import org.apache.brooklyn.core.entity.StartableApplication; +import org.apache.brooklyn.core.mgmt.rebind.RebindOptions; +import org.apache.brooklyn.core.mgmt.rebind.RebindTestFixture; +import org.apache.brooklyn.enricher.stock.Propagator; +import org.apache.brooklyn.entity.proxy.nginx.NginxController; +import org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster; +import org.apache.brooklyn.entity.webapp.DynamicWebAppCluster; +import org.apache.brooklyn.entity.webapp.tomcat.Tomcat8Server; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.test.HttpTestUtils; +import org.apache.brooklyn.test.WebAppMonitor; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.time.Duration; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.apache.brooklyn.entity.database.mysql.MySqlNode; +import org.apache.brooklyn.entity.group.DynamicCluster; +import org.apache.brooklyn.entity.java.JavaEntityMethods; +import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy; +import org.apache.brooklyn.policy.enricher.HttpLatencyDetector; + +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; + + +public class RebindWebClusterDatabaseExampleAppIntegrationTest extends RebindTestFixture<StartableApplication> { + + private static final Logger LOG = LoggerFactory.getLogger(RebindWebClusterDatabaseExampleAppIntegrationTest.class); + + private Location origLoc; + private List<WebAppMonitor> webAppMonitors = new CopyOnWriteArrayList<WebAppMonitor>(); + private ExecutorService executor; + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + super.setUp(); + origLoc = origManagementContext.getLocationRegistry().resolve("localhost"); + executor = Executors.newCachedThreadPool(); + webAppMonitors.clear(); + } + + @AfterMethod(alwaysRun=true) + @Override + public void tearDown() throws Exception { + for (WebAppMonitor monitor : webAppMonitors) { + monitor.terminate(); + } + if (executor != null) executor.shutdownNow(); + super.tearDown(); + } + + @Override + protected StartableApplication createApp() { + StartableApplication result = origManagementContext.getEntityManager().createEntity(EntitySpec.create(StartableApplication.class) + .impl(WebClusterDatabaseExampleApp.class) + .configure(DynamicCluster.INITIAL_SIZE, 2)); + return result; + } + + private WebAppMonitor newWebAppMonitor(String url, int expectedResponseCode) { + WebAppMonitor monitor = new WebAppMonitor(url) +// .delayMillis(0) FIXME Re-enable to fast polling + .expectedResponseCode(expectedResponseCode) + .logFailures(LOG); + webAppMonitors.add(monitor); + executor.execute(monitor); + return monitor; + } + + @Test(groups="Integration") + public void testRestoresSimpleApp() throws Exception { + origApp.start(ImmutableList.of(origLoc)); + + assertAppFunctional(origApp); + + String clusterUrl = checkNotNull(origApp.getAttribute(WebClusterDatabaseExampleApp.ROOT_URL), "cluster url"); + WebAppMonitor monitor = newWebAppMonitor(clusterUrl, 200); + + newApp = rebind(RebindOptions.create().terminateOrigManagementContext(true)); + assertAppFunctional(newApp); + + // expect no failures during rebind + monitor.assertNoFailures("hitting nginx url"); + monitor.terminate(); + } + + private void assertAppFunctional(StartableApplication app) throws Exception { + // expect standard config to (still) be set + assertNotNull(app.getConfig(WebClusterDatabaseExampleApp.WAR_PATH)); + assertEquals(app.getConfig(WebClusterDatabaseExampleApp.USE_HTTPS), Boolean.FALSE); + assertNotNull(app.getConfig(WebClusterDatabaseExampleApp.DB_SETUP_SQL_URL)); + + // expect entities to be there + MySqlNode mysql = (MySqlNode) Iterables.find(app.getChildren(), Predicates.instanceOf(MySqlNode.class)); + ControlledDynamicWebAppCluster web = (ControlledDynamicWebAppCluster) Iterables.find(app.getChildren(), Predicates.instanceOf(ControlledDynamicWebAppCluster.class)); + final NginxController nginx = (NginxController) Iterables.find(web.getChildren(), Predicates.instanceOf(NginxController.class)); + DynamicWebAppCluster webCluster = (DynamicWebAppCluster) Iterables.find(web.getChildren(), Predicates.instanceOf(DynamicWebAppCluster.class)); + Collection<Entity> appservers = web.getMembers(); + assertEquals(appservers.size(), 2); + String clusterUrl = checkNotNull(app.getAttribute(WebClusterDatabaseExampleApp.ROOT_URL), "cluster url"); + String dbUrl = checkNotNull(mysql.getAttribute(MySqlNode.DATASTORE_URL), "database url"); + final String expectedJdbcUrl = String.format("jdbc:%s%s?user=%s\\&password=%s", dbUrl, WebClusterDatabaseExampleApp.DB_TABLE, + WebClusterDatabaseExampleApp.DB_USERNAME, WebClusterDatabaseExampleApp.DB_PASSWORD); + + // expect web-app to be reachable, and wired up to database + HttpTestUtils.assertHttpStatusCodeEventuallyEquals(clusterUrl, 200); + for (Entity appserver : appservers) { + String appserverUrl = checkNotNull(appserver.getAttribute(Tomcat8Server.ROOT_URL), "appserver url of "+appserver); + + HttpTestUtils.assertHttpStatusCodeEventuallyEquals(appserverUrl, 200); + assertEquals(expectedJdbcUrl, appserver.getConfig(JavaEntityMethods.javaSysProp("brooklyn.example.db.url")), "of "+appserver); + } + + WebAppMonitor monitor = newWebAppMonitor(clusterUrl, 200); + + // expect auto-scaler policy to be there, and to be functional (e.g. can trigger resize) + AutoScalerPolicy autoScalerPolicy = (AutoScalerPolicy) Iterables.find(webCluster.policies(), Predicates.instanceOf(AutoScalerPolicy.class)); + + autoScalerPolicy.config().set(AutoScalerPolicy.MIN_POOL_SIZE, 3); + EntityAsserts.assertGroupSizeEqualsEventually(web, 3); + final Collection<Entity> webMembersAfterGrow = web.getMembers(); + + for (final Entity appserver : webMembersAfterGrow) { + Asserts.succeedsEventually(MutableMap.of("timeout", Duration.TWO_MINUTES), new Runnable() { + @Override public void run() { + String appserverUrl = checkNotNull(appserver.getAttribute(Tomcat8Server.ROOT_URL), "appserver url of "+appserver); + HttpTestUtils.assertHttpStatusCodeEquals(appserverUrl, 200); + assertEquals(expectedJdbcUrl, appserver.getConfig(JavaEntityMethods.javaSysProp("brooklyn.example.db.url")), "of "+appserver); + Asserts.assertEqualsIgnoringOrder(nginx.getAttribute(NginxController.SERVER_POOL_TARGETS).keySet(), webMembersAfterGrow); + }}); + } + + // expect enrichers to be there + Iterables.find(web.enrichers(), Predicates.instanceOf(HttpLatencyDetector.class)); + Iterable<Enricher> propagatorEnrichers = Iterables.filter(web.enrichers(), Predicates.instanceOf(Propagator.class)); + assertEquals(Iterables.size(propagatorEnrichers), 3, "propagatorEnrichers="+propagatorEnrichers); + + // Check we see evidence of the enrichers having an effect. + // Relying on WebAppMonitor to stimulate activity. + EntityAsserts.assertAttributeEqualsEventually(app, WebClusterDatabaseExampleApp.APPSERVERS_COUNT, 3); + EntityAsserts.assertAttributeChangesEventually(web, DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW); + EntityAsserts.assertAttributeChangesEventually(app, DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW); + EntityAsserts.assertAttributeChangesEventually(web, HttpLatencyDetector.REQUEST_LATENCY_IN_SECONDS_MOST_RECENT); + EntityAsserts.assertAttributeChangesEventually(web, HttpLatencyDetector.REQUEST_LATENCY_IN_SECONDS_IN_WINDOW); + + // Restore the web-cluster to its original size of 2 + autoScalerPolicy.config().set(AutoScalerPolicy.MIN_POOL_SIZE, 2); + EntityAsserts.assertGroupSizeEqualsEventually(web, 2); + + final Entity removedAppserver = Iterables.getOnlyElement(Sets.difference(ImmutableSet.copyOf(webMembersAfterGrow), ImmutableSet.copyOf(web.getMembers()))); + Asserts.succeedsEventually(new Runnable() { + @Override public void run() { + assertFalse(Entities.isManaged(removedAppserver)); + }}); + + monitor.assertNoFailures("hitting nginx url"); + monitor.terminate(); + } +}