MuranoPL Reference

To develop applications, murano project refers to Murano Programming Language (MuranoPL). It is represented by easily readable YAML and YAQL languages. The sections below describe these languages.

YAML

YAML is an easily readable data serialization format that is a superset of JSON. Unlike JSON, YAML is designed to be read and written by humans and relies on visual indentation to denote nesting of data structures. This is similar to how Python uses indentation for block structures instead of curly brackets in most C-like languages. Also YAML may contain more data types as compared to JSON. See http://yaml.org/ for a detailed description of YAML.

MuranoPL is designed to be representable in YAML so that MuranoPL code could remain readable and structured. Usually MuranoPL files are YAML encoded documents. But MuranoPL engine itself does not deal directly with YAML documents, and it is up to the hosting application to locate and deserialize the definitions of particular classes. This gives the hosting application the ability to control where those definitions can be found (a file system, a database, a remote repository, etc.) and possibly use some other serialization formats instead of YAML.

MuranoPL engine relies on a host deserialization code when detecting YAQL expressions in a source definition. It provides them as instances of the YaqlExpression class rather than plain strings. Usually, YAQL expressions can be distinguished by the presence of $ (the dollar sign) and operators, but in YAML, a developer can always state the type by using YAML tags explicitly. For example:

1
2
3
4
5
 Some text - a string
 $.something() - a YAQL expression
 "$.something()" - a string because quotes are used
 !!str $ - a string because a YAML tag is used
 !yaql "text" - a YAQL expression because a YAML tag is used

YAQL

YAQL (Yet Another Query Language) is a query language that was also designed as a part of the murano project. MuranoPL makes an extensive use of YAQL. A description of YAQL can be found here.

Simply speaking, YAQL is the language for expression evaluation. The following examples are all valid YAQL expressions: 2 + 2, foo() > bar(), true != false.

The interesting thing in YAQL is that it has no built in list of functions. Everything YAQL can access is customizable. YAQL cannot call any function that was not explicitly registered to be accessible by YAQL. The same is true for operators. So the result of the expression 2 * foo(3, 4) completely depends on explicitly provided implementations of “foo” and “operator_*”.

YAQL uses a dollar sign ($) to access external variables, which are also explicitly provided by the host application, and function arguments. $variable is a syntax to get a value of the variable “$variable”, $1, $2, etc. are the names for function arguments. “$” is a name for current object: data on which an expression is evaluated, or a name of a single argument. Thus, “$” in the beginning of an expression and “$” in the middle of it can refer to different things.

By default, YAQL has a lot of functions that can be registered in a YAQL context. This is very similar to how SQL works but uses more Python-like syntax. For example: $.where($.myObj.myScalar > 5, $.myObj.myArray.len() > 0, and $.myObj.myArray.any($ = 4)).select($.myObj.myArray[0]) can be executed on $ = array of objects, and result in another array that is a filtration and projection of a source data.

Note

There is no assignment operator in YAQL, and = means comparison, the same what == means in Python.

As YAQL has no access to underlying operating system resources and is fully controllable by the host, it is secure to execute YAQL expressions without establishing a trust to the executed code. Also, because functions are not predefined, different methods can be accessible in different context. So, YAQL expressions that are used to specify property contracts are not necessarily valid in workflow definitions.

Common class structure

Here is a common template for class declarations. Note, that it is in the YAML format.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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 be
any scalar or data structure

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

Usage

Usage states the purpose of the property. This implies who and how can access it. The following usages are available:

Property
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.

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:
    Usage: Action
    Arguments:
       - list
       - of
       - arguments
    Body:
       - list
       - of
       - instructions

Action is an optional parameter that specifies methods to be executed by direct triggering after deployment.

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:

