[cwctl] modify command list to check for cube dependency problems stable
authorNicolas Chauvat <nicolas.chauvat@logilab.fr>
Tue, 23 Feb 2010 08:41:06 +0100
branchstable
changeset 4656 027bbff3659f
parent 4655 573a6451b424
child 4657 79005a48b221
child 4658 25de2eb0432b
[cwctl] modify command list to check for cube dependency problems
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