Author: aconway
Date: Fri Jan 30 02:04:08 2015
New Revision: 1655905

URL: http://svn.apache.org/r1655905
Log:
DISPATCH-108: Identify settable, updatable and read-only attributes in 
management schema.

Added attribute 'update' and 'create' flags to schema indicate which attributes 
can be
updated and created with validation to check that update and create operations 
are legal.

Other improvements:
- Move READ operation to base entity types, remove CREATE from singleton types.
- For "router.node" entity type added "routerId" attribute
- Moved all identity generation to python code.
- Updated man page and book doc generators, factor out common logic.

Added:
    qpid/dispatch/trunk/python/qpid_dispatch_internal/management/markdown.py
Modified:
    qpid/dispatch/trunk/doc/book/schema_md.py
    qpid/dispatch/trunk/doc/man/qdrouterd_conf_man.py
    qpid/dispatch/trunk/python/qpid_dispatch/management/qdrouter.json
    qpid/dispatch/trunk/python/qpid_dispatch/management/qdrouter.json.readme.txt
    qpid/dispatch/trunk/python/qpid_dispatch_internal/management/agent.py
    qpid/dispatch/trunk/python/qpid_dispatch_internal/management/config.py
    qpid/dispatch/trunk/python/qpid_dispatch_internal/management/schema.py
    qpid/dispatch/trunk/src/alloc.c
    qpid/dispatch/trunk/src/router_agent.c
    qpid/dispatch/trunk/src/router_pynode.c
    qpid/dispatch/trunk/src/server.c
    qpid/dispatch/trunk/tests/system_test.py
    qpid/dispatch/trunk/tests/system_tests_management.py
    qpid/dispatch/trunk/tests/system_tests_two_routers.py

Modified: qpid/dispatch/trunk/doc/book/schema_md.py
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/doc/book/schema_md.py?rev=1655905&r1=1655904&r2=1655905&view=diff
==============================================================================
--- qpid/dispatch/trunk/doc/book/schema_md.py (original)
+++ qpid/dispatch/trunk/doc/book/schema_md.py Fri Jan 30 02:04:08 2015
@@ -22,64 +22,10 @@ Generate the schema.md chapter for the d
 """
 
 import sys, re
-from pkgutil import get_data
 from qpid_dispatch_internal.management.qdrouter import QdSchema
-from qpid_dispatch_internal.management.schema import quotestr
+from qpid_dispatch_internal.management.markdown import SchemaWriter
 
-class SchemaWriter(object):
-    """Write the schema as a markdown document"""
-
-    def __init__(self, out, quiet=True):
-        self.out = out
-        self.schema = QdSchema()
-        self.quiet = quiet
-
-
-    def write(self, value):
-        self.out.write(value)
-
-    def warn(self, message):
-        if not self.quiet: print >>sys.stderr, message
-
-    def attribute(self, attr, thing):
-        default = attr.default
-        if isinstance(default, basestring) and default.startswith('$'):
-            default = None  # Don't show defaults that are references, 
confusing.
-        self.write('\n*%s* '%(attr.name))
-        self.write('(%s)\n'%(', '.join(
-            filter(None, [str(attr.atype),
-                          attr.required and "required",
-                          attr.unique and "unique",
-                          default and "default=%s" % quotestr(default)]))))
-        if attr.description:
-            self.write(":   %s\n"%attr.description)
-        else:
-            self.warn("Warning: No description for %s in %s" % (attr, 
thing.short_name))
-
-    def attributes(self, thing):
-        for attr in thing.my_attributes:
-            self.attribute(attr, thing)
-
-    def preface(self, thing):
-        self.write('\n### %s\n' % thing.short_name)
-        if thing.description:
-            self.write('\n%s\n' % thing.description)
-        else:
-            self.warn("Warning no description for %s" % entity_type)
-
-    def entity_type(self, entity_type):
-        self.preface(entity_type)
-        for a in entity_type.annotations: self.attributes(a)
-        self.attributes(entity_type)
-        ops = entity_type.operations
-        if entity_type.singleton: ops.remove('CREATE')
-        if ops:
-            self.write("\nOperations allowed: %s\n\n" % ", 
".join(entity_type.operations))
-
-    def entity_types(self, base_name):
-        base = self.schema.entity_type(base_name)
-        for entity_type in self.schema.filter(lambda t: t.extends(base)):
-            self.entity_type(entity_type)
+class BookSchemaWriter(SchemaWriter):
 
     def run(self):
         self.write("""
@@ -118,7 +64,7 @@ running using the `qdrouterd(8)` tool's
 be modified using the `update` operation, see the entity descriptions below.
 
 """)
-        self.entity_types("configurationEntity")
+        self.entity_types_extending("configurationEntity")
 
         self.write("""\n## Operational Entities
 
