Merge "Event Notification pattern implemented"
This commit is contained in:
@@ -306,10 +306,41 @@ following template::
|
||||
Public is an optional parameter that specifies methods to be executed
|
||||
by direct triggering after deployment.
|
||||
|
||||
|
||||
.. _method_arguments:
|
||||
|
||||
Method arguments
|
||||
++++++++++++++++
|
||||
|
||||
Arguments are optional too, and are declared using the same syntax
|
||||
as class properties, except for the Usage attribute that is meaningless
|
||||
for method parameters. For example, arguments also have a contract and
|
||||
optional default::
|
||||
as class properties. Same as properties, arguments also have contracts and
|
||||
optional defaults.
|
||||
|
||||
Unlike class properties Arguments may have a different set of Usages:
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
:widths: 20 80
|
||||
:stub-columns: 0
|
||||
:class: borderless
|
||||
|
||||
* - | Value
|
||||
- | Explanation
|
||||
|
||||
* - | Standard
|
||||
- | Regular method argument. Holds a single value based on its contract.
|
||||
This is the default value for the Usage key.
|
||||
|
||||
* - | VarArgs
|
||||
- | A variable length argument. Method body sees it as a list of values,
|
||||
each matching a contract of the argument.
|
||||
|
||||
* - | KwArgs
|
||||
- | A keywrod-based argument, Method body sees it as a dict of values,
|
||||
with keys being valid keyword strings and values matching a contract
|
||||
of the argument.
|
||||
|
||||
Arguments example::
|
||||
|
||||
scaleRc:
|
||||
Arguments:
|
||||
@@ -317,6 +348,17 @@ optional default::
|
||||
Contract: $.string().notNull()
|
||||
- newSize:
|
||||
Contract: $.int().notNull()
|
||||
- rest:
|
||||
Contract: $.int()
|
||||
Usage: VarArgs
|
||||
- others:
|
||||
Contract: $.int()
|
||||
Usage: KwArgs
|
||||
|
||||
.. method_body:
|
||||
|
||||
Method body
|
||||
+++++++++++
|
||||
|
||||
The Method body is an array of instructions that get executed sequentially.
|
||||
There are 3 types of instructions that can be found in a workflow body:
|
||||
|
||||
@@ -248,14 +248,16 @@ Method arguments
|
||||
- `True` if argument has default value, `False` otherwise
|
||||
* - ``declaringMethod``
|
||||
- method - owner of argument
|
||||
* - ``usage``
|
||||
- argument's usage type. See :ref:`method_arguments` for details
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
- $firstArgument: $selectedMethod.arguments.first()
|
||||
# store argument's name
|
||||
- $argName = $firstArgument.name
|
||||
- $argName: $firstArgument.name
|
||||
# store owner's name
|
||||
- $methodName = $firstArgument.declaringMethod.name
|
||||
- $methodName: $firstArgument.declaringMethod.name
|
||||
- $log.info("Hi, my name is {a_name} ! My owner is {m_name}",
|
||||
a_name => $argName,
|
||||
m_name => $methodName)
|
||||
|
||||
104
meta/io.murano.applications/Classes/events.yaml
Normal file
104
meta/io.murano.applications/Classes/events.yaml
Normal file
@@ -0,0 +1,104 @@
|
||||
|
||||
Namespaces:
|
||||
=: io.murano.applications
|
||||
std: io.murano
|
||||
py: # empty, for python-originating exceptions
|
||||
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
|
||||
Name: Event
|
||||
|
||||
Properties:
|
||||
name:
|
||||
Contract: $.string().notNull()
|
||||
|
||||
Methods:
|
||||
|
||||
.init:
|
||||
Body:
|
||||
- $this._handlers: {}
|
||||
|
||||
subscribe:
|
||||
Arguments:
|
||||
- subscriber:
|
||||
Contract: $.class(std:Object).notNull()
|
||||
- methodName:
|
||||
Contract: $.string()
|
||||
Body:
|
||||
- If: not $methodName
|
||||
Then:
|
||||
- $methodName: format('handle{0}', $this.name.substring(0,1).toUpper()+
|
||||
$this.name.substring(1))
|
||||
|
||||
- Try:
|
||||
- $method: typeinfo($subscriber).methods.where($.name = $methodName).single()
|
||||
Catch:
|
||||
With: py:StopIteration
|
||||
Do:
|
||||
- Throw: NoHandlerMethodException
|
||||
Message: format('Unknown method {0} for
|
||||
receiver {1} to handle event {2}',
|
||||
$methodName, $subscriber, $this.name)
|
||||
|
||||
# This check ensures that the method passed as a handler has at least one
|
||||
# standard (i.e. non vararg or kwarg) argument which is supposed to be
|
||||
# "sender" object of the event.
|
||||
# Although having the sender in the handler is not always nessesary it's
|
||||
# still better to enforce its presence since it helps to prevent many
|
||||
# hard-to-debug errors
|
||||
- If: not $method.arguments.where($.usage=Standard).any()
|
||||
Then:
|
||||
- Throw: WrongHandlerMethodException
|
||||
Message: format("Method {0} of handler {1} should accept at least
|
||||
a 'sender' argument to handle event {2}",
|
||||
$methodName, $subscriber, $this.name)
|
||||
- $key: list($subscriber, $methodName)
|
||||
- $this._handlers[$key]: $this._handlers.get($key, 0) + 1
|
||||
|
||||
unsubscribe:
|
||||
Arguments:
|
||||
- subscriber:
|
||||
Contract: $.class(std:Object).notNull()
|
||||
- methodName:
|
||||
Contract: $.string()
|
||||
Body:
|
||||
- If: not $methodName
|
||||
Then:
|
||||
- $methodName: format('handle{0}', $this.name.substring(0,1).toUpper()+
|
||||
$this.name.substring(1))
|
||||
- $key: list($subscriber, $methodName)
|
||||
- If: $key in $this._handlers.keys()
|
||||
Then:
|
||||
- $this._handlers[$key]: $this._handlers[$key] - 1
|
||||
- If: $this._handlers[$key] = 0
|
||||
Then:
|
||||
- $this._handlers: $this._handlers.delete($key)
|
||||
|
||||
notify:
|
||||
Arguments:
|
||||
- sender:
|
||||
Contract: $.notNull()
|
||||
- args:
|
||||
Contract: $
|
||||
Usage: VarArgs
|
||||
- kwargs:
|
||||
Contract: $
|
||||
Usage: KwArgs
|
||||
Body:
|
||||
- $combinedArgs: list($sender) + $args
|
||||
- $this._handlers.keys().select(call($[1], $combinedArgs, $kwargs, $[0]))
|
||||
|
||||
notifyInParallel:
|
||||
Arguments:
|
||||
- sender:
|
||||
Contract: $.notNull()
|
||||
- args:
|
||||
Contract: $
|
||||
Usage: VarArgs
|
||||
- kwargs:
|
||||
Contract: $
|
||||
Usage: KwArgs
|
||||
Body:
|
||||
- $combinedArgs: list($sender) + $args
|
||||
- $this._handlers.keys().pselect(call($[1], $combinedArgs, $kwargs, $[0]))
|
||||
262
meta/io.murano.applications/Classes/tests/TestEvents.yaml
Normal file
262
meta/io.murano.applications/Classes/tests/TestEvents.yaml
Normal file
@@ -0,0 +1,262 @@
|
||||
Namespaces:
|
||||
=: io.murano.applications.tests
|
||||
tst: io.murano.test
|
||||
apps: io.murano.applications
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
|
||||
Name: TestSubscriber
|
||||
|
||||
Properties:
|
||||
called:
|
||||
Usage: Runtime
|
||||
Default: 0
|
||||
Contract: $.int()
|
||||
|
||||
lastSender:
|
||||
Usage: Runtime
|
||||
Contract: $
|
||||
|
||||
lastFoo:
|
||||
Usage: Runtime
|
||||
Contract: $
|
||||
|
||||
lastBar:
|
||||
Usage: Runtime
|
||||
Contract: $
|
||||
|
||||
Methods:
|
||||
handleFoo:
|
||||
Arguments:
|
||||
- sender:
|
||||
Contract: $.notNull()
|
||||
- foo:
|
||||
Contract: $
|
||||
- bar:
|
||||
Contract: $
|
||||
Body:
|
||||
- $this.called: $this.called + 1
|
||||
- $this.lastFoo: $foo
|
||||
- $this.lastBar: $bar
|
||||
- $this.lastSender: $sender
|
||||
|
||||
handleWithNoExtraArgs:
|
||||
Arguments:
|
||||
- sender:
|
||||
Contract: $.notNull()
|
||||
Body:
|
||||
- $this.called: $this.called + 1
|
||||
- $this.lastSender: $sender
|
||||
|
||||
noArgsMethod:
|
||||
Body:
|
||||
|
||||
varArgsKwArgsOnlyMethod:
|
||||
Arguments:
|
||||
- args:
|
||||
Usage: VarArgs
|
||||
Contract: $
|
||||
- kwargs:
|
||||
Usage: KwArgs
|
||||
Contract: $
|
||||
Body:
|
||||
|
||||
reset:
|
||||
Body:
|
||||
- $this.called: 0
|
||||
- $this.lastSender: null
|
||||
- $this.lastFoo: null
|
||||
- $this.lastBar: null
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
Name: TestEmitter
|
||||
|
||||
Properties:
|
||||
foo:
|
||||
Usage: Runtime
|
||||
Contract: $.class(apps:Event).notNull()
|
||||
Default:
|
||||
name: foo
|
||||
|
||||
Methods:
|
||||
onFoo:
|
||||
Body:
|
||||
- $this.foo.notify($this)
|
||||
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
|
||||
Name: TestEvents
|
||||
Extends: tst:TestFixture
|
||||
|
||||
Methods:
|
||||
testSubscribeAndNotify:
|
||||
Body:
|
||||
- $subscriber: new(TestSubscriber)
|
||||
- $event: new(apps:Event, name=>testEvent)
|
||||
- $event.subscribe($subscriber, handleFoo)
|
||||
- $event.notify($this, 'Hello Events', 42)
|
||||
- $this.assertEqual(1, $subscriber.called)
|
||||
- $this.assertEqual('Hello Events', $subscriber.lastFoo)
|
||||
- $this.assertEqual(42, $subscriber.lastBar)
|
||||
- $event.notify($this)
|
||||
- $this.assertEqual(2, $subscriber.called)
|
||||
|
||||
testNotifyWithNoSubscribers:
|
||||
Body:
|
||||
- $event: new(apps:Event, name=>testEvent)
|
||||
- $event.notify($this)
|
||||
|
||||
testUnableToNotifyWithUnexpectedArgs:
|
||||
Body:
|
||||
- $subscriber: new(TestSubscriber)
|
||||
- $event: new(apps:Event, name=>testEvent)
|
||||
- $event.subscribe($subscriber, handleFoo)
|
||||
- $event.notify($this)
|
||||
- $cought: false
|
||||
- Try:
|
||||
- $event.notify($this, qux=>1, baz=>2)
|
||||
Catch:
|
||||
With: 'yaql.language.exceptions.NoMatchingMethodException'
|
||||
Do:
|
||||
- $cought: true
|
||||
- $this.assertTrue($cought)
|
||||
|
||||
testUnsubscribe:
|
||||
Body:
|
||||
- $subscriber: new(TestSubscriber)
|
||||
- $event: new(apps:Event, name=>testEvent)
|
||||
- $event.subscribe($subscriber, handleFoo)
|
||||
- $event.notify($this)
|
||||
- $this.assertEqual(1, $subscriber.called)
|
||||
- $event.unsubscribe($subscriber, handleFoo)
|
||||
- $event.notify($this)
|
||||
- $this.assertEqual(1, $subscriber.called)
|
||||
|
||||
testSubscribeManyNotifyOnce:
|
||||
Body:
|
||||
- $subscriber: new(TestSubscriber)
|
||||
- $event: new(apps:Event, name=>testEvent)
|
||||
- $event.subscribe($subscriber, handleFoo)
|
||||
- $event.subscribe($subscriber, handleFoo)
|
||||
- $event.subscribe($subscriber, handleFoo)
|
||||
- $event.subscribe($subscriber, handleFoo)
|
||||
- $event.subscribe($subscriber, handleFoo)
|
||||
- $event.notify($this)
|
||||
- $this.assertEqual(1, $subscriber.called)
|
||||
|
||||
testUnsubscribeAsManyAsSubscribe:
|
||||
Body:
|
||||
- $subscriber: new(TestSubscriber)
|
||||
- $event: new(apps:Event, name=>testEvent)
|
||||
- $event.subscribe($subscriber, handleFoo)
|
||||
- $event.subscribe($subscriber, handleFoo)
|
||||
- $event.subscribe($subscriber, handleFoo)
|
||||
- $event.notify($this)
|
||||
- $this.assertEqual(1, $subscriber.called)
|
||||
- $subscriber.reset()
|
||||
- $event.unsubscribe($subscriber, handleFoo)
|
||||
- $event.notify($this)
|
||||
- $this.assertEqual(1, $subscriber.called)
|
||||
- $subscriber.reset()
|
||||
- $event.unsubscribe($subscriber, handleFoo)
|
||||
- $event.notify($this)
|
||||
- $this.assertEqual(1, $subscriber.called)
|
||||
- $subscriber.reset()
|
||||
- $event.unsubscribe($subscriber, handleFoo)
|
||||
- $event.notify($this)
|
||||
- $this.assertEqual(0, $subscriber.called)
|
||||
|
||||
testSubscribeSimple:
|
||||
Body:
|
||||
- $event: new(apps:Event, name=>foo)
|
||||
- $subscriber: new(TestSubscriber)
|
||||
- $event.subscribe($subscriber)
|
||||
- $event.notify($this)
|
||||
- $this.assertEqual(1, $subscriber.called)
|
||||
|
||||
testHandleWithNoExtraArgs:
|
||||
Body:
|
||||
- $event: new(apps:Event, name=>foo)
|
||||
- $subscriber: new(TestSubscriber)
|
||||
- $event.subscribe($subscriber, handleWithNoExtraArgs)
|
||||
- $event.notify($this)
|
||||
- $this.assertEqual(1, $subscriber.called)
|
||||
- $this.assertEqual($this, $subscriber.lastSender)
|
||||
|
||||
testUnableToSubscribeWithWrongMethod:
|
||||
Body:
|
||||
- $event: new(apps:Event, name=>testEvent)
|
||||
- $subscriber: new(TestSubscriber)
|
||||
- $cought: false
|
||||
- Try:
|
||||
- $event.subscribe($subscriber, handleBar)
|
||||
Catch:
|
||||
With: apps:NoHandlerMethodException
|
||||
Do:
|
||||
- $cought: true
|
||||
- $this.assertTrue($cought)
|
||||
|
||||
|
||||
testUnableToSubscribeWithWrongSimpleMethod:
|
||||
Body:
|
||||
- $event: new(apps:Event, name=>testEvent)
|
||||
- $subscriber: new(TestSubscriber)
|
||||
- $cought: false
|
||||
- Try:
|
||||
- $event.subscribe($subscriber)
|
||||
Catch:
|
||||
With: apps:NoHandlerMethodException
|
||||
Do:
|
||||
- $cought: true
|
||||
- $this.assertTrue($cought)
|
||||
|
||||
testUnableToSubscribeWithoutSender:
|
||||
Body:
|
||||
- $event: new(apps:Event, name=>testEvent)
|
||||
- $subscriber: new(TestSubscriber)
|
||||
- $cought: false
|
||||
- Try:
|
||||
- $event.subscribe($subscriber, noArgsMethod)
|
||||
Catch:
|
||||
With: apps:WrongHandlerMethodException
|
||||
Do:
|
||||
- $cought: true
|
||||
- $this.assertTrue($cought)
|
||||
|
||||
testUnableToSubscribeWithoutStandardArgs:
|
||||
Body:
|
||||
- $event: new(apps:Event, name=>testEvent)
|
||||
- $subscriber: new(TestSubscriber)
|
||||
- $cought: false
|
||||
- Try:
|
||||
- $event.subscribe($subscriber, varArgsKwArgsOnlyMethod)
|
||||
Catch:
|
||||
With: apps:WrongHandlerMethodException
|
||||
Do:
|
||||
- $cought: true
|
||||
- $this.assertTrue($cought)
|
||||
|
||||
testMultipleSubscribers:
|
||||
Body:
|
||||
- $subscriber1: new(TestSubscriber)
|
||||
- $subscriber2: new(TestSubscriber)
|
||||
- $event: new(apps:Event, name=>testEvent)
|
||||
- $event.subscribe($subscriber1, handleFoo)
|
||||
- $event.subscribe($subscriber2, handleFoo)
|
||||
- $event.notify($this, 'Hello Events', 42)
|
||||
- $this.assertEqual(1, $subscriber1.called)
|
||||
- $this.assertEqual('Hello Events', $subscriber1.lastFoo)
|
||||
- $this.assertEqual(42, $subscriber1.lastBar)
|
||||
- $this.assertEqual(1, $subscriber2.called)
|
||||
- $this.assertEqual('Hello Events', $subscriber2.lastFoo)
|
||||
- $this.assertEqual(42, $subscriber2.lastBar)
|
||||
|
||||
testEmitterWithEvent:
|
||||
Body:
|
||||
- $emitter: new(TestEmitter)
|
||||
- $subscriber: new(TestSubscriber)
|
||||
- $emitter.foo.subscribe($subscriber)
|
||||
- $emitter.onFoo()
|
||||
- $this.assertEqual(1, $subscriber.called)
|
||||
- $this.assertEqual($emitter, $subscriber.lastSender)
|
||||
|
||||
@@ -29,9 +29,11 @@ Classes:
|
||||
io.murano.applications.BaseTemplateReplicaProvider: replication.yaml
|
||||
io.murano.applications.TemplateReplicaProvider: replication.yaml
|
||||
io.murano.applications.CloneReplicaProvider: replication.yaml
|
||||
io.murano.applications.Event: events.yaml
|
||||
|
||||
io.murano.applications.tests.TestReplication: tests/TestReplication.yaml
|
||||
io.murano.applications.tests.TestCloneReplicaProvider: tests/TestCloneReplicaProvider.yaml
|
||||
io.murano.applications.tests.TestTemplateReplicaProvider: tests/TestTemplateReplicaProvider.yaml
|
||||
io.murano.applications.tests.ReplicationTarget: tests/TestTemplateReplicaProvider.yaml
|
||||
io.murano.applications.tests.NestedReplicationTarget: tests/TestTemplateReplicaProvider.yaml
|
||||
io.murano.applications.tests.TestEvents: tests/TestEvents.yaml
|
||||
|
||||
@@ -194,6 +194,12 @@ def argument_has_default(method_argument):
|
||||
return method_argument.has_default
|
||||
|
||||
|
||||
@specs.yaql_property(dsl_types.MuranoMethodArgument)
|
||||
@specs.name('usage')
|
||||
def argument_usage(method_argument):
|
||||
return method_argument.usage
|
||||
|
||||
|
||||
@specs.yaql_property(dsl_types.MuranoMethodArgument)
|
||||
@specs.name('declaring_method')
|
||||
def argument_owner(method_argument):
|
||||
@@ -236,6 +242,7 @@ def register(context):
|
||||
method_name, arguments, method_owner, method_invoke,
|
||||
types, package_name, package_version,
|
||||
argument_name, argument_has_default, argument_owner,
|
||||
argument_usage,
|
||||
cardinality, targets, inherited,
|
||||
get_meta
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user