Change-Id: I72a497658cfdaea076e1a40330b113ae35381a5e Partial-Bug: #1603950
34 KiB
Common class structure
Here is a common template for class declarations. Note, that it is in the YAML format.
Name: class name
Namespaces: namespaces specification
Extends: [list of parent classes]
Properties: properties declaration
Methods:
methodName:
Arguments:
- list
- of
- arguments
Body:
- list
- of
- instructions
Thus MuranoPL class is a YAML dictionary with predefined key names,
all keys except for Name
are optional and can be omitted
(but must be valid if specified).
Class name
Class names are alphanumeric names of the classes. Traditionally, all class names begin with an upper-case letter symbol and are written in PascalCasing.
In MuranoPL all class names are unique. At the same time, MuranoPL
supports namespaces. So, in different namespaces you can have classes
with the same name. You can specify a namespace explicitly, like ns:MyName. If you omit the namespace
specification, MyName
is expanded using the default
namespace =:
. Therefore, MyName
equals
=:MyName
if =
is a valid namespace.
Namespaces
Namespaces declaration specifies prefixes that can be used in the class body to make long class names shorter.
Namespaces:
=: io.murano.services.windows
srv: io.murano.services
std: io.murano
In the example above, the srv: Something
class name is
automatically translated to
io.murano.services.Something
.
=
means the current namespace, so that
MyClass
means
io.murano.services.windows.MyClass
.
If the class name contains the period (.) in its name, then it is
assumed to be already fully namespace qualified and is not expanded.
Thus ns.Myclass
remains as is.
Note
To make class names globally unique, we recommend specifying a developer's domain name as a part of the namespace.
Extends
MuranoPL supports multiple inheritance. If present, the
Extends
section shows base classes that are extended. If
the list consists of a single entry, then you can write it as a scalar
string instead of an array. If you do not specify any parents or omit
the key, then the class extends io.murano.Object
. Thus,
io.murano.Object
is the root class for all class
hierarchies.
Properties
Properties are class attributes that together with methods create public class interface. Usually, but not always, properties are the values, and reference other objects that have to be entered in an environment designer prior to a workflow invocation.
Properties have the following declaration format:
propertyName:
Contract: property contract
Usage: property usage
Default: property default
Contract
Contract is a YAQL expression that says what type of the value is
expected for the property as well as additional constraints imposed on a
property. Using contracts you can define what value can be assigned to a
property or argument. In case of invalid input data it may be
automatically transformed to confirm to the contract. For example, if
bool value is expected and user passes any not null value it will be
converted to True
. If converting is impossible exception
ContractViolationException
will be raised.
The following contracts are available:
Operation | Definition |
---|---|
$.int() |
an integer value (may be null). String
values consisting of digits are converted to integers |
$.int().notNull() |
a mandatory integer |
$.string() $.string().notNull() |
a string. If the value is not a string, it
is converted to a string |
$.bool() $.bool().notNull() |
bools are true and false. 0 is
converted to false, other integers to true |
$.class(ns:ClassName) $.class(ns:ClassName).notNull() |
value must be a reference to an instance of
specified class name |
$.class(ns:ClassName,
ns:DefaultClassName) |
create instance of the
ns:DefaultClassName class if no instance
provided |
$.class(ns:Name).check($.p = 12) |
the value must be of the
ns:Name type and have the p property equal to
12 |
$.class(ns:Name).owned() |
a current object must be direct or indirect
owner of the value |
$.class(ns:Name).notOwned() |
the value must be owned by any object
except current one |
[$.int()] [$.int().notNull()] |
an array of integers. Similar to other
types. |
[$.int().check($ > 0)] |
an array of the positive integers (thus not
null) |
[$.int(), $.string()] |
an array that has at least two elements,
first is int and others are strings |
[$.int(), 2] [$.int(), 2, 5] |
an array of ints with at least 2 items an array of ints with at least 2 items, and maximum of 5 items |
{ A: $.int(), B: [$.string()] } |
the dictionary with the A key
of the int type and B - an array of strings |
$ [] {} |
any scalar or data structure as is any array any dictionary |
{ $.string().notNull(): $.int().notNull()
} |
dictionary string -> int |
A: StringMap $.string().notNull(): $ |
the dictionary with the A key
that must be equal to StringMap , and other keys beany scalar or data structure |
$.check($ in
$this.myStaticMethod()) |
the value must be equal to one of a member
of a list returned by static method of the class |
$.check($this.myStaticMethod($)) |
the static method of the class must return
true for the value |
In the example above property port
must be int value
greater than 0 and less than 65536; scope
must be a string
value and one of 'public', 'cloud', 'host' or 'internal', and
protocol
must be a string value and either 'TCP' or 'UDP'.
When user passes some values to these properties it will be checked that
values confirm to the contracts.
Namespaces:
=: io.murano.apps.docker
std: io.murano
Name: ApplicationPort
Properties:
port:
Contract: $.int().notNull().check($ > 0 and $ < 65536)
scope:
Contract: $.string().notNull().check($ in list(public, cloud, host, internal))
Default: private
protocol:
Contract: $.string().notNull().check($ in list(TCP, UDP))
Default: TCP
Methods:
getRepresentation:
Body:
Return:
port: $.port
scope: $.scope
protocol: $.protocol
Property usage
Usage states the purpose of the property. This implies who and how can access it. The following usages are available:
Value |
Explanation |
---|---|
In |
Input property. Values of such properties
are obtained from a user and cannot be modified in MuranoPL workflows.
This is the default value for the Usage key. |
Out |
A value is obtained from executing MuranoPL
workflow and cannot be modified by a user. |
InOut |
A value can be modified both by user and by
workflow. |
Const |
The same as In but once
workflow is executed a property cannot be changed neither by a user nor
by a workflow. |
Runtime |
A property is visible only from within
workflows. It is neither read from input nor serialized to a workflow
output. |
Static |
Property is defined on a class rather than
on an instance. See static_methods_and_properties for details. |
Config |
A property allows to have per-class
configuration. A value is obtained from the config file rather than from
the object model. These config files are stored in a special folder that
is configured in the [engine] section of the Murano config
file under the class_configs key. |
The usage attribute is optional and can be omitted (which implies
In
).
If the workflow tries to write to a property that is not declared with one of the types above, it is considered to be private and accessible only to that class (and not serialized to output and thus would be lost upon the next deployment). An attempt to read the property that was not initialized results in an exception.
Default
Default is a value that is used if the property value is not mentioned in the input object model, but not when it is set to null. Default, if specified, must conform to a declared property contract. If Default is not specified, then null is the default.
For properties that are references to other classes, Default can modify a default value of the referenced objects. For example:
p:
Contract: $.class(MyClass)
Default: {a: 12}
This overrides default for the a
property of
MyClass
for instance of MyClass
that is
created for this property.
Workflow
Workflows are the methods that describe how the entities that are represented by MuranoPL classes are deployed.
In a typical scenario, the root object in an input data model is of
the io.murano.Environment
type, and has the
deploy
method. This method invocation causes a series of
infrastructure activities (typically, a Heat stack modification) and the
deployment scripts execution initiated by VM agents commands. The role
of the workflow is to map data from the input object model, or a result
of previously executed actions, to the parameters of these activities
and to initiate these activities in a correct order.
Methods
Methods have input parameters, and can return a value to a caller. Methods are defined in the Workflow section of the class using the following template:
methodName:
Scope: Public
Arguments:
- list
- of
- arguments
Body:
- list
- of
- instructions
Public is an optional parameter that specifies methods to be executed by direct triggering after deployment.
Method arguments
Arguments are optional too, and are declared using the same syntax as class properties. Same as properties, arguments also have contracts and optional defaults.
Unlike class properties Arguments may have a different set of Usages:
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:
- rcName:
Contract: $.string().notNull()
- newSize:
Contract: $.int().notNull()
- rest:
Contract: $.int()
Usage: VarArgs
- others:
Contract: $.int()
Usage: KwArgs
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:
- Expressions,
- Assignments,
- Block constructs.
Method usage
Usage states the purpose of the method. This implies who and how can access it. The following usages are available:
Value |
Explanation |
---|---|
Runtime |
Normal instance method. |
Static |
Static method that does not require class
instance. See static_methods_and_properties for details. |
Extension |
Extension static method that extends some
other type. See extension_methods for details. |
Action |
Method can be invoked from outside (using
Murano API). This option is deprecated for the package format versions
> 1.3 in favor of Scope: Public and occasionally will be
no longer supported. See actions for details. |
The Usage
attribute is optional and can be omitted
(which implies Runtime
).
Method scope
The Scope
attribute declares method visibility. It can
have two possible values:
- Session - regular method that is accessible from anywhere in the current execution session. This is the default if the attribute is omitted;
- Public - accessible anywhere, both within the session and from outside through the API call.
The Scope
attribute is optional and can be omitted
(which implies Session
).
Expressions
Expressions are YAQL expressions that are executed for their side
effect. All accessible object methods can be called in the expression
using the $obj.methodName(arguments)
syntax.
Expression | Explanation |
---|---|
$.methodName() $this.methodName() |
invoke method 'methodName' on this (self)
object |
$.property.methodName() $this.property.methodName() |
invocation of method on object that is in
property |
$.method(1, 2, 3) |
methods can have arguments |
$.method(1, 2, thirdParameter =>
3) |
named parameters also supported |
list($.foo().bar($this.property),
$p) |
complex expressions can be
constructed |
Assignment
Assignments are single key dictionaries with a YAQL expression as a key and arbitrary structure as a value. Such a construct is evaluated as an assignment.
Assignment | Explanation |
---|---|
$x: value |
assigns value to the local
variable $x |
$.x: value $this.x: value |
assign value to the object's
property |
$.x: $.y |
copies the value of the property
y to the property x |
$x: [$a, $b] |
sets $x to the array of two
values: $a and $b |
$x: SomeKey: NestedKey: $variable |
structures of any level of complexity can be
evaluated |
$.x[0]: value |
assigns value to the first
array entry of the x property |
$.x: $.x.append(value) |
appends value to the array in
the x property |
$.x: $.x.insert(1, value) |
inserts value into position 1
of the array in the x property |
$x: list($a, $b).delete(0) |
sets $x to the list without the
item at index 0 |
$.x.key.subKey: value $.x[key][subKey]: value |
deep dictionary modification |
Block constructs
Block constructs control a program flow. They are dictionaries that have strings as all their keys.
The following block constructs are available:
Assignment | Explanation |
---|---|
Return: value |
Returns value from a method |
If: predicate() Then: - code - block Else: - code - block |
predicate() is a YAQL
expression that must be evaluated to True or
False The Else section is optionalOne-line code blocks can be written as scalars rather than an array. |
While: predicate() Do: - code - block |
predicate() must be evaluated
to True or False |
For: variableName In: collection Do: - code - block |
collection must be a YAQL
expression returning iterable collection or evaluatable array as in
assignment instructions, for example, [1, 2, $x] Inside a code block loop, a variable is
accessible as $variableName |
Repeat: Do: - code - block |
Repeats the code block specified number of
times |
Break: |
Breaks from loop |
Match: case1: - code - block case2: - code - block Value: $valExpression() Default: - code - block |
Matches the result of
$valExpression() against a set of possible values (cases).
The code block of first matched case is executed.If no case matched and the default key is
present than the Default code block get executed.The case values are constant values (not expressions). |
Switch: $predicate1(): - code - block $predicate2(): - code - block Default: - code - block |
All code blocks that have their predicate
evaluated to
True are executed, but the order of predicate
evaluation is not fixed.The
Default key is optional.If no predicate evaluated to True ,
the Default code block get executed. |
Parallel: - code - block Limit: 5 |
Executes all instructions in code block in a
separate green threads in parallel.
The limit is optional and means the maximum
number of concurrent green threads. |
Try: - code - block Catch: With: keyError As: e Do: - code - block Else: - code - block Finally: - code - block |
Try and Catch are keywords that represent
the handling of exceptions due to data or coding errors during program
execution. A
Try block is the block of code in which
exceptions occur. A Catch block is the block of code, that
is executed if an exception occurred.Exceptions are not declared in Murano PL. It means that exceptions of any types can be handled and generated. Generating of exception can be done with construct: Throw: keyError .The
Else is optional block.
Else block is executed if no exception occurred.The Finally also is optional. It's
a place to put any code that will be executed, whether the try-block
raised an exception or not. |
Notice, that if you have more then one block construct in your workflow, you need to insert dashes before each construct. For example:
Body:
- If: predicate1()
Then:
- code
- block
- While: predicate2()
Do:
- code
- block
Object model
Object model is a JSON serialized representation of objects and their properties. Everything you do in the OpenStack dashboard is reflected in an object model. The object model is sent to the Application catalog engine when the user decides to deploy the built environment. On the engine side, MuranoPL objects are constructed and initialized from the received Object model, and a predefined method is executed on the root object.
Objects are serialized to JSON using the following template:
{
"?": {
"id": "globally unique object ID (UUID)",
"type": "fully namespace-qualified class name",
"optional designer-related entries can be placed here": {
"key": "value"
}
},
"classProperty1": "propertyValue",
"classProperty2": 123,
"classProperty3": ["value1", "value2"],
"reference1": {
"?": {
"id": "object id",
"type": "object type"
},
"property": "value"
},
"reference2": "referenced object id"
}
Objects can be identified as dictionaries that contain the
?
entry. All system fields are hidden in that entry.
There are two ways to specify references:
reference1
as in the example above. This method allows inline definition of an object. When the instance of the referenced object is created, an outer object becomes its parent/owner that is responsible for the object. The object itself may require that its parent (direct or indirect) be of a specified type, like all applications require to haveEnvironment
somewhere in a parent chain.- Referring to an object by specifying other object ID. That object
must be defined elsewhere in an object tree. Object references
distinguished from strings having the same value by evaluating property
contracts. The former case would have
$.class(Name)
while the later - the$.string()
contract.