@@ -127,11 +73,11 @@ The `qdstat(8)` tool provides a convenie
 You can also use the general-purpose management tool `qdmanage(8)` to query 
 operational attributes.
 """)
-        self.entity_types("operationalEntity")
+        self.entity_types_extending("operationalEntity")
 
 def main():
     """Generate schema markdown documentation from L{QdSchema}"""
-    SchemaWriter(sys.stdout).run()
+    BookSchemaWriter(sys.stdout, QdSchema()).run()
 
 if __name__ == '__main__':
     main()

Modified: qpid/dispatch/trunk/doc/man/qdrouterd_conf_man.py
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/doc/man/qdrouterd_conf_man.py?rev=1655905&r1=1655904&r2=1655905&view=diff
==============================================================================
--- qpid/dispatch/trunk/doc/man/qdrouterd_conf_man.py (original)
+++ qpid/dispatch/trunk/doc/man/qdrouterd_conf_man.py Fri Jan 30 02:04:08 2015
@@ -23,8 +23,21 @@ Generate the qdrouterd.conf.md man page
 
 import sys
 from qpid_dispatch_internal.management.qdrouter import QdSchema
+from qpid_dispatch_internal.management.markdown import SchemaWriter
 
-PREFACE = r"""
+class ManPageWriter(SchemaWriter):
+
+    def __init__(self, filename):
+        super(ManPageWriter, self).__init__(open(filename, 'w'), QdSchema())
+
+    def attribute_type(self, attr, holder):
+        # Don't repeat annotationd attributes or show non-create attributes.
+        if (attr.annotation and attr.annotation != holder) or not attr.create:
+            return
+        super(ManPageWriter, self).attribute_type(attr, holder, 
show_create=False, show_update=False)
+
+    def man_page(self):
+        self.write(r"""
 # Name
 
 qdrouterd.conf - Configuration file for the Qpid Dispatch router
@@ -65,64 +78,25 @@ attribute of 'ssl-profile' sections.
         port: 20102
         sasl-mechanisms: ANONYMOUS
     }
-"""
+""")
 
-def make_man_page(filename):
-    """Generate a man page for the configuration file from L{QdSchema} 
descriptions"""
-
-    with open(filename, 'w') as f:
-        f.write(PREFACE)
-
-        schema = QdSchema()
-
-        def write_attribute(attr, attrs):
-            if attr.annotation and attr.annotation != attrs:
-                return          # Don't repeat annotationd 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('\n%s '%(attr.name))
-            f.write('(%s)\n'%(', '.join(
-                filter(None, [str(attr.atype),
-                              attr.required and "required",
-                              attr.unique and "unique",
-                              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')
-
-        f.write("\n\n# Annotation Sections\n\n")
-        for annotation in schema.annotations.itervalues():
-            used_by = [e.short_name for e in schema.entity_types.itervalues()
+        self.write("\n\n# Annotation Sections\n\n")
+        for annotation in self.schema.annotations.itervalues():
+            used_by = [e.short_name for e in 
self.schema.entity_types.itervalues()
                        if annotation in e.annotations]
-            f.write('\n\n## %s\n'%annotation.short_name)
-            write_attributes(annotation)
-            if used_by: f.write('Used by %s.\n'%(', '.join(used_by)))
-
-        f.write("\n\n# Configuration Sections\n\n")
-        config = schema.entity_type("configurationEntity")
-        for entity_type in schema.entity_types.itervalues():
+            self.write('\n\n## %s\n'%annotation.short_name)
+            if used_by: self.write('Used by: **%s**.\n'%('**, 
**'.join(used_by)))
+            self.attribute_types(annotation)
+
+        self.write("\n\n# Configuration Sections\n\n")
+        config = self.schema.entity_type("configurationEntity")
+        for entity_type in self.schema.entity_types.itervalues():
             if config in entity_type.all_bases:
-                f.write('\n## %s\n'% entity_type.short_name)
-                write_attributes(entity_type)
+                self.write('\n## %s\n'% entity_type.short_name)
                 if entity_type.annotations:
-                    f.write('Annotations %s.\n'%(', '.join(
+                    self.write('Annotations: **%s**.\n'%('**, **'.join(
                         [a.short_name for a in entity_type.annotations])))
-
+                self.attribute_types(entity_type)
 
 if __name__ == '__main__':
-    make_man_page(sys.argv[1])
+    ManPageWriter(sys.argv[1]).man_page()

Modified: qpid/dispatch/trunk/python/qpid_dispatch/management/qdrouter.json
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch/management/qdrouter.json?rev=1655905&r1=1655904&r2=1655905&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch/management/qdrouter.json (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch/management/qdrouter.json Fri Jan 
30 02:04:08 2015
@@ -9,14 +9,17 @@
             "description": "Attributes for internet address and port.",
             "attributes": {
                 "addr": {
-                    "description":"Host address: ipv4 or ipv6 literal or a 
host name.",
+                    "description":"IP address: ipv4 or ipv6 literal or a host 
name.",
                     "type": "String",
-                    "default": "0.0.0.0"
+                    "default": "0.0.0.0",
+                    "create": true
                 },
                 "port": {
-                    "description":"Port number or symbolic service name.",
+                    "description": "Port number or symbolic service name.",
                     "type": "String",
-                    "default": "amqp"
+                    "default": "amqp",
+                    "create": true
+
                 }
             }
         },
@@ -27,7 +30,9 @@
                 "saslMechanisms": {
                     "type": "String",
                     "required": true,
-                    "description": "Comma separated list of accepted SASL 
authentication mechanisms."
+                    "description": "Comma separated list of accepted SASL 
authentication mechanisms.",
+                    "create": true
+
                 }
             }
         },
@@ -42,7 +47,8 @@
                         "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 interRouter connections."
+                    "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 interRouter connections.",
+                    "create": true
                 }
             }
         },
@@ -52,23 +58,32 @@
             "attributes": {
                 "certDb": {
                     "type": "String",
-                    "description": "The path to the database that contains the 
public certificates of trusted certificate authorities (CAs)."
+                    "description": "The path to the database that contains the 
public certificates of trusted certificate authorities (CAs).",
+                    "create": true
                 },
                 "certFile": {
                     "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."
+                    "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.",
+                    "create": true
+
                 },
                 "keyFile": {
                     "type": "String",
-                    "description": "The path to the file containing the 
PEM-formatted private key for the above certificate."
+                    "description": "The path to the file containing the 
PEM-formatted private key for the above certificate.",
+                    "create": true
+
                 },
                 "passwordFile": {
                     "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."
+                    "description": "If the above private key is password 
protected, this is the path to a file containing the password that unlocks the 
certificate key.",
+                    "create": true
+
                 },
                 "password": {
                     "type": "String",
-                    "description": "An alternative to storing the password in 
a file referenced by passwordFile 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 passwordFile in the same 
profile."
+                    "description": "An alternative to storing the password in 
a file referenced by passwordFile 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 passwordFile in the same 
profile.",
+                    "create": true
+
                 }
             }
         }
@@ -81,21 +96,21 @@
             "attributes": {
                 "name": {
                     "type": "String",
-                    "required": false,
                     "unique": true,
-                    "description": "Unique name, can be changed."
+                    "description": "Unique name optionally assigned by user. 
Can be changed.",
+                    "create": true
                 },
                 "identity": {
                     "type": "String",
-                    "required": false,
                     "unique": true,
-                    "description": "Unique identity, will not change."
+                    "description": "Unique identity generated by the system. 
Will not change."
                 },
                 "type": {
                     "type": "String",
                     "required": true,
                     "value": "$$entityType",
-                    "description": "Management entity type."
+                    "description": "Management entity type.",
+                    "create": true
                 }
             }
         },
@@ -111,13 +126,14 @@
             "description": "Extends the standard org.amqp.management node 
interface.",
             "extends": "org.amqp.management",
             "singleton": true,
-            "operations": ["CREATE", "GET-SCHEMA", "GET-JSON-SCHEMA"]
+            "operations": ["GET-SCHEMA", "GET-JSON-SCHEMA"]
         },
 
         "configurationEntity": {
             "description": "Base type for entities containing configuration 
information.",
             "extends": "entity",
-            "attributes": {}
+            "attributes": {},
+            "operations": ["READ"]
         },
 
         "operationalEntity": {
@@ -130,21 +146,27 @@
         "container": {
             "description":"Attributes related to the AMQP container.",
             "extends": "configurationEntity",
-            "operations": ["CREATE", "READ"],
+
             "singleton": true,
             "attributes": {
                 "containerName": {
                     "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."
+                    "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 autoally assigned container name is not guaranteed to be 
persistent across restarts of the container.",
+                    "create": true
+
                 },
                 "workerThreads": {
                     "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.) ."
+                    "description": "The number of threads that will be created 
to process message traffic and other application work (timers, non-amqp file 
descriptors, etc.) .",
+                    "create": true
+
                 },
                 "debugDump": {
                     "type": "String",
-                    "description": "A file to dump debugging information that 
can't be logged normally."
+                    "description": "A file to dump debugging information that 
can't be logged normally.",
+                    "create": true
+
                 }
             }
         },
@@ -152,12 +174,12 @@
         "router": {
             "description":"Tracks peer routers and computes routes to 
destinations.",
             "extends": "configurationEntity",
-            "operations": ["CREATE", "READ"],
             "singleton": true,
             "attributes": {
                 "routerId": {
                     "description":"Router's unique identity.",
-                    "type": "String"
+                    "type": "String",
+                    "create": true
                 },
                 "mode": {
                     "type": [
@@ -167,7 +189,8 @@
                         "endpoint"
                     ],
                     "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."
+                    "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.",
+                    "create": true
                 },
                 "area": {
                     "type": "String",
@@ -176,43 +199,58 @@
                 "helloInterval": {
                     "type": "Integer",
                     "default": 1,
-                    "description": "Interval in seconds between HELLO messages 
sent to neighbor routers."
+                    "description": "Interval in seconds between HELLO messages 
sent to neighbor routers.",
+                    "create": true
                 },
                 "helloMaxAge": {
                     "type": "Integer",
                     "default": 3,
-                    "description": "Time in seconds after which a neighbor is 
declared lost if no HELLO is received."
+                    "description": "Time in seconds after which a neighbor is 
declared lost if no HELLO is received.",
+                    "create": true
                 },
                 "raInterval": {
                     "type": "Integer",
                     "default": 30,
-                    "description": "Interval in seconds between 
Router-Advertisements sent to all routers in a stable network."
+                    "description": "Interval in seconds between 
Router-Advertisements sent to all routers in a stable network.",
+                    "create": true
                 },
                 "raIntervalFlux": {
                     "type": "Integer",
                     "default": 4,
-                    "description": "Interval in seconds between 
Router-Advertisements sent to all routers during topology fluctuations."
+                    "description": "Interval in seconds between 
Router-Advertisements sent to all routers during topology fluctuations.",
+                    "create": true
                 },
                 "remoteLsMaxAge": {
                     "type": "Integer",
                     "default": 60,
-                    "description": "Time in seconds after which link state is 
declared stale if no RA is received."
+                    "description": "Time in seconds after which link state is 
declared stale if no RA is received.",
+                    "create": true
                 },
                 "mobileAddrMaxAge": {
                     "type": "Integer",
                     "default": 60,
-                    "description": "Deprecated - This value is no longer used 
in the router."
+                    "description": "Deprecated - This value is no longer used 
in the router.",
+                    "create": true
+                },
+               "addrCount": {
+                    "type":
+                    "Integer", "description":"Number of addresses known to the 
router."
                 },
-               "addrCount": {"type": "Integer", "description":"Number of 
addresses known to the router."},
-               "linkCount": {"type": "Integer", "description":"Number of links 
attached to the router node."},
-               "nodeCount": {"type": "Integer", "description":"Number of known 
peer router nodes."}
+               "linkCount": {
+                    "type": "Integer",
+                    "description":"Number of links attached to the router 
node."
+                },
+               "nodeCount": {
+                    "type": "Integer",
+                    "description":"Number of known peer router nodes."
+                }
             }
         },
 
         "listener": {
             "description": "Listens for incoming connections to the router.",
             "extends": "configurationEntity",
-            "operations": ["CREATE", "READ"],
+            "operations": ["CREATE"],
             "annotations": [
                 "addrPort",
                 "connectionRole",
@@ -223,26 +261,31 @@
                 "requirePeerAuth": {
                     "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."
+                    "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.",
+                    "create": true
                 },
                 "trustedCerts": {
                     "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."
+                    "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.",
+                    "create": true
                 },
                 "allowUnsecured": {
                     "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."
+                    "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.",
+                    "create": true
                 },
                 "allowNoSasl": {
                     "type": "Boolean",
                     "default": false,
-                    "description": "If set to 'yes', this option causes the 
listener to allow clients to connect even if they skip the SASL authentication 
protocol."
+                    "description": "If set to 'yes', this option causes the 
listener to allow clients to connect even if they skip the SASL authentication 
protocol.",
+                    "create": true
                 },
                 "maxFrameSize": {
                     "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."
+                    "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.",
+                    "create": true
                 }
             }
         },
@@ -250,7 +293,7 @@
         "connector": {
             "description": "Establishes an outgoing connections from the 
router.",
             "extends": "configurationEntity",
-            "operations": ["CREATE", "READ"],
+            "operations": ["CREATE"],
             "annotations": [
                 "addrPort",
                 "connectionRole",
@@ -261,20 +304,22 @@
                 "allowRedirect": {
                     "type": "Boolean",
                     "default": true,
-                    "description": "Allow the peer to redirect this connection 
to another address."
+                    "description": "Allow the peer to redirect this connection 
to another address.",
+                    "create": true
                 },
                 "maxFrameSize": {
                     "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."
+                    "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.",
+                    "create": true
                 }
             }
         },
 
         "log": {
-            "description": "Configure logging for a particular module.",
+            "description": "Configure logging for a particular module. 'log' 
entities are pre-created by the router for all valid modules..",
             "extends": "configurationEntity",
-            "operations": ["CREATE", "READ", "UPDATE"],
+            "operations": ["UPDATE"],
             "attributes": {
                 "module": {
                     "type":[
@@ -298,19 +343,23 @@
                     "type": "String",
                     "default": "default",
                     "required": true,
-                    "description": "Levels are: trace, debug, info, notice, 
warning, error, critical. The enable string is a comma-separated list of 
levels. A level may have a trailing '+' to enable that level and above. For 
example 'trace,debug,warning+' means enable trace, debug, warning, error and 
critical. The value 'none' means disable logging for the module. The value 
'default' means use the value from the DEFAULT module."
+                    "description": "Levels are: trace, debug, info, notice, 
warning, error, critical. The enable string is a comma-separated list of 
levels. A level may have a trailing '+' to enable that level and above. For 
example 'trace,debug,warning+' means enable trace, debug, warning, error and 
critical. The value 'none' means disable logging for the module. The value 
'default' means use the value from the DEFAULT module.",
+                    "update": true
                 },
                 "timestamp": {
                     "type": "Boolean",
-                    "description": "Include timestamp in log messages."
+                    "description": "Include timestamp in log messages.",
+                    "update": true
                 },
                 "source": {
                     "type": "Boolean",
-                    "description": "Include source file and line number in log 
messages."
+                    "description": "Include source file and line number in log 
messages.",
+                    "update": true
                 },
                 "output": {
                     "type": "String",
-                    "description": "Where to send log messages. Can be 
'stderr', 'syslog' or a file name."
+                    "description": "Where to send log messages. Can be 
'stderr', 'syslog' or a file name.",
+                    "update": true
                 }
             }
         },
@@ -318,16 +367,18 @@
         "fixedAddress": {
             "description":"Establishes semantics for addresses starting with a 
prefix.",
             "extends": "configurationEntity",
-            "operations": ["CREATE", "READ"],
+            "operations": ["CREATE"],
             "attributes": {
                 "prefix": {
                     "type": "String",
                     "required": true,
-                    "description": "The address prefix (always starting with 
'/')."
+                    "description": "The address prefix (always starting with 
'/').",
+                    "create": true
                 },
                 "phase": {
                     "type": "Integer",
-                    "description": "The phase of a multi-hop address passing 
through one or more waypoints."
+                    "description": "The phase of a multi-hop address passing 
through one or more waypoints.",
+                    "create": true
                 },
                 "fanout": {
                     "type": [
@@ -335,7 +386,8 @@
                         "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."
+                    "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.",
+                    "create": true
                 },
                 "bias": {
                     "type": [
@@ -343,7 +395,8 @@
                         "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."
+                    "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.",
+                    "create": true
                 }
             }
         },
@@ -351,27 +404,31 @@
         "waypoint": {
             "description":"A remote node that messages for an address pass 
through.",
             "extends": "configurationEntity",
-            "operations": ["CREATE", "READ"],
+            "operations": ["CREATE"],
             "attributes": {
                 "address": {
                     "description":"The AMQP address of the waypoint.",
                     "type": "String",
-                    "required": true
+                    "required": true,
+                    "create": true
                 },
                 "connector": {
                     "description":"The name of the on-demand connector used to 
reach the waypoint's container.",
                     "type": "String",
-                    "required": true
+                    "required": true,
+                    "create": true
                 },
                 "inPhase": {
                     "description":"The phase of the address as it is routed 
_to_ the waypoint.",
                     "type": "Integer",
-                    "default": -1
+                    "default": -1,
+                    "create": true
                 },
                 "outPhase": {
                     "description":"The phase of the address as it is routed 
_from_ the waypoint.",
                     "type": "Integer",
-                    "default": -1
+                    "default": -1,
+                    "create": true
                 }
             }
         },
@@ -379,34 +436,23 @@
         "externalContainer": {
             "description":"A remote AMQP container that holds nodes that are 
endpoints for routed links.",
             "extends": "configurationEntity",
-            "operations": ["CREATE", "READ"],
+            "operations": ["CREATE"],
             "attributes": {
                 "prefix": {
                     "description":"The AMQP address prefix for nodes on the 
container.",
                     "type": "String",
-                    "required": true
+                    "required": true,
+                    "create": true
                 },
                 "connector": {
                     "description":"The name of the on-demand connector used to 
reach the waypoint's container.",
                     "type": "String",
-                    "required": true
+                    "required": true,
+                    "create": true
                 }
             }
         },
 
-        "dummy": {
-            "description": "Dummy entity for test purposes.",
-            "extends": "entity",
-            "operations": ["CREATE", "READ", "UPDATE", "DELETE", "CALLME"],
-            "attributes": {
-                "arg1": {"type": "String"},
-                "arg2": {"type": "String"},
-                "num1": {"type": "Integer"},
-                "num2": {"type": "Integer"}
-            }
-        },
-
-
         "router.link": {
             "description": "Link to another AMQP endpoint: router node, client 
or other AMQP process.",
             "extends": "operationalEntity",
@@ -432,18 +478,29 @@
                 "deliveriesEgress": {"type": "Integer"},
                 "deliveriesTransit": {"type": "Integer"},
                 "deliveriesToContainer": {"type": "Integer"},
-                "deliveriesFromContainer": {"type": "Integer"}
+                "deliveriesFromContainer": {"type": "Integer"},
+                "hash": {
+                    "description": "Internal unique (to this router) hash 
value for the address",
+                    "type": "String"
+                }
             }
         },
 
         "router.node": {
-            "description": "AMQP node managed by the router.",
+            "description": "Remote router node connected to this router.",
             "extends": "operationalEntity",
             "attributes": {
-                "addr": {"type": "String"},
-                "nextHop": {"type": "Integer"},
-                "routerLink": {"type": "Integer"},
-                "validOrigins": {"type": "List"}
+                "routerId": {
+                    "description": "Router ID of the remote router",
+                    "type": "String"
+                },
+                "addr": {
+                    "description": "Address hash of the remote router",
+                    "type": "String"
+                },
+                "nextHop": {"type": "Integer", "update": "automatic"},
+                "routerLink": {"type": "Integer", "update": "automatic"},
+                "validOrigins": {"type": "List", "update": "automatic"}
             }
         },
 
@@ -451,18 +508,31 @@
             "description": "Connections to the router's container.",
             "extends": "operationalEntity",
             "attributes": {
-                "container": {"type": "String"} ,
-                "state": {"type": [
+                "container": {
+                    "description": "The container for this connection",
+                    "type": "String"
+                } ,
+                "state": {
+                    "type": [
                     "connecting",
                     "opening",
                     "operational",
                     "failed",
                     "user"
                 ]},
-                "host": {"type": "String"},
-                "dir": {"type": ["in", "out"]},
+                "host": {
+                    "description": "IP address and port number in the form 
addr:port.",
+                    "type": "String"
+                },
+                "dir": {
+                    "description": "Direction of connection establishment in 
or out of the router.",
+                    "type": ["in", "out"]
+                },
                 "role": {"type": "String"},
-                "sasl": {"type": "String"}
+                "sasl": {
+                    "description": "SASL mechansim used for authentication.",
+                    "type": "String"
+                }
             }
         },
 
@@ -470,6 +540,7 @@
             "description": "Memory allocation pool.",
             "extends": "operationalEntity",
             "attributes": {
+                "typeName": {"type": "String"},
                 "typeSize": {"type": "Integer"},
                 "transferBatchSize": {"type": "Integer"},
                 "localFreeListMax": {"type": "Integer"},
@@ -480,6 +551,18 @@
                 "batchesRebalancedToThreads": {"type": "Integer"},
                 "batchesRebalancedToGlobal": {"type": "Integer"}
             }
+        },
+
+        "dummy": {
+            "description": "Dummy entity for test purposes.",
+            "extends": "entity",
+            "operations": ["CREATE", "READ", "UPDATE", "DELETE", "CALLME"],
+            "attributes": {
+                "arg1": {"type": "String", "create": true, "update": true},
+                "arg2": {"type": "String", "create": true, "update": true},
+                "num1": {"type": "Integer", "create": true, "update": true},
+                "num2": {"type": "Integer", "create": true, "update": true}
+            }
         }
     }
 }

Modified: 
qpid/dispatch/trunk/python/qpid_dispatch/management/qdrouter.json.readme.txt
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch/management/qdrouter.json.readme.txt?rev=1655905&r1=1655904&r2=1655905&view=diff
==============================================================================
--- 
qpid/dispatch/trunk/python/qpid_dispatch/management/qdrouter.json.readme.txt 
(original)
+++ 
qpid/dispatch/trunk/python/qpid_dispatch/management/qdrouter.json.readme.txt 
Fri Jan 30 02:04:08 2015
@@ -44,12 +44,17 @@ Entity type definitions also have these
 
 Attribute definition maps have the following fields:
 
+- "description": documentation string.
 - "type": one of the following:
   - "String": a unicode string value.
   - "Integer": an integer value.
   - "Boolean": a true/false value.
   - [...]: A list of strings is an enumeration. Values must be one of the 
strings or an integer integer index into the list, starting from 0.
 - "default": a default can be a literal value or a reference to another 
attribute in the form `$attributeName`.
+- "fixed": if true the attribute cannot be updated.
+- "create": if true the attribute can be set by CREATE.
+- "update": if true the attribute can be changed by UPDATE.
+Attributes with neither "create" nor "update" are set automatically by the 
system.
 
 There is the following hierarchy among entity types:
 

Modified: qpid/dispatch/trunk/python/qpid_dispatch_internal/management/agent.py
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch_internal/management/agent.py?rev=1655905&r1=1655904&r2=1655905&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/agent.py 
(original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/agent.py Fri 
Jan 30 02:04:08 2015
@@ -106,16 +106,13 @@ class AgentEntity(SchemaEntity):
         self.__dict__['_qd'] = agent.qd
         self.__dict__['_dispatch'] = agent.dispatch
 
-    def validate(self):
-        # Set default identity and name if not already set.
-        prefix = self.entity_type.short_name + "/"
-        identity = self.attributes.get('identity')
-        if identity is None:
-            self.attributes['identity'] = prefix + str(self._identifier())
-        elif not identity.startswith(prefix) and identity != 'self':
-            self.attributes['identity'] = prefix + self.attributes['identity']
+    def validate(self, **kwargs):
+        """Set default identity and name if not already set, then do schema 
validation"""
+        identity = self.attributes.get("identity")
+        if not identity:
+            self.attributes["identity"] = "%s/%s" % 
(self.entity_type.short_name, self._identifier())
         self.attributes.setdefault('name', self.attributes['identity'])
-        super(AgentEntity, self).validate()
+        super(AgentEntity, self).validate(**kwargs)
 
     def _identifier(self):
         """
@@ -152,8 +149,9 @@ class AgentEntity(SchemaEntity):
 
     def update(self, request):
         """Handle update request with new attributes from management client"""
+        self.entity_type.update_check(request.body, self.attributes)
         newattrs = dict(self.attributes, **request.body)
-        self.entity_type.validate(newattrs)
+        self.entity_type.validate(newattrs, update=True)
         self.attributes = newattrs
         self._update()
         return (OK, self.attributes)
@@ -186,11 +184,9 @@ class RouterEntity(AgentEntity):
         super(RouterEntity, self).__init__(agent, entity_type, attributes, 
validate=False)
         # Router is a mix of configuration and operational entity.
         # The "area" attribute is operational not configured.
-        # FIXME aconway 2014-12-19: clean this up.
         self._set_pointer(self._dispatch)
 
-    def _identifier(self):
-        return self.attributes.get('routerId')
+    def _identifier(self): return self.attributes.get('routerId')
 
     def create(self):
         self._qd.qd_dispatch_configure_router(self._dispatch, self)
@@ -222,6 +218,7 @@ def _addr_port_identifier(entity):
             attr, entity.entity_type.attribute(attr).missing_value())
     return "%s:%s" % (entity.attributes['addr'], entity.attributes['port'])
 
