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 componentuser.py
the user componentdeploy_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 aDepType.DATA_PROVIDE
dependency bound (i.e. used) to the transition'configured'
'service'
is aDepType.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 toDepType.DATA_PROVIDE
dependenciesDepType.USE
dependencies can only be connected toDepType.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.