Source code for assembly

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
.. module:: assembly
   :synopsis: this file contains the Assembly class.
"""

import sys
from whiteboard import *
from dependency import *
from utility import Messages, COLORS

[docs]class Connection(object): """ This class is used by the assembly to store connections between components """ def __init__(self, comp1, dep1, comp2, dep2): self.active = False self.tuple = (comp1, dep1, comp2, dep2) self.names = (comp1.getname(), dep1.getname(), comp2.getname(), dep2.getname()) def activate(self): self.active = True def gettuple(self): return self.tuple def getnames(self): return self.names def isactive(self): return self.active
[docs]class Assembly (object): """This Assembly class is used to create a assembly. An assembly is a set of component instances and the connection of their dependencies. """ # dict of Component objects: id => object components = {} # list of connections. A connection is a tuple (component1, dependency1, # component2, dependency2) connections = [] # list of white boards, 1 for each data provide wbs = {} # a dictionary to store at the assembly level a list of connections for # each place (name) of the assembly (ie provide dependencies) # this is used to improve performance of the semantics places_connections = {} # a dictionary to store at the assembly level a list of connections for # each component (name) of the assembly # this is used to improve performance of the semantics component_connections = {} """ BUILD ASSEMBLY """ def __init__(self): self.printed = False
[docs] def addComponent(self, name, comp): """ This method adds a component instance to the assembly :param comp: the component instance to add """ comp.setname(name) comp.setcolor(COLORS[len(self.components)%len(COLORS)]) if name not in self.components: self.components[name]=comp else: print(Messages.fail() + "ERROR - Already instantiate component "+name + + Messages.endc()) sys.exit(0)
[docs] def addConnection(self, comp1, name1, comp2, name2): """ This method adds a connection between two components dependencies. :param comp1: The first component to connect :param name1: The dependency name of the first component to connect :param comp2: The second component to connect :param name2: The dependency name of the second component to connect """ if DepType.validtypes(comp1.st_dependencies[name1].gettype(), comp2.st_dependencies[name2].gettype()): # multiple connections are possible within MAD, so we do not # check if a dependency is already connected # create connection new_connection = Connection(comp1, comp1.st_dependencies[name1], comp2, comp2.st_dependencies[name2]) self.connections.append(new_connection.gettuple()) # fill component_connections if comp1.getname() in self.component_connections: self.component_connections[comp1.getname()].append(new_connection) else: self.component_connections[comp1.getname()] = [new_connection] if comp2.getname() in self.component_connections: self.component_connections[comp2.getname()].append(new_connection) else: self.component_connections[comp2.getname()] = [new_connection] # fill places_connections if comp1.st_dependencies[name1].gettype() == DepType.PROVIDE or \ comp1.st_dependencies[name1].gettype() == DepType.DATA_PROVIDE: for binding in comp1.st_dependencies[name1].getbindings(): if binding in self.places_connections: self.places_connections[binding].append(new_connection) else: self.places_connections[binding] = [new_connection] elif comp2.st_dependencies[name2].gettype() == DepType.PROVIDE or \ comp2.st_dependencies[name2].gettype() == DepType.DATA_PROVIDE: for binding in comp2.st_dependencies[name2].getbindings(): if binding in self.places_connections: self.places_connections[binding].append(new_connection) else: self.places_connections[binding] = [new_connection] # white board management # if we have a DATA connection, create a whiteboard and attached it if (comp1.st_dependencies[name1].gettype() == DepType.DATA_PROVIDE) \ or (comp1.st_dependencies[name1].gettype() == DepType.DATA_USE): white_board = WhiteBoard() # get user and provider of the connection if comp1.st_dependencies[name1].gettype() == DepType.DATA_PROVIDE: provider = comp1.st_dependencies[name1] user = comp2.st_dependencies[name2] else: provider = comp2.st_dependencies[name2] user = comp1.st_dependencies[name1] if provider not in self.wbs: self.wbs[provider] = white_board provider.connectwb(self.wbs[provider]) user.connectwb(self.wbs[provider]) # else we have a service connection to connect else: comp1.st_dependencies[name1].connect() comp2.st_dependencies[name2].connect() else: print(Messages.fail() + "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." + Messages.endc()) sys.exit(0)
def get_component(self, name): if name in self.components: return self.components[name] else: print(Messages.fail() + "ERROR - Unknown component "+name + + Messages.endc()) sys.exit(0) def get_components(self): return self.components.values() """ CHECK ASSEMBLY """
[docs] def check_warnings(self): """ This method check WARNINGS in the structure of an assembly. :return: false if some WARNINGS have been detected, true otherwise """ check = True check_dep = True # Check warnings for comp in self.get_components(): check = comp.check_warnings() check_dep = comp.check_connections() if not check: print(Messages.warning() + "WARNING - some WARNINGS have been " "detected in your components, please " "check them so as to not get unwilling " "behaviors in your deployment cordination" + Messages.endc()) if not check_dep: print(Messages.warning() + "WARNING - some dependencies are not " "connected within the assembly. This " "could lead to unwilling behaviors in " "your deployment coordination." + Messages.endc()) return check and check_dep
""" OPERATIONAL SEMANTICS """ # a set of active places and connections within the assembly (global) act_places = [] act_connections = [] # set of active places and connections of the previous iteration old_places = [] old_connections = []
[docs] def init_semantics(self): """ This method activate the initial places of each component and builds the global self.act_places """ for c in self.components: comp_places = self.components[c].init_places() self.act_places.extend(comp_places) self.components[c].init_trans_connections( self.component_connections[c])
[docs] def semantics(self, dryrun, printing): """ This method runs one semantics iteration by first updating the list of enbled connections and then by running semantics of each component of the assembly. :param dryrun: boolean representing if the semantics is run in dryrun mode :param printing: boolean representing if the semantics must print output """ # enable / disable connections if self.act_places != self.old_places: # enable/disable connections new_connections = self.disable_enable_connections(printing) # highest priority according to the model to guarantee the # disconnection of services when no more provided # before doing anything else self.old_connections = self.act_connections.copy() self.act_connections = new_connections.copy() # semantics for each component new_places = [] for c in self.components: connections = [] if c in self.component_connections: for conn in self.component_connections[c]: if conn.isactive(): connections.append(conn.gettuple()) c_places = self.components[c].semantics(connections, dryrun, printing) new_places = new_places + c_places # build the new configuration / ended self.old_places = self.act_places.copy() self.act_places = new_places.copy()
[docs] def disable_enable_connections(self, printing): """ This method build the new list of enabled connections according to the current states of "activated" places (ie the ones getting a token). :param configuration: the current configuration of the deployment :return: the new list of activated connections """ # create the new list of activated connections activated_connections = [] for place in self.act_places: if place in self.places_connections: connections = self.places_connections[place] for conn in connections: activated_connections.append(conn.gettuple()) if not conn.isactive(): conn.activate() if printing: print("[Assembly] Enable connection (" + str(conn.getnames()) + ")") return activated_connections
[docs] def is_finish(self): """ This method checks if the deployment is finished :param configuration: the current configuration of the deployment :return: True if the deployment is finished, False otherwise """ # the deployment cannot be finished if at least all components have # not reached a place if len(self.act_places) >= len(self.components): # if all places are finals (ie without output docks) the # deployment has finished all_finals = True for place in self.act_places: if len(place.get_outputdocks()) > 0: all_finals = False return all_finals else: return False