+
 class ListenerEntity(AgentEntity):
     def create(self):
         self._qd.qd_dispatch_configure_listener(self._dispatch, self)
@@ -229,6 +226,7 @@ class ListenerEntity(AgentEntity):
 
     def _identifier(self): return _addr_port_identifier(self)
 
+
 class ConnectorEntity(AgentEntity):
     def create(self):
         self._qd.qd_dispatch_configure_connector(self._dispatch, self)
@@ -246,12 +244,13 @@ class WaypointEntity(AgentEntity):
         self._qd.qd_dispatch_configure_waypoint(self._dispatch, self)
         self._qd.qd_waypoint_activate_all(self._dispatch)
 
+
 class ExternalContainerEntity(AgentEntity):
     def create(self):
         self._qd.qd_dispatch_configure_external_container(self._dispatch, self)
 
-class DummyEntity(AgentEntity):
 
+class DummyEntity(AgentEntity):
     def callme(self, request):
         return (OK, dict(**request.properties))
 
@@ -260,23 +259,31 @@ class CEntity(AgentEntity):
     """
     Entity that is registered from C code rather than created via management.
     """
-    def __init__(self, agent, entity_type, pointer):
+    def __init__(self, agent, entity_type, pointer, validate=True):
         super(CEntity, self).__init__(agent, entity_type, {}, validate=False)
         self._set_pointer(pointer)
         self._refresh()
