You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ac...@apache.org on 2014/09/10 05:34:42 UTC
svn commit: r1623899 - in /qpid/dispatch/trunk: ./ doc/man/
python/qpid_dispatch/ python/qpid_dispatch/management/
python/qpid_dispatch_internal/management/ src/ tests/ tests/management/
tools/
Author: aconway
Date: Wed Sep 10 03:34:41 2014
New Revision: 1623899
URL: http://svn.apache.org/r1623899
Log:
DISPATCH-56: Make dispatch management client library public.
The dispatch management client library is now installed as a normal python package
qpid_dispatch.management
- Added management.Entity with read, update, delete operations.
- Added setup.py.in to install python packages to standard site-directory for CMAKE_INSTALL_PREFIX
- Added VERSION.txt with project version, available in cmake as QPID_DISPATCH_VERSION
- Separate proton.Messenger code from client.Node.
- Add CRUD permissions to schema. Fix status return codes for CRUD ops.
- Added DummyEntity to test CRUD, implements all 4 operations.
Added:
qpid/dispatch/trunk/VERSION.txt
qpid/dispatch/trunk/python/qpid_dispatch/
qpid/dispatch/trunk/python/qpid_dispatch/__init__.py
- copied, changed from r1618265, qpid/dispatch/trunk/config.sh
qpid/dispatch/trunk/python/qpid_dispatch/management/
qpid/dispatch/trunk/python/qpid_dispatch/management/__init__.py
- copied, changed from r1618265, qpid/dispatch/trunk/config.sh
qpid/dispatch/trunk/python/qpid_dispatch/management/client.py
- copied, changed from r1618265, qpid/dispatch/trunk/python/qpid_dispatch_internal/management/client.py
qpid/dispatch/trunk/python/qpid_dispatch/management/entity.py
qpid/dispatch/trunk/python/qpid_dispatch/management/error.py
- copied, changed from r1618265, qpid/dispatch/trunk/python/qpid_dispatch_internal/management/error.py
qpid/dispatch/trunk/setup.py.in (contents, props changed)
- copied, changed from r1618265, qpid/dispatch/trunk/python/qpid_dispatch_internal/management/__init__.py
Removed:
qpid/dispatch/trunk/python/qpid_dispatch_internal/management/client.py
qpid/dispatch/trunk/python/qpid_dispatch_internal/management/entity.py
qpid/dispatch/trunk/python/qpid_dispatch_internal/management/error.py
Modified:
qpid/dispatch/trunk/CMakeLists.txt
qpid/dispatch/trunk/config.sh
qpid/dispatch/trunk/doc/man/qdrouterd_conf_man.py
qpid/dispatch/trunk/python/qpid_dispatch_internal/management/__init__.py
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/qdrouter.json
qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.py
qpid/dispatch/trunk/python/qpid_dispatch_internal/management/schema.py
qpid/dispatch/trunk/src/dispatch.c
qpid/dispatch/trunk/src/schema_c.py
qpid/dispatch/trunk/tests/management/__init__.py
qpid/dispatch/trunk/tests/management/client.py
qpid/dispatch/trunk/tests/management/entity.py
qpid/dispatch/trunk/tests/management/qdrouter.py
qpid/dispatch/trunk/tests/management/schema.py
qpid/dispatch/trunk/tests/run.py.in
qpid/dispatch/trunk/tests/system_test.py
qpid/dispatch/trunk/tests/system_tests_management.py
qpid/dispatch/trunk/tests/system_tests_two_routers.py
qpid/dispatch/trunk/tools/qdstat
Modified: qpid/dispatch/trunk/CMakeLists.txt
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/CMakeLists.txt?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/CMakeLists.txt (original)
+++ qpid/dispatch/trunk/CMakeLists.txt Wed Sep 10 03:34:41 2014
@@ -27,6 +27,8 @@ message("Build type is \"${CMAKE_BUILD_T
project(qpid-dispatch C)
+file(STRINGS "${CMAKE_SOURCE_DIR}/VERSION.txt" QPID_DISPATCH_VERSION)
+
cmake_minimum_required(VERSION 2.6)
include(CheckLibraryExists)
include(CheckSymbolExists)
@@ -136,6 +138,12 @@ install(PROGRAMS ${TOOLS_EXECUTABLES}
install(FILES ${DOC_FILES}
DESTINATION ${DOC_INSTALL_DIR}/qpid-dispatch)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in
+ ${CMAKE_CURRENT_BINARY_DIR}/setup.py)
+
+install(CODE "execute_process(COMMAND ${PYTHON_EXECUTABLE} setup.py install --prefix=${CMAKE_INSTALL_PREFIX} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})")
+
+
## Build Tests first as other subdirs use tests/run.py
##
add_subdirectory(tests)
Added: qpid/dispatch/trunk/VERSION.txt
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/VERSION.txt?rev=1623899&view=auto
==============================================================================
--- qpid/dispatch/trunk/VERSION.txt (added)
+++ qpid/dispatch/trunk/VERSION.txt Wed Sep 10 03:34:41 2014
@@ -0,0 +1 @@
+0.3
Modified: qpid/dispatch/trunk/config.sh
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/config.sh?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/config.sh (original)
+++ qpid/dispatch/trunk/config.sh Wed Sep 10 03:34:41 2014
@@ -26,7 +26,9 @@ export SOURCE_DIR=$(pwd)
export BUILD_DIR=$SOURCE_DIR/build
export INSTALL_DIR=$SOURCE_DIR/install
+PYTHON_LIB=$(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib(prefix='$INSTALL_DIR')")
+
export QPID_DISPATCH_HOME=$INSTALL_DIR/lib/qpid-dispatch
export LD_LIBRARY_PATH=$INSTALL_DIR/lib64:$INSTALL_DIR/lib:$LD_LIBRARY_PATH
-export PYTHONPATH=$QPID_DISPATCH_HOME/python:$PYTHONPATH
+export PYTHONPATH=$QPID_DISPATCH_HOME/python:$PYTHON_LIB:$PYTHONPATH
export PATH=$INSTALL_DIR/sbin:$INSTALL_DIR/bin:$SOURCE_DIR/bin:$PATH
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=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/doc/man/qdrouterd_conf_man.py (original)
+++ qpid/dispatch/trunk/doc/man/qdrouterd_conf_man.py Wed Sep 10 03:34:41 2014
@@ -21,7 +21,7 @@
Generate the qdrouterd.conf man page from the qdrouterd management schema."""
import sys
-from qpid_dispatch_internal.management import QdSchema
+from qpid_dispatch_internal.management.qdrouter import QdSchema
def make_man_page(filename):
"""Generate a man page for the configuration file from L{QdSchema} descriptions"""
Copied: qpid/dispatch/trunk/python/qpid_dispatch/__init__.py (from r1618265, qpid/dispatch/trunk/config.sh)
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch/__init__.py?p2=qpid/dispatch/trunk/python/qpid_dispatch/__init__.py&p1=qpid/dispatch/trunk/config.sh&r1=1618265&r2=1623899&rev=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/config.sh (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch/__init__.py Wed Sep 10 03:34:41 2014
@@ -14,19 +14,6 @@
# "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.
+# under the License
#
-
-if [[ ! -f config.sh ]]; then
- echo "You must source config.sh from within its own directory"
- return
-fi
-
-export SOURCE_DIR=$(pwd)
-export BUILD_DIR=$SOURCE_DIR/build
-export INSTALL_DIR=$SOURCE_DIR/install
-
-export QPID_DISPATCH_HOME=$INSTALL_DIR/lib/qpid-dispatch
-export LD_LIBRARY_PATH=$INSTALL_DIR/lib64:$INSTALL_DIR/lib:$LD_LIBRARY_PATH
-export PYTHONPATH=$QPID_DISPATCH_HOME/python:$PYTHONPATH
-export PATH=$INSTALL_DIR/sbin:$INSTALL_DIR/bin:$SOURCE_DIR/bin:$PATH
+"""Qpid dispatch package"""
Copied: qpid/dispatch/trunk/python/qpid_dispatch/management/__init__.py (from r1618265, qpid/dispatch/trunk/config.sh)
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch/management/__init__.py?p2=qpid/dispatch/trunk/python/qpid_dispatch/management/__init__.py&p1=qpid/dispatch/trunk/config.sh&r1=1618265&r2=1623899&rev=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/config.sh (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch/management/__init__.py Wed Sep 10 03:34:41 2014
@@ -14,19 +14,8 @@
# "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.
+# under the License
#
+"""AMQP management client package."""
-if [[ ! -f config.sh ]]; then
- echo "You must source config.sh from within its own directory"
- return
-fi
-
-export SOURCE_DIR=$(pwd)
-export BUILD_DIR=$SOURCE_DIR/build
-export INSTALL_DIR=$SOURCE_DIR/install
-
-export QPID_DISPATCH_HOME=$INSTALL_DIR/lib/qpid-dispatch
-export LD_LIBRARY_PATH=$INSTALL_DIR/lib64:$INSTALL_DIR/lib:$LD_LIBRARY_PATH
-export PYTHONPATH=$QPID_DISPATCH_HOME/python:$PYTHONPATH
-export PATH=$INSTALL_DIR/sbin:$INSTALL_DIR/bin:$SOURCE_DIR/bin:$PATH
+from .client import *
Copied: qpid/dispatch/trunk/python/qpid_dispatch/management/client.py (from r1618265, qpid/dispatch/trunk/python/qpid_dispatch_internal/management/client.py)
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch/management/client.py?p2=qpid/dispatch/trunk/python/qpid_dispatch/management/client.py&p1=qpid/dispatch/trunk/python/qpid_dispatch_internal/management/client.py&r1=1618265&r2=1623899&rev=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/client.py (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch/management/client.py Wed Sep 10 03:34:41 2014
@@ -22,13 +22,13 @@ AMQP management client for Qpid dispatch
"""
import proton, re, threading
-from error import ManagementError, OK, NOT_FOUND
-from schema import Schema
+from .error import *
+from . import entity
-class Url:
+class Url(object):
"""Simple AMQP URL parser/constructor"""
- RE = re.compile(r"""
+ URL_RE = re.compile(r"""
# [ <scheme>:// ] [ <user> [ : <password> ] @] ( <host4> | \[ <host6> \] ) [ :<port> ] [ / path]
^ (?: ([^:/@]+)://)? (?: ([^:/@]+) (?: : ([^:/@]+) )? @)? (?: ([^@:/\[]+) | \[ ([a-f0-9:.]+) \] ) (?: :([0-9]+))? (?: / (.*))? $
""", re.X | re.I)
@@ -51,7 +51,7 @@ class Url:
self.__dict__.update(url.__dict__)
elif url is not None: # Parse from url
- match = Url.RE.match(url)
+ match = Url.URL_RE.match(url)
if match is None:
raise ValueError("Invalid AMQP URL: %s"%url)
self.scheme, self.user, self.password, host4, host6, port, self.path = match.groups()
@@ -84,33 +84,115 @@ class Url:
return s
def __eq__(self, url):
- if isinstance(url, basestring):
- url = Url(url)
- return \
- self.scheme==url.scheme and \
- self.user==url.user and self.password==url.password and \
- self.host==url.host and self.port==url.port and \
- self.path==url.path
+ if isinstance(url, basestring):
+ url = Url(url)
+ return \
+ self.scheme == url.scheme and \
+ self.user == url.user and self.password == url.password and \
+ self.host == url.host and self.port == url.port and \
+ self.path == url.path
def __ne__(self, url):
- return not self.__eq__(url)
+ return not self.__eq__(url)
def defaults(self):
""""Fill in defaults for scheme and port if missing """
if not self.scheme: self.scheme = self.AMQP
if not self.port:
- if self.scheme==self.AMQP: self.port = 5672
- elif self.scheme==self.AMQPS: self.port = 5671
+ if self.scheme == self.AMQP: self.port = 5672
+ elif self.scheme == self.AMQPS: self.port = 5671
else: raise ValueError("Invalid URL scheme: %s"%self.scheme)
return self
def remove_none(d):
"""@return: A copy of d without any None values. Does not modify d."""
- return dict((k, v) for k, v in d.iteritems() if v is not None)
+ return dict((unicode(k), v) for k, v in d.iteritems() if v is not None)
+
+
+class MessengerImpl(object):
+ """
+ Messaging implementation for L{Node} based on proton.Messenger
+ @ivar reply_to: address for replies to the node.
+ """
+
+ def __init__(self, address, timeout=None):
+ self.messenger = proton.Messenger()
+ self.messenger.start()
+ self.messenger.timeout = timeout
+ subscribe_address = Url(address)
+ subscribe_address.path = "#"
+ self.subscription = self.messenger.subscribe(str(subscribe_address))
+ self._flush()
+ self.reply_to = self.subscription.address
+ if not self.reply_to:
+ raise ValueError("Failed to subscribe to %s"%subscribe_address)
+
+ def send(self, request):
+ """Send a message"""
+ self.messenger.put(request)
+ self.messenger.send()
+ self._flush()
+
+ def fetch(self):
+ """Wait for a single message."""
+ self.messenger.recv(1)
+ response = proton.Message()
+ self.messenger.get(response)
+ return response
+
+ def _flush(self):
+ """Call self.messenger.work() till there is no work left."""
+ while self.messenger.work(0.1): pass
+
+ def stop(self):
+ """Stop the messaging implementation"""
+ self.messenger.stop()
+
+
+class Entity(entity.Entity):
+ """
+ Proxy for an AMQP manageable entity.
+
+ Modifying local attributes dict will not change the remote entity. Call
+ update() to send the local attributes to the remote entity.
+
+ The standard AMQP requests read, update and delete are defined
+ here. create() is defined on L{Node}.
+
+ Attribute access:
+ - via index operator: entity['foo']
+ - as python attributes: entity.foo (only if attribute name is a legal python identitfier)
+
+ @ivar attributes: Map of attribute values for this entity.
+ """
+
+ def __init__(self, node, attributes=None, **kwattrs):
+ super(Entity, self).__init__(attributes, **kwattrs)
+ self.__dict__['_node'] = node # Avoid getattr recursion
+
+ def call(self, operation, expect=OK, **arguments):
+ """Call an arbitrary management method on this entity"""
+ request = self._node.request(
+ operation=operation, type=self.type, identity=self.identity, **arguments)
+ return self._node.call(request, expect=expect).body
+
+ def read(self):
+ """Read the remote entity attributes into the local attributes."""
+ self.attributes = self.call('READ', expect=OK)
+
+ def update(self):
+ """Update the remote entity attributes from the local attributes."""
+ self.attributes = self.call('UPDATE', expect=OK, body=self.attributes)
+
+ def delete(self):
+ """Delete the remote entity"""
+ self.call('DELETE', expect=NO_CONTENT)
+
class Node(object):
+ """Client proxy for an AMQP management node"""
- def __init__(self, address=None, router=None, locales=None, timeout=10):
+ def __init__(self, address=None, router=None, locales=None, timeout=10, message_impl=None):
"""
@param address: AMQP address of the management node.
@param router: If address does not contain a path, use the management node for this router ID.
@@ -118,7 +200,7 @@ class Node(object):
@param locales: Default list of locales for management operations.
"""
self.name = self.identity = 'self'
- self.type='org.amqp.management' # AMQP management node type
+ self.type = 'org.amqp.management' # AMQP management node type
self.address = Url(address).defaults()
self.locales = locales
@@ -128,33 +210,20 @@ class Node(object):
else:
self.address.path = '$management'
self.responses = {}
-
- self.messenger = proton.Messenger()
- self.messenger.start()
- self.messenger.timeout = timeout
- subscribe_address = Url(address)
- subscribe_address.path = "#"
- self.subscription = self.messenger.subscribe(str(subscribe_address))
- self._flush()
- self.reply_to = self.subscription.address
- if not self.reply_to:
- raise ValueError("Failed to subscribe to %s"%subscribe_address)
+ self.message_impl = message_impl or MessengerImpl(self.address, timeout=timeout)
+ self.reply_to = self.message_impl.reply_to
def stop(self):
- if not self.messenger: return
- self.messenger.stop()
- self.messenger = None
+ """Shut down the node"""
+ if not self.message_impl: return
+ self.message_impl.stop()
+ self.message_impl = None
def __del__(self):
- if hasattr(self, 'messenger'):
+ if hasattr(self, 'message_impl'):
try: self.stop()
except: pass
- def _flush(self):
- """Call self.messenger.work() till there is no work left."""
- while self.messenger.work(0.1):
- pass
-
def __repr__(self):
return "%s(%s)"%(self.__class__.__name__, self.address)
@@ -167,16 +236,21 @@ class Node(object):
self.CORRELATION_ID += 1
return self.CORRELATION_ID
- def check_response(self, response, request):
+ def check_response(self, response, request, expect=OK):
"""
- Check a manaement response message for errors and correlation ID.
+ Check a management response message for errors and correlation ID.
"""
code = response.properties.get('statusCode')
- if code != OK:
- raise ManagementError(code, response.properties.get('statusDescription'))
+ if code != expect:
+ if 200 <= code <= 299:
+ raise ValueError("Response was %s(%s) but expected %s(%s): %s" % (
+ code, STATUS_TEXT[code], expect, STATUS_TEXT[expect],
+ response.properties.get('statusDescription')))
+ else:
+ raise ManagementError.create(code, response.properties.get('statusDescription'))
if response.correlation_id != request.correlation_id:
- raise ManagementError(NOT_FOUND, "Bad correlation id request=%s, response=%s"%(
- request.correlation_id, response.correlation_id))
+ raise NotFound("Bad correlation id request=%s, response=%s" %
+ (request.correlation_id, response.correlation_id))
def request(self, body=None, **properties):
@@ -188,18 +262,18 @@ class Node(object):
"""
if self.locales: properties.setdefault('locales', self.locales)
request = proton.Message()
- request.address=str(self.address)
- request.reply_to=self.reply_to
- request.correlation_id=self.correlation_id()
- request.properties=remove_none(properties)
- request.body=body or {}
+ request.address = str(self.address)
+ request.reply_to = self.reply_to
+ request.correlation_id = self.correlation_id()
+ request.properties = remove_none(properties)
+ request.body = body or {}
return request
def node_request(self, body=None, **properties):
+ """Construct a request for the managment node itself"""
return self.request(body, name=self.name, type=self.type, **properties)
- # TODO aconway 2014-06-03: async send/receive
- def call(self, request):
+ def call(self, request, expect=OK):
"""
Send a management request message, wait for a response.
@return: Response message.
@@ -208,13 +282,9 @@ class Node(object):
raise ValueError("Message must have an address")
if not request.reply_to:
raise ValueError("Message must have reply_to %s", request)
- self.messenger.put(request)
- self.messenger.send()
- self._flush()
- self.messenger.recv(1)
- response = proton.Message()
- self.messenger.get(response)
- self.check_response(response, request)
+ self.message_impl.send(request)
+ response = self.message_impl.fetch()
+ self.check_response(response, request, expect=expect)
return response
class QueryResponse(object):
@@ -222,18 +292,20 @@ class Node(object):
Result returned by L{query}.
@ivar attribute_names: List of attribute names for the results.
@ivar results: list of lists of attribute values in same order as attribute_names
- @ivar result_maps: Results as list of attributes maps {name: value}.
+ @ivar entities: Results as list of L{Entity}
"""
- def __init__(self, response):
+ def __init__(self, node, response):
"""
@param response: the respose message to a query.
"""
+ self.node = node
self.attribute_names = response.body['attributeNames']
self.results = response.body['results']
@property
- def result_maps(self):
- return [dict(zip(self.attribute_names, r)) for r in self.results]
+ def entities(self):
+ """Results as list of entities."""
+ return [Entity(self.node, dict(zip(self.attribute_names, r))) for r in self.results]
def __str__(self):
return "QueryResponse(attribute_names=%s, results=%s"%(
@@ -254,7 +326,7 @@ class Node(object):
operation='QUERY', entityType=type, offset=offset, count=count)
response = self.call(request)
- return Node.QueryResponse(response)
+ return Node.QueryResponse(self, response)
def create(self, type, name, attributes=None):
"""
@@ -262,20 +334,30 @@ class Node(object):
@param type: Type of entity to create.
@param name: Name for the new entity.
@param attributes: Map of attribute name:value for the new entity.
- @return: Map of attributes returned by create (with defaults etc.)
+ @return: Entity proxy for the new entity.
"""
request = self.request(operation='CREATE', type=type, name=name, body=attributes or {})
- response = self.call(request)
- return response.body
+ return Entity(self, self.call(request, expect=CREATED).body)
+
def read(self, type, name=None, identity=None):
"""
- Send an AMQP management read request. Must specify one of name or identity.
+ Read an AMQP entity, specify exactly one of name or identity.
@param type: Type of entity to read.
- @param name: Name of entity to read.
- @param identity: Identity of entity to read.
- @return: Map of attributes for the requested entity.
+ @return: An L{Entity}
"""
request = self.request(operation='READ', type=type, name=name, identity=identity)
- response = self.call(request)
- return response.body
+ return Entity(self, self.call(request).body)
+
+ def get_types(self, type=None):
+ return self.call(self.node_request(operation="GET-TYPES", entityType=type)).body
+
+ def get_attributes(self, type=None):
+ return self.call(self.node_request(operation="GET-ATTRIBUTES", entityType=type)).body
+
+ def get_operations(self, type=None):
+ return self.call(self.node_request(operation="GET-OPERATIONS", entityType=type)).body
+
+ def get_other_nodes(self):
+ return self.call(self.node_request(operation="GET-MGMT-NODES")).body
+
Added: qpid/dispatch/trunk/python/qpid_dispatch/management/entity.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch/management/entity.py?rev=1623899&view=auto
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch/management/entity.py (added)
+++ qpid/dispatch/trunk/python/qpid_dispatch/management/entity.py Wed Sep 10 03:34:41 2014
@@ -0,0 +1,63 @@
+#
+# 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
+#
+
+"""
+AMQP Managment Entity
+"""
+
+class Entity(object):
+ """
+ A collection of named attributes.
+
+ Attribute access:
+ - via index operator: entity['foo']
+ - as python attributes: entity.foo (only if attribute name is a legal python identitfier)
+
+ @ivar attributes: Map of attribute values for this entity.
+
+ NOTE: Entity does not itself implement the python map protocol because map
+ methods (in particular 'update') can clash with AMQP methods and attributes.
+ """
+
+ def __init__(self, attributes=None, **kwargs):
+ self.__dict__['attributes'] = dict(attributes or [], **kwargs)
+
+ def __getitem__(self, name): return self.attributes[name]
+
+ def __setitem__(self, name, value): self.attributes[name] = value
+
+ def __getattr__(self, name):
+ try:
+ return self.attributes[name]
+ except KeyError:
+ raise AttributeError("'%s' object has no attribute '%s'" % (type(self).__name__, name))
+
+ def __setattr__(self, name, value):
+ if name in self.__dict__:
+ super(Entity, self).__setattr__(name, value)
+ else:
+ self.attributes[name] = value
+
+ def __delattr__(self, name):
+ try:
+ del self.attributes[name]
+ except KeyError:
+ super(Entity, self).__delattr__(name)
+
+ def __repr__(self): return "Entity(%r)" % self.attributes
Copied: qpid/dispatch/trunk/python/qpid_dispatch/management/error.py (from r1618265, qpid/dispatch/trunk/python/qpid_dispatch_internal/management/error.py)
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch/management/error.py?p2=qpid/dispatch/trunk/python/qpid_dispatch/management/error.py&p1=qpid/dispatch/trunk/python/qpid_dispatch_internal/management/error.py&r1=1618265&r2=1623899&rev=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/error.py (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch/management/error.py Wed Sep 10 03:34:41 2014
@@ -17,13 +17,13 @@
# under the License
#
-# HTTP status codes used by AMQP (extend as needed)
+"""
+ManagementError exception class and subclasses, with status codes used by AMQP.
+"""
from httplib import responses as STATUS_TEXT
-
-STATUS_CODES=['OK', 'BAD_REQUEST', 'UNAUTHORIZED', 'FORBIDDEN', 'NOT_FOUND', 'REQUEST_TIMEOUT', 'INTERNAL_SERVER_ERROR', 'NOT_IMPLEMENTED']
-
-for code in STATUS_CODES: exec("from httplib import %s" % code)
+from httplib import OK, NO_CONTENT, CREATED, \
+ BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, INTERNAL_SERVER_ERROR, NOT_IMPLEMENTED
class ManagementError(Exception):
"""
@@ -34,10 +34,25 @@ class ManagementError(Exception):
"""
def __init__(self, status, description):
self.status, self.description = status, description
+ super(ManagementError, self).__init__(description)
- def __str__(self):
- text = STATUS_TEXT.get(self.status) or "Unknown status %s"%self.status
- if text in self.description: return self.description
- else: return "%s: %s"%(text, self.description)
-
-__all__ = STATUS_CODES + ['ManagementError', 'STATUS_TEXT']
+ @staticmethod
+ def create(status, description):
+ """Create the appropriate ManagementError subclass for status"""
+ try:
+ class_name = '' + (STATUS_TEXT[status].replace(' ',''))
+ return globals()[class_name](description)
+ except KeyError, e:
+ return ManagementError(status, description)
+
+def _error_class(code):
+ class Error(ManagementError):
+ def __init__(self, description): ManagementError.__init__(self, code, description)
+ return Error
+
+class BadRequest(_error_class(BAD_REQUEST)): pass
+class Unauthorized(_error_class(UNAUTHORIZED)): pass
+class Forbidden(_error_class(FORBIDDEN)): pass
+class NotFound(_error_class(NOT_FOUND)): pass
+class InternalServerError(_error_class(INTERNAL_SERVER_ERROR)): pass
+class NotImplemented(_error_class(NOT_IMPLEMENTED)): pass
Modified: qpid/dispatch/trunk/python/qpid_dispatch_internal/management/__init__.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch_internal/management/__init__.py?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/__init__.py (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/__init__.py Wed Sep 10 03:34:41 2014
@@ -16,16 +16,5 @@
# specific language governing permissions and limitations
# under the License.
#
-"""Management package"""
+"""Qpid Dispatch internal management package."""
-
-from .client import Node, Url
-from .qdrouter import QdSchema
-from .config import Config, configure_dispatch
-from .schema import Type, BooleanType, EnumType, EnumValue, AttributeType, EntityType, Schema, schema_file, ValidationError
-from .entity import Entity
-
-from .error import ManagementError, STATUS_CODES, STATUS_TEXT
-for code in STATUS_CODES: exec("from .error import %s"%code)
-
-__all__ = ["ManagementError", "Node", "Url", "QdSchema", "Config", "configure_dispatch", "Type", "BooleanType", "EnumType", "EnumValue", "AttributeType", "EntityType", "Schema", "schema_file", "ValidationError", "ManagementError", "STATUS_CODES", "STATUS_TEXT"] + STATUS_CODES
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=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/agent.py (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/agent.py Wed Sep 10 03:34:41 2014
@@ -17,39 +17,55 @@
# under the License
#
-import re
-from qpid_dispatch_internal import dispatch_c
+"""
+Pyton agent for dispatch router.
+
+Implements the server side of the AMQP management protocol for the dispatch router.
+Manages a set of manageable Entities that can be Created, Read, Updated and Deleted.
+Entity types are described by the schema in qdrouter.json.
+"""
+
+from traceback import format_exc
+from threading import Lock
from dispatch import IoAdapter, LogAdapter, LOG_DEBUG, LOG_ERROR
-from error import ManagementError, OK, BAD_REQUEST, INTERNAL_SERVER_ERROR, NOT_IMPLEMENTED, NOT_FOUND
-from schema import ValidationError, Entity as SchemaEntity
-from qdrouter import QdSchema
+from qpid_dispatch.management.error import ManagementError, OK, CREATED, NO_CONTENT, STATUS_TEXT, \
+ BadRequest, InternalServerError, NotImplemented, NotFound, Forbidden
+from .. import dispatch_c
+from .schema import ValidationError, Entity as SchemaEntity
+from .qdrouter import QdSchema
from ..router.message import Message
-from traceback import format_exc
-from ..compat import OrderedDict
def dictstr(d):
"""Stringify a dict in the form 'k=v, k=v ...' instead of '{k:v, ...}'"""
return ", ".join("%s=%r" % (k, v) for k, v in d.iteritems())
def required_property(prop, request):
+ """Raise exception if required property is missing"""
if prop not in request.properties:
- raise ManagementError(BAD_REQUEST, "No '%s' property: %s"%(prop,request))
+ raise BadRequest("No '%s' property: %s"%(prop, request))
return request.properties[prop]
-def not_implemented(operation, type):
- raise ManagementError(NOT_IMPLEMENTED,
- "Operation '%s' not implemented on %s" % (operation, type))
-
+def not_implemented(operation, entity_type):
+ """Raise NOT_IMPLEMENTED exception"""
+ raise NotImplemented("Operation '%s' not implemented on %s" % (operation, entity_type))
+
+class AtomicCount(object):
+ """Simple atomic counter"""
+ def __init__(self, count=0):
+ self.count = count
+ self.lock = Lock()
+
+ def next(self):
+ with self.lock:
+ self.count += 1
+ return self.count
class Entity(SchemaEntity):
"""
Base class for agent entities with operations as well as attributes.
-
- Implements "read" operation to return attributes, other basic
- operations throw NOT_IMPLEMENTED, they must be implemented by subclasses.
"""
- def __init__(self, qd, dispatch, entity_type, attributes):
+ def __init__(self, agent, entity_type, attributes):
"""
@para qd: Dispatch C library.
@param dispatch: Pointer to qd_dispatch C object.
@@ -58,16 +74,29 @@ class Entity(SchemaEntity):
"""
super(Entity, self).__init__(entity_type, attributes)
# Direct __dict__ access to avoid validation as schema attributes
- self.__dict__['_qd'] = qd
- self.__dict__['_dispatch'] = dispatch
+ self.__dict__['_agent'] = agent
+ self.__dict__['_qd'] = agent.qd
+ self.__dict__['_dispatch'] = agent.dispatch
- def create(self, request): not_implemented('create', self.type)
- def read(self, request): return dict(self.attributes)
-
- def update(self, request): not_implemented('update', self.type)
+ def create(self, request):
+ """Subclasses can add extra create actions here"""
+ pass
- def delete(self, request): not_implemented('delete', self.type)
+ def read(self, request):
+ """Handle read request, default is to return attributes."""
+ return (OK, self.attributes)
+
+ def update(self, request):
+ self.entity_type.allowed('update')
+ self.attributes.update(request.body)
+ self.validate()
+ return (OK, self.attributes)
+
+ def delete(self, request):
+ self.entity_type.allowed('delete')
+ self._agent.delete(self)
+ return (NO_CONTENT, {})
class ContainerEntity(Entity):
@@ -106,23 +135,24 @@ class FixedAddressEntity(Entity):
class WaypointEntity(Entity):
def create(self, request):
self._qd.qd_dispatch_configure_waypoint(self._dispatch, self)
- self._qd.qd_waypoint_activate_all(self._dispatch);
+ self._qd.qd_waypoint_activate_all(self._dispatch)
+class DummyEntity(Entity):
-class Agent(object):
+ id_count = AtomicCount()
+
+ def create(self, request):
+ self['identity'] = self.next_id()
+ return (OK, self.attributes)
+
+ def next_id(self): return self.type+str(self.id_count.next())
+
+ def callme(self, request):
+ return (OK, dict(**request.properties))
- def entity(self, attributes):
- """Create an instance of the implementation class for an entity"""
- if not 'type' in attributes:
- raise ManagementError(BAD_REQUEST, "No 'type' attributre in %s" % attributes)
- entity_type = self.schema.entity_type(attributes['type'])
- class_name = ''.join([n.capitalize() for n in entity_type.short_name.split('-')])
- class_name += 'Entity'
- entity_class = globals().get(class_name)
- if not entity_class:
- raise ManagementError(INTERNAL_SERVER_ERROR,
- "Can't find implementation for %s" % entity_type)
- return entity_class(self.qd, self.dispatch, entity_type, attributes)
+
+class Agent(object):
+ """AMQP managment agent"""
def __init__(self, dispatch, attribute_maps=None):
self.qd = dispatch_c.instance()
@@ -130,58 +160,73 @@ class Agent(object):
# FIXME aconway 2014-06-26: merge with $management
self.io = [IoAdapter(self.receive, "$management2"),
IoAdapter(self.receive, "$management2", True)] # Global
- self.log = LogAdapter("AGENT").log
+ self.log = LogAdapter("PYAGENT").log # FIXME aconway 2014-09-08: AGENT
self.schema = QdSchema()
- self.entities = [self.entity(attributes) for attributes in attribute_maps or []]
+ self.entities = [self.create_entity(attributes) for attributes in attribute_maps or []]
self.name = self.identity = 'self'
- self.type='org.amqp.management' # AMQP management node type
+ self.type = 'org.amqp.management' # AMQP management node type
+
+ def create_entity(self, attributes):
+ """Create an instance of the implementation class for an entity"""
+ if not 'type' in attributes:
+ raise BadRequest("No 'type' attribute in %s" % attributes)
+ entity_type = self.schema.entity_type(attributes['type'])
+ class_name = ''.join([n.capitalize() for n in entity_type.short_name.split('-')])
+ class_name += 'Entity'
+ entity_class = globals().get(class_name)
+ if not entity_class:
+ raise InternalServerError("Can't find implementation for %s" % entity_type)
+ return entity_class(self, entity_type, attributes)
- def respond(self, request, status=OK, description="OK", body=None):
+ def respond(self, request, status=OK, description=None, body=None):
+ """Send a response to the client"""
+ description = description or STATUS_TEXT[status]
response = Message(
address=request.reply_to,
correlation_id=request.correlation_id,
- properties={'statusCode': status, 'statusDescription': description },
+ properties={'statusCode': status, 'statusDescription': description},
body=body or {})
- self.log(LOG_DEBUG, "Agent response %s (to %s)"%(response, request))
+ self.log(LOG_DEBUG, "Agent response:\n %s\n Responding to: \n %s"%(response, request))
try:
self.io[0].send(response)
except:
self.log(LOG_ERROR, "Can't respond to %s: %s"%(request, format_exc()))
def receive(self, request, link_id):
+ """Called when a management request is received."""
self.log(LOG_DEBUG, "Agent request %s on link %s"%(request, link_id))
def error(e, trace):
+ """Raise an error"""
self.log(LOG_ERROR, "Error dispatching %s: %s\n%s"%(request, e, trace))
self.respond(request, e.status, e.description)
try:
- self.respond(request, body=self.handle(request))
+ status, body = self.handle(request)
+ self.respond(request, status=status, body=body)
except ManagementError, e:
error(e, format_exc())
except ValidationError, e:
- error(ManagementError(BAD_REQUEST, str(e)), format_exc())
+ error(BadRequest(str(e)), format_exc())
except Exception, e:
- error(ManagementError(INTERNAL_SERVER_ERROR,
- "%s: %s"%(type(e).__name__ , e)), format_exc())
+ error(InternalServerError("%s: %s"%(type(e).__name__, e)), format_exc())
def handle(self, request):
"""
Handle a request.
Dispatch management node requests to self, entity requests to the entity.
+ @return: (response-code, body)
"""
operation = required_property('operation', request)
type = required_property('type', request)
+ if type == self.type or operation.lower() == 'create':
+ target = self
+ else:
+ target = self.find_entity(request)
+ target.entity_type.allowed(operation)
try:
- if type == self.type or operation.lower() == 'create':
- # This is a management node request, handle it myself.
- method = getattr(self, operation.lower())
- return method(request)
- else:
- # This is an entity request, find the entity to handle it.
- entity = self.find_entity(request)
- method = getattr(entity, operation.lower())
- return method(request)
+ method = getattr(target, operation.lower())
except AttributeError:
- not_implemented(operation, self.type)
+ not_implemented(operation, type)
+ return method(request)
def query(self, request):
"""Management node query operation"""
@@ -190,7 +235,7 @@ class Agent(object):
try:
entity_type = self.schema.entity_type(entity_type)
except:
- raise ManagementError(BAD_REQUEST, "Unknown entity type '%s'" % entity_type)
+ raise BadRequest("Unknown entity type '%s'" % entity_type)
attribute_names = request.body.get('attributeNames')
if not attribute_names:
if entity_type:
@@ -204,7 +249,7 @@ class Agent(object):
results = [[e.attributes.get(a) for a in attribute_names]
for e in self.entities
if not entity_type or e.type == entity_type.name]
- return { 'attributeNames': attribute_names, 'results': results }
+ return (OK, {'attributeNames': attribute_names, 'results': results})
def create(self, request):
"""
@@ -216,29 +261,35 @@ class Agent(object):
for a in ['type', 'name']:
value = required_property(a, request)
if a in attributes and attributes[a] != value:
- raise ManagementError(BAD_REQUEST, "Conflicting values for '%s'"%a)
+ raise BadRequest("Conflicting values for '%s'"%a)
attributes[a] = value
- entity = self.entity(attributes)
+ entity = self.create_entity(attributes)
+ entity.entity_type.allowed('create')
# Validate in the context of the existing entities for uniqueness
self.schema.validate_all([entity]+self.entities)
entity.create(request) # Send the create request to the entity
self.entities.append(entity)
- return dict(entity.attributes)
+ return (CREATED, entity.attributes)
def find_entity(self, request):
+ """Find the entity addressed by request"""
attr = [(name, value) for name, value in request.properties.iteritems()
if name in ['name', 'identity'] and value is not None]
if len(attr) != 1:
- raise ManagementError(
- BAD_REQUEST, "Specify exactly one of 'name' or 'identity': %s" % request)
+ raise BadRequest("Specify exactly one of 'name' or 'identity': %s" % request)
name, value = attr[0]
try:
entity = (e for e in self.entities if e.attributes.get(name) == value).next()
except StopIteration:
- raise ManagementError(NOT_FOUND, "No entity with %s='%s'" % (name, value))
+ raise NotFound("No entity with %s='%s'" % (name, value))
expect_type = request.properties.get('type')
if expect_type and entity.type != expect_type:
- raise ManagementError(
- NOT_FOUND, "Type mismatch, expected '%s', found '%s'" %
- (expect_type, entity.type))
+ raise NotFound("Type mismatch, expected '%s', found '%s'" %
+ (expect_type, entity.type))
return entity
+
+ def delete(self, entity):
+ try:
+ self.entities.remove(entity)
+ except ValueError:
+ raise NotFound("Cannot delete, entity not found: %s"%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=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/config.py (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/config.py Wed Sep 10 03:34:41 2014
@@ -22,9 +22,9 @@ Configuration file parsing
"""
import json, re, sys
-import schema
from copy import copy
-from qpid_dispatch_internal import dispatch_c
+from .schema import ValidationError
+from .. import dispatch_c
from .qdrouter import QdSchema
from ..compat import json_load_kwargs
@@ -34,7 +34,7 @@ class Config(object):
def __init__(self, filename=None, schema=QdSchema()):
self.schema = schema
if filename:
- e = self.load(filename)
+ self.load(filename)
else:
self.entities = []
@@ -113,9 +113,9 @@ class Config(object):
self.load(f)
except:
ex_type, ex_value, ex_trace = sys.exc_info()
- raise schema.ValidationError, "Loading '%s', %s: %s"%(source, ex_type.__name__, ex_value), ex_trace
+ raise ValidationError, "Loading '%s', %s: %s"%(source, ex_type.__name__, ex_value), ex_trace
else:
- sections = self._parse(source);
+ sections = self._parse(source)
# Add missing singleton sections
for et in self.schema.entity_types.itervalues():
if et.singleton and not [s for s in sections if s[0] == et.short_name]:
@@ -152,9 +152,9 @@ def configure_dispatch(dispatch, filenam
for w in config.by_type('waypoint'): qd.qd_dispatch_configure_waypoint(dispatch, w)
for l in config.by_type('listener'): qd.qd_dispatch_configure_listener(dispatch, l)
for c in config.by_type('connector'): qd.qd_dispatch_configure_connector(dispatch, c)
- qd.qd_connection_manager_start(dispatch);
- qd.qd_waypoint_activate_all(dispatch);
+ qd.qd_connection_manager_start(dispatch)
+ qd.qd_waypoint_activate_all(dispatch)
- # TODO aconway 2014-07-09: messy dependency, can't import till after prepare
+ # NOTE: Can't import agent till after qd_dispatch_prepare
from .agent import Agent
qd.qd_dispatch_set_agent(dispatch, Agent(dispatch, config.entities))
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=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.json (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.json Wed Sep 10 03:34:41 2014
@@ -85,6 +85,7 @@
},
"entity_types": {
"container": {
+ "allows": "CR",
"singleton": true,
"include": [
"common"
@@ -103,6 +104,7 @@
},
"router": {
+ "allows": "CR",
"singleton": true,
"include": [
"common"
@@ -155,6 +157,7 @@
"listener": {
"description": "Listens for incoming connections to the router",
+ "allows": "CR",
"include": [
"common",
"ssl-profile",
@@ -190,6 +193,7 @@
},
"connector": {
"description": "Establishes an outgoing connections from the router",
+ "allows": "CR",
"include": [
"common",
"ssl-profile",
@@ -215,6 +219,7 @@
"include": [
"common"
],
+ "allows": "CR",
"attributes": {
"module": {
"type":[
@@ -222,6 +227,7 @@
"MESSAGE",
"SERVER",
"AGENT",
+ "PYAGENT",
"CONTAINER",
"CONFIG",
"DEFAULT",
@@ -257,6 +263,7 @@
},
"fixed-address": {
+ "allows": "CR",
"include": [
"common"
],
@@ -290,6 +297,7 @@
},
"waypoint": {
+ "allows": "CR",
"include": [
"common"
],
@@ -311,6 +319,18 @@
"default": -1
}
}
+ },
+
+ "dummy": {
+ "description": "Dummy entity for test purposes.",
+ "allows": "CRUD",
+ "include": ["common"],
+ "attributes": {
+ "arg1": {"type": "String"},
+ "arg2": {"type": "String"},
+ "num1": {"type": "Integer"},
+ "num2": {"type": "Integer"}
+ }
}
}
}
Modified: qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.py?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.py (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/qdrouter.py Wed Sep 10 03:34:41 2014
@@ -22,7 +22,7 @@ Qpid Dispatch Router management schema a
"""
import json
-import schema
+from . import schema
from ..compat import json_load_kwargs
class QdSchema(schema.Schema):
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=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/schema.py (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/schema.py Wed Sep 10 03:34:41 2014
@@ -26,10 +26,10 @@ check for uniqueness of enties/attribute
A Schema can be loaded/dumped to a json file.
"""
-import os, sys, re
+import os, sys
+from qpid_dispatch.management import entity
+from qpid_dispatch.management.error import Forbidden
from ..compat import OrderedDict
-import entity
-from collections import MutableMapping
class ValidationError(Exception):
"""Error raised if schema validation fails"""
@@ -294,7 +294,7 @@ class AttributeTypeHolder(object):
return self.name
class IncludeType(AttributeTypeHolder):
-
+ """An include type defines a set of attributes that can be re-used by multiple EntityTypes"""
def __init__(self, name, schema, attributes=None, description=""):
super(IncludeType, self).__init__(name, schema, attributes, description)
attributes = attributes or {}
@@ -313,7 +313,7 @@ class EntityType(AttributeTypeHolder):
#ivar include: List of names of sections included by this entity.
"""
def __init__(self, name, schema, singleton=False, include=None, attributes=None,
- description=""):
+ description="", allows=""):
"""
@param name: name of the entity type.
@param schema: schema for this type.
@@ -321,12 +321,14 @@ class EntityType(AttributeTypeHolder):
@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.
+ @param allows: Allowed operations, string of "CRUD"
"""
super(EntityType, self).__init__(name, schema, attributes, description)
self.short_name = schema.short_name(name)
self.refs = {'entity-type': name}
self.singleton = singleton
self.include = include
+ self.allows = allows.upper()
if include and self.schema.includes:
for i in include:
if not i in schema.includes:
@@ -397,10 +399,17 @@ class EntityType(AttributeTypeHolder):
return attributes
+ def allowed(self, operation):
+ """Raise excepiton if op is not a valid operation on entity."""
+ op = operation.upper()
+ if op[0] not in self.allows:
+ raise Forbidden("Operation '%s' not allowed for '%s'" % (op, self.name))
+
def __repr__(self): return "%s(%s)" % (type(self).__name__, self.name)
def __str__(self): return self.name
+
class Schema(object):
"""
Schema defining entity types.
@@ -454,6 +463,7 @@ class Schema(object):
])
def entity_type(self, name):
+ """Look up an EntityType by name"""
try:
return self.entity_types[self.long_name(name)]
except KeyError:
@@ -512,39 +522,19 @@ class Schema(object):
def entities(self, attribute_maps):
"""Convert a list of attribute maps into a list of L{Entity}"""
- return map(self.entity, attribute_maps)
+ return [self.entity(m) for m in attribute_maps]
class Entity(entity.Entity):
- """
- An L{entity.Entity} with schema enforced entity type.
- @ivar _entity_type: not private, _ prefix to avoid clash with attribute names.
- """
-
- def __init__(self, entity_type, attributes):
- """
- @param entity_type: L{EntityType}
- @param attributes: map of atrribute name:value or list of [(name, value)]
- """
- super(Entity, self).__init__(attributes)
- self.__dict__['_entity_type'] = entity_type # Avoid __getattr__ recursion
- entity_type.validate(self)
-
- @staticmethod
- def from_schema(schema, attributes):
- """
- Look up attributes['type'] in schema and create an Entity.
- """
- entity_type = schema.entity_type.get(attributes['type'])
- if not entity_type:
- raise ValidationError("No 'type' attributre in %s" % attributes)
- return Entity(entity_type, attributes)
-
-
- def __setitem__(self, attrname, value):
- if attrname not in self._entity_type.attributes:
- raise ValidationError("Illegal attribute '%s' for %s" % (attrname, self._entity_type))
- super(Entity, self).__setitem__(attrname, value);
+ """A map of attributes associated with an L{EntityType}"""
+ def __init__(self, entity_type, attributes=None, **kwattrs):
+ super(Entity, self).__init__(attributes, **kwattrs)
+ self.__dict__['entity_type'] = entity_type
+ self.validate()
+
+ def __setitem__(self, name, value):
+ super(Entity, self).__setitem__(name, value)
+ self.validate()
- def __delitem__(self, attrname):
- raise TypeError("Can't delete attributes from %s" % self)
+ def validate(self):
+ self.entity_type.validate(self)
Copied: qpid/dispatch/trunk/setup.py.in (from r1618265, qpid/dispatch/trunk/python/qpid_dispatch_internal/management/__init__.py)
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/setup.py.in?p2=qpid/dispatch/trunk/setup.py.in&p1=qpid/dispatch/trunk/python/qpid_dispatch_internal/management/__init__.py&r1=1618265&r2=1623899&rev=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/__init__.py (original)
+++ qpid/dispatch/trunk/setup.py.in Wed Sep 10 03:34:41 2014
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
@@ -16,16 +17,30 @@
# specific language governing permissions and limitations
# under the License.
#
-"""Management package"""
+from distutils.core import setup
+pypi_long_description = """
-from .client import Node, Url
-from .qdrouter import QdSchema
-from .config import Config, configure_dispatch
-from .schema import Type, BooleanType, EnumType, EnumValue, AttributeType, EntityType, Schema, schema_file, ValidationError
-from .entity import Entity
+============
+qpid_dispatch
+=============
-from .error import ManagementError, STATUS_CODES, STATUS_TEXT
-for code in STATUS_CODES: exec("from .error import %s"%code)
+qpid_dispatch contains the following sub-packages:
+- qpid_dispatch.management: AMQP management client library.
-__all__ = ["ManagementError", "Node", "Url", "QdSchema", "Config", "configure_dispatch", "Type", "BooleanType", "EnumType", "EnumValue", "AttributeType", "EntityType", "Schema", "schema_file", "ValidationError", "ManagementError", "STATUS_CODES", "STATUS_TEXT"] + STATUS_CODES
+"""
+
+setup(
+ name="qpid_dispatch",
+ version="${QPID_DISPATCH_VERSION}",
+ author="Apache Qpid",
+ author_email="dev@qpid.apache.org",
+ package_dir={'' : '${CMAKE_SOURCE_DIR}/python'},
+ packages=["qpid_dispatch", "qpid_dispatch.management"],
+ scripts=[], # Installed by cmake
+ data_files=[], # Installed by cmake
+ url="http://qpid.apache.org/",
+ license="Apache Software License",
+ description="Management libraries for Apache Qpid Dispatch.",
+ long_description=pypi_long_description
+)
Propchange: qpid/dispatch/trunk/setup.py.in
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: qpid/dispatch/trunk/setup.py.in
------------------------------------------------------------------------------
svn:executable = *
Modified: qpid/dispatch/trunk/src/dispatch.c
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/dispatch.c?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/dispatch.c (original)
+++ qpid/dispatch/trunk/src/dispatch.c Wed Sep 10 03:34:41 2014
@@ -78,7 +78,7 @@ qd_error_t qd_dispatch_load_config(qd_di
{
PyObject *module=0, *configure_dispatch=0, *result=0;
bool ok =
- (module = PyImport_ImportModule("qpid_dispatch_internal.management")) &&
+ (module = PyImport_ImportModule("qpid_dispatch_internal.management.config")) &&
(configure_dispatch = PyObject_GetAttrString(module, "configure_dispatch")) &&
(result = PyObject_CallFunction(configure_dispatch, "(ls)", (long)qd, config_path));
Py_XDECREF(module);
Modified: qpid/dispatch/trunk/src/schema_c.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/schema_c.py?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/schema_c.py (original)
+++ qpid/dispatch/trunk/src/schema_c.py Wed Sep 10 03:34:41 2014
@@ -22,7 +22,8 @@ Generate C code from the router schema.
"""
import re
-from qpid_dispatch_internal.management import EnumType, QdSchema
+from qpid_dispatch_internal.management.schema import EnumType
+from qpid_dispatch_internal.management.qdrouter import QdSchema
copyright="""/*
* Licensed to the Apache Software Foundation (ASF) under one
Modified: qpid/dispatch/trunk/tests/management/__init__.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/management/__init__.py?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/management/__init__.py (original)
+++ qpid/dispatch/trunk/tests/management/__init__.py Wed Sep 10 03:34:41 2014
@@ -17,7 +17,9 @@
## under the License
##
-from schema import SchemaTest
-from client import UrlTest
-from qdrouter import QdrouterTest
-from entity import EntityTest
+"""Management unit test package"""
+
+from client import *
+from schema import *
+from qdrouter import *
+from entity import *
Modified: qpid/dispatch/trunk/tests/management/client.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/management/client.py?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/management/client.py (original)
+++ qpid/dispatch/trunk/tests/management/client.py Wed Sep 10 03:34:41 2014
@@ -20,7 +20,7 @@
"""Tests for management.amqp"""
import unittest
-from qpid_dispatch_internal.management import Url
+from qpid_dispatch.management import Url
class UrlTest(unittest.TestCase):
Modified: qpid/dispatch/trunk/tests/management/entity.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/management/entity.py?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/management/entity.py (original)
+++ qpid/dispatch/trunk/tests/management/entity.py Wed Sep 10 03:34:41 2014
@@ -1,49 +1,43 @@
-##
-## 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
-##
-
-#pylint: disable=wildcard-import,missing-docstring,too-many-public-methods
+#
+# 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
+#
import unittest
-from qpid_dispatch_internal.management import Entity
+from qpid_dispatch.management.entity import Entity
class EntityTest(unittest.TestCase):
def test_entity(self):
e = Entity({'foo-bar': 'baz'}, type='container', name='x')
- self.assertEqual(e.name, 'x')
self.assertEqual(e.attributes, {'type': 'container', 'name':'x', 'foo-bar': 'baz'})
- self.assertEqual(e.foo_bar, 'baz')
- self.assertEqual(e.attributes['foo-bar'], 'baz')
+ self.assertEqual(e.name, 'x')
+ self.assertEqual(e['name'], 'x')
- e.foo_bar = 'x'
- self.assertEqual(e.foo_bar, 'x')
- self.assertEqual(e.attributes['foo-bar'], 'x')
+ e.name = 'y'
+ self.assertEqual(e.name, 'y')
+ self.assertEqual(e['name'], 'y')
+ self.assertEqual(e.attributes['name'], 'y')
e.xx = 'xx'
self.assertEqual(e.xx, 'xx')
+ self.assertEqual(e['xx'], 'xx')
self.assertEqual(e.attributes['xx'], 'xx')
- e.attributes['y-y'] = 'yy'
- self.assertEqual(e.y_y, 'yy')
- self.assertEqual(e.attributes['y-y'], 'yy')
- def assign(): e.attributes['foo/bar'] = 1
- self.assertRaises(KeyError, assign)
if __name__ == '__main__':
unittest.main()
Modified: qpid/dispatch/trunk/tests/management/qdrouter.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/management/qdrouter.py?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/management/qdrouter.py (original)
+++ qpid/dispatch/trunk/tests/management/qdrouter.py Wed Sep 10 03:34:41 2014
@@ -20,7 +20,7 @@
#pylint: disable=wildcard-import,unused-wildcard-import,missing-docstring,too-many-public-methods
import unittest
-from qpid_dispatch_internal.management import Config
+from qpid_dispatch_internal.management.config import Config
class QdrouterTest(unittest.TestCase):
"""Tests for qpid_dispatch_internal.config.qdrouter"""
Modified: qpid/dispatch/trunk/tests/management/schema.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/management/schema.py?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/management/schema.py (original)
+++ qpid/dispatch/trunk/tests/management/schema.py Wed Sep 10 03:34:41 2014
@@ -21,7 +21,7 @@
#pylint: disable=wildcard-import,missing-docstring,too-many-public-methods
import unittest, json
-from qpid_dispatch_internal.management import Schema, BooleanType, EnumType, AttributeType, schema_file, ValidationError, EnumValue, EntityType
+from qpid_dispatch_internal.management.schema import Schema, BooleanType, EnumType, AttributeType, schema_file, ValidationError, EnumValue, EntityType
import collections
def replace_od(thing):
@@ -228,10 +228,10 @@ class SchemaTest(unittest.TestCase):
self.assertRaises(ValidationError, s.entity, {'type': 'listener', 'nosuch': 'x'})
e = s.entity({'type': 'listener', 'name':'x', 'addr':'foo'})
self.assertEqual(e.attributes, {'type': 'org.example.listener', 'name':'x', 'addr':'foo'})
- self.assertEqual(e.addr, 'foo')
+ self.assertEqual(e['addr'], 'foo')
self.assertRaises(ValidationError, e.__setitem__, 'nosuch', 'x')
try:
- e.nosuch = x
+ e.nosuch = 'x'
self.fail("Expected exception")
except: pass
Modified: qpid/dispatch/trunk/tests/run.py.in
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/run.py.in?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/run.py.in (original)
+++ qpid/dispatch/trunk/tests/run.py.in Wed Sep 10 03:34:41 2014
@@ -146,5 +146,5 @@ if __name__ == "__main__":
os.execvp(args[0], args)
except Exception, e:
print "%s%s: %s"%(error_prefix, type(e).__name__, e)
- sys.exit(1)
+ raise
Modified: qpid/dispatch/trunk/tests/system_test.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/system_test.py?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/system_test.py (original)
+++ qpid/dispatch/trunk/tests/system_test.py Wed Sep 10 03:34:41 2014
@@ -56,7 +56,7 @@ import os, time, socket, random, subproc
from copy import copy
import proton
from proton import Message
-from qpid_dispatch_internal.management import Node
+from qpid_dispatch.management import Node
from run import with_valgrind
# Optional modules
@@ -352,7 +352,7 @@ 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').result_maps
+ connections = self.management.query('org.apache.qpid.dispatch.connection').entities
for c in connections:
if c['name'] == '%s:%s'%(host, port):
return c
@@ -369,7 +369,7 @@ class Qdrouterd(Process):
# FIXME aconway 2014-06-12: this should be a request by name, not a query.
addrs = self.management.query(
type='org.apache.qpid.dispatch.router.address',
- attribute_names=['name', 'subscriberCount', 'remoteCount']).result_maps
+ attribute_names=['name', 'subscriberCount', 'remoteCount']).entities
# FIXME aconway 2014-06-12: endswith check is because of M0/L prefixes
addrs = [a for a in addrs if a['name'].endswith(address)]
return addrs and addrs[0]['subscriberCount'] >= subscribers and addrs[0]['remoteCount'] >= remotes
Modified: qpid/dispatch/trunk/tests/system_tests_management.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/system_tests_management.py?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/system_tests_management.py (original)
+++ qpid/dispatch/trunk/tests/system_tests_management.py Wed Sep 10 03:34:41 2014
@@ -20,13 +20,14 @@
"""System tests for management of qdrouter"""
import unittest, system_test, re, os
-from qpid_dispatch_internal.management import Node, ManagementError, Url, BAD_REQUEST, NOT_IMPLEMENTED, STATUS_TEXT, NOT_FOUND
+from qpid_dispatch.management import Node, ManagementError, Url, BadRequest, NotImplemented, NotFound, Forbidden
from system_test import Qdrouterd, message, retry
LISTENER = 'org.apache.qpid.dispatch.listener'
CONNECTOR = 'org.apache.qpid.dispatch.connector'
FIXED_ADDRESS = 'org.apache.qpid.dispatch.fixed-address'
WAYPOINT = 'org.apache.qpid.dispatch.waypoint'
+DUMMY = 'org.apache.qpid.dispatch.dummy'
ADDRESS = 'org.apache.qpid.dispatch.router.address'
@@ -53,46 +54,29 @@ class ManagementTest(system_test.TestCas
self.node = self.cleanup(Node(self.router.addresses[0]))
# Temporary access to separate new management address.
self.node2 = self.cleanup(Node(Url(self.router.addresses[0], path='$management2')))
-
- def assertRaisesManagement(self, status, call, *args, **kwargs):
- """Assert that call(*args, **kwargs) raises a ManagementError with status"""
- try:
- call(*args, **kwargs)
- self.fail("Expected ManagementError(%r)"%(STATUS_TEXT[status]))
- except ManagementError, e:
- self.assertEqual(status, e.status,
- msg="Expected status %r, got '%s'"%(STATUS_TEXT[status], e))
+ self.maxDiff = None
+ self.longMessage = True
+ unittest.util._MAX_LENGTH = 256 # Monkey patch unittest truncation
def test_bad_query(self):
"""Test that various badly formed queries get the proper response"""
# No operation attribute
- self.assertRaisesManagement(BAD_REQUEST, self.node.call, self.node.request())
+ self.assertRaises(BadRequest, self.node.call, self.node.request())
# Unknown operation
- self.assertRaisesManagement(NOT_IMPLEMENTED, self.node.call,
- self.node.request(operation="nosuch"))
+ self.assertRaises(NotImplemented, self.node.call, self.node.request(operation="nosuch"))
# No entityType or attributeList
- self.assertRaisesManagement(BAD_REQUEST, self.node.query)
-
- def test_query_type(self):
- address = 'org.apache.qpid.dispatch.router.address'
- response = self.node.query(type=address)
- self.assertEqual(response.attribute_names[0:3], ['type', 'name', 'identity'])
- for r in response.result_maps: # Check types
- self.assertEqual(r['type'], address)
- names = [r['name'] for r in response.result_maps]
- self.assertTrue('L$management' in names)
- self.assertTrue('M0$management' in names)
+ self.assertRaises(BadRequest, self.node.query)
def test_query_type(self):
"""Query with type only"""
response = self.node2.query(type=LISTENER)
for attr in ['type', 'name', 'identity', 'addr', 'port']:
self.assertTrue(attr in response.attribute_names)
- for r in response.result_maps: # Check types
- self.assertEqual(len(response.attribute_names), len(r))
+ for r in response.entities: # Check types
+ self.assertEqual(len(response.attribute_names), len(r.attributes))
self.assertEqual(r['type'], LISTENER)
self.assertTrue(
- set(['l0', 'l1', 'l2']) <= set(r['name'] for r in response.result_maps))
+ set(['l0', 'l1', 'l2']) <= set(r['name'] for r in response.entities))
def test_query_type_attributes(self):
"""Query with type and attribute names"""
@@ -102,7 +86,7 @@ class ManagementTest(system_test.TestCas
expect = [[LISTENER, 'l%s' % i, str(self.router.ports[i])] for i in xrange(3)]
for r in expect: # We might have extras in results due to create tests
self.assertTrue(r in response.results)
- self.assertTrue(dict(zip(attribute_names, r)) in response.result_maps)
+ self.assertTrue(dict(zip(attribute_names, r)) in [e.attributes for e in response.entities])
def test_query_attributes(self):
"""Query with attributes only"""
@@ -113,18 +97,18 @@ class ManagementTest(system_test.TestCas
for r in expect: # We might have extras in results due to create tests
self.assertTrue(r in response.results)
for name in ['router0', 'log0']:
- self.assertTrue([r for r in response.result_maps if r['name'] == name],
+ self.assertTrue([r for r in response.entities if r['name'] == name],
msg="Can't find result with name '%s'" % name)
- def assertMapSubset(self, sub, super):
- """Assert that mapping sub is a subset of mapping super"""
- missing = [(k, v) for k, v in sub.items() if (k, v) not in super.items()]
- assert not missing, "Not a subset, missing %s, sub=%s, super=%s"%(missing, sub, super)
+ def assertMapSubset(self, small, big):
+ """Assert that mapping small is a subset of mapping big"""
+ missing = [(k, v) for k, v in small.items() if (k, v) not in big.items()]
+ assert not missing, "Not a subset, missing %s, sub=%s, super=%s"%(missing, small, big)
def assert_create_ok(self, type, name, attributes):
- result = self.node2.create(type, name, attributes)
- self.assertMapSubset(attributes, result)
- return result
+ entity = self.node2.create(type, name, attributes)
+ self.assertMapSubset(attributes, entity.attributes)
+ return entity
def test_create_listener(self):
"""Create a new listener on a running router"""
@@ -132,14 +116,14 @@ class ManagementTest(system_test.TestCas
port = self.get_port()
# Note qdrouter schema defines port as string not int, since it can be a service name.
attributes = {'name':'foo', 'port':str(port), 'role':'normal', 'sasl-mechanisms': 'ANONYMOUS'}
- result = self.assert_create_ok(LISTENER, 'foo', attributes)
- self.assertEqual(result['identity'], attributes['name'])
- self.assertEqual(result['addr'], '0.0.0.0')
+ entity = self.assert_create_ok(LISTENER, 'foo', attributes)
+ self.assertEqual(entity['identity'], attributes['name'])
+ self.assertEqual(entity['addr'], '0.0.0.0')
# Connect via the new listener
node3 = self.cleanup(Node(Url(port=port, path='$management')))
- router = node3.query(type='org.apache.qpid.dispatch.router')
- self.assertEqual(self.__class__.router.name, router.result_maps[0]['name'])
+ router = node3.query(type='org.apache.qpid.dispatch.router').entities
+ self.assertEqual(self.__class__.router.name, router[0]['name'])
def test_create_log(self):
"""Create a log entity"""
@@ -147,7 +131,8 @@ class ManagementTest(system_test.TestCas
# - allow auto-assigned name/identity? Use module as name/identity?
# - 1 entity with full log state, allow updates.
log = os.path.abspath("test_create_log.log")
- self.assert_create_ok('log', 'log.1', dict(module='AGENT', level="error", output=log))
+ # FIXME aconway 2014-09-08: PYAGENT->AGENT
+ self.assert_create_ok('log', 'log.1', dict(module='PYAGENT', level="error", output=log))
# Cause an error and verify it shows up in the log file.
self.assertRaises(ManagementError, self.node2.create, type='nosuch', name='nosuch')
f = self.cleanup(open(log))
@@ -206,28 +191,79 @@ class ManagementTest(system_test.TestCas
finally:
f.close()
- def test_read(self):
- attributes = self.node2.read(type=LISTENER, name='l0')
- self.assertEqual('l0', attributes['name'])
- self.assertEqual('l0', attributes['identity'])
- self.assertEqual(str(self.router.ports[0]), attributes['port'])
-
- attributes = self.node2.read(type=LISTENER, identity='l1')
- self.assertEqual('l1', attributes['name'])
- self.assertEqual('l1', attributes['identity'])
- self.assertEqual(str(self.router.ports[1]), attributes['port'])
+ def test_entity(self):
+ entity = self.node2.read(type=LISTENER, name='l0')
+ self.assertEqual('l0', entity.name)
+ self.assertEqual('l0', entity.identity)
+ self.assertEqual(str(self.router.ports[0]), entity.port)
+
+ entity = self.node2.read(type=LISTENER, identity='l1')
+ self.assertEqual('l1', entity.name)
+ self.assertEqual('l1', entity.identity)
+ self.assertEqual(str(self.router.ports[1]), entity.port)
# Can't specify both name and identity
- self.assertRaisesManagement(
- BAD_REQUEST, self.node2.read, type=LISTENER, name='l0', identity='l1')
+ self.assertRaises(BadRequest, self.node2.read, type=LISTENER, name='l0', identity='l1')
# Bad type
- self.assertRaisesManagement(
- NOT_FOUND, self.node2.read, type=CONNECTOR, name='l0')
+ self.assertRaises(NotFound, self.node2.read, type=CONNECTOR, name='l0')
# Unknown entity
- self.assertRaisesManagement(
- NOT_FOUND, self.node2.read, type=LISTENER, name='nosuch')
+ self.assertRaises(NotFound, self.node2.read, type=LISTENER, name='nosuch')
+
+ # Update and delete are not allowed by the schema
+ self.assertRaises(Forbidden, entity.update)
+ self.assertRaises(Forbidden, entity.delete)
+
+ # Non-standard request is not allowed by schema.
+ self.assertRaises(Forbidden, entity.call, 'nosuchop', foo="bar")
+
+ # Dummy entity supports all CRUD operations
+ dummy = self.node2.create(type=DUMMY, name='MyDummy', attributes={'arg1': 'START'})
+ self.assertEqual(dummy.type, DUMMY)
+ self.assertEqual(dummy.name, 'MyDummy')
+ self.assertEqual(dummy.arg1, 'START')
+ identity = dummy.identity
+ self.assertEqual(
+ dict(type=DUMMY, identity=identity, name='MyDummy', arg1='START'),
+ dummy.attributes)
+
+ dummy.attributes['num1'] = 42
+ dummy.arg1 = 'one'
+ self.assertEqual(
+ dict(type=DUMMY, identity=identity, name='MyDummy', arg1='one', num1=42),
+ dummy.attributes)
+ dummy.update()
+
+ dummy.attributes.update(dict(arg1='x', num1=0))
+ dummy.read()
+ self.assertEqual(
+ dict(type=DUMMY, name='MyDummy', identity=identity, arg1='one', num1=42),
+ dummy.attributes)
+
+ dummy2 = self.node2.read(type=DUMMY, name='MyDummy')
+ self.assertEqual(dummy.attributes, dummy2.attributes)
+
+ self.assertEqual({'operation': 'callme', 'foo': 'bar', 'type': DUMMY, 'identity': identity},
+ dummy.call('callme', foo='bar'))
+
+ dummy.badattribute = 'Bad'
+ self.assertRaises(BadRequest, dummy.update)
+
+ dummy.delete()
+ self.assertRaises(NotFound, self.node2.read, type=DUMMY, name='MyDummy')
+
+ def test_get_types(self):
+ self.assertRaises(NotImplemented, self.node2.get_types)
+
+ def test_get_attributes(self):
+ self.assertRaises(NotImplemented, self.node2.get_attributes)
+
+ def test_get_operations(self):
+ self.assertRaises(NotImplemented, self.node2.get_operations)
+
+ def test_get_other_nodes(self):
+ self.assertRaises(NotImplemented, self.node2.get_other_nodes)
if __name__ == '__main__':
unittest.main()
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=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/system_tests_two_routers.py (original)
+++ qpid/dispatch/trunk/tests/system_tests_two_routers.py Wed Sep 10 03:34:41 2014
@@ -20,7 +20,7 @@
import unittest, os
from proton import Message, PENDING, ACCEPTED, REJECTED, RELEASED, SSLDomain, SSLUnavailable
from system_test import TestCase, Qdrouterd, retry_exception
-from qpid_dispatch_internal.management import Node
+from qpid_dispatch.management import Node
class RouterTest(TestCase):
@classmethod
Modified: qpid/dispatch/trunk/tools/qdstat
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/tools/qdstat?rev=1623899&r1=1623898&r2=1623899&view=diff
==============================================================================
--- qpid/dispatch/trunk/tools/qdstat (original)
+++ qpid/dispatch/trunk/tools/qdstat Wed Sep 10 03:34:41 2014
@@ -32,7 +32,8 @@ if not home:
home = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib', 'qpid-dispatch')
sys.path.insert(0, os.path.join(home, "python"))
-from qpid_dispatch_internal.management import Url, Node, QdSchema, Entity
+from qpid_dispatch.management import Url, Node, Entity
+from qpid_dispatch_internal.management.qdrouter import QdSchema
from qpid_dispatch_internal.tools import Display, Header, Sorter, YN, Commas, TimeLong
@@ -103,8 +104,7 @@ class BusManager(Node):
schema = QdSchema()
def query(self, entity_type):
- return [Entity(m) for m in
- super(BusManager, self).query(entity_type).result_maps]
+ return super(BusManager, self).query(entity_type).entities
def displayConnections(self):
disp = Display(prefix=" ")
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org