--- a/cwctl.py Tue Feb 23 10:57:51 2010 +0100
+++ b/cwctl.py Tue Feb 23 08:41:06 2010 +0100
@@ -158,6 +158,77 @@
# base commands ###############################################################
+def version_strictly_lower(a,b):
+ if a:
+ a = a.split('.')
+ if b:
+ b = b.split('.')
+ return a < b
+
+def max_version(a, b):
+ return '.'.join(max(a.split('.'), b.split('.')))
+
+class ConfigurationProblem(object):
+ """Each cube has its own list of dependencies on other cubes/versions.
+
+ The ConfigurationProblem is used to record the loaded cubes, then to detect
+ inconsistencies in their dependencies.
+
+ See configuration management on wikipedia for litterature.
+ """
+
+ def __init__(self):
+ self.cubes = {}
+
+ def add_cube(self, name, info):
+ self.cubes[name] = info
+
+ def solve(self):
+ self.warnings = []
+ self.errors = []
+ self.read_constraints()
+ for cube, versions in sorted(self.constraints.items()):
+ oper, version = None, None
+ # simplify constraints
+ if versions:
+ for constraint in versions:
+ op, ver = constraint.split()
+ if oper is None:
+ oper = op
+ version = ver
+ elif op == '>=' and oper == '>=':
+ version = max_version(ver, version)
+ else:
+ print 'unable to handle this case', oper, version, op, ver
+ # "solve" constraint satisfaction problem
+ if cube not in self.cubes:
+ self.errors.append( ('add', cube, version) )
+ elif versions:
+ lower_strict = version_strictly_lower(self.cubes[cube].version, version)
+ if oper in ('>=','='):
+ if lower_strict:
+ self.errors.append( ('update', cube, version) )
+ else:
+ print 'unknown operator', oper
+
+ def read_constraints(self):
+ self.constraints = {}
+ self.reverse_constraints = {}
+ for cube, info in self.cubes.items():
+ if hasattr(info,'__depends_cubes__'):
+ use = info.__depends_cubes__
+ if not isinstance(use, dict):
+ use = dict((key,None) for key in use)
+ self.warnings.append('cube %s should define __depends_cubes__ as a dict not a list')
+ else:
+ self.warnings.append('cube %s should define __depends_cubes__' % cube)
+ use = dict((key,None) for key in info.__use__)
+ for name, constraint in use.items():
+ self.constraints.setdefault(name,set())
+ if constraint:
+ self.constraints[name].add(constraint)
+ self.reverse_constraints.setdefault(name, set()).add(cube)
+
class ListCommand(Command):
"""List configurations, cubes and instances.
@@ -185,6 +256,7 @@
continue
print ' ', line
print
+ cfgpb = ConfigurationProblem()
try:
cubesdir = pathsep.join(cwcfg.cubes_search_path())
namesize = max(len(x) for x in cwcfg.available_cubes())
@@ -200,6 +272,7 @@
try:
tinfo = cwcfg.cube_pkginfo(cube)
tversion = tinfo.version
+ cfgpb.add_cube(cube, tinfo)
except ConfigurationError:
tinfo = None
tversion = '[missing cube information]'
@@ -235,7 +308,21 @@
else:
print 'No instance available in %s' % regdir
print
-
+ # configuration management problem solving
+ cfgpb.solve()
+ if cfgpb.warnings:
+ print 'Warnings:\n', '\n'.join('* '+txt for txt in cfgpb.warnings)
+ if cfgpb.errors:
+ print 'Errors:'
+ for op, cube, version in cfgpb.errors:
+ if op == 'add':
+ print '* cube', cube,
+ if version:
+ print ' version', version,
+ print 'is not installed, but required by %s' % ' '.join(cfgpb.reverse_constraints[cube])
+ else:
+ print '* cube %s version %s is installed, but version %s is required by (%s)' % (
+ cube, cfgpb.cubes[cube].version, version, ', '.join(cfgpb.reverse_constraints[cube]))
class CreateInstanceCommand(Command):
"""Create an instance from a cube. This is an unified