Skip to content

Conversation

@grkvlt
Copy link
Member

@grkvlt grkvlt commented May 23, 2016

Adds a new DSL function $brooklyn:effector that takes two parameters, a string with the effector name and an optional map of the named effector arguments. Use in a blueprint wherever you can substitute a $brooklyn function, like this:

thing:
  $brooklyn:entity("other").effector("findInformation")
stuff:
  $brooklyn:effector:
  - "getData"
  - arg1: "value"
    arg2: 3.14159d
    arg3: $brooklyn:config("key")

See #153 for a complementary mechanism to create effectors in YAML directly.

Issues

It is hard to determine when the effector will be executed, but lazy evaluation can be effected by assigning the result of an attributeWhenReady() call to one of the effector arguments. It may be useful to have a version of the function that explicitly waits until some sensor has a specific value. Different predicates could be defined for start conditions, or implicit or explicit latches could be used. It would also be interesting to allow the tasks created when running an effector to be used in places like the install.command configuration of a VanillaSoftwareProcess in place of the SSH command executing task. This would require a version of the function that returned a Task<?> rather than a value directly, perhaps via another new function like $brooklyn:task()?

Further work is still required to complete this feature, including more tests and documentation.

}

@Override
public boolean equals(Object obj) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should equals include the args? Not sure when equals would actually be used in anger.

@aledsage
Copy link
Contributor

You said: "It is hard to determine when the effector will be executed". But it will follow the same rules as for other deferred config: when someone attempts to retrieve the config value, then it will be executed. So we can use all the standard launch.latch config etc to control this. That seems good enough to me. Are there other specific use-cases you're thinking of?

@aledsage
Copy link
Contributor

I'm not sure I follow when you say: "This would require a version of the function that returned a Task<?> rather than a value directly, perhaps via another new function like $brooklyn:task()?"

Can you elaborate on that use-case, and why you'd need this?

@aledsage
Copy link
Contributor

I was thinking we might configure this with:

$brooklyn:entity("other").effector("findInformation"):
  args:
    arg1: "value"
    arg2: 3.14159d
    arg3: $brooklyn:config("key")        

You could imagine (in the future) supporting more options such as timeout, non-blocking, on-error, etc. Putting the arguments inside "args" gives us better extensibility for the future, I think.

@grkvlt
Copy link
Member Author

grkvlt commented May 29, 2016

@aledsage I'll post a message to the Brooklyn dev list that answers your questions more completely. I like the idea for combining the arguments in an args map, allowing for expansion, as it fits with some other ideas I have, which I will also elaborate on in my post.

@geomacy
Copy link
Contributor

geomacy commented Jun 22, 2016

I have pulled this and was doing some tests around it. I noticed that given the test YAML

services:
- type: org.apache.brooklyn.core.test.entity.TestEntity
  id: entity1
  brooklyn.config:
    test.confName: $brooklyn:component("entity1").effector("myEffector")

and a test that does a getConfig, which should evaluate the DSL and invoke the effector:

        Assert.assertNull(testEntity.getConfig(TestEntity.CONF_NAME));

the expectation is that the call history on the test entity should contain one call to “myEffector”. In fact it contains two, because the config item is also evaluated during validation in construction of the entity, in a task kicked off from stack trace

"main@1" prio=5 tid=0x1 nid=NA runnable
  java.lang.Thread.State: RUNNABLE
at org.apache.brooklyn.util.core.task.ValueResolver.getMaybeInternal(ValueResolver.java:326)
at org.apache.brooklyn.util.core.task.ValueResolver.getMaybe(ValueResolver.java:257)
at org.apache.brooklyn.util.core.task.ValueResolver.get(ValueResolver.java:250)
at org.apache.brooklyn.core.objs.AbstractConfigurationSupportInternal.getNonBlocking(AbstractConfigurationSupportInternal.java:68)
at org.apache.brooklyn.core.config.ConfigConstraints.validateAll(ConfigConstraints.java:126)
at org.apache.brooklyn.core.config.ConfigConstraints.getViolations(ConfigConstraints.java:115)
at org.apache.brooklyn.core.config.ConfigConstraints.assertValid(ConfigConstraints.java:60)
at org.apache.brooklyn.core.objs.proxy.InternalEntityFactory.validateDescendantConfig(InternalEntityFactory.java:288)
at org.apache.brooklyn.core.objs.proxy.InternalEntityFactory.initEntityAndDescendants(InternalEntityFactory.java:306)
at org.apache.brooklyn.core.objs.proxy.InternalEntityFactory.createEntity(InternalEntityFactory.java:188)
at org.apache.brooklyn.core.mgmt.internal.LocalEntityManager.createEntity(LocalEntityManager.java:145)
at org.apache.brooklyn.camp.brooklyn.AbstractYamlTest.createAndStartApplication(AbstractYamlTest.java:128)
at org.apache.brooklyn.camp.brooklyn.AbstractYamlTest.createAndStartApplication(AbstractYamlTest.java:123)
at org.apache.brooklyn.camp.brooklyn.AbstractYamlTest.createAndStartApplication(AbstractYamlTest.java:115)
at org.apache.brooklyn.camp.brooklyn.EffectorsYamlTest.testWithAppEnricher(EffectorsYamlTest.java:39)

