# HG changeset patch # User Nicolas Chauvat # Date 1266910866 -3600 # Node ID 027bbff3659f90f8070a39b17b83f666e0d1c82c # Parent 573a6451b424bf6fc4bd10b7686d138cb019830c [cwctl] modify command list to check for cube dependency problems diff -r 573a6451b424 -r 027bbff3659f cwctl.py --- 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