Author: aconway
Date: Mon Jun  2 19:26:53 2014
New Revision: 1599320

URL: http://svn.apache.org/r1599320
Log:
DISPATCH-56: Generate qdrouterd.conf man file from management schema.

Move documentation from qdrouterd.conf.5.in to the qdrouter.json schema, 
generate
the man file from the schema.

Added:
    qpid/dispatch/trunk/doc/man/CMakeLists.txt   (with props)
    qpid/dispatch/trunk/doc/man/qdrouterd_man.py   (with props)
Modified:
    qpid/dispatch/trunk/doc/CMakeLists.txt
    qpid/dispatch/trunk/doc/api/CMakeLists.txt
    qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.json
    qpid/dispatch/trunk/python/qpid_dispatch_internal/management/schema.py
    qpid/dispatch/trunk/tests/CMakeLists.txt
    qpid/dispatch/trunk/tests/management/schema.py

Modified: qpid/dispatch/trunk/doc/CMakeLists.txt
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/doc/CMakeLists.txt?rev=1599320&r1=1599319&r2=1599320&view=diff
==============================================================================
--- qpid/dispatch/trunk/doc/CMakeLists.txt (original)
+++ qpid/dispatch/trunk/doc/CMakeLists.txt Mon Jun  2 19:26:53 2014
@@ -6,9 +6,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
@@ -17,12 +17,5 @@
 ## under the License.
 ##
 
-configure_file(man/qdrouterd.8.in ${CMAKE_CURRENT_BINARY_DIR}/man/qdrouterd.8)
-configure_file(man/qdrouterd.conf.5.in 
${CMAKE_CURRENT_BINARY_DIR}/man/qdrouterd.conf.5)
-configure_file(man/qdstat.8.in ${CMAKE_CURRENT_BINARY_DIR}/man/qdstat.8)
-
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/qdrouterd.8 DESTINATION 
${MAN_INSTALL_DIR}/man8)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/qdrouterd.conf.5 DESTINATION 
${MAN_INSTALL_DIR}/man5)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/qdstat.8 DESTINATION 
${MAN_INSTALL_DIR}/man8)
-
 add_subdirectory(api)
+add_subdirectory(man)

Modified: qpid/dispatch/trunk/doc/api/CMakeLists.txt
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/doc/api/CMakeLists.txt?rev=1599320&r1=1599319&r2=1599320&view=diff
==============================================================================
--- qpid/dispatch/trunk/doc/api/CMakeLists.txt (original)
+++ qpid/dispatch/trunk/doc/api/CMakeLists.txt Mon Jun  2 19:26:53 2014
@@ -47,5 +47,4 @@ if (BUILD_DOCS)
     DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/doxygen.in ${API_SOURCES})
 
   add_custom_target(apidocs ALL DEPENDS user dev)
-
 endif (BUILD_DOCS)

Added: qpid/dispatch/trunk/doc/man/CMakeLists.txt
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/doc/man/CMakeLists.txt?rev=1599320&view=auto
==============================================================================
--- qpid/dispatch/trunk/doc/man/CMakeLists.txt (added)
+++ qpid/dispatch/trunk/doc/man/CMakeLists.txt Mon Jun  2 19:26:53 2014
@@ -0,0 +1,42 @@
+##
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements.  See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership.  The ASF licenses this file
+## 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
+## KIND, either express or implied.  See the License for the
+## specific language governing permissions and limitations
+## under the License.
+##
+
+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
+
+file (GLOB_RECURSE MAN_PAGE_GENERATOR
+  ${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_MAN}
+    COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qdrouterd_man.py 
${QDROUTERD_MAN}
+    DEPENDS ${MAN_PAGE_GENERATOR})
+
+add_custom_target(man ALL DEPENDS ${QDROUTERD_MAN})

