Commit 2a3ddb70 authored by Maverick Chardet's avatar Maverick Chardet
Browse files

Added MadeusComponent and MadeusAssembly classes

Added an example of deployment using the Madeus abstraction layer
Added convenience scripts to transform and view JSON Gantt charts
Added an HTML/SVG Gantt exporter which does not require extra dependencies
Fixed Gantt charts using too bright colors
Fixed a few styling errors
Fixed graph generation when a vertex is removed while being in a subgraph
Removed useless configuration.py file
parent bb00a40e
......@@ -156,8 +156,7 @@ class Component (object, metaclass=ABCMeta):
self.add_groups(self.groups)
self.add_transitions(self.transitions)
self.add_dependencies(self.dependencies)
def set_verbosity(self, level : int):
self._verbosity = level
......
from concerto.component import Component
from concerto.dependency import DepType
class DataProvider(Component):
def __init__(self, data):
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
.. module:: configuration
:synopsis: this file contains the Configuration class.
"""
class Configuration (object):
"""
This class represents a configuration of the Madeus formal model.
However, unlike the formal model and for performance reasons, the global
configuration only stores active places and connections. Actually,
each component stores its local set of actives places, transitions,
input docks and output docks.
A configuration is used by the engine to know the state of a deployment.
"""
"""
BUILD CONFIGURATION
"""
# list of Place objects currently containing a token
places = []
# list of activated connections
connections = []
def __init__(self, pla, conn):
self.places = pla
self.connections = conn
"""
UPDATE CONFIGURATION
"""
def update_connections(self, conn):
"""
This method updates the connections of the configuration
:param conn: new list of active connections
"""
self.connections = conn.copy()
#def update_transitions(self, trans):
# """
# This method updates the transitions of the configuration
#
# :param trans: new list of active transitions
# """
# self.transitions = trans
def update_places(self, pla):
"""
This method updates the places of the configuration
:param pla: new list of active places
"""
self.places = pla.copy()
#def update_input_docks(self, id):
# """
# This method updates the input docks of the configuration
# :param id: new list of active input docks
# """
# self.input_docks = id
#def update_output_docks(self, od):
# """
# This method updates the output docks of the configuration
# :param id: new list of active output docks
# :return:
# """
# self.output_docks = od
"""
GET CONFIGURATION
"""
#def get_transitions(self):
#"""
# This method returns the list of active transitions of the configuration
# :return: self.transitions
# """
# return self.transitions
def get_places(self):
"""
This method returns the list of active places of the configuration
:return: self.places
"""
return self.places
def get_connections(self):
"""
This method returns the list of active connections of the configuration
:return: self.connections
"""
return self.connections
#def get_input_docks(self):
# """
# This method returns the list of active input docks of the configuration
# :return: self.input_docks
# """
# return self.input_docks
#def get_output_docks(self):
# """
# This method returns the list of active output docks of the
# configuration
# :return: self.output_docks
# """
# return self.output_docks
......@@ -64,7 +64,7 @@ class GanttChart:
'#17becf' # blue-teal
]
while len(self._transitions) > len(colors):
colors.append(f'rgb({randint(0,256)}, {randint(0,256)}, {randint(0,256)})')
colors.append(f'rgb({randint(0,220)}, {randint(0,220)}, {randint(0,220)})')
# for component_name in self._transitions:
# colors[component_name] = f'rgb({randint(0,256)}, {randint(0,256)}, {randint(0,256)})'
......@@ -111,6 +111,18 @@ class GanttChart:
fig = self.get_plotly(title, show_behaviors=show_behaviors, override_time_format=override_time_format)
fig.write_image(filename)
def html_export_plotly(self, filename_no_ext, title="", show_behaviors=None, override_time_format=None, **kwargs):
from plotly.offline import plot
fig = self.get_plotly(title,
show_behaviors=show_behaviors,
override_time_format=override_time_format)
plot(fig,
image='svg',
filename=filename_no_ext + ".html",
image_filename=filename_no_ext,
auto_open=False,
**kwargs)
def export_json(self, filename):
readable_dict = dict()
for component, bt in self._transitions.items():
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from concerto.madeus.madeus_assembly import MadeusAssembly
from concerto.madeus.madeus_component import MadeusComponent
from concerto.dependency import DepType
from abc import ABCMeta, abstractmethod
from typing import Dict, Tuple, List, Set
from concerto.assembly import Assembly
from concerto.reconfiguration import Reconfiguration
from concerto.madeus.madeus_component import MadeusComponent, _MadeusConcertoComponent
class MadeusAssembly(Assembly):
@abstractmethod
def create(self):
pass
def __init__(self, dryrun: bool = False, gantt_chart: bool = False, verbosity: int = 0, print_time: bool = False):
super().__init__()
self.components: Dict[str, MadeusComponent] = dict()
self.dependencies: List[Tuple[str, str, str, str]] = []
self.create()
self.set_dryrun(dryrun)
self.set_record_gantt(gantt_chart)
self.set_verbosity(verbosity)
self.set_print_time(print_time)
self._reconf = Reconfiguration()
for component_name, component in self.components.items():
self._reconf.add(component_name, _MadeusConcertoComponent, component)
for component_name, component in self.components.items():
if len(component.transitions) > 0:
self._reconf.push_behavior(component_name, _MadeusConcertoComponent.AUTO_BEHAVIOR)
for (c1, p1, c2, p2) in self.dependencies:
self._reconf.connect(c1, p1, c2, p2)
self._reconf.wait_all()
def get_concerto_reconfiguration(self):
return self._reconf
def run(self, auto_synchronize=True):
self.run_reconfiguration(self.get_concerto_reconfiguration())
if auto_synchronize:
self.synchronize()
self.terminate()
from abc import ABCMeta, abstractmethod
from typing import Dict, Tuple, List
from concerto.component import Component
class MadeusComponent(metaclass=ABCMeta):
@abstractmethod
def create(self):
pass
def __init__(self):
self.places: List[str] = []
self.transitions: Dict[str, Tuple] = {}
self.groups: Dict[str, List[str]] = {}
self.dependencies: Dict[str, Tuple] = {}
self.initial_place: str = None
self.create()
self._concerto_component = None
def print_color(self, string: str):
self._concerto_component.print_color(string)
def read(self, name: str):
return self._concerto_component.read(name)
def write(self, name: str, val):
return self._concerto_component.write(name, val)
class _MadeusConcertoComponent(Component):
AUTO_BEHAVIOR = 'autodeploy'
def __init__(self, mc: MadeusComponent):
mc._concerto_component = self
self.mc = mc
super().__init__()
def create(self):
self.places = self.mc.places
self.groups = self.mc.groups
self.dependencies = self.mc.dependencies
self.initial_place = self.mc.initial_place
# Converting transitions
for t in self.mc.transitions:
if len(self.mc.transitions[t]) != 3:
raise Exception("Error: invalid Madeus transition '%s'!" % t)
(source, destination, action) = self.mc.transitions[t]
self.transitions[t] = (source, destination, self.AUTO_BEHAVIOR, 0, action)
......@@ -77,8 +77,12 @@ class WeightedGraph:
working_graph = deepcopy(self._graph)
identifiers = dict()
vertices = set(working_graph.keys())
for source, arcs in working_graph.items():
for dest, _, _ in arcs:
vertices.add(dest)
i = 0
for v in working_graph:
for v in vertices:
identifiers[v] = "v%d" % i
i += 1
......@@ -93,6 +97,8 @@ class WeightedGraph:
gstr += "\t\tcolor = black;\n"
gstr += "\t\tlabel = \"%s\";\n" % cl_name
for v in cl_contents:
if v not in vertices: # was removed
continue
gstr += "\t\t%s [label=\"%s\"] [shape=%s];\n" % (
identifiers[v], f_get_vertex_label(v), self._shapes.get(v, "oval"))
treated_vertices.add(v)
......@@ -526,7 +532,7 @@ class ComponentPerfAnalyzer:
class ReconfigurationPerfAnalyzer:
def __init__(self, reconfiguration: Reconfiguration, existing_components=()):
def __init__(self, reconfiguration: Reconfiguration, existing_components=(), debug=False):
self.reconfiguration = reconfiguration
self.source_vertex = "source"
self._graph = WeightedGraph([self.source_vertex])
......@@ -541,7 +547,7 @@ class ReconfigurationPerfAnalyzer:
self._component_analyzers[name] = ca
self._handle_instructions()
self._generate_total_graph()
self._generate_total_graph(debug=debug)
def _handle_instructions(self):
for ri in self.reconfiguration._get_instructions():
......@@ -618,12 +624,12 @@ class ReconfigurationPerfAnalyzer:
for v in idle_vertices:
self._graph.add_arc(v, wait_s, 0)
def _generate_total_graph(self):
def _generate_total_graph(self, debug=False):
for component_analyzer in self._component_analyzers.values():
self._graph += component_analyzer.get_graph()
removed = self._graph.remove_unreachable_from(self.source_vertex)
if removed:
print("Warning: unreachable vertices were removed from the graph: %s" % removed)
if debug and removed:
print("Debug: unreachable vertices were removed from the graph: %s" % removed)
def get_graph(self):
return self._graph
......
# -*- coding: utf-8 -*-
from typing import List, Any, Callable
from datetime import datetime
from contextlib import contextmanager
"""
......@@ -8,7 +9,8 @@ from datetime import datetime
:synopsis: this file contains utility classes.
"""
class Messages():
class Messages:
"""
This class is not instanciated. It is used for valid, warning, and fail
color-printed messages.
......@@ -40,7 +42,7 @@ COLORS = ['\33[35m', # magenta
]
class Printer():
class Printer:
def __init__(self, show : bool = True):
self._show = show
......@@ -75,8 +77,7 @@ class Printer():
print(Printer._format_tprint(message))
if flush:
stdout.flush()
@staticmethod
def st_err_tprint(message : str, flush : bool = True):
from sys import stderr
......@@ -91,18 +92,16 @@ def remove_if(l : List[Any], remove_cond : Callable[[Any], bool]):
if remove_cond(l[i]):
del l[i]
continue
i+=1
i += 1
def empty_transition():
pass
import signal
from contextlib import contextmanager
@contextmanager
def timeout(time):
import signal
# Register a function to raise a TimeoutError on the signal.
signal.signal(signal.SIGALRM, raise_timeout)
# Schedule the signal to be sent after ``time``.
......
from concerto.madeus.all import *
from concerto.meta import ReconfigurationPerfAnalyzer
def sleep_print(component: MadeusComponent, start_message: str, time: float, end_message: str):
from time import sleep
component.print_color(start_message)
sleep(time)
component.print_color(end_message)
class Server(MadeusComponent):
def create(self):
self.places = ['undeployed', 'vm_started', 'downloaded', 'configured', 'running', 'running_checked']
self.initial_place = "undeployed"
self.transitions = {
'start_vm': ('undeployed', 'vm_started', self.start_vm),
'download': ('vm_started', 'downloaded', self.download),
'configure': ('vm_started', 'configured', self.configure),
'install': ('downloaded', 'configured', self.install),
'run': ('configured', 'running', self.run),
'check': ('running', 'running_checked', self.check),
}
self.groups = {
'providing_service': ['running', 'running_checked']
}
self.dependencies = {
'ip': (DepType.DATA_PROVIDE, ['vm_started']),
'service': (DepType.PROVIDE, ['providing_service'])
}
def start_vm(self):
sleep_print(self, "Starting VM...", 3, "VM started!")
self.write("ip", "121.94.42.42")
def download(self):
sleep_print(self, "Downloading...", 3, "Downloaded!")
def configure(self):
sleep_print(self, "Configuring...", 3, "Configured!")
def install(self):
sleep_print(self, "Installing...", 4, "Installed!")
def run(self):
sleep_print(self, "Running...", 2, "Running!")
def check(self):
sleep_print(self, "Checking...", 5, "Checked!")
class Client(MadeusComponent):
def create(self):
self.places = ['undeployed', 'downloaded', 'configured', 'running']
self.initial_place = "undeployed"
self.transitions = {
'download': ('undeployed', 'downloaded', self.download),
'configure1': ('downloaded', 'configured', self.configure1),
'configure2': ('downloaded', 'configured', self.configure2),
'run': ('configured', 'running', self.run),
}
self.dependencies = {
'server_ip': (DepType.DATA_USE, ['configure1']),
'server_service': (DepType.USE, ['run'])
}
def download(self):
sleep_print(self, "Downloading...", 1, "Downloaded!")
def configure1(self):
server_ip = self.read('server_ip')
sleep_print(self, "Configuring (part 1) with server IP %s..." % server_ip, 2, "Configured (part 1)!")
def configure2(self):
sleep_print(self, "Configuring (part 2)...", 5, "Configured (part 2)!")
def run(self):
sleep_print(self, "Running...", 1, "Running!")
class ServerClientAssembly(MadeusAssembly):
def create(self):
self.components = {
'server': Server(),
'client': Client()
}
self.dependencies = [
('server', 'ip',
'client', 'server_ip'),
('server', 'service',
'client', 'server_service'),
]
if __name__ == '__main__':
sca = ServerClientAssembly()
# Analysis
pa = ReconfigurationPerfAnalyzer(sca.get_concerto_reconfiguration())
pa.get_graph().save_as_dot("graph.dot")
# Running the deployment
sca.set_print_time(True)
sca.set_record_gantt(True)
sca.run()
# Gantt chart
gc = sca.get_gantt_record().get_gantt_chart()
gc.export_json("gantt_chart.json")
......@@ -56,7 +56,9 @@ times = {
}
sc = ServerClient(times)
sc.set_record_gantt(True)
pa = ReconfigurationPerfAnalyzer(sc.deploy_reconf)
g = pa.get_graph()
g.save_as_dot("server_client.dot")
......@@ -71,6 +73,9 @@ sc.deploy()
end_time: float = time.perf_counter()
sc.terminate()
print("Time actually measured:\n%f" % (end_time-start_time))
sc.set_record_gantt(True)
# ...
gr = sc.get_gantt_record()
gc = gr.get_gantt_chart()
gc.export_json("gantt_chart.json")
import datetime
class Printer():
def __init__(self, show : bool = True):
class Printer:
def __init__(self, show: bool = True):
self._show = show
def tprint(self, message : str):
def tprint(self, message: str):
if self._show:
self.st_tprint(message)
@staticmethod
def st_tprint(message : str):
def st_tprint(message: str):
now = datetime.datetime.now()
hour = ("%d"%now.hour).rjust(2, '0')
minute = ("%d"%now.minute).rjust(2, '0')
second = ("%d"%now.second).rjust(2, '0')
ms = ("%d"%(now.microsecond/1000)).rjust(3, '0')
print("[%s:%s:%s:%s] %s"%(hour,minute,second,ms, message))
hour = ("%d" % now.hour).rjust(2, '0')
minute = ("%d" % now.minute).rjust(2, '0')
second = ("%d" % now.second).rjust(2, '0')
ms = ("%d" % (now.microsecond/1000)).rjust(3, '0')
print("[%s:%s:%s:%s] %s" % (hour, minute, second, ms, message))
from concerto.exporters.gantt_chart import GanttChart
import sys
if len(sys.argv) < 2:
print("Error: expecting to convert at least one file!")
for file in sys.argv[1:]:
gc = GanttChart.from_json(file)
gc.export_plotly(file[:-5] + ".pdf", title=file[:-5])
from concerto.exporters.gantt_chart import GanttChart
import sys
if len(sys.argv) != 2:
print("Error: expecting one file as argument!")
gc = GanttChart.from_json(sys.argv[1])
gc.show_plotly(title=sys.argv[1][:-5])
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment