.. _gettingstarted: 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``: .. code-block:: python 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. .. code-block:: python 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``. .. code-block:: python 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. .. code-block:: python 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. .. code-block:: python user = User() An assembly is also a class instanciation. The ``Assembly`` class is available in MAD. .. code-block:: python 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. .. code-block:: python ass.addComponent('user', user) Finally connections need to be added between components by calling the method ``addConnection`` of the assembly object. .. code-block:: python 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: .. code-block:: console 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. .. code-block:: python mad = Mad(ass) mad.run() To run this example you need to do .. code-block:: console cd ``/path/to/MAD/examples/user_providers/`` python3 deploy_user_provider.py The following output will be printed .. code-block:: console [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.