-        self.validate()
+        if validate: self.validate()
 
 
 class RouterLinkEntity(CEntity): pass
 
-class RouterNodeEntity(CEntity): pass
+class RouterNodeEntity(CEntity):
+    def _identifier(self):
+        return self.attributes.get('routerId')
 
-class RouterAddressEntity(CEntity): pass
+class RouterAddressEntity(CEntity):
+    def _identifier(self):
+        return self.attributes.get('hash')
 
-class ConnectionEntity(CEntity): pass
+class ConnectionEntity(CEntity):
+    def _identifier(self):
+        return self.attributes.get('host')
 
-class AllocatorEntity(CEntity): pass
 
+class AllocatorEntity(CEntity):
+    def _identifier(self):
+        return self.attributes.get('typeName')
 
 class EntityCache(object):
     """
@@ -549,35 +556,45 @@ class Agent(object):
             return self.create(request)
         else:
             target = self.find_entity(request)
-            target.entity_type.allowed(operation)
+            target.entity_type.allowed(operation, request.body)
             try:
                 method = getattr(target, operation.lower().replace("-", "_"))
             except AttributeError:
                 not_implemented(operation, target.type)
             return method(request)
 
-    def create(self, request=None, attributes=None):
+    def _create(self, attributes):
+        """Create an entity, called externally or from configuration file."""
+        entity = self.create_entity(attributes)
+        self.add_entity(entity)
+        entity.create()
+        return entity
+
+    def create(self, request):
         """
