Madeus

Code

The Madeus deployment model has been implemented in Python. Its implementation is called MAD for Madeus Application Deployer.

Attention

Please note that the lab described by this documentation works with a precise version of MAD. The lab code includes this MAD version at https://gitlab.inria.fr/Madeus/mad-openstack

Below is described the Installation and Getting Started for the specific versio of MAD indicated above.

Note

The official and up-to-date source code of MAD is available at https://gitlab.inria.fr/Madeus/mad

Installation

MAD has different dependencies as listed in requirements.txt. A simple way to install them is to use pip. To that end, python3-pip must be installed on your system (the installation procedure depends on your system - for instance, type sudo apt-get install python3-pip for debian-based distributions).

Before going further, please be sure python3-pip is installed. This can be checked by typing: which pip3, which should return the path to python3-pip.

Once python3-pip is installed, you can run make install_deps to install the dependencies in the user home directory (required to experiment on testbeds like Grid5000). You can afterward remove those dependencies by typing make remove_deps to clean your system.

Getting Started

Component definition

Here is the definition of a component which describes the deployment of a MySQL server, contained in the file ./components/hello_world/mysql.py:

from mad import PetriNet

class MySQL(object):
   """ Define and initialize a component of type MySQL:
   1. Define our component's places,
   2. Define places' transitions with their related callbacks,
   3. Define our component's ports with their methods,
   4. Initialize the component.
   """

   # 1. Here, we define four places for our component:
   places = [
     'initiated',
     'installed',
     'configured',
     'running'
   ]

   # 2. We now define their transitions, with their associated callbacks:
   transitions = [
     {'name': 'install', 'source': 'initiated', 'dest': 'installed'},
     {'name': 'configure', 'source': 'installed', 'dest': 'configured'},
     {'name': 'run', 'source': 'configured', 'dest': 'running' }
   ]

   # Callbacks can be mapped to <transition> when defined as 'func_<transition>':
   def func_install(self):
     print("(1/3) Installation of the Component MySQL")
   def func_configure(self):
     print("(2/3) Configuration of the Component MySQL")
   def func_run(self):
     print("(3/3) Component MySQL is running")

   # 3. We then define two ports, their linked elements, and their callbacks:
   ports = [
     {'name': 'mysql', 'inside_link': 'running'},
     {'name': 'mysql_address', 'inside_link': 'initiated'}
   ]

   # Methods can be mapped to <port> when defined as '<port>':
   def mysql(self):
     # Returns 'True' if the place 'running' is validated:
     return self.net.get_place('running').state
   def mysql_address(self):
     # Returns 'data' if the place 'initiated' is validated:
     return self.data if self.net.get_place('initiated').state else None

   # Data related to the dataflow port
   data = {
     'mysql_ip': '192.168.0.1',
     'mysql_port': '3306'
   }

   # 4. Initialize our component as a PetriNet, with the related places,
   # transitions, ports and set the initial place:
   def __init__(self):
     self.net = PetriNet(self, self.places, self.transitions, self.ports,
            initial='initiated')

Explore the component

To explore the previous MySQL component, run python3:

from components.hello_world.mysql import MySQL

# Instantiate the MySQL component:
>>> mysql = MySQL()

# Get the component's places:
>>> mysql.places
['initiated', 'installed', 'configured', 'running']

# Get the component's transitions:
>>> mysql.transitions
[{'dest': 'installed', 'name': 'install', 'source': 'initiated'},
{'dest': 'configured', 'name': 'configure', 'source': 'installed'},
{'dest': 'running', 'name': 'run', 'source': 'configured'}]

# Get the component's ports:
>>> mysql.ports
[{'inside_link': 'running', 'name': 'mysql'},
{'inside_link': 'initiated', 'name': 'mysql_address'}

Play manually with the component

# Initialize the component (activate the initial state)
>>> mysql.net.initialize()

# Get the current place in the PetriNet:
>>> mysql.current_places
{'initiated'}

# Get the possible transitions:
mysql.net.get_current_transitions()
['install']

# Run the transition:
>>> mysql.install()
(1/3) Installation of the Component MySQL

# Check the new position:
>>> mysql.current_places
{'installed'}

>>> mysql.configure()
(2/3) Configuration of the Component MySQL

>>> mysql.net.get_current_transitions()
['run']

Automatic deployment of the component

An assembly object can be used to automatically deploy components. To depict assemblies, we are going to use a component with parallel transitions. Such component, based on the previous one, can be found in ./components/hello_world/parallel_mysql.py. Open this file to understand the component’s internals. Then run the following with python3:

from components.hello_world.parallel_mysql import MySQL
from assembly import Assembly

# Initialize a MySQL component:
>>> mysql = MySQL()

# Initialize an assembly to manage the `mysql` component:
>>> assembly = Assembly([[mysql, 'mysql']])

# Trigger automatic deployment of the assembly:
>>> assembly.auto_run()
(1/2) [Parallel] Install the Component MySQL
(1/2) [Parallel] Bootstrap the Component MySQL
[MySQL] Successfully moved from initiated to ready

(2/2) Component MySQL is running
[MySQL] Successfully moved from ready to running

[MySQL] Successfully moved from initiated to ready

[MySQL] Successfully moved from initiated to ready

[MySQL] Reach the final place.

Play with multiple components

A set of pre-defined components are available in the components/ directory. In this example, we deploy both MySQL and Keystone:

from components.keystone_multi import Keystone
from components.mysql import MySQL
from assembly import Assembly

# Initialize two components:
>>> mysql = MySQL()
>>> keystone = Keystone()

# Initialize an assembly to manage both components:
>>> assembly = Assembly([[mysql, 'mysql'], [keystone, 'keystone']])

# Let the assembly deploy automatically the components:
>>> assembly.auto_run()
# This operation must fail at some point because `keystone` has dependencies
# with `mysql`.

# While `mysql` has reached its final place, `keystone` is stuck because we
# have not connected these components.
>>> mysql.current_places
{'running'}
>>> keystone.current_places
{'configured', 'installed'}

# Let's connect both components:
>>> assembly.auto_connect('mysql', 'keystone')
# When ports are connected, they notify the automaton which continues the
# deployment where it was stuck.

>>> mysql.current_places
{'running'}
>>> keystone.current_places
{'running'}