You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ac...@apache.org on 2014/07/08 20:43:16 UTC

svn commit: r1608882 - in /qpid/dispatch/trunk: doc/man/ include/qpid/dispatch/ python/qpid_dispatch_internal/config/ python/qpid_dispatch_internal/management/ router/src/ src/ tests/ tests/management/

Author: aconway
Date: Tue Jul  8 18:43:15 2014
New Revision: 1608882

URL: http://svn.apache.org/r1608882
Log:
DISPATCH-56: Replace qpid_dispatch_internal.config with qpid_dispatch_internal.management.

Replace the old config schema with the new management schema.
- Various error handling improvements.
- Router code calls out to new config parsing/schema code.
- Renamed waypoint.name to address, conflict with standard managemenet entity name attribute.
- Usign new qdrouterd.conf man page, generated from schema.
- Added references and fixed value attributes to schema.

Added:
    qpid/dispatch/trunk/doc/man/qdrouterd_conf_man.py
      - copied, changed from r1608881, qpid/dispatch/trunk/doc/man/qdrouterd_man.py
Removed:
    qpid/dispatch/trunk/doc/man/qdrouterd.conf.5.in
    qpid/dispatch/trunk/doc/man/qdrouterd_man.py
    qpid/dispatch/trunk/python/qpid_dispatch_internal/config/__init__.py
    qpid/dispatch/trunk/python/qpid_dispatch_internal/config/parser.py
    qpid/dispatch/trunk/python/qpid_dispatch_internal/config/schema.py
Modified:
    qpid/dispatch/trunk/doc/man/CMakeLists.txt
    qpid/dispatch/trunk/include/qpid/dispatch/config.h
    qpid/dispatch/trunk/python/qpid_dispatch_internal/management/__init__.py
    qpid/dispatch/trunk/python/qpid_dispatch_internal/management/node.py
    qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.json
    qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.py
    qpid/dispatch/trunk/python/qpid_dispatch_internal/management/schema.py
    qpid/dispatch/trunk/router/src/main.c
    qpid/dispatch/trunk/src/config.c
    qpid/dispatch/trunk/src/config_private.h
    qpid/dispatch/trunk/src/dispatch.c
    qpid/dispatch/trunk/src/error.c
    qpid/dispatch/trunk/src/router_agent.c
    qpid/dispatch/trunk/src/router_config.c
    qpid/dispatch/trunk/src/router_node.c
    qpid/dispatch/trunk/src/router_private.h
    qpid/dispatch/trunk/src/waypoint.c
    qpid/dispatch/trunk/tests/management/schema.py
    qpid/dispatch/trunk/tests/system_tests_management.py
    qpid/dispatch/trunk/tests/system_tests_qdstat.py
    qpid/dispatch/trunk/tests/system_tests_two_routers.py

Modified: qpid/dispatch/trunk/doc/man/CMakeLists.txt
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/doc/man/CMakeLists.txt?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/doc/man/CMakeLists.txt (original)
+++ qpid/dispatch/trunk/doc/man/CMakeLists.txt Tue Jul  8 18:43:15 2014
@@ -18,25 +18,23 @@
 ##
 
 configure_file(qdrouterd.8.in ${CMAKE_CURRENT_BINARY_DIR}/qdrouterd.8)
-configure_file(qdrouterd.conf.5.in ${CMAKE_CURRENT_BINARY_DIR}/qdrouterd.conf.5)
 configure_file(qdstat.8.in ${CMAKE_CURRENT_BINARY_DIR}/qdstat.8)
 
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qdrouterd.8 DESTINATION ${MAN_INSTALL_DIR}/man8)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qdrouterd.conf.5 DESTINATION ${MAN_INSTALL_DIR}/man5)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qdstat.8 DESTINATION ${MAN_INSTALL_DIR}/man8)
-
-# Generate a man page from the new qdrouter.json schema. This will eventually replace
-# the hand-written man page generated from qdrouterd.conf.5.in
+# Generate a man page from the qdrouter.json schema.
+set (QDROUTERD_CONF qdrouterdconf.conf.5)
 
