Getting Started

In this section we will study the example examples/user_providers/deploy_user_provider.py.

In MAD a deployment process is described under the form of an assembly of components. A component represents a software part to deploy. An assembly of components represents how components are connected through their dependencies.

The studied examples is composed of three different files:

  • provider.py the provider component
  • user.py the user component
  • deploy_user_provider the assembly of component de deploy and its automatic deployment

First, the description of a component deployment will be explained. Second, the description of an assembly will be detailed.

Component

The deployment of a component is described as a kind of petri net structure. The deployement of a component is composed of:

  • a set of places representing the different states of the deployment of the component,
  • a set of transitions representing the different actions to perform to move from one place to another,
  • a set of dependencies representing the different dependencies provided or required by external components and bound to internal places or transitions.

Lets focus on the component description provider.py:

from mad import *
from transitions import *


class Provider(Component):

  def create(self):
    places = [
      'waiting',
      'initiated',
      'configured',
      'started'
    ]

    transitions = {
      'init': ('waiting', 'initiated', self.init),
      'config': ('initiated', 'configured', self.config),
      'start': ('configured', 'started', self.start)
    }

    dependencies = {
      'ip': (DepType.DATA_PROVIDE, ['configured']),
      'service': (DepType.PROVIDE, ['started'])
    }

  def init(self):
      time.sleep(10)

  def config(self):
      time.sleep(5)

  def start(self):
      time.sleep(5)

One can note in this example that a component is a new class that inherits the already existing Component class of MAD. For this reason, it is needed to import mad objects.

It is asked to the MAD developer to instanciate the abstract method create inside her new Component class. In this method will be initiated three data structures:

  • places which is a simple list of strings. Each string representing the name of a given place.
  • transitions which is a dictionary representing the set of transitions.
  • dependencies which is a dictionary representing the set of dependencies of the component and their bindings to internal places and transitions.

Places

The Provider component contains four places, namely ‘waiting’, ‘initiated’, ‘configured’ and ‘started’.

Transitions

The Provider component contains three transitions defined within a dictionary. The key of the dictionary is a string representing the name of the transition. Thus, Provider transitions are ‘init’, ‘config’, and ‘start’. One can note that in this example transitions names are verbs. These names have been chosen to represent the fact that a transition is an action to perform.

Each key in the dictionary is associated to a triplet (source, destination, action, [arguments]) where:

  • source is the name of the source place of the transition
  • destination is the name of the destination place of the transition
  • action is a functor
  • arguments is an optional tuple containing the arguments to give to the functor

For example, the 'init' transition of Provider action is the internal method self.init, the source place is 'waiting', and the destination place is 'initiated'. In other words, 'init' is applied when moving from 'waiting' to 'initiated', by performing the action associated to the method init.

The init method is defined within the component.

def init(self):
    time.sleep(10)

Inside the transition method is implemented the action to perform within the transition. In our example (see transitions.py) all functions contains a sleep system call to emulate that the action takes sometime to run.

Note

Transitions functors can be defined outside the component definition as it will be shown in other examples. However, the component object contained in self will be usefull for many reasons, as in deploy_up_read_write.py for instance. For this reason, it is more convenient to define transitions methods inside the component.

Dependencies

Dependencies of a component are defined as a dictionary. Each key of dictionary elements represents the name of a given dependency. For example, Provider component has two dependencies 'ip' and 'service'.

Each key is associated to a pair (type, list_bindings) where:

  • type is the type of dependency
  • list_bindings is a list of transitions or places to which the dependency is bound

Four types of dependencies are available in MAD:

  • DepType.USE represents a use dependency, meaning that the component needs to use an external service provided by another component during its deployment process.
  • DepType.PROVIDE represents a provide dependency, meaning that the component provides to external components some services during its deployment process.
  • DepType.DATA_USE represents a data-use dependency, meaning that the component needs to use an external data provided by another component during its deployment process.
  • DepType.DATA_PROVIDE represents a data-provide dependency, meaning that the component provides to external components a data during its deployment process.

The only difference between a service and a data is that once delivered a data is always available while a service could be disabled.

Each dependency is bound to a list of places or transitions. DepType.USE and DepType.DATA_USE can be bound to transitions only. Actually, an action of a transition may need a service or a data provided by external components. On the opposite, DepType.PROVIDE and DepType.DATA_PROVIDE can be bound to places only. When reaching a place a component is able to provide a data or a service to external components.

For example, Provider contains two dependencies:

  • 'ip' is a DepType.DATA_PROVIDE dependency bound (i.e. used) to the transition 'configured'
  • 'service' is a DepType.PROVIDE dependency bound (i.e. used) to the transition 'started'

Note

One can note that more than one place can be bound to a DepType.PROVIDE or DepType.DATA_PROVIDE dependency. When more than one place is given, a group is created and will be illustrated in advanced examples of this documentation.

Attention

MAD is a low-level deployement tool. It is asked to the developer to precise dependencies between the different components, however, the developer has the responsability to handle real communications between components. The developer has the liberty to choose the best way to do it, through environment variables and ssh connections, through file transfers, through RPC calls etc. Many libraries are available in Python3. This will be illustrated in advanced examples.

