Repository: brooklyn-docs Updated Branches: refs/heads/master f858b171e -> 5676dda50
Update/improve docs for java entities - In particular, updates the docs to follow the archetype changes made in https://github.com/apache/brooklyn-dist/pull/10 Project: http://git-wip-us.apache.org/repos/asf/brooklyn-docs/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-docs/commit/44e812ba Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-docs/tree/44e812ba Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-docs/diff/44e812ba Branch: refs/heads/master Commit: 44e812ba782f613f3df28220c82b6831b1ecc19a Parents: a41e0d7 Author: Aled Sage <aled.s...@gmail.com> Authored: Mon Mar 7 20:42:00 2016 +0000 Committer: Aled Sage <aled.s...@gmail.com> Committed: Mon Mar 7 20:42:00 2016 +0000 ---------------------------------------------------------------------- guide/java/archetype.md | 62 +++-- guide/java/common-usage.md | 235 ++++++++++++------- guide/java/defining-and-deploying.md | 226 +++++++++++------- guide/java/entity.md | 26 +- guide/java/feeds.md | 203 ++++++++++++++++ guide/java/gist_generator/GistGenerator.java | 29 +++ .../java/gist_generator/GistGeneratorImpl.java | 47 ++++ .../java/gist_generator/GistGeneratorTest.java | 20 ++ .../gist_generator/GistGeneratorYamlTest.java | 39 +++ guide/java/gist_generator/gist_create_token.png | Bin 0 -> 390046 bytes guide/java/gist_generator/gist_generator.bom | 15 ++ guide/java/gist_generator/gist_grant_access.png | Bin 0 -> 411974 bytes guide/java/index.md | 19 +- guide/java/java_app/ExampleWebApp.java | 30 +++ guide/java/policies.md | 10 +- ...topology-dependencies-management-policies.md | 69 ------ guide/java/topology-dependencies.md | 49 ++++ 17 files changed, 804 insertions(+), 275 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/archetype.md ---------------------------------------------------------------------- diff --git a/guide/java/archetype.md b/guide/java/archetype.md index ba3bc0e..3bd3426 100644 --- a/guide/java/archetype.md +++ b/guide/java/archetype.md @@ -7,9 +7,13 @@ categories: [use, guide, defining-applications] ### Maven Archetype -Brooklyn includes a maven archetype, which can be used to create the project structure for a new application. +Brooklyn includes a maven archetype, which can be used to create the project structure for +developing a new Java entity, and generating the OSGi bundle for it. -This can be done interactively using: + +#### Generating the Project + +The archetype can be used interactively, by running: {% highlight bash %} $ mvn archetype:generate {% endhighlight %} @@ -33,32 +37,52 @@ $ mvn archetype:generate \ {% endhighlight %} This will create a directory with the artifact name (e.g. "autobrick" in the example above). -Note that if run from a directory containing a pom, it will also modify that pom to add this as a module! +Note that if run from a directory containing a pom, it will also modify that pom to add this as +a module! + +The project will contain an example Java entity. You can test this using the supplied unit tests, +and also replace it with your own code. + +The `README.md` file within the project gives further guidance. -The project will contain an example app. You can run this, and also replace it with your own -application code. + +#### Building To build, run the commands: {% highlight bash %} $ cd autobrick -$ mvn clean install assembly:assembly +$ mvn clean install {% endhighlight %} -The assembly command will build a complete standalone distribution archive in `target/autobrick-0.1.0-SNAPSHOT-dist.tar.gz`, -suitable for redistribution and containing `./start.sh` in the root. -An unpacked equivalent is placed in `target/autobrick-0.1.0-SNAPSHOT-dist`, -thus you can run the single-node sample locally with: +#### Adding to the Catalog -{% highlight bash %} -$ cd target/autobrick-0.1.0-SNAPSHOT-dist/autobrick-0.1.0-SNAPSHOT/ -$ ./start.sh launch --single -{% endhighlight %} +The build will produce an OSGi bundle in `target/autobrick-0.1.0-SNAPSHOT.jar`, suitable for +use in the [Brooklyn catalog]({{ site.path.guide }}/ops/catalog/) (using `brooklyn.libraries`). + +The project comes with a `sample.bom` file, located in `src/test/resources`. You will first have +to copy the target jar to a suitable location, and update the URL in `sample.bom` to point at that +jar. + +The command below will use the REST api to add this to the catalog of a running Brooklyn instance: + + curl -u admin:pa55w0rd http://127.0.0.1:8081/v1/catalog --data-binary @src/test/resources/sample.bom + +The YAML blueprint below shows an example usage of this blueprint: + + name: my sample + services: + - type: com.acme.MySampleInCatalog:1.0 + + +### Testing Entities -This `start.sh` script has all of the same options as the default `brooklyn` script, -including `./start.sh help` and the `--location` argument for `launch`, -with a couple of extra `launch` options for the sample blueprints in the archetype project: +The project comes with unit tests that demonstrate how to test entities, both within Java and +also using YAML-based blueprints. -- `./start.sh launch --single` will launch a single app-server instance -- `./start.sh launch --cluster` will launch a cluster of app-servers +A strongly recommended way is to write a YAML test blueprint using the test framework, and making +this available to anyone who will use your entity. This will allow users to easily run the test +blueprint in their own environment (simply by deploying it to their own Brooklyn server) to confirm +that the entity is working as expected. An example is contained within the project at +`src/test/resources/sample-test.yaml`. http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/common-usage.md ---------------------------------------------------------------------- diff --git a/guide/java/common-usage.md b/guide/java/common-usage.md index 8f0ed32..d0f7ec5 100644 --- a/guide/java/common-usage.md +++ b/guide/java/common-usage.md @@ -7,134 +7,197 @@ layout: website-normal ### Entity Class Hierarchy -By convention in Brooklyn the following words have a particular meaning, both as types (which extend ``Group``, which extends ``Entity``) and when used as words in other entities (such as ``TomcatFabric``): +By convention in Brooklyn the following words have a particular meaning: -- *Cluster* - a homogeneous collection of entities -- *Fabric* - a multi-location collection of entities, with one per location; often used with a cluster per location -- *Stack* - heterogeneous (mixed types of children) -- *Application* - user's entry point +* *Group* - a homogeneous grouping of entities (which need not all be managed by the same parent + entity) +* *Cluster* - a homogeneous collection of entities (all managed by the "cluster" entity) +* *Fabric* - a multi-location collection of entities, with one per location; often used with a cluster per location +* *Application* - a top-level entity, which can have one or more child entities. -<!--- -TODO ---> +The following constructs are often used for Java entities: -- *entity spec* defines an entity, so that one or more such entities can be created; often used by clusters/groups to define how to instantiate new children. -- *entity factories* are often used by clusters/groups to define how to instantiate new children. -- *traits* (mixins) providing certain capabilities, such as Resizable and Balanceable -- *Resizable* entities can re-sized dynamically, to increase/decrease the number of child entities. -- *Movable* entities can be migrated between *balanceable containers*. -- *Balanceable containers* can contain *movable* entities, where each contained entity is normally associated with - a piece of work within that container. +* *entity spec* defines an entity to be created; used to define a child entity, or often to + define the type of entity in a cluster. +* *traits* (mixins) providing certain capabilities, such as *Resizable* and *Startable*. +* *Resizable* entities can re-sized dynamically, to increase/decrease the number of child entities. + For example, scaling up or down a cluster. It could similarly be used to vertically scale a VM, + or to resize a disk. +* *Startable* indicates the effector to be executed on initial deployment (`start()`) and on + tear down (`stop()`). -### Off-the-Shelf Entities -brooklyn includes a selection of entities already available for use in applications, -including appropriate sensors and effectors, and in some cases include Cluster and Fabric variants. -(These are also useful as templates for writing new entities.) - -These include: +### Configuration -- **Web**: Tomcat, JBoss, Jetty (external), Play (external); nginx; GeoScaling -- **Data**: MySQL, Redis, MongoDB, Infinispan, GemFire (external) -- **Containers**: Karaf -- **Messaging**: ActiveMQ, Qpid, Rabbit MQ -- **PaaS**: Cloud Foundry, Stackato; OpenShift +Configuration keys are typically defined as static named fields on the Entity interface. These +define the configuration values that can be passed to the entity during construction. For +example: +{% highlight java %} +public static final ConfigKey<String> ROOT_WAR = new ConfigKeys.newStringConfigKey( + "wars.root", + "WAR file to deploy as the ROOT, as URL (supporting file: and classpath: prefixes)"); +{% endhighlight %} -### Sensors +One can optional define a `@SetFromFlag("war")`. This defines a short-hand for configuring the +entity. However, it should be used with caution - the long form defined in the constructor should +be meaningful and sufficient. Its use may be deprecated in a future release. -Sensors are typically defined as static named fields on the Entity subclass. These define the channels of events and activity that interested parties can track remotely. For example: -{% highlight java %} -/** a sensor for saying hi (illustrative), carrying a String value - which is typically the name of the person to whom we are saying hi */ -public static final Sensor<String> HELLO_SENSOR = ... +The type `AttributeSensorAndConfigKey<?>` can be used to indicate that a config key should be resolved, +and its value set as a sensor on the entity (when `ConfigToAttributes.apply(entity)` is called). + +A special case of this is `PortAttributeSensorAndConfigKey`. This is resolved to find an available +port (by querying the target location). For example, the value `8081+` means that then next available +port starting from 8081 will be used. + + +### Declaring Sensors + +Sensors are typically defined as static named fields on the Entity interface. These define +the events published by the entity, which interested parties can subscribe to. For example: +{% highlight java %} +AttributeSensor<String> MANAGEMENT_URL = Sensors.newStringSensor( + "crate.managementUri", + "The address at which the Crate server listens"); {% endhighlight %} -If the entity is local (e.g. to a policy) these can be looked up using ``get(Sensor)``. If it may be remote, you can subscribe to it through various APIs. -Sensors are used by operators and policies to monitor health and know when to invoke the effectors. The sensor data forms a nested map (i.e. JSON), which can be subscribed to through the ``ManagementContext``. +### Declaring Effectors -Often ``Policy`` instances will subscribe to sensor events on their associated entity or its children; these events might be an ``AttributeValueEvent`` â an attribute value being reported on change or periodically â or something transient such as ``LogMessage`` or a custom ``Event`` such as "TOO_HOT". +Effectors are the operations that an entity supports. There are multiple ways that an entity can +be defined. Examples of each are given below. -<!--- -TODO check classes above; is this much detail needed here? ---> +#### Effector Annotation + +A method on the entity interface can be annotated to indicate it is an effector, and to provide +metadata about the effector and its parameters. + +{% highlight java %} +@org.apache.brooklyn.core.annotation.Effector(description="Retrieve a Gist") +public String getGist(@EffectorParam(name="id", description="Gist id") String id); +{% endhighlight %} -Sensor values form a map-of-maps. An example of some simple sensor information is shown below in JSON: - - { - config : { - url : "jdbc:mysql://ec2-50-17-19-65.compute-1.amazonaws.com:3306/mysql" - status : "running" - } - workrate : { - msgsPerSec : 432 - } - } -Sensor values are defined as statics, which can be used to programmatically drive the subscription. +#### Static Field Effector Declaration -A range of `Feed` instances are available to simplify reading sensor information. +A static field can be defined on the entity to define an effector, giving metadata about that effector. + +{% highlight java %} +public static final Effector<String> EXECUTE_SCRIPT = Effectors.effector(String.class, "executeScript") + .description("invokes a script") + .parameter(ExecuteScriptEffectorBody.SCRIPT) + .impl(new ExecuteScriptEffectorBody()) + .build(); +{% endhighlight %} + +In this example, the implementation of the effector is an instance of `ExecuteScriptEffectorBody`. +This implements `EffectorBody`. It will be invoked whenever the effector is called. -### Effectors +#### Dynamically Added Effectors -Like sensors and config info, effectors are also static fields on the Entity class. These describe actions available on the entity, similar to methods. Their implementation includes details of how to invoke them, typically this is done by calling a method on the entity. Effectors are typically defined as follows: +An effector can be added to an entity dynamically - either as part of the entity's `init()` +or as separate initialization code. This allows the implementation of the effector to be shared +amongst multiple entities, without sub-classing. For example: {% highlight java %} -/** an effector which returns no value, - but which causes the entity to emit a HELLO sensor event */ -public static Effector<Void> SAY_HI = ... +Effector<Void> GET_GIST = Effectors.effector(Void.class, "createGist") + .description("Create a Gist") + .parameter(String.class, "id", "Gist id") + .buildAbstract(); + +public static void CreateGistEffectorBody implements EffectorBody<Void>() { + @Override + public Void call(ConfigBag parameters) { + // impl + return null; + } +} +@Override +public void init() { + getMutableEntityType().addEffector(CREATE_GIST, new CreateGistEffectorBody()); +} {% endhighlight %} -Effectors are invoked by calling ``invoke(SAY_HI, name:"Bob")`` or similar. The method may take an entity if context is not clear, and it takes parameters as named parameters or a Map. -Invocation returns a ``Task`` object (extending ``Future``). This allows the caller to understand progress and errors on the task, as well as ``Task.get()`` the return value. Be aware that ``task.get()`` is a blocking function that will wait until a value is available before returning. +### Effector Invocation + +There are several ways to invoke an effector programmatically: + +* Where there is an annotated method, simply call the method on the interface. -The management framework ensures that execution occurs on the machine where the ``Entity`` is mastered, with progress, result, and/or any errors reported back to the caller. It does this through the ``ExecutionManager`` which, where necessary, creates proxy ``Task`` instances. The ``ExecutionManager`` associates ``Tasks`` with the corresponding ``Entity`` so that these can be tracked externally (and relocated if the Entity is remastered to a different location). +* Call the `invoke` method on the entity, using the static effector declaration. For example: + `entity.invoke(CREATE_GIST, ImmutableMap.of("id", id));`. -It is worth noting that where a method corresponds to an effector, direct invocation of that method on an ``Entity`` will implicitly generate the ``Task`` object as though the effector had been invoked. For example, invoking ``Cluster.resize(int)``, where ``resize`` provides an ``Effector RESIZE``, will generate a ``Task`` which can be observed remotely. +* Call the utility method `org.apache.brooklyn.core.entity.Entities.invokeEffector`. For example: + `Entities.invokeEffector(this, targetEntity, CREATE_GIST, ImmutableMap.of("id", id));`. +When an effector is invoked, the call is intercepted to wrap it in a task. In this way, the +effector invocation is tracked - it is shown in the Activity view. -### Tasks and the Execution Manager +When `invoke` or `invokeEffector` is used, the call returns a `Task` object (which extends +`Future`). This allows the caller to understand progress and errors on the task, as well as +calling `task.get()` to retrieve the return value. Be aware that `task.get()` is a blocking +function that will wait until a value is available before returning. -The ``ExecutionManager`` is responsible for tracking simultaneous executing tasks and associating these with given **tags**. -Arbitrary tasks can be run by calling ``Task submit(Runnable)`` (similarly to the standard ``Executor``, although it also supports ``Callable`` arguments including Groovy closures, and can even be passed ``Task`` instances which have not been started). ``submit`` also accepts a few other named parameters, including ``description``, which allow additional metadata to be kept on the ``Task``. The main benefit then is to have rich metadata for executing tasks, which can be inspected through methods on the ``Task`` interface. -By using the ``tag`` or ``tags`` named parameters on ``submit`` (or setting ``tags`` in a ``Task`` that is submitted), execution can be associated with various categories. This allows easy viewing can be examined by calling -``ExecutionManager.getTasksWithTag(...)``. +### Tasks -The following example uses Groovy, with time delays abused for readability. brooklyn's test cases check this using mutexes, which is recommended. - - ExecutionManager em = [] - em.submit(tag:"a", description:"One Mississippi", { Thread.sleep(1000) }) - em.submit(tags:["a","b"], description:"Two Mississippi", { Thread.sleep(1000) }) - assert em.getTasksWithTag("a").size()==2 - assert em.getTasksWithTag("a").every { Task t -> !t.isDone() } - Thread.sleep(1500) - assert em.getTasksWithTag("a").size()==2 - assert em.getTasksWithTag("a").every { Task t -> t.isDone() } +_Warning: the task API may be changed in a future release. However, backwards compatibility +will be maintained where possible._ -It is possible to define `ParallelTask` and sequential `Task` instancess -and to specify inter-task relationships with `TaskPreprocessor` instances. -This allows building quite sophisticated workflows relatively easily. +When implementing entities and policies, all work done within Brooklyn is executed as Tasks. +This makes it trackable and visible to administrators. For the activity list to show a break-down +of an effector's work (in real-time, and also after completion), tasks and sub-tasks must be +created. + +In common situations, tasks are implicitly created and executed. For example, when implementing +an effector using the `@Effector` annotation on a method, the method invocation is automatically +wrapped as a task. Similarly, when a subscription is passed an event (e.g. when using +`SensorEventListener.onEvent(SensorEvent<T> event)`, that call is done inside a task. + +Within a task, it is possible to create and execute sub-tasks. A common way to do this is to +use `DynamicTasks.queue`. If called from within a a "task queuing context" (e.g. from inside an +effector implementation), it will add the task to be executed. By default, the outer task will not be +marked as done until its queued sub-tasks are complete. + +When creating tasks, the `TaskBuilder` can be used to create simple tasks or to create compound tasks +whose sub-tasks are to be executed either sequentially or in parallel. For example: + +{% highlight java %} +TaskBuilder.<Integer>builder() + .displayName("stdout-example") + .body(new Callable<Integer>() { public Integer call() { System.out.println("example"; } }) + .build(); +{% endhighlight %} -Continuing the example above, submitting a `SequentialTask` -or specifying ``em.setTaskPreprocessorForTag("a", SingleThreadedExecution.class)`` -will cause ``Two Mississippi`` to run after ``One Mississippi`` completes. +There are also builder and factory utilities for common types of operation, such as executing SSH +commands using `SshTasks`. -It is also possible to register `ScheduledTask` instances which run periodically. +A lower level way to submit tasks within an entity is to call `getExecutionContext().submit(...)`. +This automatically tags the task to indicate that its context is the given entity. -**The `Tasks` factory supplies a number of conveniences including builders to make working with tasks easier -and should be the entry point in most cases.** +An even lower level way to execute tasks (to be ignored except for power-users) is to go straight +to the `getManagementContext().getExecutionManager().submit(...)`. This is similar to the standard +Java `Executor`, but also supports more metadata about tasks such as descriptions and tags. +It also supports querying for tasks. There is also support for submitting `ScheduledTask` +instances which run periodically. +The `Tasks` and `BrooklynTaskTags` classes supply a number of conveniences including builders to +make working with tasks easier. ### Subscriptions and the Subscription Manager -In addition to scheduled tasks, tasks can triggered by subscriptions on other events including sensors. +Entities, locations, policies and enrichers can subscribe to events. These events could be +attribute-change events from other entities, or other events explicitly published by the entities. -To register low-level listeners to events, use the `SubscriptionManager` API. +A subscription is created by calling `subscriptions().subscribe(entity, sensorType, sensorEventListener)`. +The `sensorEventListener` will be called with the event whenever the given entity emits a sensor of +the given type. If `null` is used for either the entity or sensor type, this is treated as a +wildcard. +It is very common for a policy or enricher to subscribe to events, to kick off actions or to +publish other aggregated attributes or events. http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/defining-and-deploying.md ---------------------------------------------------------------------- diff --git a/guide/java/defining-and-deploying.md b/guide/java/defining-and-deploying.md index 59c0947..fda711b 100644 --- a/guide/java/defining-and-deploying.md +++ b/guide/java/defining-and-deploying.md @@ -5,121 +5,171 @@ layout: website-normal ## Intro -This walkthrough will set up a sample application which you can use as foundation for creating your own applications. +This walkthrough will set up a simple entity, add it to the catalog, and provision it. -The sample application is a three tier web service, composed of: +For illustration purposes, we will write an integration with [Github Gist](https://gist.github.com/), +with an effector to create new gists. -* an Nginx load-balancer -* a cluster of JBoss appservers -* a MySQL database -## Define your Application Blueprint +## Project Setup -An application blueprint is defined as a Java class, as follows: +Follow the instructions to create a new Java project using the [archetype](archetype.html), and +import it into your [favorite IDE]({{ site.path.guide }}/dev/env/ide/). This example assumes you +used the groupId `com.acme` and artifact id `autobrick`. + +First ensure you can build this project at the command line, using `mvn clean install`. + + +## Java Entity Classes + +For this particular example, we will use a third party Gist library, so will need to add that as +a dependency. Add the following to your `pom.xml` inside the `<dependencies>` section +(see [Maven](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html) +for more details): + +{% highlight xml %} +<dependency> + <groupId>org.eclipse.mylyn.github</groupId> + <artifactId>org.eclipse.egit.github.core</artifactId> + <version>2.1.5</version> +</dependency> +{% endhighlight %} + +Create a new Java interface, `GistGenerator`, to describe the entity's interface (i.e. the +configuration options, sensors, and effectors). The code below assumes you have created this +in the package `com.acme` for `src/main/java`. {% highlight java %} -public class ClusterWebServerDatabaseSample extends AbstractApplication { - @Override - public void init() { - MySqlNode mysql = addChild(EntitySpec.create(MySqlNode.class)); - ControlledDynamicWebAppCluster web = addChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)); - } -} +{% readj gist_generator/GistGenerator.java %} {% endhighlight %} -The file `ClusterWebServerDatabaseSample.java` in `src/main/java/com/acme/sample/brooklyn/sample/app/` -provides a template to follow. +To describe each part of this: +* The `@ImplementedBy` indicates the implementation class for this entity type - i.e. the class + to instantiate when an entity of this type is created. +* By extending `Entity`, we indicate that this interface is an Entity type. We could alternatively + have extended one of the other sub-types of Entity. +* The `OAUTH_KEY` is a configuration key - it is configuration that can be set on the entity when + it is being instantiated. +* The `@Effector` annotation indicates the the given method is an effector, so should be presented + and tracked as such. Execution of the effector is intercepted, to track it as a task and show its + execution in the Activity view. +* The `@EffectorParam` annotations give metadata about the effector's parameters. These will be + presented in users of the entity, e.g. when invoking the effector via the web-console. -## Deploying the Application +Note there is an alternative way of defining effectors - adding them to the entity dynamically, +discussed in the section [Dynamic Effectors](dynamic_effectors.html). -If you have not already done so, follow the instructions -[here]({{site.path.guide}}/ops/locations/) to create a `brooklyn.properties` -file containing credentials for your preferred cloud provider. +Next lets add the implementation. Create a new Java class named `GistGeneratorImpl`. -To launch this application, build the project and run the `start.sh` script in the resulting assembly: +{% highlight java %} +{% readj gist_generator/GistGeneratorImpl.java %} +{% endhighlight %} -{% highlight bash %} -$ mvn clean assembly:assembly +To describe each part of this: -$ cd target/brooklyn-sample-0.1.0-SNAPSHOT-dist/brooklyn-sample-0.1.0-SNAPSHOT/ +* Extends `AbstractEntity` - all entity implementations should extend this, or one of its + sub-types. +* Implements `GistGenerator`: this is the Entity type definition, so must be implemented. + Users of the entity will only refer to the interface; they will never be given an instance + of the concrete class - instead a [dynamic proxy](https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html) + is used (to allow remoting). +* `org.slf4j.Logger` is the logger used throughout Apache Brooklyn. +* Implements the `createGist` effector - we do not need to re-declare all the annotations. +* If no `oath.key` parameter was passed in, then use the configuration set on the entity. +* Use the third party library to create the gist. -$ ./start.sh launch \ - --app com.acme.sample.brooklyn.sample.app.ClusterWebServerDatabaseSample \ - --location jclouds:aws-ec2:eu-west-1 -{% endhighlight %} -(Amazon is used in this walkthrough, but lots of targets are supported, -including `--location localhost`, fixed IP addresses, and -everything supported by [jclouds](http://jclouds.org), from OpenStack to Google Compute.) +### Configuring GitHub + +First, create a github.com account, if you do not already have one. + +Before running the blueprint, we'll need to generate an access token that has permissions to +create a gist programmatically. + +First [create a new access token](https://help.github.com/articles/creating-an-access-token-for-command-line-use/) +that our blueprint will use to create a gist: -Your console will inform you that it has started a Brooklyn console at [http://localhost:8081](http://localhost:8081) +[![Create a new access key.](gist_generator/gist_create_token.png "Create a new access key")](gist_generator/gist_create_token.png) -[![Web Console]({{ page.url_basedir }}wt-starting-700.png "Web Console")](wt-starting.png) +Next, grant the token rights to create gists: -The management console provides a view on to the entities that launched, -including the hierarchy (appservers grouped into a cluster) and their locations. +[![Grant access.](gist_generator/gist_grant_access.png "Grant access")](gist_generator/gist_grant_access.png) -Brooklyn collects information from these entities ("sensors"), -aggregates these for clusters and other groups (using "enrichers"), -and exposes operations ("effectors") that can be performed on entities. -[![Web Console Details]({{ page.url_basedir }}wt-tree-jboss-sensors-700.png "Web Console Details")](wt-tree-jboss-sensors.png) +### Testing -## What Next? - -In addition to the sample project created by the archetype, with its README and -`assembly` build, you can find additional code related to this example included with Brooklyn as the ``simple-web-cluster`` example. -{% comment %} -described [in detail here]({{site.path.guide}}/use/examples/webcluster). -{% endcomment %} +The archetype project comes with example unit tests that demonstrate how to test entities, +both within Java and also using YAML-based blueprints. -For your applications, you might want to mix in other data stores, messaging systems, or on-line services including PaaS. -Brooklyn supports some of these out-of-the-box, including a wide-range of tools which it can use Whirr to provision, such as Hadoop. -But if you have something you don't see, -[let us know]({{site.path.website}}/community/) -- -we want to work with you to -[write a new entity]({{site.path.guide}}/java/entity.html) or -[policy]({{site.path.guide}}/java/policy.html) -and [contribute it]({{site.path.website}}/developers/how-to-contribute.html). +We will create a similar Java-based test for this blueprint. Create a new Java class named +`GistGeneratorTest` in the package `com.acme`, inside `src/test/java`. +You will need to substitute the github access token you generated in the previous section for +the placeholder text `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`. -<!-- +{% highlight java %} +{% readj gist_generator/GistGeneratorTest.java %} +{% endhighlight %} -Alternatively you can just add a ``main`` method to the application class as follows: +Similarly, we can write a test that uses the `GistGenerator` from a YAML blueprint. +Create a new Java class named `GistGeneratorYamlTest` in the package `com.acme`, +inside `src/test/java`. + +Again you will need to substitute the github access token you generated in the previous section for +the placeholder text `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`. See the section on +[externalised configuration]({{ site.path.guide }}/ops/externalized-configuration.html) +for how to store these credentials more securely. {% highlight java %} - 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); - - 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); - } +{% readj gist_generator/GistGeneratorYamlTest.java %} {% endhighlight %} -Compile and run this with the [``brooklyn-all`` jar]({{site.path.guide}}/start/download.html) on the classpath, -pointing at your favourite WAR on your filesystem. -(If the ``import`` packages aren't picked up correctly, -you can cheat by looking at [the file in Github](https://github.com/apache/brooklyn-library/blob/master/examples/simple-web-cluster/src/main/java/org/apache/brooklyn/demo/WebClusterDatabaseExample.java); -and you'll find a sample WAR which uses the database as configured above -[here](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).) - TODO example webapp url - -If you want to adventure beyond ``localhost`` (the default), -simply supply the your favourite cloud (e.g. ``aws-ec2:eu-west-1``) -with credentials set up as described [here]({{ site.path.guide }}/use/guide/management/index.html#startup-config). - ---> + +## Building the OSGi Bundle + +We will build this as an [OSGi Bundle](https://www.osgi.org/developer/architecture/) so that it +can be added to the Apache Brooklyn server at runtime, and so multiple versions of the blueprint +can be managed. + +The `mvn clean install` will automatically do this, creating a jar inside the `target/` sub-directory +of the project. This works by using the +[Maven Bundle Plugin](http://felix.apache.org/documentation/subprojects/apache-felix-maven-bundle-plugin-bnd.html) +which we get automatically by declaring the `pom.xml`'s parent as `brooklyn-downstream-parent`. + + +## Adding to the catalog + +Similar to the `sample.bom` entity that ships with the archetype, we will define a `.bom` file +to add our `GistGenerator` to the catalog. Substitute the URL below for your own newly built +artifact (which will be in the `target` sub-directory after running `mvn clean install`). + +{% highlight yaml %} +{% readj gist_generator/gist_generator.bom %} +{% endhighlight %} + +*Unfortunately the file org.eclipse.egit.github.core-2.1.5.jar (available on maven central) was +generated incorrectly as an OSGi bundle (there is a missing quotation mark from the manfest file, +making it invalid). The above YAML references a corrected version of this OSGi bundle, made +available for test purposes.* + +The command below will use the REST api to add this to the catalog of a running Brooklyn instance. +Substitute the credentials, URL and port for those of your server. + + curl -u admin:pa55w0rd https://127.0.0.1:8443/v1/catalog --data-binary @gist_generator.bom + + +## Using the blueprint + +The YAML blueprint below shows an example usage of this blueprint: + + name: my sample + services: + - type: example.GistGenerator:1.0 + brooklyn.config: + oauth.key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +Note the type name matches the id and version defined in the `.bom` file. + +You can now call the effector by any of the standard means - [web console]({{ site.path.guide }}/ops/gui/), +[REST api]({{ site.path.guide }}/ops/rest.html), or [Client CLI]({{ site.path.guide }}/ops/cli/). http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/entity.md ---------------------------------------------------------------------- diff --git a/guide/java/entity.md b/guide/java/entity.md index ec06347..d3ed881 100644 --- a/guide/java/entity.md +++ b/guide/java/entity.md @@ -7,10 +7,23 @@ title: Writing an Entity There are several ways to write a new entity: -* Write pure-java, extending existing base-classes and using utilities such as `HttpTool` and `BashCommands`. -* Write scripts, and configure (e.g. using YAML) a **`VanillaSoftwareProcess`**. -* Use Chef recipes, and wire these into the entity by using `ChefConfig` and `ChefLifecycleEffectorTasks`. -* Use an equivalent of Chef (e.g. Salt or Puppet; support for these is currently less mature than for Chef) +* For Unix/Linux, write YAML blueprints, for example using a **`VanillaSoftwareProcess`** and + configuring it with your scripts. +* For Windows, write YAML blueprints using **`VanillaWindowsProcess`** and configure the PowerShell + scripts. +* For composite entities, use YAML to compose exiting types of entities (potentially overwriting + parts of their configuration), and wire them together. +* Use **[Chef recipes]({{site.path.guide}}/yaml/chef)**. +* Use **[Salt formulas]({{site.path.guide}}/yaml/salt)**. +* Use **[Ansible playbooks]({{site.path.guide}}/yaml/ansible)**. +* Write pure-java, extending existing base-classes. For example, the `GistGenerator` + [example](defining-and-deploying.html). These can use utilities such as `HttpTool` and + `BashCommands`. +* Write pure-Java blueprints that extend `SoftwareProcess`. However, the YAML approach is strongly + recommended over this approach. +* Write pure-Java blueprints that compose together existing entities, for example to manage + a cluster. Often this is possible in YAML and that approach is strongly recommended. However, + sometimes the management logic may be so complex that it is easier to use Java. The rest of this section covers writing an entity in pure-java (or other JVM languages). @@ -24,8 +37,7 @@ Entities are created through the management context (rather than calling the constructor directly). This returns a proxy for the entity rather than the real instance, which is important in a distributed management plane. -All entity implementations inherit from `AbstractEntity`, -often through one of the following: +All entity implementations inherit from `AbstractEntity`, often through one of the following: * **`SoftwareProcessImpl`**: if it's a software process * **`VanillaJavaAppImpl`**: if it's a plain-old-java app @@ -48,6 +60,8 @@ Choose one (or more) as appropriate. ## Key Steps +*NOTE: Consider instead writing a YAML blueprint for your entity.* + So to get started: 1. Create your entity interface, extending the appropriate selection from above, http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/feeds.md ---------------------------------------------------------------------- diff --git a/guide/java/feeds.md b/guide/java/feeds.md new file mode 100644 index 0000000..88e005d --- /dev/null +++ b/guide/java/feeds.md @@ -0,0 +1,203 @@ +--- +title: Feeds +layout: website-normal +--- + +<!-- TODO old, needs work (refactoring!) and use of java_link --> + +### Feeds + +A `Feed` is used to populate an entity's sensors. A variety of feed types are available. + + +#### Persistence + +There are two ways to associate a feed with an entity. The recommended way is (within the +entity) to call `feeds().addFeed(...)`. This persists the feed: the feed will be automatically +added to the entity when the Brooklyn server restarts. It is important that all configuration +of the feed is persistable (e.g. not using any in-line anonymous inner classes to define +functions). + +The feed builders can be passed a `uniqueTag(...)`, which will be used to ensure that on +rebind there will not be multiple copied of the feed (e.g. if `rebind()` had already re-created +the feed). + +The second way is to just pass to the feed's builder the entity. When using this mechanism, +the feed will be wired up to the entity but it will not be persisted. In this case, it is +important that the entity's `rebind()` method recreates the feed. + + +#### Types of Feed + +##### HTTP Feed + +An `HttpFeed` polls over http(s). An example is shown below: + +{% highlight java %} +private HttpFeed feed; + +//@Override +protected void connectSensors() { + super.connectSensors(); + + feed = feeds().addFeed(HttpFeed.builder() + .period(200) + .baseUri(String.format("http://%s:%s/management/subsystem/web/connector/http/read-resource", host, port)) + .baseUriVars(ImmutableMap.of("include-runtime","true")) + .poll(new HttpPollConfig(SERVICE_UP) + .onSuccess(HttpValueFunctions.responseCodeEquals(200)) + .onError(Functions.constant(false))) + .poll(new HttpPollConfig(REQUEST_COUNT) + .onSuccess(HttpValueFunctions.jsonContents("requestCount", Integer.class))) + .build()); +} + +@Override +protected void disconnectSensors() { + super.disconnectSensors(); + if (feed != null) feed.stop(); +} +{% endhighlight %} + + +##### SSH Feed + +An SSH feed executes a command over ssh periodically. An example is shown below: + +{% highlight java %} +private SshFeed feed; + +//@Override +protected void connectSensors() { + super.connectSensors(); + + feed = feeds.addFeed(SshFeed.builder() + .machine(mySshMachineLachine) + .poll(new SshPollConfig(SERVICE_UP) + .command("rabbitmqctl -q status") + .onSuccess(new Function() { + public Boolean apply(SshPollValue input) { + return (input.getExitStatus() == 0); + }})) + .build()); +} + +@Override +protected void disconnectSensors() { + super.disconnectSensors(); + if (feed != null) feed.stop(); +} +{% endhighlight %} + + +##### Windows Performance Counter Feed + +This type of feed retrieves performance counters from a Windows host, and posts the values to sensors. + +One must supply a collection of mappings between Windows performance counter names and Brooklyn +attribute sensors. + +This feed uses WinRM to invoke the windows utility <tt>typeperf</tt> to query for a specific set +of performance counters, by name. The values are extracted from the response, and published to the +entity's sensors. An example is shown below: + +{% highlight java %} +private WindowsPerformanceCounterFeed feed; + +@Override +protected void connectSensors() { + feed = feeds.addFeed(WindowsPerformanceCounterFeed.builder() + .addSensor("\\Processor(_total)\\% Idle Time", CPU_IDLE_TIME) + .addSensor("\\Memory\\Available MBytes", AVAILABLE_MEMORY) + .build()); +} + +@Override +protected void disconnectSensors() { + super.disconnectSensors(); + if (feed != null) feed.stop(); +} +{% endhighlight %} + + +##### JMX Feed + +This type of feed queries over JMX to retrieve sensor values. This can query attribute +values or call operations. + +The JMX connection details can be automatically inferred from the entity's standard attributes, +or it can be explicitly supplied. + +An example is shown below: + +{% highlight java %} +private JmxFeed feed; + +@Override +protected void connectSensors() { + super.connectSensors(); + + feed = feeds().addFeed(JmxFeed.builder() + .period(5, TimeUnit.SECONDS) + .pollAttribute(new JmxAttributePollConfig<Integer>(ERROR_COUNT) + .objectName(requestProcessorMbeanName) + .attributeName("errorCount")) + .pollAttribute(new JmxAttributePollConfig<Boolean>(SERVICE_UP) + .objectName(serverMbeanName) + .attributeName("Started") + .onError(Functions.constant(false))) + .build()); +} + +Override +protected void disconnectSensors() { + super.disconnectSensors(); + if (feed != null) feed.stop(); +} +{% endhighlight %} + + + +##### Function Feed + +This type of feed periodically executes something to compute the attribute values. This +can be a `Callable`, `Supplier` or Groovy `Closure`. It must be persistable (e.g. not use +an in-line anonymous inner classes). + +An example is shown below: + +{% highlight java %} +public static class ErrorCountRetriever implements Callable<Integer> { + private final Entity entity; + + public ErrorCountRetriever(Entity entity) { + this.entity = entity; + } + + @Override + public Integer call() throws Exception { + // TODO your implementation... + return 0; + } +} + +private FunctionFeed feed; + +@Override +protected void connectSensors() { + super.connectSensors(); + + feed = feeds().addFeed(FunctionFeed.builder() + .poll(new FunctionPollConfig<Object, Integer>(ERROR_COUNT) + .period(500, TimeUnit.MILLISECONDS) + .callable(new ErrorCountRetriever(this)) + .onExceptionOrFailure(Functions.<Integer>constant(null)) + .build()); +} + +@Override +protected void disconnectSensors() { + super.disconnectSensors(); + if (feed != null) feed.stop(); +} +{% endhighlight %} http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/gist_generator/GistGenerator.java ---------------------------------------------------------------------- diff --git a/guide/java/gist_generator/GistGenerator.java b/guide/java/gist_generator/GistGenerator.java new file mode 100644 index 0000000..678d1e0 --- /dev/null +++ b/guide/java/gist_generator/GistGenerator.java @@ -0,0 +1,29 @@ +package com.acme; + +import java.io.IOException; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.ImplementedBy; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.annotation.Effector; +import org.apache.brooklyn.core.annotation.EffectorParam; +import org.apache.brooklyn.core.config.ConfigKeys; + +@ImplementedBy(GistGeneratorImpl.class) +public interface GistGenerator extends Entity { + + ConfigKey<String> OAUTH_KEY = ConfigKeys.newStringConfigKey("oauth.key", "OAuth key for creating a gist", + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + + @Effector(description="Create a Gist") + String createGist( + @EffectorParam(name="gistName", description="Gist Name", defaultValue="Demo Gist") String gistName, + @EffectorParam(name="fileName", description="File Name", defaultValue="Hello.java") String fileName, + @EffectorParam(name="gistContents", description="Gist Contents", defaultValue="System.out.println(\"Hello World\");") String gistContents, + @EffectorParam(name="oauth.key", description="OAuth key for creating a gist", defaultValue="") String oauthKey) throws IOException; + + @Effector(description="Retrieve a Gist") + public String getGist( + @EffectorParam(name="id", description="Gist id") String id, + @EffectorParam(name="oauth.key", description="OAuth key for creating a gist", defaultValue="") String oauthKey) throws IOException; +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/gist_generator/GistGeneratorImpl.java ---------------------------------------------------------------------- diff --git a/guide/java/gist_generator/GistGeneratorImpl.java b/guide/java/gist_generator/GistGeneratorImpl.java new file mode 100644 index 0000000..db1ecdb --- /dev/null +++ b/guide/java/gist_generator/GistGeneratorImpl.java @@ -0,0 +1,47 @@ +package com.acme; + +import java.io.IOException; +import java.util.Collections; + +import org.apache.brooklyn.core.entity.AbstractEntity; +import org.apache.brooklyn.util.text.Strings; +import org.eclipse.egit.github.core.Gist; +import org.eclipse.egit.github.core.GistFile; +import org.eclipse.egit.github.core.service.GistService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Iterables; + +public class GistGeneratorImpl extends AbstractEntity implements GistGenerator { + + private static final Logger LOG = LoggerFactory.getLogger(GistGeneratorImpl.class); + + @Override + public String createGist(String gistName, String fileName, String gistContents, String oathToken) throws IOException { + if (Strings.isBlank(oathToken)) oathToken = config().get(OAUTH_KEY); + + GistFile file = new GistFile(); + file.setContent(gistContents); + Gist gist = new Gist(); + gist.setDescription(gistName); + gist.setFiles(Collections.singletonMap(fileName, file)); + gist.setPublic(true); + + GistService service = new GistService(); + service.getClient().setOAuth2Token(oathToken); + LOG.info("Creating Gist: " + gistName); + Gist result = service.createGist(gist); + return result.getId(); + } + + @Override + public String getGist(String id, String oathToken) throws IOException { + if (Strings.isBlank(oathToken)) oathToken = config().get(OAUTH_KEY); + + GistService service = new GistService(); + service.getClient().setOAuth2Token(oathToken); + Gist gist = service.getGist(id); + return Iterables.getOnlyElement(gist.getFiles().values()).getContent(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/gist_generator/GistGeneratorTest.java ---------------------------------------------------------------------- diff --git a/guide/java/gist_generator/GistGeneratorTest.java b/guide/java/gist_generator/GistGeneratorTest.java new file mode 100644 index 0000000..9f5cc1a --- /dev/null +++ b/guide/java/gist_generator/GistGeneratorTest.java @@ -0,0 +1,20 @@ +package com.acme; + +import static org.testng.Assert.assertEquals; + +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.testng.annotations.Test; + +public class GistGeneratorTest extends BrooklynAppUnitTestSupport { + + @Test + public void testEntity() throws Exception { + String oathKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + GistGenerator entity = app.createAndManageChild(EntitySpec.create(GistGenerator.class)); + String id = entity.createGist("myGistName", "myFileName", "myGistContents", oathKey); + + String contents = entity.getGist(id, oathKey); + assertEquals(contents, "myGistContents"); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/gist_generator/GistGeneratorYamlTest.java ---------------------------------------------------------------------- diff --git a/guide/java/gist_generator/GistGeneratorYamlTest.java b/guide/java/gist_generator/GistGeneratorYamlTest.java new file mode 100644 index 0000000..2051993 --- /dev/null +++ b/guide/java/gist_generator/GistGeneratorYamlTest.java @@ -0,0 +1,39 @@ +package com.acme; + +import static org.testng.Assert.assertEquals; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; +import org.apache.brooklyn.core.entity.Entities; +import org.testng.annotations.Test; + +import com.google.common.base.Joiner; +import com.google.common.collect.Iterables; + +public class GistGeneratorYamlTest extends AbstractYamlTest { + + private String contents; + + @Test + public void testEntity() throws Exception { + String oathKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + + String yaml = Joiner.on("\n").join( + "name: my test", + "services:", + "- type: com.acme.GistGenerator", + " brooklyn.config:", + " oauth.key: "+oathKey); + + Entity app = createAndStartApplication(yaml); + waitForApplicationTasks(app); + + Entities.dumpInfo(app); + + GistGenerator entity = (GistGenerator) Iterables.getOnlyElement(app.getChildren()); + String id = entity.createGist("myGistName", "myFileName", "myGistContents", null); + + contents = entity.getGist(id, null); + assertEquals(contents, "myGistContents"); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/gist_generator/gist_create_token.png ---------------------------------------------------------------------- diff --git a/guide/java/gist_generator/gist_create_token.png b/guide/java/gist_generator/gist_create_token.png new file mode 100644 index 0000000..2a9f621 Binary files /dev/null and b/guide/java/gist_generator/gist_create_token.png differ http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/gist_generator/gist_generator.bom ---------------------------------------------------------------------- diff --git a/guide/java/gist_generator/gist_generator.bom b/guide/java/gist_generator/gist_generator.bom new file mode 100644 index 0000000..5be6b50 --- /dev/null +++ b/guide/java/gist_generator/gist_generator.bom @@ -0,0 +1,15 @@ +brooklyn.catalog: + id: example.GistGenerator + version: 1.0 + description: For programmatically generating GitHub Gists + displayName: Gist Generator + iconUrl: classpath:///sample-icon.png + itemType: template + libraries: + - http://search.maven.org/remotecontent?filepath=com/google/code/gson/gson/2.2.2/gson-2.2.2.jar + - http:/.developers.cloudsoftcorp.com/brooklyn/guide/java/gist_generator/org.eclipse.egit.github.core-2.1.5.jar + - http://developers.cloudsoftcorp.com/brooklyn/guide/java/gist_generator/autobrick-0.1.0-SNAPSHOT.jar + + item: + services: + - type: com.acme.GistGenerator http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/gist_generator/gist_grant_access.png ---------------------------------------------------------------------- diff --git a/guide/java/gist_generator/gist_grant_access.png b/guide/java/gist_generator/gist_grant_access.png new file mode 100644 index 0000000..48cc669 Binary files /dev/null and b/guide/java/gist_generator/gist_grant_access.png differ http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/index.md ---------------------------------------------------------------------- diff --git a/guide/java/index.md b/guide/java/index.md index e2b7984..0dce159 100644 --- a/guide/java/index.md +++ b/guide/java/index.md @@ -6,8 +6,9 @@ started-pdf-exclude: true children: - archetype.md - defining-and-deploying.md -- topology-dependencies-management-policies.md +- topology-dependencies.md - common-usage.md +- feeds.md - entity.md - entities.md - policies.md @@ -16,9 +17,21 @@ children: - entitlements.md --- -Java blueprints are much more powerful than YAML but is also rather more difficult. +Java blueprints are powerful, but also rather more difficult to write than YAML. Advanced Java skills are required. {% include list-children.html %} -Brooklyn makes it easy to describe the structure and management of sophisticated distributed applications, and then it makes it easy to launch them in a cloud, with on-going automated management. +The main uses of Java-based blueprints are: + +* Integration with a service's API (e.g. for an on-line DNS service). This could take advantage of + existing Java-based clients, or of Java's flexibility to chain together multiple calls. +* Complex management logic, for example when the best practices for adding/removing nodes from a + cluster is fiddly and has many conditionals. +* Where the developer has a strong preference for Java. Anything that can be done in YAML can be done in + the Java API. Once the blueprint is added to the catalog, the use of Java will be entirely hidden + from users of that blueprint. + +The Apache Brooklyn community is striving to make YAML-based blueprints as simple as possible - +if you come across a use-case that is hard to do in YAML then please let the community know. + http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/java_app/ExampleWebApp.java ---------------------------------------------------------------------- diff --git a/guide/java/java_app/ExampleWebApp.java b/guide/java/java_app/ExampleWebApp.java new file mode 100644 index 0000000..97b6910 --- /dev/null +++ b/guide/java/java_app/ExampleWebApp.java @@ -0,0 +1,30 @@ +package com.acme.autobrick; + +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.core.entity.AbstractApplication; +import org.apache.brooklyn.core.sensor.DependentConfiguration; +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; + +public class ExampleWebApp extends AbstractApplication { + + @Override + public void init() { + 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) + .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(TomcatServer.class) + .configure(TomcatServer.ROOT_WAR, + "wars.root: 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")))); + + addChild(EntitySpec.create(NginxController.class) + .configure(NginxController.SERVER_POOL, cluster)); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/policies.md ---------------------------------------------------------------------- diff --git a/guide/java/policies.md b/guide/java/policies.md index 4eb50fb..9781d6e 100644 --- a/guide/java/policies.md +++ b/guide/java/policies.md @@ -5,14 +5,16 @@ layout: website-normal --- Policies perform the active management enabled by Brooklyn. -They can subscribe to entity sensors and be triggered by them or they can run periodically. +They can subscribe to entity sensors and be triggered by them (or they can run periodically, +or be triggered by external systems). <!--- -TODO, clarify below, memebers of what? +TODO, clarify below, members of what? --> -Policies can add subscriptions to sensors on any entity. Normally a policy will subscribe to its related entity, to the child entities, and/or those entities which are members. +Policies can add subscriptions to sensors on any entity. Normally a policy will subscribe to its +associated entity, to the child entities, and/or to the members of a "group" entity. -When a policy runs it can: +Common uses of a policy include the following: * perform calculations, * look up other values, http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/topology-dependencies-management-policies.md ---------------------------------------------------------------------- diff --git a/guide/java/topology-dependencies-management-policies.md b/guide/java/topology-dependencies-management-policies.md deleted file mode 100644 index 249c99a..0000000 --- a/guide/java/topology-dependencies-management-policies.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -layout: website-normal -title: Topology, Dependencies, and Management Policies -title_in_menu: Topology, Dependencies, and Management Policies ---- -Of course in the real world, application deployments are more interesting; -they do things and need configuration. For instance you might need to: - -* specify a WAR file -* initialize the database -* tell the webapp servers where to find the database - -Let's show how these are done using Brooklyn. -We assume the WAR file and the database init script are accessible -on the classpath, but a range of URL formats is supported. -The "dependent inter-process configuration" -- giving the database's URL -to the webapps -- we'll do here with a JVM system property, -but you're free to use any mechanism you wish. - -Under the covers, ``attributeWhenReady`` is monitoring a sensor from MySQL -and generating a string to pass to the webapp software processes; ``formatString`` -is a similar utility that returns a string once all of its parts have been resolved. -Due to the use of futures, the Brooklyn webapp entities will automatically -block "at the last moment" when the value is needed -(but after e.g. the VMs have been provisioned, to speed things up). - -{% highlight java %} -public class ClusterWebServerDatabaseSample extends AbstractApplication { - @Override - public void init() { - MySqlNode mysql = addChild(EntitySpec.create(MySqlNode.class) - .configure(MySqlNode.CREATION_SCRIPT_URL, "classpath://visitors-database-setup.sql")); - - ControlledDynamicWebAppCluster web = addChild(EntitySpec.create(ControlledDynamicWebAppCluster.class) - .configure("memberSpec", EntitySpec.create(JBoss7Server.class) - .configure("httpPort", "8080+") - .configure("war", 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)))); - } -} -{% endhighlight %} - -We now see our app at the Nginx URL: - -[![Our Web App]({{ page.url_basedir }}wt-deployed-application-700.png "Screenshot of our Web App")](wt-deployed-application.png) - -Finally, we'll bring in some active management: we're going to monitor requests per second, -and scale out if this exceeds 100 up to a maximum of 5 servers. -This is a naively simple policy, but it shows Brooklyn's real metier, -running management policies for applications whose topology it knows. - -{% highlight java %} - web.getCluster().addPolicy(AutoScalerPolicy.builder(). - metric(DynamicWebAppCluster.AVERAGE_REQUESTS_PER_SECOND). - sizeRange(1, 5). - metricRange(10, 100). - build()); -{% endhighlight %} - -*Policies* in Brooklyn typically subscribe to sensors, perform some computation, and if necessary invoke effectors on entities. This is where the ability to group entities -becomes very useful -- policies can be attached to group entities, and groups themselves can be hierarchical. It's also handy that often Brooklyn creates the entities, -so it knows what the hierarchy is. - -Under the covers, this ``AutoScalerPolicy`` attaches to any ``Resizable`` entity (exposing a ``resize`` effector), and monitors a specified sensor (or function) attempting to keep it within healthy limits. A separate policy operates at the ``Controlled`` cluster to ensure the load-balancer is updated as the pool of web servers expands and contracts. - -Fire up a JMeter session (or other load testing tool) and blast the Nginx address. The auto-scaler policy will scale up the cluster. - http://git-wip-us.apache.org/repos/asf/brooklyn-docs/blob/44e812ba/guide/java/topology-dependencies.md ---------------------------------------------------------------------- diff --git a/guide/java/topology-dependencies.md b/guide/java/topology-dependencies.md new file mode 100644 index 0000000..a1b86ca --- /dev/null +++ b/guide/java/topology-dependencies.md @@ -0,0 +1,49 @@ +--- +layout: website-normal +title: Topology, Dependencies, and Management Policies +title_in_menu: 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: + +{% highlight java %} +{% readj java_app/ExampleWebApp.java %} +{% endhighlight %} + +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 `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 return 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).