Propchange: qpid/dispatch/trunk/doc/man/CMakeLists.txt
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: qpid/dispatch/trunk/doc/man/CMakeLists.txt
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: qpid/dispatch/trunk/doc/man/qdrouterd_man.py
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/doc/man/qdrouterd_man.py?rev=1599320&view=auto
==============================================================================
--- qpid/dispatch/trunk/doc/man/qdrouterd_man.py (added)
+++ qpid/dispatch/trunk/doc/man/qdrouterd_man.py Mon Jun  2 19:26:53 2014
@@ -0,0 +1,113 @@
+##
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements.  See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership.  The ASF licenses this file
+## 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
+## KIND, either express or implied.  See the License for the
+## specific language governing permissions and limitations
+## under the License
+##
+
+"""
+Generate the qdrouterd.conf man page from the qdrouterd management schema."""
+
+import sys
+from qpid_dispatch_internal.management.qdrouter import SCHEMA
+
+def make_man_page(filename):
+    """Generate a man page for the configuration file from L{SCHEMA} 
descriptions"""
+    with open(filename, 'w') as f:
+
+        f.write(
+            r""".\" -*- nroff -*-
+.\"
+.\" Licensed to the Apache Software Foundation (ASF) under one
+.\" or more contributor license agreements.  See the NOTICE file
+.\" distributed with this work for additional information
+.\" regarding copyright ownership.  The ASF licenses this file
+.\" 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
+.\" KIND, either express or implied.  See the License for the
+.\" specific language governing permissions and limitations
+.\" under the License
+.\"
+.TH QDROUTERD.CONF 5
+.SH NAME
+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.
+
+.nf
+<section-name> {
+    <attribute-name>: <attribute-value>
+    <attribute-name>: <attribute-value>
+    ...
+}
+
+<section-name> { ...
+.fi
+
+.SH SECTIONS
+""")
+        def write_attribute(attr, attrs):
+            if attr.include and attr.include != attrs:
+                return          # Don't repeat included attributes
+            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)
+
+        def write_attributes(attrs):
+            if attrs.description:
+                f.write('\n%s\n'%attrs.description)
+            for attr in attrs.attributes.itervalues():
+                write_attribute(attr, attrs)
+
+        for include in SCHEMA.includes.itervalues():
+            f.write('.SS "\'%s\' include group"\n'% include.name)
+            write_attributes(include)
+
+        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)))
+            write_attributes(entity_type)
+
+
+if __name__ == '__main__':
+    make_man_page(sys.argv[1])

Propchange: qpid/dispatch/trunk/doc/man/qdrouterd_man.py
------------------------------------------------------------------------------
    svn:eol-style = native

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=1599320&r1=1599319&r2=1599320&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.json 
(original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.json 
Mon Jun  2 19:26:53 2014
@@ -1,80 +1,289 @@
 {
   "prefix": "org.apache.qpid.dispatch",
-  
   "includes": {
     "entity-id": {
-      "name": {"type":"String", "required":true, "unique":true},
-      "identity": {"type":"String", "required":true, "unique":true}
+      "description":"Name and identity attributes common to all entity types",
+      "attributes": {
+        "name": {
+          "type": "String",
+          "required": true,
+          "unique": true,
+          "description": "Unique name, can be changed."
+        },
+        "identity": {
+          "type": "String",
+          "required": true,
+          "unique": true,
+          "description": "Unique identity, will not change."
+        }
+      }
     },
-    
     "ssl-profile": {
-      "cert-db" : {"type":"String"},
-      "cert-file" : {"type":"String"},
-      "key-file" : {"type":"String"},
-      "password-file" : {"type":"String"},
-      "password" : {"type":"String"}
+      "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. "
+        }
+      }
     },
-
     "ip-addr": {
-      "addr" : {"type":"String", "default":"0.0.0.0"},
-      "port" : {"type":"String", "default":"amqp"}
+      "description": "IP address to be referenced in listeners (for incoming 
connections) or connectors (for outgoing connectors).",
+      "attributes": {
+        "addr": {
+         "description":"IP address: ipv4 or ipv6 literal or a host name",
+          "type": "String",
+          "default": "0.0.0.0"
+        },
+        "port": {
+         "description":"Port number or symbolic service name",
+          "type": "String",
+          "default": "amqp"
+        }
+      }
+    },
+    "connection": {
+      "description": "Common connection attributes for listeners and 
connectors.",
+      "attributes": {
+        "sasl-mechanisms": {
+          "type": "String",
+          "required": true,
+          "description": "Comma separated list of accepted SASL mechanisms."
+        },
+        "role": {
+          "type": [
+            "normal",
+            "inter-router",
+           "on-demand"
+          ],
+          "default": "normal",
+          "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. 
"
+        }
+      }
     }
   },