I've been looking at updating the ValueResolver to be a bit smarter about this but need to do a bit more testing on it before doing a PR.

@aledsage
Copy link
Contributor

aledsage commented Jul 4, 2016

@grkvlt @geomacy can we get Geoff's changes (that are specific to the effector invocation) into this PR, and merge it? Or should we close this PR and have Geoff open a new PR with his changes on top of this?

@geomacy
Copy link
Contributor

geomacy commented Jul 4, 2016

I've PRed my changes to Andrew's branch but can re-do this to master if preferred, once Andrew's is merged.

@grkvlt
Copy link
Member Author

grkvlt commented Jul 5, 2016

@geomacy I have merged your changes and rebased to master

@geomacy
Copy link
Contributor

geomacy commented Jul 8, 2016

Just noting the issues with this that we observed earlier, to do with effector invocations with parameters that are DSL expressions:

At present there is no ability to have multi line effector invocations, which would enable simple writing of effector calls where the parameters were themselves DSL expressions. See disabled test "testEffectorMultiLine" in EffectorsYamlTest.java.

Also when doing such invocations on one line there are problems. The test EffectorsYamlIntegrationTest fails. This appears to be a byproduct of 8bfff1635c Fix config().getNonBlocking for MapConfigKey. It is cancelling the subtask for the effector call during the validation phase of the blueprint (per attached), before it gets evaluated when it’s meant to be. See attached stack trace.

threads_report.txt

@aledsage-tmp
Copy link

@grkvlt @geomacy what else needs done before we merge this?

@geomacy
Copy link
Contributor

geomacy commented Aug 8, 2016

@aledsage iirc I think this can be merged in its present state but it would be worth recording a todo item for the points noted in #155 (comment).