+        Create operation called from an external client.
         Create is special: it is directed at an entity but the entity
         does not yet exist so it is handled initially by the agent and
         then delegated to the new entity.
         """
-        if request:
-            attributes = request.body
-            for a in ['type', 'name']:
-                prop = request.properties.get(a)
-                if prop:
-                    old = attributes.setdefault(a, prop)
-                    if old is not None and old != prop:
-                        raise BadRequestStatus("Conflicting values for '%s'" % 
a)
-                attributes[a] = prop
+        attributes = request.body
+        for a in ['type', 'name']:
+            prop = request.properties.get(a)
+            if prop:
+                old = attributes.setdefault(a, prop)
+                if old is not None and old != prop:
+                    raise BadRequestStatus("Conflicting values for '%s'" % a)
+            attributes[a] = prop
         if attributes.get('type') is None:
             raise BadRequestStatus("No 'type' attribute in %s" % attributes)
-        self.schema.entity_type(attributes['type']).allowed('create')
-        entity = self.create_entity(attributes)
-        self.add_entity(entity)
-        entity.create()
-        return (CREATED, entity.attributes)
+        et = self.schema.entity_type(attributes['type'])
+        et.allowed("CREATE", attributes)
+        et.create_check(attributes)
+        return (CREATED, self._create(attributes).attributes)
+
+    def configure(self, attributes):
+        """Created via configuration file"""
+        self._create(attributes)
 
     def add_entity(self, entity): self.entities.add(entity)
 

Modified: qpid/dispatch/trunk/python/qpid_dispatch_internal/management/config.py
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch_internal/management/config.py?rev=1655905&r1=1655904&r2=1655905&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/config.py 
(original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/config.py Fri 
Jan 30 02:04:08 2015
@@ -136,7 +136,7 @@ def configure_dispatch(dispatch, lib_han
 
     def configure(attributes):
         """Configure an entity and remove it from config"""
-        agent.create(attributes=attributes)
+        agent.configure(attributes)
         config.remove(attributes)
 
     modules = 
set(agent.schema.entity_type("log").attributes["module"].atype.tags)
@@ -144,7 +144,7 @@ def configure_dispatch(dispatch, lib_han
         configure(l)
         modules.remove(l["module"])
     # Add default entities for any log modules not configured.
-    for m in modules: agent.create(attributes=dict(type="log", module=m))
+    for m in modules: agent.configure(attributes=dict(type="log", module=m))
 
     # Configure and prepare container and router before we can activate the 
agent.
     configure(config.by_type('container')[0])

Added: qpid/dispatch/trunk/python/qpid_dispatch_internal/management/markdown.py
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch_internal/management/markdown.py?rev=1655905&view=auto
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/markdown.py 
(added)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/markdown.py 
Fri Jan 30 02:04:08 2015
@@ -0,0 +1,81 @@
+#
+# 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
+#
+
+"""Library for generating markdown documentation from a L{schema.Schema}"""
+
+from collections import namedtuple
+import sys, re
+from .schema import quotestr
+
+
+class SchemaWriter(object):
+    """Write the schema as a markdown document"""
+
+    def __init__(self, output, schema, quiet=True):
+        self.output, self.schema, self.quiet = output, schema, quiet
+        # Options affecting how output is written
+
+    def write(self, what):
+        self.output.write(what)
+
+    def warn(self, message):
+        if not self.quiet: print >>sys.stderr, message
+
+    def attribute_type(self, attr, holder, show_create=True, show_update=True):
+        default = attr.default
+        if isinstance(default, basestring) and default.startswith('$'):
+            default = None  # Don't show defaults that are references, 
confusing.
+
+        self.write('\n*%s* '%(attr.name))
+        self.write('(%s)\n'%(', '.join(
+            filter(None, [str(attr.atype),
+                          default and "default=%s" % quotestr(default),
+                          attr.required and "required",
+                          attr.unique and "unique",
+                          show_create and attr.create and "`CREATE`",
+                          show_update and attr.update and "`UPDATE`"
+                      ]))))
+        if attr.description:
+            self.write(":   %s\n" % attr.description)
+        else:
+            self.warn("Warning: No description for %s in %s" % (attr, 
attr.defined_in.short_name))
+
+    def holder_preface(self, holder):
+        self.write('\n### %s\n' % holder.short_name)
+        if holder.description:
+            self.write('\n%s\n' % holder.description)
+        else:
+            self.warn("Warning no description for %s" % entity_type)
+
+    def attribute_types(self, holder):
+        for attr in holder.my_attributes:
+            self.attribute_type(attr, holder)
+
+    def entity_type(self, entity_type):
+        self.holder_preface(entity_type)
+        if entity_type.operations:
+            self.write("\nOperations allowed: `%s`\n\n" % "`, 
`".join(entity_type.operations))
+        for a in entity_type.annotations: self.attribute_types(a)
+        self.attribute_types(entity_type)
+
+    def entity_types_extending(self, base_name):
+        base = self.schema.entity_type(base_name)
+        for entity_type in self.schema.filter(lambda t: t.extends(base)):
+            self.entity_type(entity_type)
+

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=1655905&r1=1655904&r2=1655905&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/schema.py 
(original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/schema.py Fri 
Jan 30 02:04:08 2015
@@ -201,7 +201,7 @@ class AttributeType(object):
     """
 
     def __init__(self, name, type=None, defined_in=None, default=None, 
required=False, unique=False,
-                 value=None, annotation=None, description=""):
+                 value=None, annotation=None, description="", create=False, 
update=False):
         """
         See L{AttributeType} instance variables.
         """