-
   "entity_types": {
     "container": {
       "singleton": true,
-      "include" : ["entity-id"],
+      "include": [
+        "entity-id"
+      ],
       "attributes": {
-       "worker-threads" : {"type":"Integer", "default":"1"}
+        "worker-threads": {
+          "type": "Integer",
+          "default": "1",
+          "description": "The number of threads that will be created to 
process message traffic and other application work (timers, non-amqp file 
descriptors, etc.) "
+        }
       }
     },
-    
+
     "router": {
       "singleton": true,
-      "include" : ["entity-id"],
+      "include": [
+        "entity-id"
+      ],
       "attributes": {
-       "mode" : {"type": ["standalone", "interior"], "default":"standalone"},
-       "area" : {"type": "String"},
-       "hello-interval" : {"type": "Integer", "default": 1},
-       "hello-max-age" : {"type": "Integer", "default": 3},
-       "ra-Integererval" : {"type": "Integer", "default": 30},
-       "remote-ls-max-age" : {"type": "Integer", "default": 60},
-       "mobile-addr-max-age" : {"type": "Integer", "default": 60}
+        "mode": {
+          "type": [
+            "standalone",
+            "interior",
+           "edge"
+          ],
+          "default": "standalone",
+          "description": "In standalone mode, the router operates as a single 
component.  It does not participate in the routing protocol and therefore will 
not coorperate with other routers. In interior mode, the router operates in 
cooreration with other interior routers in an interconnected network.  In edge 
mode, the router operates with an uplink into an interior router network. Edge 
routers are typically used as connection concentrators or as security firewalls 
for access into the interior network. "
+        },
+        "area": {
+          "type": "String",
+          "description": ""
+        },
+        "hello-interval": {
+          "type": "Integer",
+          "default": 1,
+          "description": ""
+        },
+        "hello-max-age": {
+          "type": "Integer",
+          "default": 3,
+          "description": ""
+        },
+        "ra-interval": {
+          "type": "Integer",
+          "default": 30,
+          "description": ""
+        },
+        "remote-ls-max-age": {
+          "type": "Integer",
+          "default": 60,
+          "description": ""
+        },
+        "mobile-addr-max-age": {
+          "type": "Integer",
+          "default": 60,
+          "description": ""
+        }
       }
     },
-    
+
     "listener": {
-      "include" : ["entity-id", "ssl-profile", "ip-addr"],
+      "include": [
+        "entity-id",
+        "ssl-profile",
+        "ip-addr",
+       "connection"
+      ],
       "attributes": {
-       "role" : {"type":["normal", "inter-router"], "default":"normal"},
-       "sasl-mechanisms" : {"type":"String", "required":true},
-       "require-peer-auth" : {"type": "Boolean", "default":true},
-       "trusted-certs" : {"type": "String"},
-       "allow-unsecured" : {"type": "Boolean", "default":false},
-       "max-frame-size" : {"type": "Integer", "default":65536}
+        "require-peer-auth": {
+          "type": "Boolean",
+          "default": true,
+          "description": "Only for listeners using SSL.  If set to \"yes\", 
attached clients will be required to supply a certificate.  If the certificate 
is not traceable to a CA in the ssl profile's cert-db, authentication fails for 
the connection. "
+        },
+        "trusted-certs": {
+          "type": "String",
+          "description": "This optional setting can be used to reduce the set 
of available CAs for client authentication.  If used, this setting must provide 
a path to a PEM file that contains the trusted certificates. "
+        },
+        "allow-unsecured": {
+          "type": "Boolean",
+          "default": false,
+          "description": "For listeners using SSL only.  If set to \"yes\", 
this option causes the listener to watch the initial network traffic to 
determine if the client is using SSL or is running in-the-clear.  The listener 
will enable SSL only if the client uis using SSL. "
+        },
+        "max-frame-size": {
+          "type": "Integer",
+          "default": 65536,
+          "description": "Defaults to 65536.  If specified, it is the 
maximum-frame-size in octets that will be used in the connection-open 
negotiation with a connected peer.  The frame size is the largest contiguous 
set of uniterruptible data that can be sent for a message delivery over the 
connection. Interleaving of messages on different links is done at frame 
granularity. "
+        }
       }
     },
-    
     "connector": {
-      "include" : ["entity-id", "ssl-profile", "ip-addr"],
+      "include": [
+        "entity-id",
+        "ssl-profile",
+        "ip-addr",
+       "connection"
+      ],
       "attributes": {
-       "role" : {"type": ["normal", "inter-router", "on-demand"], 
"default":"normal"},
-       "sasl-mechanisms" : {"type":"String", "required":true},
-       "allow-redirect" : {"type": "Boolean", "default":true},
-       "max-frame-size" : {"type": "Integer", "default":65536}
+        "allow-redirect": {
+          "type": "Boolean",
+          "default": true,
+          "description": ""
+        },
+        "max-frame-size": {
+          "type": "Integer",
+          "default": 65536,
+          "description": "Maximum frame size in octets that will be used in 
the connection-open negotiation with a connected peer.  The frame size is the 
largest contiguous set of uniterruptible data that can be sent for a message 
delivery over the connection. Interleaving of messages on different links is 
done at frame granularity. "
+        }
       }
     },
 
     "logging": {
-      "include": ["entity-id"],
+      "include": [
+        "entity-id"
+      ],
+      "attributes": {
+       "module": {
+         "type":[
+           "router",
+           "message",
+           "server",
+           "agent",
+           "container",
+           "config",
+           "default"
+         ],
+         "description": "Module to configure logging level. The special module 
'default' specifies logging for modules that don't have explicit log sections."
+       },
+        "level": {
+          "type": [
+            "none",
+            "trace",
+            "debug",
+            "info",
+            "notice",
+            "warning",
+            "error",
+            "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. "
+        },
+        "timestamp": {
+          "type": "Boolean",
+         "default": true,
+          "description": "Set a timestamp on log messages"
+        },
+        "output": {
+          "type": "String",
+          "description": "Where to send log messages. Can be 'stderr', 
'syslog' or a file name. "
+        }
+      }
+    },
+
+    "fixed-address": {
+      "include": [
+        "entity-id"
+      ],
       "attributes": {
-       "level" : {"type": ["none", "trace", "debug", "info", "notice", 
"warning", "error", "critical"], "default":"info"},
-       "timestamp" : {"type": "Boolean"},
-       "output" : {"type": "String"}
+        "prefix": {
+          "type": "String",
+          "required": true,
+          "description": "The address prefix (always starting with \"/\"). "
+        },
+        "phase": {
+          "type": "Integer",
+          "description": ""
+        },
+        "fanout": {
+          "type": [
+            "multiple",
+            "single"
+          ],
+          "default": "multiple",
+          "description": "One of \"multiple\" or \"single\".  Multiple fanout 
is a non-competing pattern.  If there are multiple consumers using the same 
address, each consumer will receive its own copy of every message sent to the 
address.  Single fanout is a competing pattern where each message is sent to 
only one consumer. "
+        },
+        "bias": {
+          "type": [
+            "closest",
+            "spread"
+          ],
+          "default": "closest",
+          "description": "Only if fanout is single.  One of \"closest\" or 
\"spread\".  Closest bias means that messages to an address will always be 
delivered to the closest (lowest cost) subscribed consumer. Spread bias will 
distribute the messages across subscribers in an approximately even manner. "
+        }
+      }
+    },
+
+    "waypoint": {
+      "include": [
+        "entity-id"
+      ],
+      "attributes": {
+        "in-phase": {
+          "type": "Integer",
+          "default": -1
+        },
+        "out-phase": {
+          "type": "Integer",
+          "default": -1
+        },
+        "connector": {
+          "type": "String",
+          "required": true
+        }
       }
     }
   }
 }
