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. The init method is called only once, when instantiating the entity instance.
  • The addChild method takes an EntitySpec. 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 each TomcatServer 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 the SERVER_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).