Merge "Event Notification pattern implemented"

This commit is contained in:
Jenkins
2016-07-28 22:45:21 +00:00
committed by Gerrit Code Review
6 changed files with 424 additions and 5 deletions

View File

@@ -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:

View File

@@ -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)

View 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]))

View 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)

View File

@@ -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

View File

@@ -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
)