-
-

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=1599320&r1=1599319&r2=1599320&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/schema.py 
(original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/schema.py Mon 
Jun  2 19:26:53 2014
@@ -1,4 +1,4 @@
-##*
+##
 ## Licensed to the Apache Software Foundation (ASF) under one
 ## or more contributor license agreements.  See the NOTICE file
 ## distributed with this work for additional information
@@ -27,6 +27,7 @@ A Schema can be loaded/dumped to a json 
 """
 
 import os
+from collections import OrderedDict
 
 class SchemaError(Exception):
     """Class for schema errors"""
@@ -64,7 +65,8 @@ class Type(object):
         """
         return self.name
 
-    def __repr__(self):
+    def __str__(self):
+        """String name of type."""
         return str(self.dump())
 
 class BooleanType(Type):
@@ -131,6 +133,10 @@ class EnumType(Type):
         """
         return self.tags
 
+    def __str__(self):
+        """String description of enum type."""
+        return "One of [%s]"%(', '.join(self.tags))
+
 BUILTIN_TYPES = dict((t.name, t) for t in [Type("String", str), 
Type("Integer", int), BooleanType()])
 
 def get_type(rep):
@@ -149,7 +155,7 @@ def _dump_dict(items):
     Remove all items with None value from a mapping.
     @return: Map of non-None items.
     """
-    return dict((k, v) for k, v in items if v)
+    return OrderedDict((k, v) for k, v in items if v)
 
 def _is_unique(found, category, value):
     """
@@ -173,7 +179,7 @@ def _is_unique(found, category, value):
         return True
 
 
-class AttributeDef(object):
+class AttributeType(object):
     """
     Definition of an attribute.
 
@@ -182,19 +188,25 @@ class AttributeDef(object):
     @ivar required: True if the attribute is reqiured.
     @ivar default: Default value for the attribute or None if no default.
     @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): # pylint: disable=redefined-builtin