Graphical representation

Here is a graphical representation of the Provider component.

Note

If you take a look at the formal Madeus model, you will notice differences with the definition of a MAD component. Indeed, MAD simplifies a bit the component definition by omiting the dock concept which is automatically inferred and handled by MAD.

User component

In the user-provide example another component is declared: user.py.

from mad import *
from transitions import *


class User(Component):

  def create(self):
      self.places = [
          'waiting',
          'initiated',
          'configured',
          'started'
      ]

      self.transitions = {
          'init': ('waiting', 'initiated', self.init),
          'config': ('initiated', 'configured', self.config),
          'start': ('configured', 'started', self.start)
      }

      self.dependencies = {
          'ipprov': (DepType.DATA_USE, ['init']),
          'service': (DepType.USE, ['config', 'start'])
      }

  def init(self):
      time.sleep(10)

  def config(self):
      time.sleep(5)

  def start(self):
      time.sleep(5)

With previous explanation you should be able to understand this component definition. The main difference with the Provider component is the type of dependencies: DepType.DATA_USE and DepType.USE. These dependencies are bound to transitions instead of places as they are used during deployment actions. The User component will be connected to the Provider component in the assembly. This will be detailed below.

Note

As for provide dependencies with multiple places, more than one transition can be bound to DepType.DATA_USE and DepType.USE. In this case more than one transition use the service or the data provided by external components.

Assembly of components

The file /path/to/MAD/examples/user_providers/deploy_user_provider.py contains the assembly of components as well as its run.

from mad import *

from provider import Provider
from user import User

if __name__ == '__main__':

  # Composant User
  user = User()

  # Composant Provider
  provider = Provider()

  ass = Assembly()
  ass.addComponent('user', user)
  ass.addComponent('provider', provider)
  ass.addConnection(user, 'ipprov', provider, 'ip')
  ass.addConnection(user, 'service', provider, 'service')

  mad = Mad(ass)
  mad.run()

Assembly description

In this example, the assembly is directly declared into the main function. This choice, of course, is left to the developer.

First, it is needed to import both mad and the components previously declared.

An assembly of components is composed of:

  • instanciations of Component objects
  • connections between components instances

Note

As an components and assemblies are defined in Python, the developer is free to pass additional arguments for object creations. This will be illustrated in advanced examples.

A Component, as previously detailed is a class. Its instanciation is a class instanciation.

user = User()

An assembly is also a class instanciation. The Assembly class is available in MAD.

ass = Assembly()

Instanciated components then need to be added to the assembly by calling addComponent. This method takes a string representing the name of the component, and the component object to add.

ass.addComponent('user', user)

Finally connections need to be added between components by calling the method addConnection of the assembly object.

ass.addConnection(user, 'ipprov', provider, 'ip')

A connection is composed of:

  • a first component to connect
  • the dependency name of the first component that will be connected
  • a second component to connect
  • the dependency name of the second component that will be connected

At this stage, it is important to understand that:

  • DepType.DATA_USE dependencies can only be connected to DepType.DATA_PROVIDE dependencies
  • DepType.USE dependencies can only be connected to DepType.PROVIDE dependencies

Note

If a bad connection is made MAD will print an error at runtime like this:

ERROR - you try to connect dependencies with incompatible types. DepType.USE
and DepType.DATA-USE should be respectively connected to DepType.PROVIDE and
DepType.DATA-PROVIDE dependencies.

Graphical representation

The graphical representation of the assembly of component is as follows:

Run the assembly

Once the assembly is created it can be run. To do so a Mad object is instanciated taking as argument the assembly object. The run methode of the Mad object is then called.

mad = Mad(ass)
mad.run()

To run this example you need to do

cd ``/path/to/MAD/examples/user_providers/``
python3 deploy_user_provider.py

The following output will be printed

[Mad] Assembly checked
[Mad] Start assembly deployment
[provider] Start transition 'init' ...
[provider] End transition 'init'
[provider] In place 'initiated'
[provider] Start transition 'config' ...
[provider] End transition 'config'
[provider] In place 'configured'
[Assembly] Enable connection (user, ipprov, provider, ip)
[user] Start transition 'init' ...
[provider] Start transition 'start' ...
[provider] End transition 'start'
[provider] In place 'started'
[Assembly] Enable connection (user, service, provider, service)
[user] End transition 'init'
[user] In place 'initiated'
[user] Start transition 'config' ...
[user] End transition 'config'
[user] In place 'configured'
[user] Start transition 'start' ...
[user] End transition 'start'
[user] In place 'started'
[Mad] Successful deployment

This output illustrates that the coordination of the deployment process is well handled by MAD. Let’s take a closer look.

First, one can note that the provider component instance is the first one to start its execution. This is due to the fact that the user component must wait its data dependency to start its first transition 'init'.

The data dependency of user is connected to the data provided in the place 'configured' of provider. Thus, the connection is enabled by MAD when provider reaches its 'configured' place. Then the user component can start its first transition.

When the component provider reaches its place 'started' MAD enable the service connection.

All this coordination process is handled by MAD as well as the parallelism between transitions. This coordination is possible because of the dependencies declared by the developer.