@@ -217,6 +217,8 @@ class AttributeType(object):
         if self.value is not None and self.default is not None:
             raise ValidationError("Attribute '%s' has default value and fixed 
value" %
                                   self.name)
+        self.create=create
+        self.update=update
 
     def missing_value(self, check_required=True, add_default=True, **kwargs):
         """
@@ -239,6 +241,8 @@ class AttributeType(object):
         @param resolve: function to resolve value references.
         @keyword check_unique: set of (name, value) to check for attribute 
uniqueness.
             None means don't check for uniqueness.
+        @param create: if true, check that the attribute allows create
+        @param update: if true, check that the attribute allows update
         @param kwargs: See L{Schema.validate_all}
         @return: value converted to the correct python type. Rais exception if 
any check fails.
         """
@@ -375,9 +379,9 @@ class EntityType(AttrsAndOps):
             overlap = set(a) & set(b)
             if overlap:
                 raise RedefinedError("'%s' cannot %s '%s', re-defines %s: %s"
-                                     % (self.name, how, other.short_name, 
what, list(overlap).join(', ')))
+                                     % (self.name, how, other.short_name, 
what, ",".join(overlap)))
         check(self.operations, other.operations, "operations")
-        self.operations = self.operations + other.operations
+        self.operations += other.operations
         check(self.attributes.iterkeys(), other.attributes.itervalues(), 
"attributes")
         self.attributes.update(other.attributes)
 
@@ -453,11 +457,24 @@ class EntityType(AttrsAndOps):
 
         return attributes
 
-    def allowed(self, op):
-        """Raise excepiton if op is not a valid operation on entity."""
+    def allowed(self, op, body):
+        """Raise exception if op is not a valid operation on entity."""
         op = op.upper()
-        if op not in self.operations:
-            raise NotImplementedStatus("Operation '%s' not implemented for 
'%s'" % (op, self.name))
+        if not op in self.operations:
+            raise NotImplementedStatus("Operation '%s' not implemented for 
'%s' %s" % (
+                op, self.name, self.operations))
+
+    def create_check(self, attributes):
+        for a in attributes:
+            if not self.attribute(a).create:
+                raise ValidationError("Cannot set attribute '%s' in CREATE" % 
a)
+
+    def update_check(self, new_attributes, old_attributes):
+        for a, v in new_attributes.iteritems():
+            # Its not an error to include an attribute in UPDATE if the value 
is not changed.
+            if not self.attribute(a).update and \
+               not (a in old_attributes and old_attributes[a] == v):
+                raise ValidationError("Cannot update attribute '%s' in UPDATE" 
% a)
 
     def __repr__(self): return "%s(%s)" % (type(self).__name__, self.name)
 