+    def __init__(self, name, type=None, default=None, required=False, 
unique=False, include=None,
+                 description=""
+    ): # pylint: disable=redefined-builtin
         """
-        See L{AttributeDef} instance variables.
+        See L{AttributeType} instance variables.
         """
         self.name = name
         self.atype = get_type(type)
         self.required = required
         self.default = default
         self.unique = unique
+        self.description = description
         if default is not None:
             self.default = self.atype.validate(default)
+        self.include = include
 
     def validate(self, value, check_required=True, add_default=True, 
check_unique=None, **kwargs):
         """
@@ -223,52 +235,81 @@ class AttributeDef(object):
         @return: Json-friendly representation of an attribute type
         """
         return _dump_dict([
-            ('type', self.atype.dump()), ('default', self.default), 
('required', self.required)])
+            ('type', self.atype.dump()),
+            ('default', self.default),
+            ('required', self.required),
+            ('unique', self.unique),
+            ('description', self.description)
+        ])
 
     def __str__(self):
-        return "AttributeDef%s"%(self.__dict__)
+        return "AttributeType%s"%(self.__dict__)
+
+class AttributeTypeHolder(object):
+    """Base class for IncludeType and EntityType - a named holder of attribute 
types"""
 
-class EntityType(object):
+    def __init__(self, name, schema, attributes=None, description=""):
+        self.name, self.schema, self.description = name, schema, description
+        self.attributes = OrderedDict()
+        if attributes:
+            self.add_attributes(attributes)
+
+    def add_attributes(self, attributes):
+        """
+        Add attributes.
+        @param attributes: Map of attributes {name: {type:, default:, 
required:, unique:}}
+        """
+        for k, v in attributes.iteritems():
+            if k in self.attributes:
+                raise SchemaError("Attribute '%s' duplicated in '%s'"%(k, 
self.name))
+            self.attributes[k] = AttributeType(k, **v)
+
+    def dump(self):
+        """Json friendly representation"""
+        return _dump_dict([
+            ('attributes', OrderedDict((k, v.dump()) for k, v in 
self.attributes.iteritems())),
+            ('description', self.description or None)
+        ])
+
+class IncludeType(AttributeTypeHolder):
+
+    def __init__(self, name, schema, attributes=None, description=""):
+        super(IncludeType, self).__init__(name, schema, attributes, 
description)
+        for a in self.attributes.itervalues():
+            a.include = self
+
+class EntityType(AttributeTypeHolder):
     """
     An entity type defines a set of attributes for an entity.
 
     @ivar name: Entity type name.
-    @ivar attributes: Map of L{AttributeDef} for entity.
+    @ivar attributes: Map of L{AttributeType} for entity.
     @ivar singleton: If true only one entity of this type is allowed.
+    #ivar include: List of names of sections included by this entity.
     """