-file (GLOB_RECURSE MAN_PAGE_GENERATOR
+file (GLOB_RECURSE QDROUTERD_CONF_DEPENDS
   ${CMAKE_CURRENT_SOURCE_DIR}/qdrouterd_man.py
   ${CMAKE_SOURCE_DIR}/python/qpid_router_internal/management/*.py
   ${CMAKE_SOURCE_DIR}/python/qpid_router_internal/management/qdrouterd.json)
 
-set (QDROUTERD_MAN qdrouterdconf.conf.5.new)
+add_custom_command (OUTPUT ${QDROUTERD_CONF}
+    COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_BINARY_DIR}/tests/run.py -s ${CMAKE_CURRENT_SOURCE_DIR}/qdrouterd_conf_man.py ${QDROUTERD_CONF}
+    DEPENDS ${QDROUTERD_CONF_DEPENDS})
 
-add_custom_command (OUTPUT ${QDROUTERD_MAN}
-    COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_BINARY_DIR}/tests/run.py -s ${CMAKE_CURRENT_SOURCE_DIR}/qdrouterd_man.py ${QDROUTERD_MAN}
-    DEPENDS ${MAN_PAGE_GENERATOR})
+add_custom_target(man ALL DEPENDS ${QDROUTERD_CONF})
 
-add_custom_target(man ALL DEPENDS ${QDROUTERD_MAN})
+# Install man pages
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qdrouterd.8 DESTINATION ${MAN_INSTALL_DIR}/man8)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${QDROUTERD_CONF} DESTINATION ${MAN_INSTALL_DIR}/man5)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qdstat.8 DESTINATION ${MAN_INSTALL_DIR}/man8)

Copied: qpid/dispatch/trunk/doc/man/qdrouterd_conf_man.py (from r1608881, qpid/dispatch/trunk/doc/man/qdrouterd_man.py)
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/doc/man/qdrouterd_conf_man.py?p2=qpid/dispatch/trunk/doc/man/qdrouterd_conf_man.py&p1=qpid/dispatch/trunk/doc/man/qdrouterd_man.py&r1=1608881&r2=1608882&rev=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/doc/man/qdrouterd_man.py (original)
+++ qpid/dispatch/trunk/doc/man/qdrouterd_conf_man.py Tue Jul  8 18:43:15 2014
@@ -28,7 +28,7 @@ def make_man_page(filename):
     with open(filename, 'w') as f:
 
         f.write(
-            r""".\" -*- nroff -*-
+r""".\" -*- nroff -*-
 .\"
 .\" Licensed to the Apache Software Foundation (ASF) under one
 .\" or more contributor license agreements.  See the NOTICE file
@@ -52,62 +52,93 @@ def make_man_page(filename):
 qdrouterd.conf \- Configuration file for the Qpid Dispatch router
 .SH DESCRIPTION
 
-The dispatch router is configured in terms of configuration "entities". Each
-type of entity has a set of associated attributes. For example there is a single
-"router" entity that has attributes to set configuration associated with the
-router as a whole. There may be multiple "listener" and "connector" entities
-that specify how to make and receive external connections.
-
-Some entities have attributes in common, for example "listener" and "connector"
-entities both have attributes to specify an IP address. All entities have "name"
-and "identity" attributes. Commonly used attribute groups are specified as an
-"include group" and referenced from entity types that want to include them.
-
-
-.SH SYNTAX
-
-This file is divided into sections. "Include" sections define groups of
-attributes that are included by multiple entity types. "Entity" sections define
-the attributes associated with each type of configuration entity.
+The configuration file is made up of sections with this syntax:
 
 .nf
-<section-name> {
-    <attribute-name>: <attribute-value>
-    <attribute-name>: <attribute-value>
+SECTION-NAME {
+    ATTRIBUTE-NAME: ATTRIBUTE-VALUE
+    ATTRIBUTE-NAME: ATTRIBUTE-VALUE
     ...
 }
-
-<section-name> { ...
 .fi
 
-.SH SECTIONS
+There are two types of sections:
+
+Entity sections correspond to management entities. They can be queried and
+configured via management tools. By default each section is assigned a
+management name and identity of <section-name>-<n>. For example
+"listener-2". You can provide explicit name and identity attributes in any
+section.
+
+Include sections define a group of attribute values that can be included in
+one or more entity sections.
+
+For example you can define an "ssl-profile" include section with SSL credentials
+that can be included in multiple "listener" entities. Here's an example, note
+how the 'ssl-profile' attribute of 'listener' sections references the 'name'
+attribute of 'ssl-profile' sections.
+
+.nf
+ssl-profile {
+    name: ssl-profile-one
+    cert-db: ca-certificate-1.pem
+    cert-file: server-certificate-1.pem
+    key-file: server-private-key.pem
+}
+
+listener {
+    ssl-profile: ssl-profile-one
+    addr: 0.0.0.0
+    port: 20102
+    sasl-mechanisms: ANONYMOUS
+}
+.fi
 """)
+        schema = QdSchema()
+
         def write_attribute(attr, attrs):
             if attr.include and attr.include != attrs:
                 return          # Don't repeat included attributes
+            if attr.value is not None:
+                return          # Don't show fixed-value attributes, they can't be set in conf file.
+
+            default = attr.default
+            if isinstance(default, basestring) and default.startswith('$'):
+                default = None  # Don't show defaults that are references, confusing.
+
             f.write('.IP %s\n'%(attr.name))
             f.write('(%s)\n\n'%(', '.join(
                 filter(None, [str(attr.atype),
                               attr.required and "required",
                               attr.unique and "unique",
-                              attr.default and "default=%s"%attr.default]))))
-            if attr.description: f.write("%s\n"%attr.description)
+                              default and "default=%s"%default]))))
+            if attr.description:
+                f.write("%s\n"%attr.description)
+            else:
+                print "Warning no description for", attr, "in", attrs
 
         def write_attributes(attrs):
             if attrs.description:
                 f.write('\n%s\n'%attrs.description)
+            else:
+                print "Warning no description for ", attrs
             for attr in attrs.attributes.itervalues():
                 write_attribute(attr, attrs)
+            f.write('\n\n')
 
-        schema = QdSchema()
+
+        f.write(".SH INCLUDE SECTIONS\n\n")
         for include in schema.includes.itervalues():
-            f.write('.SS "\'%s\' include group"\n'% include.name)
+            used_by = [e.name for e in schema.entity_types.itervalues() if include.name in e.include]
+            f.write('.SS "%s"\n'%include.name)
             write_attributes(include)
+            f.write('.IP "Included by %s."\n'%(', '.join(used_by)))
 
+        f.write(".SH ENTITY SECTIONS\n\n")
         for name, entity_type in schema.entity_types.iteritems():
-            f.write('.SS "\'%s\' entity"\n'% name)
-            f.write('Includes: %s\n\n'%(', '.join(entity_type.include)))
+            f.write('.SS "%s"\n'% name)
             write_attributes(entity_type)
+            f.write('.IP "Includes %s."\n'%(', '.join(entity_type.include)))
 
 
 if __name__ == '__main__':

Modified: qpid/dispatch/trunk/include/qpid/dispatch/config.h
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/include/qpid/dispatch/config.h?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/include/qpid/dispatch/config.h (original)
+++ qpid/dispatch/trunk/include/qpid/dispatch/config.h Tue Jul  8 18:43:15 2014
@@ -8,9 +8,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  *   http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -20,8 +20,8 @@
  */
 
 /**
- * @defgroup configuration 
- * 
+ * @defgroup configuration
+ *
  * Get configuration values from a dispatch instance.
  *@{
  */

Modified: qpid/dispatch/trunk/python/qpid_dispatch_internal/management/__init__.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch_internal/management/__init__.py?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/__init__.py (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/__init__.py Tue Jul  8 18:43:15 2014
@@ -21,4 +21,4 @@
 from .entity import Entity, EntityList
 from .node import Url, Node, ManagementError
 from .qdrouter import QdSchema, QdConfig
-from .schema import Type, BooleanType, EnumType, AttributeType, AttributeTypeHolder, IncludeType, EntityType, Schema, schema_file
+from .schema import Type, BooleanType, EnumType, AttributeType, AttributeTypeHolder, IncludeType, EntityType, Schema, schema_file, ValidationError

Modified: qpid/dispatch/trunk/python/qpid_dispatch_internal/management/node.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch_internal/management/node.py?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/node.py (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/node.py Tue Jul  8 18:43:15 2014
@@ -44,7 +44,7 @@ class ManagementError(Exception):
             raise ManagementError(status, response)
 
 
-# FIXME aconway 2014-06-03: proton URL class, conditional import?
+# TODO aconway 2014-06-03: proton URL class, conditional import?
 class Url:
     """Simple AMQP URL parser/constructor"""
 

Modified: qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.json
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.json?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.json (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.json Tue Jul  8 18:43:15 2014
@@ -1,50 +1,32 @@
 {
   "prefix": "org.apache.qpid.dispatch",
   "includes": {
-    "entity-id": {
-      "description":"Name and identity attributes are common to all entity types",
+    "common": {
+      "description":"Attributes common to all entities.",
       "attributes": {
         "name": {
           "type": "String",
           "required": true,
           "unique": true,
+	  "default": "$identity",
           "description": "Unique name, can be changed."
         },
         "identity": {
           "type": "String",
           "required": true,
           "unique": true,
+	  "default": "$name",
           "description": "Unique identity, will not change."
-        }
-      }
-    },
-    "ssl-profile": {
-      "description": "SSL profile to be referenced in listeners (for incoming connections) or connectors (for outgoing connectors).",
-      "attributes": {
-        "cert-db": {
-          "type": "String",
-          "description": "The path to the database that contains the public certificates of trusted certificate authorities (CAs). "
-        },
-        "cert-file": {
-          "type": "String",
-          "description": "The path to the file containing the PEM-formatted public certificate to be used on the local end of any connections using this profile. "
-        },
-        "key-file": {
-          "type": "String",
-          "description": "The path to the file containing the PEM-formatted private key for the above certificate. "
         },
-        "password-file": {
-          "type": "String",
-          "description": "If the above private key is password protected, this is the path to a file containing the password that unlocks the certificate key. "
-        },
-        "password": {
-          "type": "String",
-          "description": "An alternative to storing the password in a file referenced by password-file is to supply the password right here in the configuration file.  This option can be used by supplying the password in the 'password' option.  Don't use both password and password-file in the same profile. "
-        }
+	"type": {
+	  "type": "String",
+	  "required": true,
+	  "value": "$$entity-type",
+	  "description": "Management entity type."
+	}
       }
     },
-    "ip-addr": {
-      "description": "IP address to be referenced in listeners (for incoming connections) or connectors (for outgoing connectors).",
+    "ip-address": {
       "attributes": {
         "addr": {
 	  "description":"IP address: ipv4 or ipv6 literal or a host name",
@@ -59,12 +41,11 @@
       }
     },
     "connection": {
-      "description": "Common connection attributes for listeners and connectors.",
       "attributes": {
         "sasl-mechanisms": {
           "type": "String",
           "required": true,
-          "description": "Comma separated list of accepted SASL mechanisms."
+          "description": "Comma separated list of accepted SASL authentication mechanisms."
         },
         "role": {
           "type": [
@@ -76,15 +57,43 @@
           "description": "The role of an established connection. In the normal role, the connection is assumed to be used for AMQP clients that are doing normal message delivery over the connection.  In the inter-router role, the connection is assumed to be to another router in the network.  Inter-router discovery and routing protocols can only be used over inter-router connections. "
         }
       }
+    },
+    "ssl-profile": {
+      "attributes": {
+        "cert-db": {
+          "type": "String",
+          "description": "The path to the database that contains the public certificates of trusted certificate authorities (CAs). "
+        },
+        "cert-file": {
+          "type": "String",
+          "description": "The path to the file containing the PEM-formatted public certificate to be used on the local end of any connections using this profile. "
+        },
+        "key-file": {
+          "type": "String",
+          "description": "The path to the file containing the PEM-formatted private key for the above certificate. "
+        },
+        "password-file": {
+          "type": "String",
+          "description": "If the above private key is password protected, this is the path to a file containing the password that unlocks the certificate key. "
+        },
+        "password": {
+          "type": "String",
+          "description": "An alternative to storing the password in a file referenced by password-file is to supply the password right here in the configuration file.  This option can be used by supplying the password in the 'password' option.  Don't use both password and password-file in the same profile. "
+        }
+      }
     }
   },
   "entity_types": {
     "container": {
       "singleton": true,
       "include": [
-        "entity-id"
+        "common"
       ],
       "attributes": {
+	"container-name": {
+	  "type": "String",
+	  "description": "The  name of the AMQP container.  If not specified, the container name will be set to a value of the container's choosing.  The automatically assigned container name is not guaranteed to be persistent across restarts of the container."
+	},
         "worker-threads": {
           "type": "Integer",
           "default": "1",
@@ -96,9 +105,12 @@
     "router": {
       "singleton": true,
       "include": [
-        "entity-id"
+        "common"
       ],
       "attributes": {
+	"router-id": {
+	  "type": "String"
+	},
         "mode": {
           "type": [
             "standalone",
@@ -141,10 +153,11 @@
     },
 
     "listener": {
+      "description": "Listens for incoming connections to the router",
       "include": [
-        "entity-id",
+        "common",
         "ssl-profile",
-        "ip-addr",
+        "ip-address",
 	"connection"
       ],
       "attributes": {
@@ -170,10 +183,11 @@
       }
     },
     "connector": {
+      "description": "Establishes an outgoing connections from the router",
       "include": [
-        "entity-id",
+        "common",
         "ssl-profile",
-        "ip-addr",
+        "ip-address",
 	"connection"
       ],
       "attributes": {
@@ -190,23 +204,24 @@
       }
     },
 
-    "logging": {
+    "log": {
+      "description": "Set the level of logging output from a particular module",
       "include": [
-        "entity-id"
+        "common"
       ],
       "attributes": {
 	"module": {
 	  "type":[
-	    "router",
-	    "message",
-	    "server",
-	    "agent",
-	    "container",
-	    "config",
-	    "default",
-	    "error"
+	    "ROUTER",
+	    "MESSAGE",
+	    "SERVER",
+	    "AGENT",
+	    "CONTAINER",
+	    "CONFIG",
+	    "DEFAULT",
+	    "ERROR"
 	  ],
-	  "description": "Module to configure logging level. The special module 'default' specifies logging for modules that don't have explicit log sections."
+	  "description": "Module to configure logging level. The special module 'DEFAULT' specifies logging for modules that don't have explicit log sections."
 	},
         "level": {
           "type": [
@@ -220,7 +235,7 @@
             "critical"
           ],
           "default": "info",
-          "description": "Indicates the minimum logging level for the module. E.g. WARNING means log WARNING, ERROR and CRITICAL messages. TRACE logs all messages. NONE disables logging for the module. "
+          "description": "Indicates the minimum logging level for the module. E.g. 'warning' means log warning, error and critical messages. 'trace' logs all messages. 'none' disables logging for the module. "
         },
         "timestamp": {
           "type": "Boolean",
@@ -236,7 +251,7 @@
 
     "fixed-address": {
       "include": [
-        "entity-id"
+        "common"
       ],
       "attributes": {
         "prefix": {
@@ -269,9 +284,17 @@
 
     "waypoint": {
       "include": [
-        "entity-id"
+        "common"
       ],
       "attributes": {
+        "address": {
+          "type": "String",
+          "required": true
+        },
+        "connector": {
+          "type": "String",
+          "required": true
+        },
         "in-phase": {
           "type": "Integer",
           "default": -1
@@ -279,10 +302,6 @@
         "out-phase": {
           "type": "Integer",
           "default": -1
-        },
-        "connector": {
-          "type": "String",
-          "required": true
         }
       }
     }

Modified: qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.py?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.py (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.py Tue Jul  8 18:43:15 2014
@@ -21,11 +21,12 @@
 Qpid Dispatch Router management schema and config file parsing.
 """
 
-import json, re
+import json, re, sys
 import schema
-from entity import EntityList, Entity
+from entity import EntityList, Entity, OrderedDict
 from copy import copy
 
+
 class QdSchema(schema.Schema):
     """
     Qpid Dispatch Router management schema.
@@ -35,7 +36,7 @@ class QdSchema(schema.Schema):
     def __init__(self):
         """Load schema."""
         with open(self.SCHEMA_FILE) as f:
-            super(QdSchema, self).__init__(**json.load(f))
+            super(QdSchema, self).__init__(**json.load(f, object_pairs_hook=OrderedDict))
 
     def validate(self, entities, **kwargs):
         """
@@ -52,20 +53,22 @@ class QdSchema(schema.Schema):
         if entities.router[0].mode != 'interior':
             for connect in entities.get(entity_type='listeners') + entities.get(entity_type='connector'):
                 if connect['role'] != 'normal':
-                    raise schema.SchemaError("Role '%s' for entity '%s' only permitted with 'interior' mode % (entity['role'], connect.name)")
+                    raise schema.ValidationError("Role '%s' for entity '%s' only permitted with 'interior' mode % (entity['role'], connect.name)")
+
 
 class QdConfig(EntityList):
     """An L{EntityList} loaded from a qdrouterd.conf and validated against L{QdSchema}."""
 
-    def __init__(self, schema=QdSchema()):
+    def __init__(self, filename=None, schema=QdSchema()):
         self.schema = schema
+        if filename: self.load(filename)
 
     @staticmethod
     def _parse(lines):
         """Parse config file format into a section list"""
         begin = re.compile(r'([\w-]+)[ \t]*{') # WORD {
         end = re.compile(r'}')                 # }
-        attr = re.compile(r'([\w-]+)[ \t]*:[ \t]*([\w-]+)') # WORD1: WORD2
+        attr = re.compile(r'([\w-]+)[ \t]*:[ \t]*(.+)') # WORD1: VALUE
 
         def sub(line):
             """Do substitutions to make line json-friendly"""
@@ -77,7 +80,8 @@ class QdConfig(EntityList):
 
         js_text = "[%s]"%("".join([sub(l) for l in lines]))
         spare_comma = re.compile(r',\s*([]}])') # Strip spare commas
-        return json.loads(re.sub(spare_comma, r'\1', js_text))
+        js_text = re.sub(spare_comma, r'\1', js_text)
+        return json.loads(js_text, object_pairs_hook=OrderedDict)
 
     def _expand(self, content):
         """
@@ -123,11 +127,38 @@ class QdConfig(EntityList):
                 attrs['name'] = attrs['identity'] = identity
         return content
 
-    def load(self, lines):
+    def load(self, source):
         """
         Load a configuration file.
-        @param lines: A list of lines, or an open file object.
+        @param source: A file name, open file object or iterable list of lines
         """
-        sections = self._default_ids(self._expand(self._parse(lines)))
-        self[:] = [Entity(type=s[0], **s[1]) for s in sections]
-        self.validate(self.schema)
+        if isinstance(source, basestring):
+            with open(source) as f:
+                try:
+                    self.load(f)
+                except:
+                    ex_type, ex_value, ex_trace = sys.exc_info()
+                    raise ex_type, "Loading '%s': %s"%(source, ex_value), ex_trace
+        else:
+            sections = self._parse(source);
+            # Add missing singleton sections
+            for et in self.schema.entity_types.itervalues():
+                if et.singleton and not [s for s in sections if s[0] == et.name]:
+                    sections.append((et.name, {}))
+            sections = self._expand(sections)
+            sections = self._default_ids(sections)
+            self[:] = [Entity(type=s[0], **s[1]) for s in sections]
+            self.validate(self.schema)
+
+    def section_count(self, section):
+        return len(self.get(type=section))
+
+    def value(self, section, index, key, convert=lambda x: x):
+        """
+        @return: Value at section, index, key or None if absent.
+        @param as_type: A callable to convert the result to some type.
+        """
+        entities = self.get(type=section)
+        if len(entities) <= index or key not in entities[index]:
+            return None
+        return convert(entities[index][key])

Modified: qpid/dispatch/trunk/python/qpid_dispatch_internal/management/schema.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch_internal/management/schema.py?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/schema.py (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/schema.py Tue Jul  8 18:43:15 2014
@@ -26,8 +26,13 @@ check for uniqueness of enties/attribute
 A Schema can be loaded/dumped to a json file.
 """
 
-import os
-from entity import OrderedDict
+import os, sys
+from entity import OrderedDict, Entity
+
+class ValidationError(Exception):
+    """Error raised if schema validation fails"""
+    pass
+
 
 def schema_file(name):
     """Return a file name relative to the directory from which this module was loaded."""
@@ -86,7 +91,7 @@ class BooleanType(Type):
                 return self.VALUES[value.lower()]
             return bool(value)
         except:
-            raise ValueError("Invalid Boolean value '%r'"%value)
+            raise ValidationError("Invalid Boolean value '%r'"%value)
 
 class EnumType(Type):
     """An enumerated type"""
@@ -122,7 +127,7 @@ class EnumType(Type):
                         return self.tags[i]
             except (ValueError, IndexError):
                 pass
-        raise ValueError("Invalid value for %s: '%r'"%(self.name, value))
+        raise ValidationError("Invalid value for %s: '%r'"%(self.name, value))
 
     def dump(self):
         """
@@ -145,7 +150,7 @@ def get_type(rep):
         return EnumType(rep)
     if rep in BUILTIN_TYPES:
         return BUILTIN_TYPES[rep]
-    raise TypeError("No such schema type: %s"%rep)
+    raise ValidationError("No such schema type: %s"%rep)
 
 def _dump_dict(items):
     """
@@ -183,15 +188,15 @@ class AttributeType(object):
     @ivar name: Attribute name.
     @ivar atype: Attribute L{Type}
     @ivar required: True if the attribute is reqiured.
-    @ivar default: Default value for the attribute or None if no default.
+    @ivar default: Default value for the attribute or None if no default. Can be a reference.
+    @ivar value: Fixed value for the attribute. Can be a reference.
     @ivar unique: True if the attribute value is unique.
     @ivar description: Description of the attribute type.
     @ivar include: Include section or None
     """
 
-    def __init__(self, name, type=None, default=None, required=False, unique=False, include=None,
-                 description=""
-    ): # pylint: disable=redefined-builtin
+    def __init__(self, name, type=None, default=None, required=False, unique=False, value=None,
+                 include=None, description=""):
         """
         See L{AttributeType} instance variables.
         """
@@ -199,33 +204,47 @@ class AttributeType(object):
         self.atype = get_type(type)
         self.required = required
         self.default = default
+        self.value = value
         self.unique = unique
         self.description = description
-        if default is not None:
-            self.default = self.atype.validate(default)
         self.include = include
+        if self.value is not None and self.default is not None:
+            raise ValidationError("Attribute '%s' has default value and fixed value"%self.name)
 
-    def validate(self, value, check_required=True, add_default=True, check_unique=None, **kwargs):
+    def missing_value(self, check_required=True, add_default=True, **kwargs):
         """
-        Validate value for this attribute definition.
+        Fill in missing default and fixed values but don't resolve references.
         @keyword check_required: Raise an exception if required attributes are misssing.
         @keyword add_default:  Add a default value for missing attributes.
+        @param kwargs: See L{Schema.validate}
+        """
+        if self.value is not None: # Fixed value attribute
+            return self.value
+        if add_default and self.default is not None:
+            return self.default
+        if check_required and self.required:
+            raise ValidationError("Missing required attribute '%s'"%(self.name))
+
+
+    def validate(self, value, resolve=lambda x: x, check_unique=None, **kwargs):
+        """
+        Validate value for this attribute definition.
+        @param value: The value to validate.
+        @param resolve: function to resolve value references.
         @keyword check_unique: A dict to collect values to check for uniqueness.
             None means don't check for uniqueness.
         @param kwargs: See L{Schema.validate}
         @return: value converted to the correct python type. Rais exception if any check fails.
         """
-        if value is None and add_default:
-            value = self.default
-        if value is None:
-            if self.required and check_required:
-                raise ValueError("Missing value for attribute '%s'"%self.name)
-            else:
-                return None
-        else:
-            if self.unique and not _is_unique(check_unique, self.name, value):
-                raise ValueError("Multiple instances of unique attribute '%s'"%self.name)
+        value = resolve(value)
+        if self.unique and not _is_unique(check_unique, self.name, value):
+            raise ValidationError("Duplicate value '%s' for unique attribute '%s'"%(value, self.name))
+        if self.value and value != resolve(self.value):
+            raise ValidationError("Attribute '%s' has fixed value '%s' but given '%s'"%(self.name, self.value, value))
+        try:
             return self.atype.validate(value, **kwargs)
+        except (TypeError, ValueError), e:
+            raise ValidationError, str(e), sys.exc_info()[2]
 
     def dump(self):
         """
@@ -240,7 +259,7 @@ class AttributeType(object):
         ])
 
     def __str__(self):
-        return "AttributeType%s"%(self.__dict__)
+        return "%s(%s)"%(self.__class__.__name__, self.name)
 
 class AttributeTypeHolder(object):
     """Base class for IncludeType and EntityType - a named holder of attribute types"""
@@ -248,10 +267,10 @@ class AttributeTypeHolder(object):
     def __init__(self, name, schema, attributes=None, description=""):
         self.name, self.schema, self.description = name, schema, description
         self.attributes = OrderedDict()
-        self.attributes['type'] = AttributeType('type', type='String', default=name, required=True)
         if attributes:
             self.add_attributes(attributes)
 
+
     def add_attributes(self, attributes):
         """
         Add attributes.
@@ -259,7 +278,7 @@ class AttributeTypeHolder(object):
         """
         for k, v in attributes.iteritems():
             if k in self.attributes:
-                raise TypeError("Duplicate attribute in '%s': '%s'"%(self.name, k))
+                raise ValidationError("Duplicate attribute in '%s': '%s'"%(self.name, k))
             self.attributes[k] = AttributeType(k, **v)
 
     def dump(self):
@@ -270,13 +289,16 @@ class AttributeTypeHolder(object):
             ('description', self.description or None)
         ])
 
+
     def __str__(self):
-        print self.name
+        return "%s(%s)"%(self.__class__.__name__, self.name)
+
 
 class IncludeType(AttributeTypeHolder):
 
     def __init__(self, name, schema, attributes=None, description=""):
         super(IncludeType, self).__init__(name, schema, attributes, description)
+        attributes = attributes or {}
         for a in self.attributes.itervalues():
             a.include = self
 
@@ -300,10 +322,13 @@ class EntityType(AttributeTypeHolder):
         @param description: Human readable description.
         """
         super(EntityType, self).__init__(name, schema, attributes, description)
+        self.refs = {'entity-type': name}
         self.singleton = singleton
         self.include = include
         if include and self.schema.includes:
             for i in include:
+                if not i in schema.includes:
+                    raise ValidationError("Include '%s' not found in %s'"%(i, self))
                 for attr in schema.includes[i].attributes.itervalues():
                     self.attributes[attr.name] = attr
 
@@ -313,6 +338,25 @@ class EntityType(AttributeTypeHolder):
         if self.singleton: d['singleton'] = True
         return d
 
+    def resolve(self, value, attributes):
+        """Resolve a $ or $$ reference"""
+        values = [value]
+        while True:
+            if isinstance(value, basestring) and value.startswith('$$'):
+                if value[2:] not in self.refs:
+                    raise ValidationError("Invalid entity type reference '%s'"%value)
+                value = self.refs[value[2:]]
+            elif isinstance(value, basestring) and value.startswith('$'):
+                if value[1:] not in self.attributes:
+                    raise ValidationError("Invalid attribute reference '%s'"%value)
+                value = attributes.get(value[1:])
+            else:
+                return value # Not a reference, don't need to resolve
+            if value == values[0]: # Circular reference
+                raise ValidationError("Unresolved circular reference '%s'"%values)
+            values.append(value)
+
+
     def validate(self, attributes, check_singleton=None, **kwargs):
         """
         Validate attributes.
@@ -321,21 +365,32 @@ class EntityType(AttributeTypeHolder):
         @param check_singleton: dict to enable singleton checking or None to disable.
         @param kwargs: See L{Schema.validate}
         """
+
+        def drop_none(): # Drop null items in attributes
+            for name in attributes.keys():
+                if attributes[name] is None:
+                    del attributes[name]
+
         if self.singleton and not _is_unique(check_singleton, self.name, True):
-            raise ValueError("Found multiple instances of singleton entity type '%s'"%self.name)
-        # Validate
-        for name, value in attributes.iteritems():
-            attributes[name] = self.attributes[name].validate(value, **kwargs)
-        # Set defaults, check for missing required values
+            raise ValidationError("Multiple instances of singleton entity type '%s'"%self.name)
+
+        drop_none()
+
+        # Add missing values
         for attr in self.attributes.itervalues():
             if attr.name not in attributes:
-                value = attr.validate(None, **kwargs)
-                if not value is None:
-                    attributes[attr.name] = value
-        # Drop null items
-        for name in attributes.keys():
-            if attributes[name] is None:
-                del attributes[name]
+                value = attr.missing_value(**kwargs)
+                if value is not None: attributes[attr.name] = value
+
+        # Validate attributes.
+        for name, value in attributes.iteritems():
+            if name not in self.attributes:
+                raise ValidationError("%s has unknown attribute '%s'"%(self, name))
+            attributes[name] = self.attributes[name].validate(
+                value, lambda v: self.resolve(v, attributes), **kwargs)
+
+        drop_none()
+
         return attributes
 
 
@@ -394,10 +449,12 @@ class Schema(object):
         @keyword check_required: Raise exception if required attributes are missing.
         @keyword add_default: Add defaults for missing attributes.
         @keyword check_unique: Raise exception if unique attributes are duplicated.
-        @keyword check_singleton: Raise exception if singleton entities are duplicated.
+        @keyword check_singleton: Raise exception if singleton entities are duplicated or missing
         """
         if check_singleton: check_singleton = {}
         if check_unique: check_unique = {}
+
+        # Validate all entities.
         for e in entities:
             et = self.entity_types[e.type]
             et.validate(e,
@@ -406,4 +463,5 @@ class Schema(object):
                         add_default=add_default,
                         check_unique=check_unique,
                         check_singleton=check_singleton)
+
         return entities

Modified: qpid/dispatch/trunk/router/src/main.c
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/router/src/main.c?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/router/src/main.c (original)
+++ qpid/dispatch/trunk/router/src/main.c Tue Jul  8 18:43:15 2014
@@ -29,20 +29,6 @@ static int            exit_with_sigint =
 static qd_dispatch_t *dispatch = 0;
 static qd_log_source_t *log_source = 0;
 
-static const char *app_config =
-    "from qpid_dispatch_internal.config.schema import config_schema\n"
-    "config_schema['fixed-address'] = (False, {\n"
-    "   'prefix' : (str, 0, 'M', None, None),\n"
-    "   'phase'  : (int, 1, '', 0, None),\n"
-    "   'fanout' : (str, None, '', 'multiple', ['multiple', 'single']),\n"
-    "   'bias'   : (str, None, '', 'closest',  ['closest', 'spread'])})\n"
-    "config_schema['waypoint'] = (False, {\n"
-    "   'name'      : (str, 0,    'M', None, None),\n"
-    "   'in-phase'  : (int, None, '',  -1, None),\n"
-    "   'out-phase' : (int, None, '',  -1, None),\n"
-    "   'connector' : (str, None, 'M', None, None)})\n";
-
-
 /**
  * The thread_start_handler is invoked once for each server thread at thread startup.
  */
@@ -141,7 +127,6 @@ int main(int argc, char **argv)
 
     qd_error_clear();
     dispatch = qd_dispatch(python_pkgdir);
-    qd_dispatch_extend_config_schema(dispatch, app_config);
     log_source = qd_log_source("MAIN"); /* Logging is initialized by qd_dispatch. */
     check();
     qd_dispatch_load_config(dispatch, config_path);

Modified: qpid/dispatch/trunk/src/config.c
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/config.c?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/config.c (original)
+++ qpid/dispatch/trunk/src/config.c Tue Jul  8 18:43:15 2014
@@ -22,7 +22,8 @@
 #include <qpid/dispatch/alloc.h>
 #include <qpid/dispatch/log.h>
 
-#define PYTHON_MODULE "qpid_dispatch_internal.config"
+const char *MANAGEMENT_MODULE = "qpid_dispatch_internal.management";
+const char *CONFIG_CLASS = "QdConfig";
 
 static qd_log_source_t *log_source = 0;
 
@@ -50,46 +51,22 @@ void qd_config_finalize(void)
 
 qd_config_t *qd_config(void)
 {
+    qd_error_clear();
     qd_config_t *config = new_qd_config_t();
-
-    //
-    // Load the Python configuration module and get a reference to the config class.
-    //
-    PyObject *pName = PyString_FromString(PYTHON_MODULE);
-    config->pModule = PyImport_Import(pName);
-    Py_DECREF(pName);
-
-    if (!config->pModule) {
-        qd_error_py();
-        free_qd_config_t(config);
-        qd_log(log_source, QD_LOG_ERROR, "Unable to load configuration module: %s", PYTHON_MODULE);
-        return 0;
-    }
-
-    config->pClass = PyObject_GetAttrString(config->pModule, "DispatchConfig");
-    if (!config->pClass || !PyClass_Check(config->pClass)) {
-        PyErr_Print();
-        Py_DECREF(config->pModule);
-        free_qd_config_t(config);
-        qd_log(log_source, QD_LOG_ERROR, "Problem with configuration module: Missing DispatchConfig class");
-        return 0;
-    }
-
+    memset(config, 0, sizeof(*config));
     //
-    // Instantiate the DispatchConfig class
+    // Load the Python management module and get a reference to the config class.
     //
-    PyObject *pArgs = PyTuple_New(0);
-    config->pObject = PyInstance_New(config->pClass, pArgs, 0);
-    Py_DECREF(pArgs);
-
-    if (config->pObject == 0) {
-        qd_error_py();
-        Py_DECREF(config->pModule);
-        free_qd_config_t(config);
-        return 0;
+    if ((config->pModule = PyImport_ImportModule(MANAGEMENT_MODULE)) &&
+	(config->pClass = PyObject_GetAttrString(config->pModule, CONFIG_CLASS)) &&
+	(config->pObject = PyObject_CallFunction(config->pClass, NULL)))
+    {
+	return config;
+    } else {
+	qd_error_py();		/* Log python error & set qd_error */
+	qd_config_free(config);
+	return 0;
     }
-
-    return config;
 }
 
 
@@ -101,16 +78,16 @@ qd_error_t qd_config_read(qd_config_t *c
     PyObject *pArgs;
     PyObject *pResult;
 
+    assert(config);
     if (!config)
 	return qd_error(QD_ERROR_CONFIG, "No configuration object");
 
-    pMethod = PyObject_GetAttrString(config->pObject, "read_file");
+    pMethod = PyObject_GetAttrString(config->pObject, "load");
     if (!pMethod || !PyCallable_Check(pMethod)) {
 	Py_XDECREF(pMethod);
         qd_error_py();
-        return qd_error(QD_ERROR_CONFIG, "No callable 'read_file'");
+        return qd_error(QD_ERROR_CONFIG, "No callable 'load'");
     }
-
     pArgs = PyTuple_New(1);
     pPath = PyString_FromString(filepath);
     PyTuple_SetItem(pArgs, 0, pPath);
@@ -126,18 +103,12 @@ qd_error_t qd_config_read(qd_config_t *c
 }
 
 
-void qd_config_extend(qd_config_t *config, const char *text)
-{
-    PyRun_SimpleString(text);
-}
-
-
 void qd_config_free(qd_config_t *config)
 {
     if (config) {
-        Py_DECREF(config->pClass);
-        Py_DECREF(config->pModule);
-        Py_DECREF(config->pObject);
+        Py_XDECREF(config->pClass);
+        Py_XDECREF(config->pModule);
+        Py_XDECREF(config->pObject);
         free_qd_config_t(config);
     }
 }
@@ -145,134 +116,70 @@ void qd_config_free(qd_config_t *config)
 
 int qd_config_item_count(const qd_dispatch_t *dispatch, const char *section)
 {
-    const qd_config_t *config = dispatch->config;
-    PyObject *pSection;
-    PyObject *pMethod;
-    PyObject *pArgs;
-    PyObject *pResult;
-    int       result = 0;
-
-    if (!config)
-        return 0;
-
-    pMethod = PyObject_GetAttrString(config->pObject, "item_count");
-    if (!pMethod || !PyCallable_Check(pMethod)) {
-        qd_log(log_source, QD_LOG_ERROR, "Problem with configuration module: No callable 'item_count'");
-        if (pMethod) {
-            Py_DECREF(pMethod);
-        }
-        return 0;
-    }
-
-    pSection = PyString_FromString(section);
-    pArgs    = PyTuple_New(1);
-    PyTuple_SetItem(pArgs, 0, pSection);
-    pResult = PyObject_CallObject(pMethod, pArgs);
-    Py_DECREF(pArgs);
-    if (pResult && PyInt_Check(pResult))
-        result = (int) PyInt_AsLong(pResult);
-    if (pResult) {
-        Py_DECREF(pResult);
-    }
-    Py_DECREF(pMethod);
-
-    return result;
+    PyErr_Clear();
+    PyObject *result =
+	PyObject_CallMethod(dispatch->config->pObject, "section_count", "(s)", section);
+    if (qd_error_py()) return -1;
+    long count = PyInt_AsLong(result);
+    if (qd_error_py()) return -1;
+    Py_DECREF(result);
+    return count;
 }
 
 
-static PyObject *item_value(const qd_dispatch_t *dispatch, const char *section, int index, const char* key, const char* method)
+static PyObject *item_value(const qd_dispatch_t *dispatch, const char *section, int index, const char* key)
 {
-    const qd_config_t *config = dispatch->config;
-    PyObject *pSection;
-    PyObject *pIndex;
-    PyObject *pKey;
-    PyObject *pMethod;
-    PyObject *pArgs;
-    PyObject *pResult;
-
-    if (!config)
-        return 0;
-
-    pMethod = PyObject_GetAttrString(config->pObject, method);
-    if (!pMethod || !PyCallable_Check(pMethod)) {
-        qd_log(log_source, QD_LOG_ERROR, "Problem with configuration module: No callable '%s'", method);
-        if (pMethod) {
-            Py_DECREF(pMethod);
-        }
-        return 0;
-    }
-
-    pSection = PyString_FromString(section);
-    pIndex   = PyInt_FromLong((long) index);
-    pKey     = PyString_FromString(key);
-    pArgs    = PyTuple_New(3);
-    PyTuple_SetItem(pArgs, 0, pSection);
-    PyTuple_SetItem(pArgs, 1, pIndex);
-    PyTuple_SetItem(pArgs, 2, pKey);
-    pResult = PyObject_CallObject(pMethod, pArgs);
-    Py_DECREF(pArgs);
-    Py_DECREF(pMethod);
-
-    return pResult;
+    return PyObject_CallMethod(dispatch->config->pObject, "value", "(sis)", section, index, key);
 }
 
 
 bool qd_config_item_exists(const qd_dispatch_t *dispatch, const char *section, int index, const char* key)
 {
-    PyObject *pResult = item_value(dispatch, section, index, key, "value_string");
-    bool exists = pResult && pResult != Py_None;
-    if (pResult) {
-        Py_DECREF(pResult);
-    }
+    PyObject *value = item_value(dispatch, section, index, key);
+    if (!value) qd_error_py();
+    bool exists = value && (value != Py_None);
+    Py_XDECREF(value);
     return exists;
 }
 
 char *qd_config_item_value_string(const qd_dispatch_t *dispatch, const char *section, int index, const char* key)
 {
-    PyObject *pResult = item_value(dispatch, section, index, key, "value_string");
-    char     *value   = 0;
-
-    if (pResult && PyString_Check(pResult)) {
-        Py_ssize_t size = PyString_Size(pResult);
-        value = (char*) malloc(size + 1);
-        strncpy(value, PyString_AsString(pResult), size + 1);
+    PyObject *value = item_value(dispatch, section, index, key);
+    if (value && value != Py_None) {
+	PyObject *value_str = PyObject_Str(value);
+	Py_DECREF(value);
+	if (value_str) {
+	    char* result = strdup(PyString_AsString(value_str));
+	    Py_DECREF(value_str);
+	    return result;
+	}
     }
-
-    if (pResult) {
-        Py_DECREF(pResult);
-    }
-
-    return value;
+    qd_error_py();
+    return 0;
 }
 
 
 uint32_t qd_config_item_value_int(const qd_dispatch_t *dispatch, const char *section, int index, const char* key)
 {
-    PyObject *pResult = item_value(dispatch, section, index, key, "value_int");
-    uint32_t  value   = 0;
-
-    if (pResult && PyLong_Check(pResult))
-        value = (uint32_t) PyLong_AsLong(pResult);
-
-    if (pResult) {
-        Py_DECREF(pResult);
+    PyObject *value = item_value(dispatch, section, index, key);
+    if (value && value != Py_None) {
+	long result = PyLong_AsLong(value);
+	Py_DECREF(value);
+	return result;
     }
-
-    return value;
+    qd_error_py();
+    return 0;
 }
 
 
 int qd_config_item_value_bool(const qd_dispatch_t *dispatch, const char *section, int index, const char* key)
 {
-    PyObject *pResult = item_value(dispatch, section, index, key, "value_bool");
-    int       value   = 0;
-
-    if (pResult && pResult != Py_None)
-        value = 1;
-
-    if (pResult) {
-        Py_DECREF(pResult);
+    PyObject *value = item_value(dispatch, section, index, key);
+    if (value) {
+	bool result = PyObject_IsTrue(value);
+        Py_DECREF(value);
+	return result;
     }
-
-    return value;
+    qd_error_py();
+    return 0;
 }

Modified: qpid/dispatch/trunk/src/config_private.h
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/config_private.h?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/config_private.h (original)
+++ qpid/dispatch/trunk/src/config_private.h Tue Jul  8 18:43:15 2014
@@ -26,8 +26,7 @@
 void qd_config_initialize(void);
 void qd_config_finalize(void);
 qd_config_t *qd_config(void);
-qd_error_t qd_config_read(qd_config_t *config, const char *filename);
-void qd_config_extend(qd_config_t *config, const char *text);
+qd_error_t  qd_config_read(qd_config_t *config, const char *filename);
 void qd_config_free(qd_config_t *config);
 
 #endif

Modified: qpid/dispatch/trunk/src/dispatch.c
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/dispatch.c?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/dispatch.c (original)
+++ qpid/dispatch/trunk/src/dispatch.c Tue Jul  8 18:43:15 2014
@@ -68,13 +68,9 @@ qd_dispatch_t *qd_dispatch(const char *p
     qd_message_initialize();
     qd->config = qd_config();
 
-    return qd;
-}
-
+    assert(qd->config);
 
-void qd_dispatch_extend_config_schema(qd_dispatch_t *qd, const char* text)
-{
-    qd_config_extend(qd->config, text);
+    return qd;
 }
 
 

Modified: qpid/dispatch/trunk/src/error.c
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/error.c?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/error.c (original)
+++ qpid/dispatch/trunk/src/error.c Tue Jul  8 18:43:15 2014
@@ -91,17 +91,10 @@ static void py_set_item(PyObject *dict, 
     Py_DECREF(py_name);
 }
 
-static PyObject *py_import(const char* module) {
-    PyObject *py_str = PyString_FromString(module);
-    PyObject *py_module = PyImport_Import(py_str);
-    Py_DECREF(py_str);
-    return py_module;
-}
-
 static void log_trace_py(PyObject *type, PyObject *value, PyObject* trace) {
     if (!(type && value && trace)) return;
 
-    PyObject *module = py_import("traceback");
+    PyObject *module = PyImport_ImportModule("traceback");
     if (!module) return;
 
     PyObject *globals = PyDict_New();

Modified: qpid/dispatch/trunk/src/router_agent.c
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/router_agent.c?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/router_agent.c (original)
+++ qpid/dispatch/trunk/src/router_agent.c Tue Jul  8 18:43:15 2014
@@ -383,4 +383,3 @@ void qd_router_build_node_list(qd_dispat
     }
     sys_mutex_unlock(router->lock);
 }
-

Modified: qpid/dispatch/trunk/src/router_config.c
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/router_config.c?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/router_config.c (original)
+++ qpid/dispatch/trunk/src/router_config.c Tue Jul  8 18:43:15 2014
@@ -39,7 +39,7 @@ static void qd_router_configure_addresse
         char *bias   = qd_config_item_value_string(router->qd, CONF_ADDRESS, idx, "bias");
 
         if (phase < 0 || phase > 9) {
-            qd_log(router->log_source, QD_LOG_ERROR, "Phase for prefix '%s' must be between 0 and 9.  Ignoring", prefix);
+            qd_log(router->log_source, QD_LOG_ERROR, "Invalid phase %d for prefix '%s' must be between 0 and 9.  Ignoring", phase, prefix);
             free(prefix);
             free(fanout);
             free(bias);
@@ -114,14 +114,14 @@ static void qd_router_configure_waypoint
     int count = qd_config_item_count(router->qd, CONF_WAYPOINT);
 
     for (int idx = 0; idx < count; idx++) {
-        char *name      = qd_config_item_value_string(router->qd, CONF_WAYPOINT, idx, "name");
+        char *address      = qd_config_item_value_string(router->qd, CONF_WAYPOINT, idx, "address");
+        char *connector = qd_config_item_value_string(router->qd, CONF_WAYPOINT, idx, "connector");
         int   in_phase  = qd_config_item_value_int(router->qd,    CONF_WAYPOINT, idx, "in-phase");
         int   out_phase = qd_config_item_value_int(router->qd,    CONF_WAYPOINT, idx, "out-phase");
-        char *connector = qd_config_item_value_string(router->qd, CONF_WAYPOINT, idx, "connector");
 
         if (in_phase > 9 || out_phase > 9) {
-            qd_log(router->log_source, QD_LOG_ERROR, "Phases for waypoint '%s' must be between 0 and 9.  Ignoring", name);
-            free(name);
+            qd_log(router->log_source, QD_LOG_ERROR, "Phases for waypoint '%s' must be between 0 and 9.  Ignoring", address);
+            free(address);
             free(connector);
             continue;
         }
@@ -129,7 +129,7 @@ static void qd_router_configure_waypoint
         qd_waypoint_t *waypoint = NEW(qd_waypoint_t);
         memset(waypoint, 0, sizeof(qd_waypoint_t));
         DEQ_ITEM_INIT(waypoint);
-        waypoint->name           = name;
+        waypoint->address        = address;
         waypoint->in_phase       = in_phase >= 0  ? (char) in_phase  + '0' : '\0';
         waypoint->out_phase      = out_phase >= 0 ? (char) out_phase + '0' : '\0';
         waypoint->connector_name = connector;
@@ -141,8 +141,8 @@ static void qd_router_configure_waypoint
         DEQ_INSERT_TAIL(router->waypoints, waypoint);
 
         qd_log(router->log_source, QD_LOG_INFO,
-	       "Configured Waypoint: name=%s in_phase=%d out_phase=%d connector=%s",
-               name, in_phase, out_phase, connector);
+	       "Configured Waypoint: address=%s in_phase=%d out_phase=%d connector=%s",
+               address, in_phase, out_phase, connector);
     }
 }
 
@@ -172,7 +172,7 @@ void qd_router_configure_free(qd_router_
 
     for (qd_waypoint_t *wp = DEQ_HEAD(router->waypoints); wp; wp = DEQ_HEAD(router->waypoints)) {
         DEQ_REMOVE_HEAD(router->waypoints);
-        free(wp->name);
+        free(wp->address);
         free(wp->connector_name);
         free(wp);
     }

Modified: qpid/dispatch/trunk/src/router_node.c
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/router_node.c?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/router_node.c (original)
+++ qpid/dispatch/trunk/src/router_node.c Tue Jul  8 18:43:15 2014
@@ -765,7 +765,7 @@ static void router_rx_handler(void* cont
         // address for the link.
         //
         if (!iter && rlink->waypoint) {
-            iter = qd_field_iterator_string(rlink->waypoint->name, ITER_VIEW_ADDRESS_HASH);
+            iter = qd_field_iterator_string(rlink->waypoint->address, ITER_VIEW_ADDRESS_HASH);
             qd_field_iterator_set_phase(iter, rlink->waypoint->out_phase);
         }
 

Modified: qpid/dispatch/trunk/src/router_private.h
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/router_private.h?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/router_private.h (original)
+++ qpid/dispatch/trunk/src/router_private.h Tue Jul  8 18:43:15 2014
@@ -184,7 +184,7 @@ DEQ_DECLARE(qd_config_address_t, qd_conf
  */
 struct qd_waypoint_t {
     DEQ_LINKS(qd_waypoint_t);
-    char                  *name;
+    char                  *address;
     char                   in_phase;       ///< Phase for re-entering message.
     char                   out_phase;      ///< Phase for exiting message.
     char                  *connector_name; ///< On-demand connector name for outgoing messages.

Modified: qpid/dispatch/trunk/src/waypoint.c
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/waypoint.c?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/waypoint.c (original)
+++ qpid/dispatch/trunk/src/waypoint.c Tue Jul  8 18:43:15 2014
@@ -41,7 +41,7 @@ struct qd_waypoint_context_t {
 };
 
 // Convenience for logging waypoint messages, expects qd and wp to be defined.
-#define LOG(LEVEL, MSG, ...) qd_log(qd->router->log_source, QD_LOG_##LEVEL, "waypoint=%s: " MSG, wp->name, ##__VA_ARGS__)
+#define LOG(LEVEL, MSG, ...) qd_log(qd->router->log_source, QD_LOG_##LEVEL, "waypoint=%s: " MSG, wp->address, ##__VA_ARGS__)
 
 static void qd_waypoint_visit_sink_LH(qd_dispatch_t *qd, qd_waypoint_t *wp)
 {
@@ -58,7 +58,7 @@ static void qd_waypoint_visit_sink_LH(qd
         // Compose the phased-address and search the routing table for the address.
         // If it's not found, add it to the table but leave the link/router linkages empty.
         //
-        qd_field_iterator_t *iter = qd_field_iterator_string(wp->name, ITER_VIEW_ADDRESS_HASH);
+        qd_field_iterator_t *iter = qd_field_iterator_string(wp->address, ITER_VIEW_ADDRESS_HASH);
         qd_field_iterator_set_phase(iter, wp->in_phase);
         qd_hash_retrieve(router->addr_hash, iter, (void*) &addr);
 
@@ -81,8 +81,8 @@ static void qd_waypoint_visit_sink_LH(qd
         qd_connection_manager_start_on_demand(qd, wp->connector);
     }
     else if (!wp->out_link) {
-        wp->out_link = qd_link(router->node, wp->connection, QD_OUTGOING, wp->name);
-        pn_terminus_set_address(qd_link_target(wp->out_link), wp->name);
+        wp->out_link = qd_link(router->node, wp->connection, QD_OUTGOING, wp->address);
+        pn_terminus_set_address(qd_link_target(wp->out_link), wp->address);
 
         qd_router_link_t *rlink = new_qd_router_link_t();
         DEQ_ITEM_INIT(rlink);
@@ -102,7 +102,7 @@ static void qd_waypoint_visit_sink_LH(qd
         qd_router_add_link_ref_LH(&addr->rlinks, rlink);
 
         if (DEQ_SIZE(addr->rlinks) == 1) {
-            qd_field_iterator_t *iter = qd_field_iterator_string(wp->name, ITER_VIEW_ADDRESS_HASH);
+            qd_field_iterator_t *iter = qd_field_iterator_string(wp->address, ITER_VIEW_ADDRESS_HASH);
             qd_field_iterator_set_phase(iter, wp->in_phase);
             qd_router_mobile_added(router, iter);
             qd_field_iterator_free(iter);
@@ -131,7 +131,7 @@ static void qd_waypoint_visit_source_LH(
         // Compose the phased-address and search the routing table for the address.
         // If it's not found, add it to the table but leave the link/router linkages empty.
         //
-        qd_field_iterator_t *iter = qd_field_iterator_string(wp->name, ITER_VIEW_ADDRESS_HASH);
+        qd_field_iterator_t *iter = qd_field_iterator_string(wp->address, ITER_VIEW_ADDRESS_HASH);
         qd_field_iterator_set_phase(iter, wp->out_phase);
         qd_hash_retrieve(router->addr_hash, iter, (void*) &addr);
 
@@ -154,8 +154,8 @@ static void qd_waypoint_visit_source_LH(
         qd_connection_manager_start_on_demand(qd, wp->connector);
     }
     else if (!wp->in_link) {
-        wp->in_link = qd_link(router->node, wp->connection, QD_INCOMING, wp->name);
-        pn_terminus_set_address(qd_link_source(wp->in_link), wp->name);
+        wp->in_link = qd_link(router->node, wp->connection, QD_INCOMING, wp->address);
+        pn_terminus_set_address(qd_link_source(wp->in_link), wp->address);
 
         qd_router_link_t *rlink = new_qd_router_link_t();
         DEQ_ITEM_INIT(rlink);
@@ -320,4 +320,3 @@ void qd_waypoint_address_updated_LH(qd_d
         wp = DEQ_NEXT(wp);
     }
 }
-

Modified: qpid/dispatch/trunk/tests/management/schema.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/management/schema.py?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/management/schema.py (original)
+++ qpid/dispatch/trunk/tests/management/schema.py Tue Jul  8 18:43:15 2014
@@ -20,8 +20,8 @@
 
 #pylint: disable=wildcard-import,missing-docstring,too-many-public-methods
 
-import unittest, json
-from qpid_dispatch_internal.management import Schema, Entity, EntityType, BooleanType, EnumType, AttributeType, schema_file
+import unittest, json, sys
+from qpid_dispatch_internal.management import Schema, Entity, EntityType, BooleanType, EnumType, AttributeType, schema_file, ValidationError
 import collections
 
 def replace_od(thing):
@@ -37,6 +37,7 @@ SCHEMA_1 = {
         "entity-id": {
             "attributes": {
                 "name": {"type":"String", "required": True, "unique":True},
+                "type": {"type":"String", "required": True}
             }
         }
     },
@@ -71,7 +72,7 @@ class SchemaTest(unittest.TestCase):
         self.assertTrue(b.validate(True))
         self.assertFalse(b.validate(False))
         self.assertFalse(b.validate('no'))
-        self.assertRaises(ValueError, b.validate, 'x')
+        self.assertRaises(ValidationError, b.validate, 'x')
 
     def test_enum(self):
         e = EnumType(['a', 'b', 'c'])
@@ -79,17 +80,34 @@ class SchemaTest(unittest.TestCase):
         self.assertEqual(e.validate(1), 'b')
         self.assertEqual(e.validate('c', enum_as_int=True), 2)
         self.assertEqual(e.validate(2, enum_as_int=True), 2)
-        self.assertRaises(ValueError, e.validate, 'foo')
-        self.assertRaises(ValueError, e.validate, 3)
+        self.assertRaises(ValidationError, e.validate, 'foo')
+        self.assertRaises(ValidationError, e.validate, 3)
 
     def test_attribute_def(self):
-        a = AttributeType('foo', 'String', 'FOO', False)
+        a = AttributeType('foo', 'String', default='FOO')
+        self.assertEqual('FOO', a.missing_value())
         self.assertEqual(a.validate('x'), 'x')
-        self.assertEqual(a.validate(None), 'FOO')
-        a = AttributeType('foo', 'String', 'FOO', True)
-        self.assertEqual('FOO', a.validate(None))
-        a = AttributeType('foo', 'Integer', None, True)
-        self.assertRaises(ValueError, a.validate, None) # Missing default
+
+        a = AttributeType('foo', 'String', default='FOO', required=True)
+        self.assertEqual('FOO', a.missing_value())
+
+        a = AttributeType('foo', 'String', required=True)
+        self.assertRaises(ValidationError, a.missing_value) # Missing required value.
+
+        a = AttributeType('foo', 'String', value='FOO') # Fixed value
+        self.assertEqual('FOO', a.missing_value())
+        self.assertEqual(a.validate('FOO'), 'FOO')
+        self.assertRaises(ValidationError, a.validate, 'XXX') # Bad fixed value
+
+        self.assertRaises(ValidationError, AttributeType, 'foo', 'String', value='FOO', default='BAR') # Illegal
+
+        a = AttributeType('foo', 'Integer')
+        self.assertEqual(3, a.validate(3))
+        self.assertEqual(3, a.validate('3'))
+        self.assertEqual(3, a.validate(3.0))
+        self.assertRaises(ValidationError, a.validate, None)
+        self.assertRaises(ValidationError, a.validate, "xxx")
+
 
     def test_entity_type(self):
         s = Schema(includes={
@@ -100,14 +118,39 @@ class SchemaTest(unittest.TestCase):
             'foo': {'type':'String', 'default':'FOO'},
             'req': {'type':'Integer', 'required':True},
             'e': {'type':['x', 'y']}})
-        self.assertRaises(ValueError, e.validate, {}) # Missing required 'req'
-        self.assertEqual(e.validate({'req':42, 'e':None}), {'foo': 'FOO', 'req': 42, 'type': 'MyEntity'})
+        self.assertRaises(ValidationError, e.validate, {}) # Missing required 'req'
+        self.assertEqual(e.validate({'req':42, 'e':None}), {'foo': 'FOO', 'req': 42})
         # Try with an include
         e = EntityType('e2', s, attributes={'x':{'type':'Integer'}}, include=['i1', 'i2'])
-        self.assertEqual(e.validate({'x':1}), {'x':1, 'foo1': 'FOO1', 'foo2': 'FOO2', 'type': 'i2'})
+        self.assertEqual(e.validate({'x':1}), {'x':1, 'foo1': 'FOO1', 'foo2': 'FOO2'})
 
-    qdrouter_json = schema_file('qdrouter.json')
+    def test_entity_refs(self):
+        e = EntityType('MyEntity', Schema(), attributes={
+            'type': {'type': 'String', 'required': True, 'value': '$$entity-type'},
+            'name': {'type':'String', 'default':'$identity'},
+            'identity': {'type':'String', 'default':'$name', "required": True}})
+
+        self.assertEqual({'type': 'MyEntity', 'identity': 'x', 'name': 'x'},
+                         e.validate({'identity':'x'}))
+        self.assertEqual({'type': 'MyEntity', 'identity': 'x', 'name': 'x'},
+                         e.validate({'name':'x'}))
+        self.assertEqual({'type': 'MyEntity', 'identity': 'x', 'name': 'y'},
+                         e.validate({'identity': 'x', 'name':'y'}))
+        self.assertRaises(ValidationError, e.validate, {}) # Circular reference.
+
+    def test_entity_include_refs(self):
+        s = Schema(includes={
+            'i1': {'attributes': {
+                'name': {'type':'String', 'default':'$identity'},
+                'identity': {'type':'String', 'default':'$name', "required": True}}}})
+
+        e = EntityType('MyEntity', s, attributes={}, include=['i1'])
+        self.assertEqual({'identity': 'x', 'name': 'x'}, e.validate({'identity':'x'}))
+        self.assertEqual({'identity': 'x', 'name': 'x'}, e.validate({'name':'x'}))
+        self.assertEqual({'identity': 'x', 'name': 'y'}, e.validate({'identity': 'x', 'name':'y'}))
+        self.assertRaises(ValidationError, e.validate, {})
 
+    qdrouter_json = schema_file('qdrouter.json')
 
     @staticmethod
     def load_schema(fname=qdrouter_json):
@@ -167,11 +210,11 @@ class SchemaTest(unittest.TestCase):
         # Duplicate unique attribute 'name'
         m = [Entity({'type': 'listener', 'name':'x'}),
              Entity({'type': 'listener', 'name':'x'})]
-        self.assertRaises(ValueError, s.validate, m)
+        self.assertRaises(ValidationError, s.validate, m)
         # Duplicate singleton entity 'container'
         m = [Entity({'type': 'container', 'name':'x'}),
              Entity({'type': 'container', 'name':'y'})]
-        self.assertRaises(ValueError, s.validate, m)
+        self.assertRaises(ValidationError, s.validate, m)
         # Valid model
         m = [Entity({'type': 'container', 'name':'x'}),
              Entity({'type': 'listener', 'name':'y'})]

Modified: qpid/dispatch/trunk/tests/system_tests_management.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/system_tests_management.py?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/system_tests_management.py (original)
+++ qpid/dispatch/trunk/tests/system_tests_management.py Tue Jul  8 18:43:15 2014
@@ -64,7 +64,6 @@ class ManagementTest(system_test.TestCas
             self.node.query)
 
     def test_query_entity_type(self):
-        # FIXME aconway 2014-06-03: prefix support in Node, get from schema.
         address = 'org.apache.qpid.dispatch.router.address'
         response = self.node.query(entity_type=address)
         self.assertEqual(response.attribute_names[0:3], ['type', 'name', 'identity'])
@@ -74,7 +73,7 @@ class ManagementTest(system_test.TestCas
         self.assertTrue('L$management' in names)
         self.assertTrue('M0$management' in names)
 
-        # FIXME aconway 2014-06-05: negative test: offset, count not implemented on router
+        # TODO aconway 2014-06-05: negative test: offset, count not implemented on router
         try:
             # Try offset, count
             self.assertGreater(len(names), 2)
@@ -87,8 +86,7 @@ class ManagementTest(system_test.TestCas
 
     def test_query_attribute_names(self):
         response = self.node.query(attribute_names=["type", "name", "identity"])
-        # FIXME aconway 2014-06-05: negative test: attribute_names query doesn't work.
-        # Need a better test.
+        # TODO aconway 2014-06-05: negative test: attribute_names query doesn't work.
         try:
             self.assertNotEqual([], response)
             self.fail("Negative test passed!")

Modified: qpid/dispatch/trunk/tests/system_tests_qdstat.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/system_tests_qdstat.py?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/system_tests_qdstat.py (original)
+++ qpid/dispatch/trunk/tests/system_tests_qdstat.py Tue Jul  8 18:43:15 2014
@@ -46,7 +46,7 @@ class QdstatTest(system_test.TestCase):
         self.run_qdstat(['--help'], r'Usage: qdstat')
 
     def test_general(self):
-        self.run_qdstat(['--general'], r'(?s)Router Statistics.*Mode\s*Endpoint')
+        self.run_qdstat(['--general'], r'(?s)Router Statistics.*Mode\s*Standalone')
 
     def test_connections(self):
         self.run_qdstat(['--connections'], r'state.*host.*container')

Modified: qpid/dispatch/trunk/tests/system_tests_two_routers.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/system_tests_two_routers.py?rev=1608882&r1=1608881&r2=1608882&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/system_tests_two_routers.py (original)
+++ qpid/dispatch/trunk/tests/system_tests_two_routers.py Tue Jul  8 18:43:15 2014
@@ -95,7 +95,7 @@ class RouterTest(TestCase):
         tm = Message()
         rm = Message()
 
-        self.routers[0].wait_address("pre_settled/1", 0, 1, timeout=30)
+        self.routers[0].wait_address("pre_settled/1", 0, 1)
 
         tm.address = addr
         for i in range(100):



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org