169 lines
5.3 KiB
Python
169 lines
5.3 KiB
Python
# Copyright 2015 Mirantis, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
|
|
__all__ = ['add_dep', 'add_react', 'Dep', 'React', 'add_event']
|
|
|
|
import networkx as nx
|
|
|
|
from solar.core.log import log
|
|
from solar.dblayer.solar_models import Resource
|
|
from solar.events.controls import Dep
|
|
from solar.events.controls import React
|
|
|
|
|
|
def create_event(event_dict):
|
|
etype = event_dict['etype']
|
|
kwargs = {'child': event_dict['child'],
|
|
'parent': event_dict['parent'],
|
|
'child_action': event_dict['child_action'],
|
|
'parent_action': event_dict['parent_action'],
|
|
'state': event_dict['state']}
|
|
if etype == React.etype:
|
|
return React(**kwargs)
|
|
elif etype == Dep.etype:
|
|
return Dep(**kwargs)
|
|
else:
|
|
raise Exception('No support for type %s', etype)
|
|
|
|
|
|
def add_default_events(emitter, receiver):
|
|
events_to_add = [
|
|
Dep(emitter.name, 'run', 'success', receiver.name, 'run'),
|
|
Dep(emitter.name, 'update', 'success', receiver.name, 'update'),
|
|
Dep(receiver.name, 'remove', 'success', emitter.name, 'remove')
|
|
]
|
|
add_events(emitter.name, events_to_add)
|
|
|
|
|
|
def add_event(ev):
|
|
rst = all_events(ev.parent)
|
|
for rev in rst:
|
|
if ev == rev:
|
|
break
|
|
else:
|
|
add_events(ev.parent, [ev])
|
|
|
|
|
|
def add_dep(parent, dep, actions, state='success'):
|
|
for act in actions:
|
|
d = Dep(parent, act, state=state,
|
|
child=dep, child_action=act)
|
|
add_event(d)
|
|
log.debug('Added event: %s', d)
|
|
|
|
|
|
def add_react(parent, dep, actions, state='success'):
|
|
for act in actions:
|
|
r = React(parent, act, state=state,
|
|
child=dep, child_action=act)
|
|
add_event(r)
|
|
log.debug('Added event: %s', r)
|
|
|
|
|
|
def add_events(resource, lst):
|
|
resource = Resource.get(resource)
|
|
events = resource.events
|
|
# TODO: currently we don't track mutable objects
|
|
events.extend([ev.to_dict() for ev in lst])
|
|
resource.events = events
|
|
# import pdb; pdb.settrace()
|
|
resource.save_lazy()
|
|
|
|
|
|
def remove_event(ev):
|
|
to_remove = ev.to_dict()
|
|
resource = ev.parent
|
|
resource = Resource.get(resource)
|
|
# TODO: currently we don't track mutable objects
|
|
events = resource.events
|
|
events.remove(to_remove)
|
|
resource.events = events
|
|
resource.save_lazy()
|
|
|
|
|
|
def all_events(resource):
|
|
return [create_event(e) for e in Resource.get(resource).events]
|
|
|
|
|
|
def bft_events_graph(start):
|
|
"""Builds graph of events traversing events in breadth-first order
|
|
|
|
This graph doesnt necessary reflect deployment order, it is used
|
|
to show dependencies between resources
|
|
"""
|
|
dg = nx.DiGraph()
|
|
stack = [start]
|
|
visited = set()
|
|
|
|
while stack:
|
|
item = stack.pop()
|
|
current_events = all_events(item)
|
|
|
|
for ev in current_events:
|
|
dg.add_edge(ev.parent_node, ev.child_node, label=ev.state)
|
|
|
|
if ev.child in visited:
|
|
continue
|
|
|
|
# it is possible to have events leading to same resource but
|
|
# different action
|
|
if ev.child in stack:
|
|
continue
|
|
|
|
stack.append(ev.child)
|
|
visited.add(ev.parent)
|
|
return dg
|
|
|
|
|
|
def build_edges(changes_graph, events):
|
|
"""Builds graph edges
|
|
|
|
:param changes_graph: nx.DiGraph object with actions to be executed
|
|
:param events: {res: [controls.Event objects]}
|
|
"""
|
|
events_graph = nx.MultiDiGraph()
|
|
|
|
for res_evts in events.values():
|
|
for ev in res_evts:
|
|
events_graph.add_edge(ev.parent_node, ev.child_node, event=ev)
|
|
stack = changes_graph.nodes()
|
|
visited = set()
|
|
while stack:
|
|
event_name = stack.pop(0)
|
|
if event_name in events_graph:
|
|
log.debug('Next events after %s are %s', event_name,
|
|
events_graph.successors(event_name))
|
|
else:
|
|
log.debug('No outgoing events based on %s', event_name)
|
|
if event_name in visited:
|
|
continue
|
|
for parent, child, data in events_graph.edges(event_name,
|
|
data=True):
|
|
succ_ev = data['event']
|
|
# FIXME(dshulyak) interface of events should be changed
|
|
if succ_ev.insert(stack, changes_graph):
|
|
new_events = all_events(succ_ev.child)
|
|
for ev in new_events:
|
|
events_graph.add_edge(
|
|
ev.parent_node, ev.child_node, event=ev)
|
|
# re-visit all possible predecessors that were
|
|
# already visited
|
|
for pred in events_graph.predecessors(succ_ev.child_node):
|
|
if pred in visited and event_name != pred:
|
|
stack.append(pred)
|
|
visited.discard(pred)
|
|
visited.add(event_name)
|
|
return changes_graph
|