@@ -625,5 +642,5 @@ class SchemaEntity(EntityBase):
         super(SchemaEntity, self).__setitem__(name, value)
         self.validate()
 
-    def validate(self):
-        self.entity_type.validate(self.attributes)
+    def validate(self, **kwargs):
+        self.entity_type.validate(self.attributes, **kwargs)

Modified: qpid/dispatch/trunk/src/alloc.c
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/alloc.c?rev=1655905&r1=1655904&r2=1655905&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/alloc.c (original)
+++ qpid/dispatch/trunk/src/alloc.c Fri Jan 30 02:04:08 2015
@@ -357,8 +357,7 @@ void qd_alloc_finalize(void)
 
 qd_error_t qd_entity_refresh_allocator(qd_entity_t* entity, void *impl) {
     qd_alloc_type_t *alloc_type = (qd_alloc_type_t*) impl;
-    if ((qd_entity_has(entity, "identity") ||
-         qd_entity_set_string(entity, "identity", alloc_type->desc->type_name) 
== 0) &&
+    if (qd_entity_set_string(entity, "typeName", alloc_type->desc->type_name) 
== 0 &&
         qd_entity_set_long(entity, "typeSize", alloc_type->desc->total_size) 
== 0 &&
         qd_entity_set_long(entity, "transferBatchSize", 
alloc_type->desc->config->transfer_batch_size) == 0 &&
         qd_entity_set_long(entity, "localFreeListMax", 
alloc_type->desc->config->local_free_list_max) == 0 &&

Modified: qpid/dispatch/trunk/src/router_agent.c
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/router_agent.c?rev=1655905&r1=1655904&r2=1655905&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/router_agent.c (original)
+++ qpid/dispatch/trunk/src/router_agent.c Fri Jan 30 02:04:08 2015
@@ -41,8 +41,7 @@ ENUM_DEFINE(qd_router_mode, qd_router_mo
 qd_error_t qd_entity_refresh_router(qd_entity_t* entity, void *impl) {
     qd_dispatch_t *qd = (qd_dispatch_t*) impl;
     qd_router_t *router = qd->router;
-    if ((qd_entity_has(entity, "identity") || qd_entity_set_string(entity, 
"identity", router->router_id) == 0) &&
-        qd_entity_set_string(entity, "area", router->router_area) == 0 &&
+    if (qd_entity_set_string(entity, "area", router->router_area) == 0 &&
         qd_entity_set_string(entity, "mode", 
qd_router_mode_name(router->router_mode)) == 0 &&
         qd_entity_set_long(entity, "addrCount", DEQ_SIZE(router->addrs)) == 0 
&&
         qd_entity_set_long(entity, "linkCount", DEQ_SIZE(router->links)) == 0 
&&
@@ -52,22 +51,26 @@ qd_error_t qd_entity_refresh_router(qd_e
     return qd_error_code();
 }
 
-static const char *address_text(qd_address_t *addr)
-{
+static const char *address_hash(qd_address_t *addr) {
     return addr ? (const char*) qd_hash_key_by_handle(addr->hash_handle) : 0;
 }
 
+static const char *address_router_id(qd_address_t *addr) {
+    const char* hash = address_hash(addr);
+    return hash && hash[0] == 'R' ? hash+1 : "";
+}
+
 qd_error_t qd_entity_refresh_router_address(qd_entity_t* entity, void *impl) {
     qd_address_t *addr = (qd_address_t*) impl;
-    if ((qd_entity_has(entity, "identity") || qd_entity_set_string(entity, 
"identity", address_text(addr)) == 0) &&
-        qd_entity_set_bool(entity, "inProcess", addr->handler != 0) == 0 &&
+    if (qd_entity_set_bool(entity, "inProcess", addr->handler != 0) == 0 &&
         qd_entity_set_long(entity, "subscriberCount", DEQ_SIZE(addr->rlinks)) 
== 0 &&
         qd_entity_set_long(entity, "remoteCount", DEQ_SIZE(addr->rnodes)) == 0 
&&
         qd_entity_set_long(entity, "deliveriesIngress", 
addr->deliveries_ingress) == 0 &&
         qd_entity_set_long(entity, "deliveriesEgress", 
addr->deliveries_egress) == 0 &&
         qd_entity_set_long(entity, "deliveriesTransit", 
addr->deliveries_transit) == 0 &&
         qd_entity_set_long(entity, "deliveriesToContainer", 
addr->deliveries_to_container) == 0 &&
-        qd_entity_set_long(entity, "deliveriesFromContainer", 
addr->deliveries_from_container) == 0
+        qd_entity_set_long(entity, "deliveriesFromContainer", 
addr->deliveries_from_container) == 0 &&
+        qd_entity_set_string(entity, "hash", address_hash(addr))
     )
         return QD_ERROR_NONE;
     return qd_error_code();
@@ -78,10 +81,9 @@ qd_error_t qd_entity_refresh_router_addr
 qd_error_t qd_entity_refresh_router_node(qd_entity_t* entity, void *impl) {
     qd_router_node_t *rnode = (qd_router_node_t*) impl;
 
-    if (!qd_entity_has(entity, "identity")) {
-        CHECK(qd_entity_set_stringf(entity, "identity", "%s/%d", 
QD_ROUTER_NODE_TYPE, rnode->mask_bit));
-    }
-    CHECK(qd_entity_set_string(entity, "addr", 
address_text(rnode->owning_addr)));
+    /* FIXME aconway 2015-01-29: Fix all "identity settings in C" */
+    CHECK(qd_entity_set_string(entity, "routerId", 
address_router_id(rnode->owning_addr)));
+    CHECK(qd_entity_set_string(entity, "addr", 
address_hash(rnode->owning_addr)));
     long next_hop = rnode->next_hop ? rnode->next_hop->mask_bit : 0;
     CHECK(qd_entity_set_stringf(entity, "nextHop", "%ld", rnode->next_hop ? 
next_hop : 0));
     long router_link = rnode->peer_link ? rnode->peer_link->mask_bit : 0;

Modified: qpid/dispatch/trunk/src/router_pynode.c
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/router_pynode.c?rev=1655905&r1=1655904&r2=1655905&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/router_pynode.c (original)
+++ qpid/dispatch/trunk/src/router_pynode.c Fri Jan 30 02:04:08 2015
@@ -101,7 +101,6 @@ static PyObject *qd_add_router(PyObject
         rnode->valid_origins = qd_bitmask(0);
 
         DEQ_INSERT_TAIL(router->routers, rnode);
-        qd_entity_cache_add(QD_ROUTER_NODE_TYPE, rnode);
 
         //
         // Link the router record to the address record.
@@ -119,6 +118,8 @@ static PyObject *qd_add_router(PyObject
         //
         router->routers_by_mask_bit[router_maskbit] = rnode;
 
+        qd_entity_cache_add(QD_ROUTER_NODE_TYPE, rnode);
+
         sys_mutex_unlock(router->lock);
     } while (0);
 

Modified: qpid/dispatch/trunk/src/server.c
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/server.c?rev=1655905&r1=1655904&r2=1655905&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/server.c (original)
+++ qpid/dispatch/trunk/src/server.c Fri Jan 30 02:04:08 2015
@@ -86,9 +86,7 @@ qd_error_t qd_entity_refresh_connection(
     const qd_server_config_t *config =
         conn->connector ? conn->connector->config : conn->listener->config;
 
-    if ((qd_entity_has(entity, "identity") ||
-         qd_entity_set_string(entity, "identity", 
qdpn_connector_name(conn->pn_cxtr)) == 0) &&
-        qd_entity_set_string(entity, "state", conn_state_name(conn->state)) == 
0 &&
+    if (qd_entity_set_string(entity, "state", conn_state_name(conn->state)) == 
0 &&
         qd_entity_set_string(
             entity, "container",
             conn->pn_conn ? pn_connection_remote_container(conn->pn_conn) : 0) 
== 0 &&

Modified: qpid/dispatch/trunk/tests/system_test.py
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/system_test.py?rev=1655905&r1=1655904&r2=1655905&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/system_test.py (original)
+++ qpid/dispatch/trunk/tests/system_test.py Fri Jan 30 02:04:08 2015
@@ -372,11 +372,10 @@ class Qdrouterd(Process):
     def is_connected(self, port, host='0.0.0.0'):
         """If router has a connection to host:port return the management info.
         Otherwise return None"""
-        connections = 
self.management.query('org.apache.qpid.dispatch.connection').get_entities()
-        for c in connections:
-            if c['name'].endswith('%s:%s'%(host, port)):
-                return c
-        return None
+        try:
+            return self.management.read(identity="connection/%s:%s" % (host, 
port))
+        except:
+            return False
 
     def wait_address(self, address, subscribers=0, remotes=0, **retry_kwargs):
         """
@@ -396,7 +395,6 @@ class Qdrouterd(Process):
             return addrs and addrs[0]['subscriberCount'] >= subscribers and 
addrs[0]['remoteCount'] >= remotes
         assert retry(check, **retry_kwargs)
 
-
     def wait_connectors(self, **retry_kwargs):
         """
         Wait for all connectors to be connected
@@ -405,19 +403,30 @@ class Qdrouterd(Process):
         for c in self.config.sections('connector'):
             assert retry(lambda: self.is_connected(c['port']), 
**retry_kwargs), "Port not connected %s" % c['port']
 
-    def wait_ready(self):
+    def wait_ready(self, **retry_kwargs):
         """Wait for ports and connectors to be ready"""
         if not self._wait_ready:
             self._wait_ready = True
-            wait_ports(self.ports)
-            self.wait_connectors()
+            wait_ports(self.ports, **retry_kwargs)
+            self.wait_connectors(**retry_kwargs)
         return self
 
-    def wait_connected(self, router_id):
-        """Wait till this router is connected to router with router-id"""
-        node = Node(self.addresses[0], router_id, timeout=1)
-        retry_exception(lambda: node.query('org.apache.qpid.dispatch.router'))
+    def is_router_connected(self, router_id, **retry_kwargs):
+        try:
+            self.management.read(identity="router.node/%s" % router_id)
+            # TODO aconway 2015-01-29: The above check should be enough, we
+            # should not advertise a remote router in managment till it is 
fully
+            # connected. However we still get a race where the router is not
+            # actually ready for traffic. Investigate.
+            # Meantime the following actually tests send-thru to the router.
+            node = Node(self.addresses[0], router_id, timeout=1)
+            return retry_exception(lambda: 
node.query('org.apache.qpid.dispatch.router'))
+        except:
+            return False
+
 
+    def wait_router_connected(self, router_id, **retry_kwargs):
+        retry(lambda: self.is_router_connected(router_id), **retry_kwargs)
 
 class Qpidd(Process):
     """Run a Qpid Daemon"""

Modified: qpid/dispatch/trunk/tests/system_tests_management.py
URL: 
http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/system_tests_management.py?rev=1655905&r1=1655904&r2=1655905&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/system_tests_management.py (original)
+++ qpid/dispatch/trunk/tests/system_tests_management.py Fri Jan 30 02:04:08 
2015
@@ -95,8 +95,8 @@ class ManagementTest(system_test.TestCas
         """Wait on demand and return the linked interior routers"""
         if not self._routers:
             self._routers = self.__class__._routers
-            self._routers[0].wait_connected('router2')
-            self._routers[1].wait_connected('router1')
+            self._routers[0].wait_router_connected('router2')
+            self._routers[1].wait_router_connected('router1')
         return self._routers
 
     def setUp(self):
@@ -369,7 +369,8 @@ class ManagementTest(system_test.TestCas
     def test_connection(self):
         """Verify there is at least one connection"""
         response = self.node.query(type='connection')
-        self.assertTrue(response.get_dicts())
+        print "FIXME", response.get_dicts()
+        self.assertTrue(response.results)
 
     def test_router(self):
         """Verify router counts match entity counts"""
@@ -385,6 +386,8 @@ class ManagementTest(system_test.TestCas
         nodes = [self.cleanup(Node(Url(r.addresses[0]))) for r in self.routers]
         rnodes = sum([n.query(type=NODE).get_entities() for n in nodes], [])
         self.assertEqual(['Rrouter2', 'Rrouter1'], [r.addr for r in rnodes])
+        self.assertEqual(['router2', 'router1'], [r.routerId for r in rnodes])
+        self.assertEqual(['router.node/router2', 'router.node/router1'], 
[r.identity for r in rnodes])
         self.assertEqual(['0', '0'], [r.nextHop for r in rnodes])
         self.assertEqual([[], []], [r.validOrigins for r in rnodes])
 

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=1655905&r1=1655904&r2=1655905&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/system_tests_two_routers.py (original)
+++ qpid/dispatch/trunk/tests/system_tests_two_routers.py Fri Jan 30 02:04:08 
2015
@@ -49,8 +49,8 @@ class RouterTest(TestCase):
         router('B', 'client',
                ('connector', {'role': 'inter-router', 'port': 
cls.routers[0].ports[1]}))
 
-        cls.routers[0].wait_connected('QDR.B')
-        cls.routers[1].wait_connected('QDR.A')
+        cls.routers[0].wait_router_connected('QDR.B')
+        cls.routers[1].wait_router_connected('QDR.A')
 
 
     def test_00_discard(self):



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

Reply via email to