[ms, c-c] new command checking for consistency / potentian flaws and enhancements of mapping file of a multi-sources instance stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 20 Aug 2010 08:29:48 +0200
branchstable
changeset 6127 747e423093fc
parent 6126 aca6a2c357fd
child 6128 fbb8398f80dc
[ms, c-c] new command checking for consistency / potentian flaws and enhancements of mapping file of a multi-sources instance
server/checkintegrity.py
server/serverctl.py
--- a/server/checkintegrity.py	Fri Aug 20 08:21:15 2010 +0200
+++ b/server/checkintegrity.py	Fri Aug 20 08:29:48 2010 +0200
@@ -15,8 +15,12 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""Check integrity of a CubicWeb repository. Hum actually only the system database
-is checked.
+"""Integrity checking tool for instances:
+
+* integrity of a CubicWeb repository. Hum actually only the system database is
+  checked.
+
+* consistency of multi-sources instance mapping file
 """
 
 from __future__ import with_statement
@@ -28,7 +32,7 @@
 
 from logilab.common.shellutils import ProgressBar
 
-from cubicweb.schema import PURE_VIRTUAL_RTYPES
+from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, PURE_VIRTUAL_RTYPES
 from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.session import security_enabled
 
@@ -325,3 +329,94 @@
         session.set_pool()
         reindex_entities(repo.schema, session, withpb=withpb)
         cnx.commit()
+
+
+def warning(msg, *args):
+    if args:
+        msg = msg % args
+    print 'WARNING: %s' % msg
+
+def error(msg, *args):
+    if args:
+        msg = msg % args
+    print 'ERROR: %s' % msg
+
+def check_mapping(schema, mapping, warning=warning, error=error):
+    # first check stuff found in mapping file exists in the schema
+    for attr in ('support_entities', 'support_relations'):
+        for ertype in mapping[attr].keys():
+            try:
+                mapping[attr][ertype] = erschema = schema[ertype]
+            except KeyError:
+                error('reference to unknown type %s in %s', ertype, attr)
+                del mapping[attr][ertype]
+            else:
+                if erschema.final or erschema in META_RTYPES:
+                    error('type %s should not be mapped in %s', ertype, attr)
+                    del mapping[attr][ertype]
+    for attr in ('dont_cross_relations', 'cross_relations'):
+        for rtype in list(mapping[attr]):
+            try:
+                rschema = schema.rschema(rtype)
+            except KeyError:
+                error('reference to unknown relation type %s in %s', rtype, attr)
+                mapping[attr].remove(rtype)
+            else:
+                if rschema.final or rschema in VIRTUAL_RTYPES:
+                    error('relation type %s should not be mapped in %s',
+                          rtype, attr)
+                    mapping[attr].remove(rtype)
+    # check relation in dont_cross_relations aren't in support_relations
+    for rschema in mapping['dont_cross_relations']:
+        if rschema in mapping['support_relations']:
+            warning('relation %s is in dont_cross_relations and in support_relations',
+                    rschema)
+    # check relation in cross_relations are in support_relations
+    for rschema in mapping['cross_relations']:
+        if rschema not in mapping['support_relations']:
+            warning('relation %s is in cross_relations but not in support_relations',
+                    rschema)
+    # check for relation in both cross_relations and dont_cross_relations
+    for rschema in mapping['cross_relations'] & mapping['dont_cross_relations']:
+        error('relation %s is in both cross_relations and dont_cross_relations',
+              rschema)
+    # now check for more handy things
+    seen = set()
+    for eschema in mapping['support_entities'].values():
+        for rschema, ttypes, role in eschema.relation_definitions():
+            if rschema in META_RTYPES:
+                continue
+            ttypes = [ttype for ttype in ttypes if ttype in mapping['support_entities']]
+            if not rschema in mapping['support_relations']:
+                somethingprinted = False
+                for ttype in ttypes:
+                    rdef = rschema.role_rdef(eschema, ttype, role)
+                    seen.add(rdef)
+                    if rdef.role_cardinality(role) in '1+':
+                        error('relation %s with %s as %s and target type %s is '
+                              'mandatory but not supported',
+                              rschema, eschema, role, ttype)
+                        somethingprinted = True
+                    elif ttype in mapping['support_entities']:
+                        if rdef not in seen:
+                            warning('%s could be supported', rdef)
+                        somethingprinted = True
+                if rschema not in mapping['dont_cross_relations']:
+                    if role == 'subject' and rschema.inlined:
+                        error('inlined relation %s of %s should be supported',
+                              rschema, eschema)
+                    elif not somethingprinted and rschema not in seen:
+                        print 'you may want to specify something for %s' % rschema
+                        seen.add(rschema)
+            elif not ttypes:
+                warning('relation %s with %s as %s is supported but no target '
+                        'type supported', rschema, role, eschema)
+    for rschema in mapping['support_relations'].values():
+        if rschema in META_RTYPES:
+            continue
+        for subj, obj in rschema.rdefs:
+            if subj in mapping['support_entities'] and obj in mapping['support_entities']:
+                break
+        else:
+            error('relation %s is supported but none if its definitions '
+                  'matches supported entities', rschema)
--- a/server/serverctl.py	Fri Aug 20 08:21:15 2010 +0200
+++ b/server/serverctl.py	Fri Aug 20 08:29:48 2010 +0200
@@ -865,6 +865,34 @@
         mih.cmd_synchronize_schema()
 
 
+class CheckMappingCommand(Command):
+    """Check content of the mapping file of an external source.
+
+    The mapping is checked against the instance's schema, searching for
+    inconsistencies or stuff you may have forgotten. It's higly recommanded to
+    run it when you setup a multi-sources instance.
+
+    <instance>
+      the identifier of the instance.
+
+    <mapping file>
+      the mapping file to check.
+    """
+    name = 'check-mapping'
+    arguments = '<instance> <mapping file>'
+    min_args = max_args = 2
+
+    def run(self, args):
+        from cubicweb.server.checkintegrity import check_mapping
+        from cubicweb.server.sources.pyrorql import load_mapping_file
+        appid = pop_arg(args, 1, msg='No instance specified !')
+        mappingfile = pop_arg(args, msg='No mapping file specified !')
+        config = ServerConfiguration.config_for(appid)
+        config.quick_start = True
+        mih = config.migration_handler(connect=False, verbosity=1)
+        repo = mih.repo_connect() # necessary to get cubes
+        checkintegrity(config.load_schema(), load_mapping_file(mappingfile))
+
 register_commands( (CreateInstanceDBCommand,
                     InitInstanceCommand,
                     GrantUserOnInstanceCommand,
@@ -876,4 +904,5 @@
                     CheckRepositoryCommand,
                     RebuildFTICommand,
                     SynchronizeInstanceSchemaCommand,
+                    CheckMappingCommand,
                     ) )