scaleRc:
  Arguments:
    - rcName:
        Contract: $.string().notNull()
    - newSize:
        Contract: $.int().notNull()

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.

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 the 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 the value to the first array entry of the x property
$.x.append(): value
appends the value to an array in the x property
$.x.insert(1): value
inserts the value into the position 1
$x: [$a, $b].delete(0)
sets $x to the array without 0 index item
$.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 optional
One-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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
    "?": {
        "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:

  1. 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 have Environment somewhere in a parent chain.
  2. 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.

MuranoPL Core Library

Some objects and actions can be used in several application deployments. All common parts are grouped into MuranoPL libraries. Murano core library is a set of classes needed in each deployment. Class names from core library can be used in the application definitions. This library is located under the meta directory.

Classes included in the Murano core library are as follows:

io.murano

io.murano.resources

io.murano.system

Class: Object

A parent class for all MuranoPL classes. It implements the initialize, setAttr, and getAttr methods defined in the pythonic part of the Object class. All MuranoPL classes are implicitly inherited from this class.

See also

Source Object.yaml file.

Class: Application

Defines an application itself. All custom applications must be derived from this class.

See also

Source Application.yaml file.

Class: SecurityGroupManager

Manages security groups during an application deployment.

See also

Source SecurityGroupManager.yaml file.

Class: Environment

Defines an environment in terms of the deployment process and groups all Applications and their related infrastructures. It also able to deploy them at once.

Environments is intent to group applications to manage them easily.

Environment class properties
Property Description Default usage
name An environment name. In
applications A list of applications belonging to an environment. In
agentListener A property containing the io.murano.system.AgentListener object that can be used to interact with Murano Agent. Runtime
stack A property containing a HeatStack object that can be used to interact with Heat. Runtime
instanceNotifier A property containing the io.murano.system.InstanceNotifier object that can be used to keep track of the amount of deployed instances. Runtime
defaultNetworks A property containing user-defined Networks (io.murano.resources.Network) that can be used as default networks for the instances in this environment. In
securityGroupManager A property containing the SecurityGroupManager object that can be used to construct a security group associated with this environment. Runtime

See also

Source Environment.yaml file.

Class: Instance

Defines virtual machine parameters and manages an instance lifecycle: spawning, deploying, joining to the network, applying security group, and deleting.

Instance class properties
Property Description Default usage
name An instance name. In
flavor An instance flavor defining virtual machine hardware parameters. In
image An instance image defining operation system. In
keyname Optional. A key pair name used to connect easily to the instance. In
agent Configures interaction with the Murano agent using io.murano.system.Agent. Runtime
ipAddresses A list of all IP addresses assigned to an instance. Out
networks Specifies the networks that an instance will be joined to. Custom networks that extend Network class can be specified. An instance will be connected to them and for the default environment network or flat network if corresponding values are set to True. Without additional configuration, instance will be joined to the default network that is set in the current environment. In
assignFloatingIp Determines if floating IP is required. Default is False. In
floatingIpAddress IP addresses assigned to an instance after an application deployment. Out
securityGroupName Optional. A security group that an instance will be joined to. In

See also

Source Instance.yaml file.

Resources

Instance class uses the following resources:

Agent-v2.template

Python Murano Agent template.

Note

This agent is supposed to be unified. Currently, only Linux-based machines are supported. Windows support will be added later.

linux-init.sh
Python Murano Agent initialization script that sets up an agent with valid information containing an updated agent template.
Agent-v1.template
Windows Murano Agent template.
windows-init.sh
Windows Murano Agent initialization script.

Class: Network

The basic abstract class for all MuranoPL classes representing networks.

See also

Source Network.yaml file.

Class: Logger

Logging API is the part of core library since Liberty release. It was introduced to improve debuggability of MuranoPL programs.

You can get a logger instance by calling a logger function which is located in io.murano.system namespace. The logger function takes a logger name as the only parameter. It is a common recommendation to use full class name as a logger name within that class. This convention avoids names conflicts in logs and ensures a better logging subsystem configurability.

Logger class instantiation:

$log: logger('io.murano.apps.activeDirectory.ActiveDirectory')
Log levels prioritized in order of severity
Level Description
CRITICAL Very severe error events that will presumably lead the application to abort.
ERROR Error events that might not prevent the application from running.
WARNING Events that are potentially harmful but will allow the application to continue running.
INFO Informational messages highlighting the progress of the application at the coarse-grained level.
DEBUG Detailed informational events that are useful when debugging an application.
TRACE Even more detailed informational events comparing to the DEBUG level.

There are several methods that fully correspond to the log levels you can use for logging events. They are debug, trace, info, warning, error, and critical.

Logging example:

$log.info('print my info message {message}', message=>message)

Logging methods use the same format rules as the YAQL format function. Thus the line above is equal to the:

$log.info('print my info message {message}'.format(message=>message))

To print an exception stacktrace, use the exception method. This method uses the ERROR level:

Try:
  - Throw: exceptionName
    Message: exception message
Catch:
With: exceptionName
As: e
Do:
  - $log.exception($e, 'something bad happen "{message}"', message=>message)

Note

You can configure the logging subsystem through the logging.conf file of the Murano Engine.

Murano actions

Murano action is a type of MuranoPL method. The differences from a regular MuranoPL method are:

  • Action is executed on deployed objects.
  • Action execution is initiated by API request, you do not have to call the method manually.

So murano action allows performing any operations on objects:

  • Getting information from the VM, like a config that is generated during the deployment
  • VM rebooting
  • Scaling

A list of available actions is formed during the environment deployment. Right after the deployment is finished, you can call action asynchronously. Murano engine generates a task for every action. Therefore, the action status can be tracked.

Note

Actions may be called against any MuranoPL object, including Environment, Application, and any other objects.

To mark a method as an action, use Usage: Action.

The following example shows an action that returns an archive with a configuration file:

exportConfig:
    Usage: Action
    Body:
      - $._environment.reporter.report($this, 'Action exportConfig called')
      - $resources: new(sys:Resources)
      - $template: $resources.yaml('ExportConfig.template')
      - $result: $.masterNode.instance.agent.call($template, $resources)
      - $._environment.reporter.report($this, 'Got archive from Kubernetes')
      - Return: new(std:File, base64Content => $result.content,
                    filename => 'application.tar.gz')

List of available actions can be found with environment details or application details API calls. It’s located in object model special data. Take a look at the following example:

Request: http://localhost:8082/v1/environments/<id>/services/<id>

Response:

{
  "name": "SimpleVM",
  "?": {
    "_26411a1861294160833743e45d0eaad9": {
      "name": "SimpleApp"
    },
    "type": "io.murano.apps.Simple",
    "id": "e34c317a-f5ee-4f3d-ad2f-d07421b13d67",
    "_actions": {
      "e34c317a-f5ee-4f3d-ad2f-d07421b13d67_exportConfig": {
        "enabled": true,
        "name": "exportConfig"
      }
    }
  }
}