-    def __init__(self, name, schema, singleton=False, include=None, 
attributes=None):
+    def __init__(self, name, schema, singleton=False, include=None, 
attributes=None,
+                 description=""):
         """
         @param name: name of the entity type.
         @param schema: schema for this type.
         @param singleton: True if entity type is a singleton.
         @param include: List of names of include types for this entity.
         @param attributes: Map of attributes {name: {type:, default:, 
required:, unique:}}
+        @param description: Human readable description.
         """
-        self.name = name
-        self.schema = schema
+        super(EntityType, self).__init__(name, schema, attributes, description)
         self.singleton = singleton
-        self.attributes = {}
-        if attributes:
-            self.add_attributes(attributes)
+        self.include = include
         if include and self.schema.includes:
             for i in include:
-                self.add_attributes(schema.includes[i])
-
-    def add_attributes(self, attributes):
-        """
-        Add attributes.
-        @param attributes: Map of attributes {name: {type:, default:, 
required:, unique:}}
-        """
-        for k, v in attributes.iteritems():
-            if k in self.attributes:
-                raise SchemaError("Attribute '%s' duplicated in '%s'"%(k, 
self.name))
-            self.attributes[k] = AttributeDef(k, **v)
+                for attr in schema.includes[i].attributes.itervalues():
+                    self.attributes[attr.name] = attr
 
     def dump(self):
         """Json friendly representation"""
-        return _dump_dict([
-            ('singleton', self.singleton),
-            ('attributes', dict((k, v.dump()) for k, v in 
self.attributes.iteritems()))])
+        d = super(EntityType, self).dump()
+        if self.singleton: d['singleton'] = True
+        return d
 
     def validate(self, attributes, check_singleton=None, **kwargs):
         """
@@ -303,30 +344,26 @@ class Schema(object):
     @ivar prefix: Prefix to prepend to short entity names.
     @ivar entity_types: Map of L{EntityType} by name.
     """
-    def __init__(self, prefix="", includes=None, entity_types=None):
+    def __init__(self, prefix="", includes=None, entity_types=None, 
description=""):
         """
         @param prefix: Prefix for entity names.
         @param includes: Map of  { include-name: {attribute-name:value, ... }}
         @param entity_types: Map of  { entity-type-name: { singleton:, 
include:[...], attributes:{...}}}
+        @param description: Human readable description.
         """
         self.prefix = self.prefixdot = prefix
         if not prefix.endswith('.'):
             self.prefixdot += '.'