if (targetEffector.isAbsentOrNull()) {
throw new IllegalArgumentException("Effector " + effectorName + " not found on entity: " + targetEntity);
}
if (cachedTask == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cachedTask is accessed from multiple threads. Should we use some locking to make sure we are creating it only once?

grkvlt and others added 5 commits February 28, 2017 14:25
this includes the following changes:

An "avoidSideEffects" capability in ValueResolver, to allow callers to
specify that any tasks which are marked as having side effects (a new
marker Interface) should be ignored.  The use case for this is illustrated
by its use here in  AbstractConfigurationSupportInternal in the
getNonBlocking method invoked by ConfigConstraints.validateAll.  This is
called during validation of the YAML, and evaluates all config key values,
which without this change would result in unwanted repeat executions of
effectors (and at a point when their target entities have not yet been started).
Similarly it is called during calculation of a hash for the extra salt for
VanillaSoftwareProcessSshDriver, again a situation where we do not want
effectors being called.

An additional effector() method on BrooklynDslCommon, to add support for
varArgs passing of the effector arguments, and a matching new method on
BrooklynDslDeferredSupplier.

The task created by ExecuteEffector is cached, in order to avoid unwanted
repeated executor invocations,  as illustrated in the test
testEffectorCalledOncePerConfigKey in EffectorsYamlTest.

test-app-with-effectors.yaml is removed as it's easier to see what's going
on with the YAML inline within the tests.

New tests are added in EffectorsYamlTest.

A new 'sequenceEffector' is added, just added directly  to TestEntity, in case
it happens to be useful for anyone else.
Also adds a formatString test that was used to get the right
structure for the entity test, may as well include it now
that it is written.
@grkvlt
Copy link
Member Author

grkvlt commented Feb 28, 2017

@drigodwin I had to pull in grkvlt#2 manually but it is there in 70d9aca so could you have a look please?

Copy link
Member

@drigodwin drigodwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good thankyou @grkvlt, merging now.

@asfgit asfgit merged commit 70d9aca into apache:master Feb 28, 2017
asfgit pushed a commit that referenced this pull request Feb 28, 2017
@ahgittin
Copy link
Contributor

Ouch -- I don't think this is a good idea. Apologies, I hadn't reviewed it because it was marked [WIP] -- in any case we shouldn't be merging WIPs :). My main objections are as follows:

  • invoking an effector is often a heavyweight task which we should do sparingly, only exposing in the DSL if there is a compelling use case (has this been presented?), and if so can we achieve it a better way?
  • the side-effect marker is designed to avoid that but it is likely not to be used often and so we risk assuming things aren't side-effecting when they are

And there is a minor but aggravating issue that there are other PRs I have being reviewed which clean up immediately semantics and this clobbers them. I think it will be easier to put this on top of those if we decide we need it.

@ahgittin
Copy link
Contributor

@drigodwin @grkvlt @aledsage @neykov WDYT? ^ could we revert?

@drigodwin
Copy link
Member

I agree this should probably not have been marked as a WIP @ahgittin . This initially came about through work on the CA entity in clocker, there was no good way to share keys between entities. I have no problem reverting it but I think it is a useful addition which we shouldn't throw away without discussion.

@ahgittin
Copy link
Contributor

right, so the use case is one entity wanting to get information from another. is there no way this can be accommodated using sensors? my philosophical objection is that we're introducing first-class support for a new class of dependency injection:

  • 0 - simplest - static: DEP.x is set to a constant
  • 1 - blocking - sensor: DEP.x can wait if a value isn't ready yet, ie it is a promise, $brooklyn:entity(SRC).attributeWhenReady(...), which once resolved is always taken as the value
  • 2 - triggering - effector: every lookup to DEP.x invokes a call somewhere eg $brooklyn:entity(SRC).effector(...)

a widespread use of 2 scares me and it's worth avoiding this if at all possible. it also means lookups aren't idempotent (which is why the SideEffecting marker is introduced here, but it isn't going to work.

could your problem with the CA entity be solved another way, if not with waiting on a sensor, by the config pointing at the source entity rather than the key value itself, and whenever it is accessed there is code which invokes the effector to get the key on that source entity?

@ahgittin
Copy link
Contributor

ahgittin commented Mar 1, 2017

@drigodwin @grkvlt Reply requested this morning. I'm going to revert at noon if I hear nothing. Something of this magnitude should have been discussed on list, and definitely should not have been merged while still apparently in a [WIP] state (no "probably" about it). I know there was related discussion on the list but don't think it was agreed we facilitate config lookup invoking effectors.

@aledsage
Copy link
Contributor

aledsage commented Mar 1, 2017

On balance, I agree with @ahgittin that we should revert this, and first merge #480

The changes in this PR were suggested on the mailing list as part of @grkvlt's message "[PROPOSAL] Enabling Effective Effectors" on 30/05/2016, but it wasn't properly discussed there.

After reverting this, we need to kick off that discussion on the mailing list again. We need to more clearly describe the use-case that motivated this change. We can then discuss the philosophy behind it.

For clarification, I believe the intent for the CA-server use-case was that the effector call happens only once; subsequent queries for the config key gets the value from that initial invocation (or blocks until the effector returns, if another thread is still executing it). But let's discuss that more on the mailing list.

@ahgittin
Copy link
Contributor

ahgittin commented Mar 1, 2017

Okay, the semantics that some-config: $brooklyn:effector("foo") means a single call to foo (rather than a call on each lookup) is reasonable, although non-obvious, but that means the call will happen once, very early in the entity's lifecycle (when validation occurs), so it's really a back-door way to trigger initialization actions.

The ideal way to do this I think would be to get on to the sequence-effector idea we've talked about. But in the absence of that a simple and I think more transparent way to do this would be to provide an EffectorOnStartAndStopEntity which you could do as follows:

- type: EffectorOnStartAndStopEntity
  id:  key-generator-util-entity
  start.effector:
    component: $brooklyn:entity("target")
    effector-name: make-key
    effector-params: [ 1, 2, 3]
    publish-result-as-sensor: my.key

then wherever you need the "populate config on startup" behaviour you add the above as a child that starts in parallel and you set the desired config to $brooklyn:entity("key-generator-util-entity").attributeWhenReady("my.key") .

@geomacy
Copy link
Contributor

geomacy commented Mar 1, 2017

+1 to reverting, but retaining the branch for possible future use after more discussion.

asfgit pushed a commit that referenced this pull request Mar 1, 2017
This reverts commit 4422617, reversing
changes made to 91a35e2.

As discussed at #155 after the commit;
in short, some (me) believe $brooklyn:effector is an anti-pattern,
and there are other (i think clearer) ways to achieve the intentions
@ahgittin
Copy link
Contributor

ahgittin commented Mar 1, 2017

reverted and pushed, with FormatStringIntegrationTest manually restored

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants