Topology, Dependencies, and Management Policies
Applications written in YAML can similarly be written in Java. However, the YAML approach is recommended.
Define your Application Blueprint
The example below creates a three tier web service, composed of an Nginx load-balancer, a cluster of Tomcat app-servers, and a MySQL database. It is similar to the YAML policies example, but also includes the MySQL database to demonstrate the use of dependent configuration.
package com.acme.autobrick;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.policy.PolicySpec;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.api.sensor.EnricherSpec;
import org.apache.brooklyn.core.entity.AbstractApplication;
import org.apache.brooklyn.core.sensor.DependentConfiguration;
import org.apache.brooklyn.core.sensor.Sensors;
import org.apache.brooklyn.enricher.stock.Enrichers;
import org.apache.brooklyn.entity.database.mysql.MySqlNode;
import org.apache.brooklyn.entity.group.DynamicCluster;
import org.apache.brooklyn.entity.proxy.nginx.NginxController;
import org.apache.brooklyn.entity.webapp.tomcat.TomcatServer;
import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
import org.apache.brooklyn.policy.ha.ServiceFailureDetector;
import org.apache.brooklyn.policy.ha.ServiceReplacer;
import org.apache.brooklyn.policy.ha.ServiceRestarter;
import org.apache.brooklyn.util.time.Duration;
public class ExampleWebApp extends AbstractApplication {
@Override
public void init() {
AttributeSensor<Double> reqsPerSecPerNodeSensor = Sensors.newDoubleSensor(
"webapp.reqs.perSec.perNode",
"Reqs/sec averaged over all nodes");
MySqlNode db = addChild(EntitySpec.create(MySqlNode.class)
.configure(MySqlNode.CREATION_SCRIPT_URL, "https://bit.ly/brooklyn-visitors-creation-script"));
DynamicCluster cluster = addChild(EntitySpec.create(DynamicCluster.class)
.displayName("Cluster")
.configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(TomcatServer.class)
.configure(TomcatServer.ROOT_WAR,
"http://search.maven.org/remotecontent?filepath=org/apache/brooklyn/example/brooklyn-example-hello-world-sql-webapp/0.8.0-incubating/brooklyn-example-hello-world-sql-webapp-0.8.0-incubating.war")
.configure(TomcatServer.JAVA_SYSPROPS.subKey("brooklyn.example.db.url"),
DependentConfiguration.formatString("jdbc:%s%s?user=%s&password=%s",
DependentConfiguration.attributeWhenReady(db, MySqlNode.DATASTORE_URL),
"visitors", "brooklyn", "br00k11n"))
.policy(PolicySpec.create(ServiceRestarter.class)
.configure(ServiceRestarter.FAIL_ON_RECURRING_FAILURES_IN_THIS_DURATION, Duration.minutes(5)))
.enricher(EnricherSpec.create(ServiceFailureDetector.class)
.configure(ServiceFailureDetector.ENTITY_FAILED_STABILIZATION_DELAY, Duration.seconds(30))))
.policy(PolicySpec.create(ServiceReplacer.class))
.policy(PolicySpec.create(AutoScalerPolicy.class)
.configure(AutoScalerPolicy.METRIC, reqsPerSecPerNodeSensor)
.configure(AutoScalerPolicy.METRIC_LOWER_BOUND, 1)
.configure(AutoScalerPolicy.METRIC_UPPER_BOUND, 3)
.configure(AutoScalerPolicy.RESIZE_UP_STABILIZATION_DELAY, Duration.seconds(2))
.configure(AutoScalerPolicy.RESIZE_DOWN_STABILIZATION_DELAY, Duration.minutes(1))
.configure(AutoScalerPolicy.MAX_POOL_SIZE, 3))
.enricher(Enrichers.builder().aggregating(TomcatServer.REQUESTS_PER_SECOND_IN_WINDOW)
.computingAverage()
.fromMembers()
.publishing(reqsPerSecPerNodeSensor)
.build()));
addChild(EntitySpec.create(NginxController.class)
.configure(NginxController.SERVER_POOL, cluster)
.configure(NginxController.STICKY, false));
}
}
To describe each part of this:
- The application extends
AbstractApplication
. - It implements
init()
, to add its child entities. Theinit
method is called only once, when instantiating the entity instance. - The
addChild
method takes anEntitySpec
. This describes the entity to be created, defining its type and its configuration. - The
brooklyn.example.db.url
is a system property that will be passed to eachTomcatServer
instance. Its value is the database's URL (discussed below). - The policies and enrichers provide in-life management of the application, to restart failed instances and to replace those components that repeatedly fail.
- The
NginxController
is the load-balancer and reverse-proxy: by default, it round-robins to the ip:port of each member of the cluster configured as theSERVER_POOL
.
Dependent Configuration
Often a component of an application will depend on another component, where the dependency information is only available at runtime (e.g. it requires the IP of a dynamically provisioned component). For example, the app-servers in the example above require the database URL to be injected.
The "DependentConfiguration" methods returns a future (or a "promise" in the language of
some other programming languages): when the value is needed, the caller will block to wait for
the future to resolve. It will block only "at the last moment" when the value is needed (e.g.
after the VMs have been provisioned and the software is installed, thus optimising the
provisioning time). It will automatically monitor the given entity's sensor, and generate the
value when the sensor is populated.
The attributeWhenReady
is used to generate a configuration value that depends on the dynamic
sensor value of another entity - in the example above, it will not be available until that
MySqlNode.DATASTORE_URL
sensor is populated. At that point, the JDBC URL will be constructed
(as defined in the formatString
method, which also returns a future).