-        self.includes = includes or {}
-        self.entity_types = {}
+        self.description = description
+        self.includes = OrderedDict()
+        if includes:
+            for k, v in includes.iteritems():
+                self.includes[k] = IncludeType(k, self, **v)
+        self.entity_types = OrderedDict()
         if entity_types:
             for k, v in entity_types.iteritems():
-                self.add_entity_type(k, **v)
+                self.entity_types[k] = EntityType(k, self, **v)
 
-    def add_entity_type(self, name, singleton=False, include=None, 
attributes=None):
-        """
-        Add an entity type to the schema.
-        @param name: Entity type name.
-        @param singleton: True if this is a singleton.
-        @param include: List of names of include sections for this entity.
-        @param attributes: Map of attributes {name: {type:, default:, 
required:, unique:}}
-        """
-        self.entity_types[name] = EntityType(name, self, singleton, include, 
attributes)
 
     def short_name(self, name):
         """Remove prefix from name if present"""
@@ -342,9 +379,9 @@ class Schema(object):
 
     def dump(self):
         """Return json-friendly representation"""
-        return {'prefix':self.prefix,
-                'includes':self.includes,
-                'entity_types':dict((k, v.dump()) for k, v in 
self.entity_types.iteritems())}
+        return {'prefix': self.prefix,
+                'includes': OrderedDict((k, v.dump()) for k, v in 
self.includes.iteritems()),
+                'entity_types': OrderedDict((k, v.dump()) for k, v in 
self.entity_types.iteritems())}
 
     def validate(self, entities, enum_as_int=False, check_required=True, 
add_default=True, check_unique=True, check_singleton=True):
         """

Modified: qpid/dispatch/trunk/tests/CMakeLists.txt
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/CMakeLists.txt?rev=1599320&r1=1599319&r2=1599320&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/CMakeLists.txt (original)
+++ qpid/dispatch/trunk/tests/CMakeLists.txt Mon Jun  2 19:26:53 2014
@@ -6,9 +6,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
@@ -52,6 +52,7 @@ add_test(unit_tests_size_2     unit_test
 add_test(unit_tests_size_1     unit_tests_size 1)
 add_test(unit_tests            unit_tests 
${CMAKE_CURRENT_SOURCE_DIR}/threads4.conf)
 add_test(router_tests          python 
${CMAKE_CURRENT_SOURCE_DIR}/router_engine_test.py -v)
+add_test(management_tests      python -m unittest -v management)
 
 set(SYSTEM_TEST_FILES system_test.py system_tests_one_router.py 
system_tests_two_routers.py system_tests_broker.py)
 
@@ -64,7 +65,7 @@ install(FILES ${SYSTEM_TEST_FILES}
         DESTINATION ${QPID_DISPATCH_HOME_INSTALLED}/tests
         )
 
-install(FILES qdstat_test.sh 
+install(FILES qdstat_test.sh
         DESTINATION ${QPID_DISPATCH_HOME_INSTALLED}/tests
         )
 

Modified: qpid/dispatch/trunk/tests/management/schema.py
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/management/schema.py?rev=1599320&r1=1599319&r2=1599320&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/management/schema.py (original)
+++ qpid/dispatch/trunk/tests/management/schema.py Mon Jun  2 19:26:53 2014
@@ -21,15 +21,25 @@
 #pylint: disable=wildcard-import,missing-docstring,too-many-public-methods
 
 import unittest, json
-from qpid_dispatch_internal.management.schema import Schema, EntityType, 
BooleanType, EnumType, AttributeDef, SchemaError, schema_file
+from qpid_dispatch_internal.management.schema import Schema, EntityType, 
BooleanType, EnumType, AttributeType, SchemaError, schema_file
 from qpid_dispatch_internal.management.entity import Entity
+import collections
+
+def replace_od(thing):
+    if isinstance(thing, collections.Mapping):
+        return dict((k, replace_od(v)) for k,v in thing.iteritems())
+    if isinstance(thing, list):
+        return [replace_od(t) for t in thing]
+    return thing
 
 SCHEMA_1 = {
     "prefix":"org.example",
     "includes": {
         "entity-id": {
-            "name": {"type":"String", "required": True, "unique":True}
-        },
+            "attributes": {
+                "name": {"type":"String", "required": True, "unique":True}
+            }
+        }
     },
     "entity_types": {
         "container": {
@@ -54,7 +64,6 @@ SCHEMA_1 = {
     }
 }
 
-
 class SchemaTest(unittest.TestCase):
 
     def test_bool(self):
@@ -75,18 +84,18 @@ class SchemaTest(unittest.TestCase):
         self.assertRaises(ValueError, e.validate, 3)
 
     def test_attribute_def(self):
-        a = AttributeDef('foo', 'String', 'FOO', False)
+        a = AttributeType('foo', 'String', 'FOO', False)
         self.assertEqual(a.validate('x'), 'x')
         self.assertEqual(a.validate(None), 'FOO')
-        a = AttributeDef('foo', 'String', 'FOO', True)
+        a = AttributeType('foo', 'String', 'FOO', True)
         self.assertEqual('FOO', a.validate(None))
-        a = AttributeDef('foo', 'Integer', None, True)
+        a = AttributeType('foo', 'Integer', None, True)
         self.assertRaises(SchemaError, a.validate, None) # Missing default
 
     def test_entity_type(self):
         s = Schema(includes={
-            'i1':{'foo1': {'type':'String', 'default':'FOO1'}},
-            'i2':{'foo2': {'type':'String', 'default':'FOO2'}}})
+            'i1':{'attributes': { 'foo1': {'type':'String', 
'default':'FOO1'}}},
+            'i2':{'attributes': { 'foo2': {'type':'String', 
'default':'FOO2'}}}})
 
         e = EntityType('MyEntity', s, attributes={
             'foo': {'type':'String', 'default':'FOO'},
@@ -110,32 +119,46 @@ class SchemaTest(unittest.TestCase):
     def test_schema_dump(self):
         s = Schema(**SCHEMA_1)
         self.maxDiff = None     # pylint: disable=invalid-name
+        self.longMessage = True     # pylint: disable=invalid-name
         expect = {
             "prefix":"org.example",
-            "includes": {"entity-id": {"name": {"required": True, 
"unique":True, "type": "String"}}},
+
+            "includes": {
+                "entity-id": {
+                    "attributes": {
+                        "name": {"required": True,
+                                 "unique":True,
+                                 "type": "String"}
+                    }
+                }
+            },
+
             "entity_types": {
                 "container": {
                     "singleton": True,
                     "attributes": {
-                        "name": {"type":"String", "required": True},
+                        "name": {"type":"String", "unique":True, "required": 
True},
                         "worker-threads": {"type":"Integer", "default": 1}
                     }
                     },
                     "listener": {
                         "attributes": {
-                            "name": {"type":"String", "required": True},
+                            "name": {"type":"String", "unique":True, 
"required": True},
                             "addr" : {"type":"String"}
                         }
                     },
                 "connector": {
                     "attributes": {
-                        "name": {"type":"String", "required": True},
+                        "name": {"type":"String", "unique":True, "required": 
True},
                         "addr" : {"type":"String"}
                     }
                 }
             }
         }
-        self.assertEquals(s.dump(), expect)
+        def jsontof(j,fname):
+            with open(fname,'w') as f:
+                json.dump(j, f, indent=4)
+        self.assertDictEqual(replace_od(s.dump()), expect)
 
         s2 = Schema(**s.dump())
         self.assertEqual(s.dump(), s2.dump())



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to