## Problem

The current model in Brooklyn for executing effectors is to do it in parallel, 
without regard for already running instances of the same effector. This makes 
writing certain classes of YAML blueprints harder - use-cases which need to 
limit the number of concurrent executions. Currently this gets worked around on 
per-blueprint basis, shifting the burden of synchronizing/locking to the 
blueprint which have limited means to do it.

Some concrete examples:
  * A haproxy blueprint which needs to have at most one "update configuration" 
effector running - solved in bash by using flock
    
https://github.com/brooklyncentral/clocker/blob/9d3487198f426e8ebc6efeee94af3dc50383fa71/common/catalog/common/haproxy.bom
  * Some clusters have a limit on how many members can join at a time 
(Cassandra notably)
  * A DNS blueprint needs to make sure that updates to the records happen 
sequentially so no records get lost
  * To avoid API rate limits in certain services we need to limit how many 
operations we do at any moment - say we want to limit provisioning of entities, 
but not installing/launching them.

A first step in solving the above has been made in 
https://github.com/apache/brooklyn-server/pull/443 which adds 
"maxConcurrentChildCommands" to the DynamicCluster operations (start, resize, 
stop). This allows us to limit how many entities get created/destroyed by the 
cluster in parallel. The goal of this proposal is to extend it by making it 
possible to apply finer grained limits (say just on the launch step of the 
start effector) and to make it more general (not just start/stop in cluster but 
any effector).

## Proposed solution

Add functionality which allows external code (e.g. adjuncts) to plug into the 
lifecycle of entities **synchronously** and influence their behaviour. This 
will allow us to influence the execution of effectors on entities  and for this 
particular proposal to block execution until some condition is met.

## Possible approaches (alternatives)

### Effector execution notifications

Provide the functionality to subscribe callbacks to be called when an effector 
is about to execute on an entity. The callback has the ability to mutate the 
effector, for example by adding a wrapper task to ensure certain concurrency 
limits. A simpler alternative would be to add pre and post execution callbacks. 
For this to be useful we need to split big effectors into smaller pieces. For 
example the start effectors will be a composition of provision, install, 
customize, launch effectors.
The reason not to work at the task level is that tasks are anonymous so we 
can't really subscribe to them. To do that we'd need to add identifiers to them 
which essentially turns them into effectors.

### Add hooks to the existing effectors

We could add fixed pre and post hooks to the start/stop effectors which execute 
callbacks synchronously at key points around tasks.

--

Both of the above will allow us to plug additional logic into the lifecycle of 
entities, making it possible to block execution. For clusters we'd plug into 
the members' lifecycle and provide cluster-wide limits (say a semaphore shared 
by the members). For more complex scenarios we could name the synchronising 
entity explicitly, for example to block execution until a step in a separate 
entity is complete (say registering DNS records after provisioning but before 
launch application-wide).

## Examples

Here are some concrete examples which give you a taste of what it would look 
like (thanks Geoff for sharing these)


### Limit the number of entities starting at any moment in the cluster (but 
provision them in parallel)
services:
- type: cluster
  brooklyn.enrichers:
### plugs into the lifecycle provided callbacks and limits how many tasks can 
execute in parallel after provisioning the machines
### by convention concurrency is counted down at the last stage if not 
explicitly defined
  - type: org.apache.brooklyn.enricher.stock.LimitGroupTasksSemaphore
    brooklyn.config:
      stage: post.provisioning
      parallel.operation.size: auto # meaning the whole cluster; or could be 
integer e.g. 10 for 10-at-a-time
  brooklyn.config:
    initialSize: 50
    memberSpec:
      $brooklyn:entitySpec:
        type: cluste-member



---


### Use an third entity to control the concurrency
brooklyn.catalog:
  items:
  - id: provisionBeforeInstallCluster
    version: 1.0.0
    item:
      type: cluster
      id: cluster
      brooklyn.parameters:
      - name: initial.cluster.size
        description: Initial Cluster Size
        default: 50
      brooklyn.config:
        initialSize: $brooklyn:config("initial.cluster.size")
        memberSpec:
          $brooklyn:entitySpec:
            type: cluster-member
            brooklyn.enrichers:
            - type: org.apache.brooklyn.enricher.stock.AquirePermissionToProceed
              brooklyn.config:
                stage: post.provisioning
### Delegate the concurrency decisions to the referee entity
                authorisor: $brooklyn:entity("referee")
      brooklyn.children:
      - type: org.apache.brooklyn.entity.TaskRegulationSemaphore
        id: referee
        brooklyn.config:
          initial.value: 
$brooklyn:entity("cluster").config("initial.cluster.size") # or 1 for 
sequential execution


---

Some thoughts from Alex form previous discussions on how it would look like in 
YOML with initd-style effectors:

I’d like to have a semaphore on normal nodes cluster and for the 
⁠⁠⁠⁠launch⁠⁠⁠⁠ step each node acquires that semaphore, releasing when 
confirmed joined.  i could see a task you set in yaml eg if using the initdish 
idea

035-pre-launch-get-semaphore: { acquire-semaphore: { scope: $brooklyn:parent(), 
name: "node-launch" } }
040-launch: { ssh: "service cassandra start" }
045-confirm-service-up: { wait: { sensor: service.inCluster, timeout: 20m } }
050-finish-release-semaphore: semaphore-release

tasks of type ⁠⁠⁠⁠acquire-semaphore⁠⁠⁠⁠ would use (create if needed) a named 
semaphore against the given entity … but somehow we need to say when it should 
automatically be released (eg on failure) in addition to explicit release (the 
⁠⁠⁠⁠050⁠⁠⁠⁠ which assumes some scope, not sure how/if to implement that)

---

Thanks to Geoff who shared his thoughts on the subject, with this post based on 
them.

Svet.




Reply via email to