bootstrap-vz/base/tasklist.py
Anders Ingemann 96028f96e1 Various improvements and additions.
I couldn't be bothered to untangle this, so here it goes:
* Log colors depending on loglevel
* Simplified Filelogger
* Remove description=None from basetask
* create_list creates task list from argument now
* Task rollback feature: If a task fails, the tasklist calls rollback() on the completed tasks in reverse order
* Added TaskException to common.exceptions as a base to extend from
* Added TriggerRollback task to common.tasks for development purposes
* An EBS volume for bootstrapping is now created and attached to the instance (including rollback actions)
* EC2 Connect task now depends on host.GetInfo
2013-07-08 23:14:00 +02:00

131 lines
3.4 KiB
Python

from common.exceptions import TaskListError
import logging
log = logging.getLogger(__name__)
class TaskList(object):
def __init__(self):
self.tasks = set()
def add(self, *args):
self.tasks.update(args)
def remove(self, task):
self.tasks.discard(self.get(task))
def replace(self, task, replacement):
self.remove(task)
self.add(replacement)
def get(self, ref):
return next(task for task in self.tasks if type(task) is ref)
def run(self, bootstrap_info):
task_list = self.create_list(self.tasks)
log.debug('Tasklist:\n\t{list}'.format(list='\n\t'.join(repr(task) for task in task_list)))
tasks_completed = []
try:
for task in task_list:
if hasattr(task, 'description'):
log.info(task.description)
else:
log.info('Running {task}'.format(task=task))
task.run(bootstrap_info)
tasks_completed.append(task)
except Exception, e:
log.exception(e)
log.error('Rolling back')
for task in reversed(tasks_completed):
rollback = getattr(task, 'rollback', None)
if not callable(rollback):
continue
if hasattr(task, 'rollback_description'):
log.warning(task.rollback_description)
else:
log.warning('Rolling back {task}'.format(task=task))
task.rollback(bootstrap_info)
log.info('Successfully completed rollback')
def create_list(self, tasks):
from common.phases import order
graph = {}
for task in tasks:
graph[task] = []
graph[task].extend([self.get(succ) for succ in task.before])
graph[task].extend([succ for succ in tasks if type(task) in succ.after])
succeeding_phases = order[order.index(task.phase)+1:]
graph[task].extend([succ for succ in tasks if succ.phase in succeeding_phases])
components = self.strongly_connected_components(graph)
cycles_found = 0
for component in components:
if len(component) > 1:
cycles_found += 1
log.debug('Cycle: {list}\n'.format(list=', '.join(repr(task) for task in component)))
if cycles_found > 0:
msg = ('{0} cycles were found in the tasklist, '
'consult the logfile for more information.'.format(cycles_found))
raise TaskListError(msg)
sorted_tasks = self.topological_sort(graph)
return sorted_tasks
def strongly_connected_components(self, graph):
# Source: http://www.logarithmic.net/pfh-files/blog/01208083168/sort.py
# Find the strongly connected components in a graph using Tarjan's algorithm.
# graph should be a dictionary mapping node names to lists of successor nodes.
result = [ ]
stack = [ ]
low = { }
def visit(node):
if node in low: return
num = len(low)
low[node] = num
stack_pos = len(stack)
stack.append(node)
for successor in graph[node]:
visit(successor)
low[node] = min(low[node], low[successor])
if num == low[node]:
component = tuple(stack[stack_pos:])
del stack[stack_pos:]
result.append(component)
for item in component:
low[item] = len(graph)
for node in graph:
visit(node)
return result
def topological_sort(self, graph):
# Source: http://www.logarithmic.net/pfh-files/blog/01208083168/sort.py
count = { }
for node in graph:
count[node] = 0
for node in graph:
for successor in graph[node]:
count[successor] += 1
ready = [ node for node in graph if count[node] == 0 ]
result = [ ]
while ready:
node = ready.pop(-1)
result.append(node)
for successor in graph[node]:
count[successor] -= 1
if count[successor] == 0:
ready.append(successor)
return result